OpenCVで透明人間


先日,透明人間になって写真や動画を撮影できるAndroidアプリ「Invisible Camera」をリリースしました.OpenCV for Unityで作りました.せっかくなのでOpenCVで透明人間になる方法をメモとして残します.

なお,下記ソースコードは以下のようにusing staticでクラス名を省略しています.

using static OpenCVForUnity.Imgproc;
using static OpenCVForUnity.Core;

HSV画像に変換

GaussianBlur(webcamMat, blurred, new Size(7, 7), 0);
cvtColor(blurred, hsv, COLOR_RGBA2RGB);
cvtColor(hsv, hsv, COLOR_RGB2HSV);

webcamMatはカメラ映像をMatに変換したものです.OpenCV for Unityでは,WebCamTextureToMatHelperというクラスのGetMat()メソッドを使うとカメラ映像をRGBAのMatとして取得できます.別にRGBでも構いません.

後述のinRangeメソッドが使えるように,入力画像をHSVに変換します.ノイズ除去はお好みで.
COLOR_RGBA2RGBはないのでRGBを経由しています.

肌と髪の後ろにある背景を抽出する

//肌領域が255,それ以外が0のマスクを作成
//Hが0~180で循環しているので,両端とも取り出す
inRange(hsv, SKIN_LOWER1, SKIN_UPPER1, skinMask1);
inRange(hsv, SKIN_LOWER2, SKIN_UPPER2, skinMask2);

//髪(黒)領域が255,それ以外が0のマスクを作成
bitwise_or(skinMask1, skinMask2, skinMask1);
inRange(hsv, HAIR_LOWER, HAIR_UPPER, hairMask);

//肌と髪の領域が255のマスクを作成
bitwise_or(skinMask1, hairMask, skinAndHairMask);

//肌と髪の領域と同じ位置の背景を切り出す
bitwise_and(bg, bg, bgOnSkinAndHair, skinAndHairMask);

bgは背景(Background)のRGBA画像です.

inRangeメソッドで指定した範囲内の画素を255(白),範囲外の画素を0(黒)としたマスクを作成します.範囲は以下のように設定しました.

Scalar SKIN_LOWER1 = new Scalar(  0,  30,  88); //H(0~180)の左側  
Scalar SKIN_UPPER1 = new Scalar( 25, 173, 255);             
Scalar SKIN_LOWER2 = new Scalar(160,  30,  88); //H(0~180)の右側
Scalar SKIN_UPPER2 = new Scalar(180, 173, 255);            
Scalar HAIR_LOWER  = new Scalar(  0,   0,   0);                   
Scalar HAIR_UPPER  = new Scalar(255, 150,  90);              

bitwise_orは,ざっくり言うと2つのマスクの0でない領域を足し合わせます.

bitwise_andは,本来は2つの画像の両方で0でない領域だけを抽出するメソッドです.上記コードでは同じ画像のandを取っているのでそれだけだとbgがbgOnSkinAndHairにコピーされるだけですが,第4引数にマスク画像を指定することでマスク画像で0でない領域のみ抽出することができます.これはbitwise_orでも同じです.

skinAndHairMaskとbgOnSkinAndHairはそれぞれ以下のような画像になります(毎回撮り直しているので少しずれてますが気にしないでください).

カメラ映像から肌と髪以外の領域を抽出する

bitwise_not(skinAndHairMask, skinAndHairMask);
bitwise_and(webcamMat, webcamMat, withoutSkinAndHair, skinAndHairMask);

skinAndHairMaskを反転してからカメラ映像に適用することで,髪と肌以外の領域を抽出します.反転後のskinAndHairMaskとwithoutSkinAndHairは以下のようになります.

肌と神の領域を背景で置換

bitwise_or(withoutSkinAndHair, bgOnSkinAndHair, invisible);

withoutSkinAndHairの0領域にbgOnSkinAndHairの0でない領域がはめ込まれるイメージです.invisibleは以下のようになります.