OpenCV plus Unityで画像の背景を動的に削除する方法


やりたいこと

らくがきARの様なアプリを作りたいと思ったので、今回は手始めにOpenCVを使ってUnityにインポートした画像の背景を削除(透明化)します。
ソースコードは以下にアップロードしてあります。
https://github.com/AzetaTakuya/MakeImageBackgroundTransparentUsingOpenCV

結果

こんな感じ

左から【元画像 -> グレースケール化 -> 二値化 -> マスク化 -> 背景削除】といった工程から画像の背景を透明化することができました。

実装

環境

OpenCV plus Unity を試す

まずUnityプロジェクトを作成し、AssetStoreからOpenCV plus Unityをインポートします。

インポートが完了すると、エラー[ error CS0227: Unsafe code may only appear if compiling with /unsafe. Enable "Allow 'unsafe' code" in Player Settings to fix this error.]が出るかと思います。

unsafeコードを使用するので 【File -> BuildSettings -> PlayerSettings -> Player -> OtherSettings -> Allow 'unsafe' Code】 にチェックを入れます。
エラーがなくなったので、早速OpenCVplusUnityのデモを見てみます。
今回は画像の輪郭を抽出したりしたいので、それに近いデモである【Assets -> OpenCV+Unity -> Demo -> Identifiy_Contours_by_Shape -> ContoursByShapeScene.scene】を開きます。
実行結果はこんな感じ。

白黒画像に色とテキストが付きました。

このデモを元に画像の背景を透明化します。

画像の背景を削除

画像をインポート

Unityに画像ファイルをインポートします。そのままの状態では読み込めない為、Read/Write Enabledにチェックを入れます。

コード

元画像(m_texture)から
【元画像 -> グレースケール化画像 -> 輪郭抽出画像 -> マスク化画像 -> 背景透明化画像】を表示します。

namespace OpenCvSharp.Demo
{
    using UnityEngine;
    using System.Collections;
    using OpenCvSharp;
    using UnityEngine.UI;
    using System.Threading.Tasks;
    using System.Collections.Generic;

    public class MakeImageBackgroundTransparentUsingOpenCV : MonoBehaviour
    {
        #region public members
        public Texture2D m_texture;

        public RawImage m_image_origin;
        public RawImage m_image_gray;
        public RawImage m_Image_binarization;
        public RawImage m_image_mask;
        public RawImage m_image_backgroundTransparent;

        public double v_thresh = 180;
        public double v_maxval = 255;
        #endregion

        private void Start()
        {
            #region load texture
            Mat origin = Unity.TextureToMat(this.m_texture);
            m_image_origin.texture = Unity.MatToTexture(origin);
            #endregion

            #region  Gray scale image
            Mat grayMat = new Mat();
            Cv2.CvtColor(origin, grayMat, ColorConversionCodes.BGR2GRAY);
            m_image_gray.texture = Unity.MatToTexture(grayMat);
            #endregion

            #region Find Edge
            Mat thresh = new Mat();
            Cv2.Threshold(grayMat, thresh, v_thresh, v_maxval, ThresholdTypes.BinaryInv);
            m_Image_binarization.texture = Unity.MatToTexture(thresh);
            #endregion

            #region Create Mask
            Mat Mask = Unity.TextureToMat(Unity.MatToTexture(grayMat));
            Point[][] contours; HierarchyIndex[] hierarchy;
            Cv2.FindContours(thresh, out contours, out hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxNone, null);
            for(int i = 0; i < contours.Length; i++)
            {
                Cv2.DrawContours(Mask, new Point[][] { contours[i] }, 0, new Scalar(0, 0, 0), -1);
            }
            Mask = Mask.CvtColor(ColorConversionCodes.BGR2GRAY);
            Cv2.Threshold(Mask, Mask, v_thresh, v_maxval, ThresholdTypes.Binary);
            m_image_mask.texture = Unity.MatToTexture(Mask);
            #endregion

            #region TransparentBackground
            Mat transparent = origin.CvtColor(ColorConversionCodes.BGR2BGRA);
            unsafe
            {
                byte* b_transparent = transparent.DataPointer;
                byte* b_mask = Mask.DataPointer;
                float pixelCount = transparent.Height * transparent.Width;

                for (int i = 0; i < pixelCount; i++)
                {
                    if (b_mask[0] == 255)
                    {
                        b_transparent[0] = 0;
                        b_transparent[1] = 0;
                        b_transparent[2] = 0;
                        b_transparent[3] = 0;
                    }
                    b_transparent = b_transparent + 4;
                    b_mask = b_mask + 1;
                }
            }
            m_image_backgroundTransparent.texture = Unity.MatToTexture(transparent);
            #endregion
        }

    }
}

シーン

上のスクリプトをアタッチするGameObjectと5つのRawImageを用意します。
MakeImageBackgroundTransparentUsingOpenCVにインポートした画像とRawImageをアタッチします。

実行

実行すると【元画像 -> グレースケール化画像 -> 輪郭抽出画像 -> マスク化画像 -> 背景透明化画像】が表示されます。

※うまくいかなかった場合はv_threshの値を弄ってみてください。

まとめ

  • ARアプリにガンガン使っていけそう。
  • 白背景の画像以外も対応出来る様にする必要がある。

参考