cv::findContours の使い方【OpenCV/C++】〜輪郭を検出する〜
cv::findContours は、2値画像から物体の輪郭(境界線の座標列)を抽出する関数です。検出した輪郭を使えば、物体の数を数える・面積でフィルタする・バウンディングボックスを取得するといった処理を簡潔に実装できます。最小限の呼び出しは次のとおりです。
cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
動作環境
- OpenCV 4.x(4.5 以降推奨)
- コンパイル例:
g++ -std=c++17 main.cpp `pkg-config --cflags --libs opencv4` -o main
環境構築がまだの方は環境構築ガイドを先にどうぞ。
基本の使い方
完全プログラム
#include <opencv2/opencv.hpp>
#include <iostream>
int main()
{
// グレースケールで読み込み
cv::Mat src = cv::imread("shapes.png", cv::IMREAD_GRAYSCALE);
if (src.empty()) {
std::cerr << "画像を読み込めませんでした\n";
return 1;
}
// 2値化(findContours は 2値画像を入力とする)
cv::Mat binary;
cv::threshold(src, binary, 128, 255, cv::THRESH_BINARY);
// 輪郭検出
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(binary, contours, hierarchy,
cv::RETR_EXTERNAL, // 最外輪郭のみ取得
cv::CHAIN_APPROX_SIMPLE); // 冗長点を省略
std::cout << "検出した輪郭数: " << contours.size() << "\n";
// カラー画像に輪郭を描画して確認
cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);
cv::drawContours(dst, contours, -1, cv::Scalar(0, 0, 255), 2);
cv::imwrite("result.png", dst);
std::cout << "result.png を保存しました\n";
return 0;
}
実行結果
検出した輪郭数: 3
result.png を保存しました
result.png を開くと、2値化された白領域の境界が赤色でなぞられた画像が確認できます。
主要行の解説
| 行 | 解説 |
|---|---|
cv::threshold(...) |
findContours は 2値画像が前提。まずここで 0/255 に変換する |
std::vector<std::vector<cv::Point>> contours |
各輪郭 = cv::Point の配列。外側の vector が輪郭ごとの配列になる |
std::vector<cv::Vec4i> hierarchy |
輪郭の親子関係を格納。使わない場合も宣言して渡す必要がある |
cv::RETR_EXTERNAL |
最外輪郭のみを取得するモード。穴(内側輪郭)は無視される |
cv::CHAIN_APPROX_SIMPLE |
直線部の中間点を省略し、端点だけを保持。メモリを節約できる |
cv::drawContours(..., -1, ...) |
インデックスを -1 にすると全輪郭を一括描画 |
引数と戻り値
void cv::findContours(
InputOutputArray image, // [入出力] 8ビット2値画像(処理で内容が変わる場合あり)
OutputArrayOfArrays contours, // [出力] 検出した輪郭(Point の vector の vector)
OutputArray hierarchy, // [出力] 輪郭の親子関係(Vec4i の vector)
int mode, // 輪郭取得モード(RetrievalModes)
int method, // 輪郭近似方法(ContourApproximationModes)
Point offset = Point() // 輪郭座標に加算するオフセット(省略可)
);
mode(RetrievalModes)
| 定数 | 説明 |
|---|---|
cv::RETR_EXTERNAL |
最外輪郭のみ。物体の外形だけ欲しいときに使う |
cv::RETR_LIST |
全輪郭をフラットなリストで返す。階層不要のときに効率的 |
cv::RETR_CCOMP |
2階層(外輪郭 + 穴)で返す |
cv::RETR_TREE |
全階層を完全なツリー構造で返す |
method(ContourApproximationModes)
| 定数 | 説明 |
|---|---|
cv::CHAIN_APPROX_NONE |
輪郭上の全ピクセルを保持(点数が多くなる) |
cv::CHAIN_APPROX_SIMPLE |
直線部の端点のみ保持(実務では最もよく使う) |
cv::CHAIN_APPROX_TC89_L1 |
Teh-Chin チェーン近似(より積極的に点を削減) |
実践例
例1: 面積フィルタで小さなノイズ輪郭を除去する
ノイズが多い画像をそのまま2値化すると、微小な輪郭が大量に検出されます。cv::contourArea で面積閾値を設けて除去するのが実務での定番パターンです。
#include <opencv2/opencv.hpp>
#include <iostream>
int main()
{
cv::Mat src = cv::imread("noisy_shapes.png", cv::IMREAD_GRAYSCALE);
if (src.empty()) {
std::cerr << "画像を読み込めませんでした\n";
return 1;
}
cv::Mat binary;
cv::threshold(src, binary, 128, 255, cv::THRESH_BINARY);
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(binary, contours, hierarchy,
cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
// 面積 200px² 以上の輪郭だけ残す
const double minArea = 200.0;
std::vector<std::vector<cv::Point>> filtered;
for (const auto& c : contours) {
if (cv::contourArea(c) >= minArea) {
filtered.push_back(c);
}
}
std::cout << "全輪郭数: " << contours.size()
<< " / フィルタ後: " << filtered.size() << "\n";
cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);
cv::drawContours(dst, filtered, -1, cv::Scalar(0, 200, 0), 2);
cv::imwrite("filtered.png", dst);
return 0;
}
例2: バウンディングボックスで各物体を囲む
輪郭から cv::boundingRect で矩形を取り出し、cv::rectangle で描画します。物体の個数カウントや ROI 切り出しの前処理として頻繁に使います。
#include <opencv2/opencv.hpp>
#include <iostream>
int main()
{
cv::Mat src = cv::imread("objects.png", cv::IMREAD_GRAYSCALE);
if (src.empty()) {
std::cerr << "画像を読み込めませんでした\n";
return 1;
}
cv::Mat binary;
cv::threshold(src, binary, 128, 255, cv::THRESH_BINARY);
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(binary, contours, hierarchy,
cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);
for (size_t i = 0; i < contours.size(); ++i) {
if (cv::contourArea(contours[i]) < 100.0) continue; // 微小輪郭をスキップ
cv::Rect bbox = cv::boundingRect(contours[i]);
cv::rectangle(dst, bbox, cv::Scalar(255, 128, 0), 2);
// 番号をバウンディングボックスの左上に描画
cv::putText(dst, std::to_string(i),
bbox.tl(), cv::FONT_HERSHEY_SIMPLEX, 0.6,
cv::Scalar(255, 128, 0), 2);
}
cv::imwrite("bbox.png", dst);
std::cout << "bbox.png を保存しました\n";
return 0;
}
実行すると、各物体を青いバウンディングボックスで囲み、インデックス番号を付けた bbox.png が保存されます。
つまずきポイント
⚠️ 入力画像は「8ビット2値画像」でなければならない
cv::findContours に渡す画像は 8ビット(CV_8U)でなければなりません。cv::Canny や cv::threshold の出力は CV_8U なので問題ありませんが、CV_8UC3(カラー)や CV_32F を渡すと実行時に assertion エラーが発生します。
OpenCV Error: Assertion failed (image.type() == CV_8UC1 || image.type() == CV_8S C1)
カラー画像を渡す場合は事前に cv::cvtColor でグレースケール変換し、cv::threshold や cv::Canny で2値化してから渡してください。
⚠️ OpenCV 4.x から関数シグネチャが変わっている
OpenCV 3.x では戻り値として cv::Mat が返っていました(image, contours, hierarchy を含む戻り値)。OpenCV 4.x では void 型 に変わっており、シグネチャは (image, contours, hierarchy, mode, method) の5引数です。古いサンプルコードをコピーすると error: too many arguments や型不一致でコンパイルエラーになります。
⚠️ hierarchy は省略できないが内容を使わないケースが多い
hierarchy は内部輪郭(穴)の親子関係を追うときにしか使いません。cv::RETR_EXTERNAL を使う場合は中身を参照する必要はほぼありませんが、引数として宣言して渡すことは必須です。渡し忘れるとコンパイルエラーになります。ダミーとして宣言するだけでかまいません。
関連する関数
- cv::Canny — エッジ画像から輪郭検出する場合は
cv::thresholdの代わりにこちらを使うことも多い。cv::Canny の使い方【OpenCV/C++】〜エッジ検出の基本と閾値の決め方〜 - cv::morphologyEx — 2値化後のモルフォロジー処理でノイズを整形してから
findContoursに渡すと検出精度が上がる。cv::morphologyEx の使い方【OpenCV/C++】〜オープニング・クロージングでノイズ除去〜 - cv::erode / cv::dilate — モルフォロジー処理の基本。輪郭検出前の前処理として頻出。cv::erode / cv::dilate の使い方【OpenCV/C++】〜収縮・膨張処理の基本と実践〜
- cv::drawContours —
findContoursの結果を画像に描画する。本記事でも使用 - cv::contourArea — 輪郭の面積を計算。面積フィルタに必須
- cv::boundingRect — 輪郭を囲む最小矩形を取得
まとめ
cv::findContours は2値画像から輪郭の座標列を取得する関数です。cv::RETR_EXTERNAL + cv::CHAIN_APPROX_SIMPLE の組み合わせが実務では最も頻繁に使われます。cv::contourArea による面積フィルタや cv::boundingRect によるROI抽出と組み合わせることで、物体検出・計数・切り出しの前処理として幅広い場面で活用できます。
🛠 画像処理のプロが開発するSDK/API
本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。

