SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

OpenCvSharpでGrabCutを使った領域抽出

久しぶりに、OpenCVネタ。

OpenCvSharpを使って、grubcutでの領域抽出をやってみました。
OpenCVに用意されているGrabCut関数を使うと、画像中の前景/背景領域を簡単に抽出できます。

GrabCutは、「InitWithRect」「InitiWithMask」という二種類の方法で、抽出領域の指定ができます。
それぞれの方法を試してみました。

全コードは、↓のリポジトリに上げてます。

矩形の範囲指定での領域抽出

GrabCut関数の引数でGrabCutModes.InitWithRectを用いると、前景となるオブジェクトが含まれる部分を矩形で指定して抽出を行います。

        private static void GrabCutWithRect()
        {
            using (var img = new Mat(@"Images/1.jpg"))
            using (var mask = new Mat(img.Size(), MatType.CV_8UC3))
            {
                // 領域指定でGrabCutを実行
                var bmModel = new Mat();
                var fgModel = new Mat();
                Cv2.GrabCut(img, mask, new Rect(115, 20, 630, 625), bmModel, fgModel, 5, GrabCutModes.InitWithRect);

                // 前景(1)/おそらく前景(3)とラベリングされた部分⇒1、それ以外は0のマスク画像を作る
                var lut2 = new byte[256];
                lut2[0] = 0; lut2[1] = 1; lut2[2] = 0; lut2[3] = 1;
                Cv2.LUT(mask, lut2, mask);

                // 前景部分を抽出
                var foreground = new Mat(img.Size(), MatType.CV_8UC3, new Scalar(0, 0, 0));
                img.CopyTo(foreground, mask);
                Cv2.ImShow("GrabCut(InitWithRect)", foreground);
            }
        }

マスク画像を使った領域抽出

今度は、InitWithMaskです。
このオプションでは、マスク用の画像を用いた抽出を行います。 マスク画像では、各ピクセル値で以下のように指定します。

意味
0 背景
1 おそらく背景
2 前景
3 おそらく前景

こういう、0,1,2,3だけの値で構成された画像を、ペイントなどのアプリで書くのはとても面倒です。
⇒というか、0~3の色の違いでは、ディスプレイ上ではほとんど区別がつきません。。。

ということで、まず以下のようなマスク画像を用意しました。
f:id:minami_SC:20181008234737p:plain

で、これをプログラムで読み込んでから、以下のようにピクセル値を置換してからGrabCut関数に渡しています。

  • 0・・・たぶん背景の部分⇒2
  • 255・・・たぶん前景の部分⇒3

コードは以下のような感じ。

        private static void GrabCutWithMask()
        {
            using (var img = new Mat(@"Images/1.jpg"))
            using (var mask = new Mat(@"Images/1_mask.png", ImreadModes.GrayScale))
            {
                var lut = Enumerable.Repeat<byte>(2, 256)
                                    .ToArray();
                // マスク画像として用意したbmpの各ピクセル値を以下のように変換する
                // 黒(0)      ⇒おそらく背景(2)
                // 白(255)    ⇒おそらく前景(3)
                lut[0] = (byte)2;
                lut[255] = (byte)3;
                Cv2.LUT(mask, lut, mask);

                // マスク画像を用いてGrabCutを実行
                var bmModel = new Mat();
                var fgModel = new Mat();
                Cv2.GrabCut(img, mask, Rect.Empty, bmModel, fgModel, 5, GrabCutModes.InitWithMask);

                // 前景(1)/おそらく前景(3)とラベリングされた部分⇒1、それ以外は0のマスク画像を作る
                var lut2 = new byte[256];
                lut2[0] = 0; lut2[1] = 1; lut2[2] = 0; lut2[3] = 1;
                Cv2.LUT(mask, lut2, mask);

                // 前景部分を抽出
                var foreground = new Mat(img.Size(), MatType.CV_8UC3, new Scalar(0, 0, 0));
                img.CopyTo(foreground, mask);
                Cv2.ImShow("GrabCut(InitWithMask)", foreground);
            }
        }

実行すると、それぞれの方法での抽出結果が各ウィンドウに表示されます。

この画像が
f:id:minami_SC:20181008234623j:plain

こうなる!!
f:id:minami_SC:20181008234644j:plain

このマスク画像作成用のUIでも別途作れば、 お手軽に画像切り抜きアプリなども作れそうですね。

今日のとこは、この辺までで。