ラベリング3度目です。好きですね。露骨に作者の需要が現れています。
OpenCV3.0では、cv::connectedComponents
という待望のラベリング(連結成分の抽出)関数が追加されました。
- ・OpenCV: Structural Analysis and Shape Descriptors
- ・New functionality in OpenCV 3.0 | PACKT Books
- ・OpenCVを使ったラベリング · atinfinity/lab Wiki · GitHub
この関数を3.0.0.20150819版以降のラッパーでサポートしましたので、それを紹介します。
好みかもしれませんが、素のcv::connectedComponents
は、あまり使いやすい設計とは思えません。そこで、昔から添付しているOpenCvSharp.Blob
に使い勝手を近づけたラッパーも作成しました。それも紹介します。
使い方
元画像サンプル
準備
過去の記事同様ですが、二値化等の手法により、ラベル付けしたい領域が白、その他が黒である画像を用意します。
Mat src = new Mat("shapes.png", ImreadModes.Color); Mat gray = src.CvtColor(ColorConversionCodes.BGR2GRAY); Mat binary = gray.Threshold(0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary);
ラベリング実行
準備した二値画像から、Cv2.ConnectedComponents
により実行します。
Mat label = new Mat(); int nLabels = Cv2.ConnectedComponents(binary, label, PixelConnectivity.Connectivity8, MatType.CV_32SC1);
label
は入力画像と同じサイズで、ラベル付けされた座標はその数値、されなかった座標は0が入っています。戻り値はラベルの個数-1
です。
たとえば以下のようにして、ラベルごとに色分けした画像を作成できます。
Scalar[] colors = Enumerable.Range(0, nLabels+1).Select(_ => Scalar.RandomColor()).ToArray(); colors[0] = Scalar.Black; int rows = binary.Rows; int cols = binary.Cols; var dst = new Mat(rows, cols, MatType.CV_8UC3); var labelIndexer = label.GetGenericIndexer<int>(); var dstIndexer = dst.GetGenericIndexer<Vec3b>(); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { int labelValue = labelIndexer[y, x]; dstIndexer[y, x] = colors[labelValue].ToVec3b(); } }
ConnectedComponentsWithStats
ラベルごとのさらに詳しい情報を得られるメソッドも用意されています。Cv2.ConnectedComponentsWithStats
です。重心・面積・外接矩形の座標が得られます。
以下は、Matの型を明示してみました。仕様は、この記事の最初に挙げた記事を参照してください。
var label = new MatOfInt(); var stats = new MatOfInt(); // 行:ラベル数 列:5 var centroids = new MatOfDouble(); // 行:ラベル数 列:2 (x,y) var nLabels = Cv2.ConnectedComponentsWithStats(binary, label, stats, centroids, PixelConnectivity.Connectivity8, MatType.CV_32SC1);
例えば、ラベルごとの矩形はこのように取れます。
var statsIndexer = stats.GetGenericIndexer<int>(); for (int i = 0; i < nLabels; i++) { var rect = new Rect { X = statsIndexer[i, 0], Y = statsIndexer[i, 1], Width = statsIndexer[i, 2], Height = statsIndexer[i, 3] }; }
ラッパー Cv2.ConnectedComponentsEx の使い方
以上のように、情報は全てMatから引っ張る仕様で、インデックスは仕様を見ないと分からず(一応enumがありますが)、直感的ではありません。そこで、OpenCvSharp.Blob
に近づけたCv2.ConnectedComponentsEx
を作成しました。
ラベリング実行
Mat src = new Mat("data/shapes.png", ImreadModes.Color); Mat gray = src.CvtColor(ColorConversionCodes.BGR2GRAY); Mat binary = gray.Threshold(0, 255, ThresholdTypes.Otsu | ThresholdTypes.Binary); ConnectedComponents cc = Cv2.ConnectedComponentsEx(binary);
ConnectedComponents
に、WithStatsで取れる情報込みでラベル情報が入っています。
各ラベルへのアクセス
ConnectedComponents.Labels
から取得できます。int[,]
型で、入力画像と同じサイズです。
for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int labelValue = cc.Labels[y, x]; } }
各連結成分へのアクセス
適当な画像に、連結成分の外接矩形を描いていくサンプルです。Blobs
プロパティから連結成分オブジェクトにアクセスでき、WithStatsで取れる重心・矩形・面積の情報が得られます。
cv::connectedComponents
は、背景領域をラベル値0の1つのblobとして扱う仕様なので、大抵は最初の要素を読み飛ばすと意図通りになりそうです。
foreach (var blob in cc.Blobs.Skip(1)) { img.Rectangle(blob.Rect, Scalar.Red); }
希望の連結成分の絞り込みは、Linqを使うなどしましょう。以下は最大面積のblobを求める例です。(これも、普通は背景が最大なので、最初をSkipしないといつも背景が返ります。)
ConnectedComponents.Blob maxBlob1 = cc.Blobs.Skip(1).OrderByDescending(b => b.Area).First(); ConnectedComponents.Blob maxBlob2 = cc.GetLargestBlob(); // OpenCvSharp.Blob相当の便利メソッド
デバッグ用の描画
ラベル値から地道に描くのは面倒なので、ヘルパメソッドを用意しています。
var dst = new Mat();
cc.RenderBlobs(dst);
指定したラベル部分のみを切り抜く
FilterByBlob
で可能です。複数あるなら FilterByBlobs
で。これもラベルのMatから地道にするのはやや面倒なので用意しました。
var maxBlob = cc.GetLargestBlob();
var filtered = new Mat();
cc.FilterByBlob(src, filtered, maxBlob);
FilterByLabel
FilterByLabels
により、Blobオブジェクトではなくラベルの数値でフィルタをかけることもできます。
おわりに
たぶん楽ですので、Cv2.ConnectedComponentsEx
をぜひお使いください。
もう少しcv::connectedComponents
ががんばってくれてたら...と思いますが、用意してくれただけまず感謝します。