OpenCvSharpをつかう その12 (ラベリング)

【注意】

この記事の内容は古く、最新のOpenCvSharpでは動作しません。記録としてあえて残しますが、ラベリングについては以下の記事を参照してください。
http://schima.hatenablog.com/entry/2014/01/19/120540


ラップしたときに少しだけ書いていますが(http://d.hatena.ne.jp/Schima/20090404)、改めてまとめておきます。

ラベリング処理はOpenCvSharp.Blob名前空間のクラスを用いることで行えます。利用に当たってはOpenCvSharp.Blob.dllへの参照設定が必要です。

OpenCvSharpではラベリングのライブラリとしてcvblobを利用しています(トップページにリンクも貼って頂いたようですね)。他にラベリングをするライブラリとしてはcvBlobsLibなどがあります。またcvauxにもあるらしいです。cvblobを選んだ理由としては、設計がシンプルでラップしやすかったためです。

環境構築

OpenCvSharp.Blobを使うためには、C++と.NETの橋渡しをするためのOpenCvSharpExtern.dllが必要です。以下のページを参考に、実行ファイルと同じディレクトリに常に存在するよう設定してください。

http://d.hatena.ne.jp/Schima/20090616
http://code.google.com/p/opencvsharp/wiki/Tutorial_Windows

(ラベリングの機能はすべてOpenCvSharpExtern.dllの中に入っているのですが、このDLLはラッパーのほかの部分でも使いまわしているためにml210.dllやcvaux210.dllといったOpenCVのDLLにも依存しています。どうせ使わないだろうとcv, cxcore, highguiだけ用意してml, cvauxが見つからないという場合にはP/Invokeのエラーが出てしまいます。全てのOpenCVのDLLが存在するところにPATHを通す、またはそれらDLLを実行ファイルの場所に全て持ってくるのが必要なことに注意してください。)

対象とする画像の準備

ラベリングする画像は、blobとするところが白で、その他の部分が黒の2値画像である必要があります。たいていの入力画像はそうではないので、変換をする必要があります。

例えば以下のような画像に対しラベリングする場合を考えます(cvblobのサンプルから借用)。


以下のように、グレースケールへの変換と二値化を行ってラベリングできる画像へ変換します。なおここで、imgSrc は入力画像、imgBinary は imgSrc と同じサイズで8ビット1チャンネルの画像です。

Cv.CvtColor(imgSrc, imgBinary, ColorConversion.BgrToGray);
Cv.Threshold(imgBinary, imgBinary, 100, 255, ThresholdType.Binary);

もしこの変換では2値画像の白黒が反対になってしまう場合は、Cv.Notで画素値を反転するか、Cv.Thresholdで ThresholdType.BinaryInv を用いて二値化をするかして対処します。

ラベリングの実行

あらかじめ CvBlobsインスタンスを作成しておいたうえで、Labelメソッドを呼び出します。なお、ラベリングにはデータ保存用の画像が一つ必要です。

IplImage imgLabel = new IplImage(imgSrc.Size, CvBlobLib.DepthLabel, 1);
CvBlobs blobs = new CvBlobs();
blobs.Label(imgBinary, imgLabel);

CvBlobsの中には検出されたblobが CvBlob としてすべて格納されています。CvBlobsは std::map のtypedefで、OpenCvSharpではIDictionaryとしてメソッドを公開しています。すべてのblobに対し処理したいときは foreach で簡単にできます。

foreach(KeyValuePair<uint, CvBlob> item in blobs)
{
    ...
}

もしラベルの数値が要らなければ、Valuesに対しイテレータを回せばblobだけを見ることができます。

foreach(CvBlob item in blobs.Values)
{
    ...
}

blobのデータ

CvBlobには様々なプロパティがあり、それによって検出されたblobの情報を取得することが出来ます。

全ては説明しませんが、例えばCentroidプロパティではblobの重心を求めることが出来ます。

foreach(CvBlob item in blobs.Values)
{
    CvPoint2D64f centroid = item.Centroid;
    Console.WriteLine(centroid);
}

Rectプロパティではblobの外接矩形が得られます。

foreach(CvBlob item in blobs.Values)
{
    img.Rectangle(item.Rect, CvColor.Red);
}

ラベリング結果の描画

RenderBlobs メソッドを使います。imgDst は 入力画像と同じサイズの8ビット3チャンネル画像です。

blobs.RenderBlobs(imgLabel, imgSrc, imgDst);

面積でフィルタリング

FliterByArea メソッドを使います。指定した下限・上限の面積の範囲外のblobを削除します。

blobs.FilterByArea(1000, 5000);

上限または下限だけを設定したいときは、uint.MaxValueやuint.MinValueを設定すればできます。以下のコードは、面積が1000未満のblobを削除します。

blobs.FilterByArea(1000, uint.MaxValue);

残ったblobのみを画像に描画

上述のFilterAreaの他にも、CvBlobsはIDictionaryですからRemoveメソッドを使うことでも1つ1つのblobを削除することができます。このようにして条件に合致するblobのみを残した後、それのみを画像に描画するには、FilterLabels を使います。

blobs.FilterLabels(imgLabel, imgDst);

ここでの imgDst は2値画像です。

最大の面積のblobを得る

GreaterBlob メソッドにより、最大の面積のblobのラベルが得られます。これをキーとしてCvBlobsから値を取り出せば、対応するblobが得られます。

uint label = blobs.GreaterBlob();
CvBlob max = blobs[label];

サンプルコード

using System;
using System.Collections.Generic;
using OpenCvSharp;
using OpenCvSharp.Blob;

class BlobTest
{
    static void Main(string[] args)
    {
        using (IplImage imgSrc = new IplImage("shapes.png", LoadMode.Color))
        using (IplImage imgBinary = new IplImage(imgSrc.Size, BitDepth.U8, 1))
        using (IplImage imgLabel = new IplImage(imgSrc.Size, CvBlobLib.DepthLabel, 1))
        using (IplImage imgDst = new IplImage(imgSrc.Size, BitDepth.U8, 3))
        {
            Cv.CvtColor(imgSrc, imgBinary, ColorConversion.BgrToGray);
            Cv.Threshold(imgBinary, imgBinary, 100, 255, ThresholdType.Binary);
            using (CvBlobs blobs = new CvBlobs())
            {
                blobs.Label(imgBinary, imgLabel);
                foreach (var item in blobs)
                {
                    Console.WriteLine("{0} | Centroid:{1} Area:{2}", item.Key, item.Value.Centroid, item.Value.Area);
                }
                blobs.RenderBlobs(imgLabel, imgSrc, imgDst);
            }

            using (new CvWindow("cvblob test", imgDst))
            {
                Cv.WaitKey(0);
            }
        }
    }
}