cv::drawContours の使い方【OpenCV/C++】〜輪郭を描画する〜
cv::drawContours は、cv::findContours で取得した輪郭データを画像上に描画する関数です。全輪郭の一括描画・特定輪郭の指定・塗りつぶしまで、1関数で対応できます。最小の呼び出し形は次のとおりです。
cv::drawContours(dst, contours, -1, cv::Scalar(0, 255, 0), 2);
動作環境
- OpenCV 4.x
- コンパイル例:
g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)
環境構築がまだの方は環境構築ガイドを先にどうぞ。
基本の使い方
グレースケール化→二値化→輪郭検出→描画という、実務で最もよく使うパターンです。
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
int main()
{
// 元画像の読み込み
cv::Mat src = cv::imread("input.png");
if (src.empty()) {
std::cerr << "画像の読み込みに失敗しました" << std::endl;
return -1;
}
// グレースケール変換 → 二値化
cv::Mat gray, binary;
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
cv::threshold(gray, binary, 127, 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 = src.clone();
// 全輪郭を緑色・線幅2で描画(contourIdx = -1 で全描画)
cv::drawContours(dst, contours, -1, cv::Scalar(0, 255, 0), 2);
cv::imwrite("output.png", dst);
std::cout << "輪郭数: " << contours.size() << std::endl;
return 0;
}
実行結果:
輪郭数: 5
output.png には、検出された全オブジェクトの外縁が緑色の線で描かれた画像が保存されます。
主要な行の解説:
cv::RETR_EXTERNAL— 最外郭の輪郭のみ取得。内側の穴は無視しますcv::CHAIN_APPROX_SIMPLE— 直線上の中間点を省略して頂点のみ保持するため、メモリ効率が良いcontourIdx = -1— 全輪郭を一括描画。特定インデックスを指定すると1本だけ描画できますcv::Scalar(0, 255, 0)— BGR 順で緑色- 第6引数の
2— 線幅(px)
引数と戻り値
| 引数 | 型 | 説明 |
|---|---|---|
image |
cv::InputOutputArray |
描画対象の画像(上書き)。8ビット or 浮動小数点 |
contours |
cv::InputArrayOfArrays |
findContours が返す輪郭点列のベクタ |
contourIdx |
int |
描画する輪郭のインデックス。-1 で全輪郭 |
color |
cv::Scalar |
描画色(BGR) |
thickness |
int |
線幅(px)。cv::FILLED(= -1)で塗りつぶし |
lineType |
int |
線種。cv::LINE_8(デフォルト)/ cv::LINE_AA など |
hierarchy |
cv::InputArray |
findContours の階層情報。省略可 |
maxLevel |
int |
描画する輪郭の階層の深さ。デフォルト INT_MAX(全階層) |
offset |
cv::Point |
全頂点に加算するオフセット。デフォルト (0,0) |
戻り値: void
実践例
例1: 特定の輪郭だけ描画する(面積フィルタ付き)
小さなノイズ輪郭を除き、面積が一定以上の輪郭だけを描画するパターンです。
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat src = cv::imread("input.png");
if (src.empty()) {
std::cerr << "画像の読み込みに失敗しました" << std::endl;
return -1;
}
cv::Mat gray, binary;
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
cv::threshold(gray, binary, 127, 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 = src.clone();
for (int i = 0; i < static_cast<int>(contours.size()); ++i) {
double area = cv::contourArea(contours[i]);
if (area < 500.0) continue; // 面積 500px² 未満は無視
// 輪郭を1本ずつインデックス指定で描画
cv::drawContours(dst, contours, i, cv::Scalar(0, 0, 255), 2);
}
cv::imwrite("output_filtered.png", dst);
return 0;
}
面積フィルタを挟むことで、二値化後に残る細かなノイズ輪郭を除外できます。ノイズ自体を事前に消したい場合は cv::morphologyEx によるオープニング処理が効果的です。
例2: 輪郭を塗りつぶしてマスク画像を作る
thickness に cv::FILLED(-1)を渡すと輪郭内部を塗りつぶします。ROI マスクの生成などに使えます。
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat src = cv::imread("input.png");
if (src.empty()) {
std::cerr << "画像の読み込みに失敗しました" << std::endl;
return -1;
}
cv::Mat gray, binary;
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
cv::threshold(gray, binary, 127, 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 mask = cv::Mat::zeros(src.size(), CV_8UC1);
// 全輪郭を白(255)で塗りつぶし
cv::drawContours(mask, contours, -1, cv::Scalar(255), cv::FILLED);
cv::imwrite("mask.png", mask);
return 0;
}
実行すると、検出した全オブジェクト領域が白く塗られた mask.png が保存されます。この mask を cv::bitwise_and などと組み合わせれば、背景を除いた領域だけを処理できます。
エッジ検出で輪郭を取りたい場合は cv::Canny の使い方【OpenCV/C++】 も参照してください。
つまずきポイント
⚠️ findContours 後に元画像が変わっていた(OpenCV 3 からの移行)
OpenCV 3 以前の cv::findContours は入力画像を破壊することがありました。OpenCV 4.x では入力を変更しない実装になっていますが、念のため binary.clone() を渡す習慣をつけておくと安全です。
⚠️ contourIdx に範囲外のインデックスを渡すとクラッシュする
cv::drawContours(dst, contours, 99, ...); // contours.size() が 5 なら NG
ループ内でインデックスを使う場合は static_cast<int>(contours.size()) と比較して範囲チェックを必ず行ってください。
⚠️ 描画先がグレースケール画像(CV_8UC1)なのにカラー Scalar を渡す
cv::Scalar(0, 255, 0) を 1 チャネル画像に使うと緑にはならず、B チャネルの 0 だけが適用されます(アサーションエラーになるケースもあります)。グレースケール画像に描画するときは cv::Scalar(255) のように1値で指定するか、事前に cv::cvtColor でBGRに変換してください。
関連する関数
cv::findContours—drawContoursの入力となる輪郭データを生成する。セットで使う必須関数cv::contourArea— 輪郭の面積を取得。面積フィルタと組み合わせると効果的cv::boundingRect— 輪郭を囲む矩形を取得。cv::rectangleと組み合わせてバウンディングボックス描画に使う- cv::Canny の使い方【OpenCV/C++】 — エッジ検出結果を
findContoursに渡すことで輪郭取得できる - cv::morphologyEx の使い方【OpenCV/C++】 — 二値化画像のノイズ除去に使い、輪郭検出の精度向上に貢献する
まとめ
cv::drawContours は cv::findContours とセットで使う描画関数です。contourIdx = -1 で全輪郭一括描画、インデックス指定で1本描画、thickness = cv::FILLED で塗りつぶしと、引数の組み合わせだけで多くの用途に対応できます。面積フィルタなどを組み合わせれば、実務レベルの物体検出パイプラインの出力可視化がすぐに実装できます。
🛠 画像処理のプロが開発するSDK/API
本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。

