Cv2.ConnectedComponentsWithStats の使い方【OpenCV/C#(OpenCvSharp)】〜ラベリング処理で連結領域を解析する〜
Cv2.ConnectedComponentsWithStats は、2値画像の連結領域(blob)にラベルを付け、各領域の面積・外接矩形・重心をまとめて返す関数です。FindContours との使い分けでよく問われますが、面積でフィルタリングしたい・全領域を一括で数値化したいシーンではこちらが素直に書けます。
最小の呼び出し形は次の通りです。
int nLabels = Cv2.ConnectedComponentsWithStats(
binary, labelMap, stats, centroids,
connectivity: PixelConnectivity.Connectivity8, ltype: MatType.CV_32S);
動作環境
- OpenCvSharp4(OpenCV 4.x 系)
- NuGet:
OpenCvSharp4+OpenCvSharp4.runtime.win(Windows 実行時) - .NET 8 コンソールアプリ想定
環境構築がまだの方は環境構築ガイドを先にどうぞ。
基本の使い方
黒背景に白い矩形を3つ描いたテスト画像を使い、各領域の統計情報を取得して結果を画像に描画します。
using OpenCvSharp;
// ---- テスト画像を生成(実運用では Cv2.ImRead に差し替え) ----
using var src = new Mat(300, 400, MatType.CV_8UC1, Scalar.Black);
Cv2.Rectangle(src, new Rect(20, 20, 80, 60), Scalar.White, -1);
Cv2.Rectangle(src, new Rect(150, 50, 120, 40), Scalar.White, -1);
Cv2.Rectangle(src, new Rect(60, 180, 50, 90), Scalar.White, -1);
// ---- ラベリング ----
using var labelMap = new Mat(); // ラベル画像(CV_32S)
using var stats = new Mat(); // 統計情報(nLabels × 5)
using var centroids = new Mat(); // 重心(nLabels × 2)
int nLabels = Cv2.ConnectedComponentsWithStats(
src, labelMap, stats, centroids,
connectivity: PixelConnectivity.Connectivity8,
ltype: MatType.CV_32S);
// ---- 結果を表示(ラベル 0 は背景なので 1 から走査) ----
using var disp = new Mat(src.Size(), MatType.CV_8UC3, Scalar.Black);
for (int i = 1; i < nLabels; i++)
{
// stats は ConnectedComponentsTypes の列インデックスで参照
int x = stats.At<int>(i, (int)ConnectedComponentsTypes.Left);
int y = stats.At<int>(i, (int)ConnectedComponentsTypes.Top);
int w = stats.At<int>(i, (int)ConnectedComponentsTypes.Width);
int h = stats.At<int>(i, (int)ConnectedComponentsTypes.Height);
int area = stats.At<int>(i, (int)ConnectedComponentsTypes.Area);
double cx = centroids.At<double>(i, 0);
double cy = centroids.At<double>(i, 1);
// ランダムな色で外接矩形と重心を描画
var color = new Scalar(60 * i % 256, 120 * i % 256, 200 * i % 256);
Cv2.Rectangle(disp, new Rect(x, y, w, h), color, 2);
Cv2.Circle(disp, new Point((int)cx, (int)cy), 4, color, -1);
Console.WriteLine($"Label {i}: area={area}, rect=({x},{y},{w},{h}), center=({cx:F1},{cy:F1})");
}
Cv2.ImWrite("result.png", disp);
Console.WriteLine("result.png に保存しました。");
実行すると次のような出力が得られます。
Label 1: area=4800, rect=(20,20,80,60), center=(59.5,49.5)
Label 2: area=4800, rect=(150,50,120,40), center=(209.5,69.5)
Label 3: area=4500, rect=(60,180,50,90), center=(84.5,224.5)
result.png に保存しました。
コードの要点
| 行 | 解説 |
|---|---|
Cv2.ConnectedComponentsWithStats(...) |
連結成分解析を実行。戻り値はラベル数(背景込み) |
labelMap |
各画素にラベル番号が書き込まれた CV_32S の Mat |
stats |
行 = ラベル、列 = Left/Top/Width/Height/Area の5列 |
centroids |
行 = ラベル、列 = X座標・Y座標(double) |
i = 1 から走査 |
ラベル 0 は必ず背景。1 以上が前景領域 |
ConnectedComponentsTypes 列挙体 |
列インデックスをマジックナンバーで書かずに済む |
引数と戻り値
| 引数名 | 型 | 説明 |
|---|---|---|
image |
Mat |
入力2値画像(CV_8U) |
labels |
Mat |
出力ラベル画像(CV_32S 推奨) |
stats |
Mat |
出力統計情報行列(nLabels × 5) |
centroids |
Mat |
出力重心行列(nLabels × 2、double) |
connectivity |
PixelConnectivity |
連結性。PixelConnectivity.Connectivity4 または Connectivity8(既定 Connectivity8) |
ltype |
MatType |
ラベル画像の型。CV_32S(既定)または CV_16U |
| 戻り値 | int |
検出ラベル数(背景込み)。前景は 戻り値 - 1 個 |
ConnectedComponentsTypes 列インデックス
| 定数名 | 値 | 意味 |
|---|---|---|
Left |
0 | 外接矩形の左端 X |
Top |
1 | 外接矩形の上端 Y |
Width |
2 | 外接矩形の幅 |
Height |
3 | 外接矩形の高さ |
Area |
4 | 面積(画素数) |
実践例
面積でノイズ除去して大きな領域だけを取り出す
実務では「面積が小さい領域は外来ノイズ」として除去する処理が頻出です。
using OpenCvSharp;
// グレースケール画像を読み込んで2値化
using var gray = Cv2.ImRead("input.png", ImreadModes.Grayscale);
using var binary = new Mat();
Cv2.Threshold(gray, binary, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
using var labelMap = new Mat();
using var stats = new Mat();
using var centroids = new Mat();
int nLabels = Cv2.ConnectedComponentsWithStats(
binary, labelMap, stats, centroids);
// 面積閾値以上の領域だけ残したマスクを生成
int areaThreshold = 500;
using var filtered = new Mat(binary.Size(), MatType.CV_8UC1, Scalar.Black);
for (int i = 1; i < nLabels; i++)
{
int area = stats.At<int>(i, (int)ConnectedComponentsTypes.Area);
if (area < areaThreshold) continue;
// labelMap の該当ラベル画素を抽出して filtered に合成
using var mask = labelMap.Equals(i); // 対象ラベルのマスク
Cv2.BitwiseOr(filtered, mask, filtered);
}
Cv2.ImWrite("filtered.png", filtered);
Console.WriteLine($"前景領域: {nLabels - 1} → 閾値通過: 確認は filtered.png で");
⚠️
labelMap.Equals(i)はMatの要素比較を返し、対象ラベルの画素が 255 になるマスクを生成します。OpenCvSharp 固有の書き方です。
外接矩形を使って各領域を個別にクロップする
using OpenCvSharp;
using var src = Cv2.ImRead("input.png", ImreadModes.Grayscale);
using var binary = new Mat();
Cv2.Threshold(src, binary, 128, 255, ThresholdTypes.Binary);
using var labelMap = new Mat();
using var stats = new Mat();
using var centroids = new Mat();
int nLabels = Cv2.ConnectedComponentsWithStats(binary, labelMap, stats, centroids);
for (int i = 1; i < nLabels; i++)
{
int x = stats.At<int>(i, (int)ConnectedComponentsTypes.Left);
int y = stats.At<int>(i, (int)ConnectedComponentsTypes.Top);
int w = stats.At<int>(i, (int)ConnectedComponentsTypes.Width);
int h = stats.At<int>(i, (int)ConnectedComponentsTypes.Height);
// ROI でクロップして個別保存
using var roi = new Mat(src, new Rect(x, y, w, h));
Cv2.ImWrite($"crop_{i}.png", roi);
}
各領域の外接矩形を直接使えるのが ConnectedComponentsWithStats の強みです。FindContours + BoundingRect の組み合わせと比べてコードが短くまとまります(Cv2.BoundingRect の使い方も参考にしてください)。
つまずきポイント
⚠️ stats / centroids の Mat を使い回しても型が変わる
stats は CV_32S(int)、centroids は CV_64F(double)で出力されます。At<T> の型を間違えると 実行時例外(アクセス違反) になります。
// NG: stats を double で読む
double x = stats.At<double>(i, 0); // クラッシュの原因
// OK
int x = stats.At<int>(i, (int)ConnectedComponentsTypes.Left);
double cx = centroids.At<double>(i, 0);
⚠️ ラベル 0 は背景・処理対象は 1 から
ConnectedComponentsWithStats は背景もラベル 0 として必ず含みます。for (int i = 0; ...) から始めると背景の統計値を誤って処理する原因になります。常に i = 1 から走査してください。
⚠️ labelMap の型指定ミス
ltype を省略すると CV_32S になります。領域数が少ない場合に CV_16U を指定してメモリを節約することも可能ですが、At<T> で読む型も ushort に合わせる必要があります。型を混在させると値が化けるため、CV_32S 固定で統一するのが無難です。
✅ using による Mat の確実な解放
labelMap / stats / centroids はネイティブヒープを持つ Mat です。using を付け忘れると Dispose されずメモリリークします。本記事のサンプルのように全ての Mat に using を宣言してください。
関連する関数
- Cv2.FindContours — 輪郭ベースの領域抽出。ポリゴン近似が必要な場合はこちら(Cv2.ApproxPolyDP の使い方も参照)
- Cv2.BoundingRect / Cv2.MinAreaRect — 輪郭から外接矩形を求める(使い方はこちら)
- Cv2.ConnectedComponents — Stats・重心が不要な場合の軽量版。ラベル画像だけを返す
- Cv2.ContourArea / Cv2.ArcLength — 輪郭の面積・周長を個別に算出(使い方はこちら)
まとめ
Cv2.ConnectedComponentsWithStats は、2値画像の連結領域を1回の呼び出しでラベリング・面積・外接矩形・重心まで一括取得できる関数です。ノイズ除去や領域クロップなど面積ベースのフィルタリングに特に有効です。stats は CV_32S(int)、centroids は CV_64F(double)という型の違いと、ラベル 0 が背景である点を押さえれば実装上のトラブルはほぼ防げます。
🛠 画像処理のプロが開発するSDK/API
本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/APIを開発しています。SDK は C#/.NET 8 に正式対応しており、OpenCvSharp を使うプロジェクトからそのまま組み込めます。
→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

