はじめに
こんにちは、避雷です。お仕事の関係でOpenCV叩いてAR顔ハメパネルを作る、みたいな案件に取り組んだのでその時のノウハウを提供します。ちなみに案件自体は提案したコンサルの先生が渋い顔をしたので消滅しました。合掌。
用いる技術
OpenCV plus Unity
基本的に高価なイメージのある(出来が良いので仕方なくは有りますが)OpenCV x Unity系のAssetの救世主、OpenCV plus Unityくんです。OpenCVsharp(OpenCVをC#で使えるようにしたライブラリ)をベースに、Unityに適応させたブツです。基本的にテクスチャ→画像処理用の変数→加工→テクスチャに戻すというステップで画像を加工します。
https://assetstore.unity.com/packages/tools/integration/opencv-plus-unity-85928
Unity
言わずと知れたゲームエンジンくん。説明不要でしょう。今回取り扱う部分ではバージョンを特にこだわる必要はないと思います(もちろんOpenCVplusUnityの対応範囲内で)
試しに顔認識部分を動かしてみよう
アセットのインポート
OpenCVplusUnityをAssetStoreで開いてダウンロード、インポートします。
https://assetstore.unity.com/packages/tools/integration/opencv-plus-unity-85928
UnsafeCodeを許可する
もしこの時点で警告が出たら、Unsafeなコードを許可してあげる必要があります。
BuildSettings→PlayerSettings→OtherSettings→Configuration→Allow 'Unsafe' Codeをチェックすると動くようになります。
ウェブカメラがあるか確認する
今回はカメラから画像を取り込んで、顔認識して、顔の部分を四角で囲む、みたいなサンプルなのでウェブカメラが存在しないとそもそも動きません。PCに内蔵してあるカメラかElecom当たりのUSBでつなぐウェブカメラとかを買いましょう。(最安で1000円ぐらいで買えそう)
BuildSettingsにSceneを加える
次にOpenCV+Unity/Demo内にあるLobby.sceneというシーンを開きます。これは各種サンプルにつながるハブの役割を果たすシーンなのですが、BuildSettingsで移動先のシーンを追加しないとシーン間の移動ができません、追加しましょう。今回は顔認識のサンプルだけ確認できればいいのでFaceDetector.sceneだけ追加。
サンプルシーンを起動する
早速サンプルシーンを起動しましょう。LobbyシーンからFaceDetectorのボタンを押すとカメラの視界が出てきて、その中にオタクの顔が四角い線でハイライトされていれば成功です。こんな感じ。
じゃあ、顔の部分だけ切り抜こう
ここからが本記事独自の内容です。これより上は多分他の記事でも書いてある気がします。
サンプルをコピーする
折角うまく動いているサンプル(FaceDetector.cs)があるのでこれを参考に作っていきます。と言っても顔の部分を切り抜くだけなら全体を把握する必要はなくて、顔認識の結果を処理しているProcessTexture
の部分だけに注目すればよいです。
protected override bool ProcessTexture(WebCamTexture input, ref Texture2D output)
{
// detect everything we're interested in
processor.ProcessTexture(input, TextureParameters);
// mark detected objects
processor.MarkDetected();
// processor.Image now holds data we'd like to visualize
output = Unity.MatToTexture(processor.Image, output); // if output is valid texture it's buffer will be re-used, otherwise it will be re-created
return true;
}
(このコードでやっていること自体は、ウェブカメラの映像からテクスチャを取得→顔認識をかける→結果をもとに顔を四角くマークする、というもの(WebCamera.csを継承する形で最終画像を出力するprocess部分を書き換えている))
コレを少しずつ書き換えて動かしていきます。
顔が何個あるか取得する
顔の個数はprocessor.Faces.Count
で取得します。Facesクラス内にそれぞれの顔のデータが入っています。
顔がある矩形範囲を取得する
顔を含む長方形の範囲の各頂点(右上、左上、右下、左下)のテクスチャ上の座標(この座標はテクスチャのピクセル上の物で、UnityのTransformではない)は それぞれ
processor.Faces[i].Region.TopRight
processor.Faces[i].Region.TopLeft
processor.Faces[i].Region.BottomRight
processor.Faces[i].Region.BottomLeft
で取得することができます。今回は長方形の各辺がx軸とy軸に平行なので右上と左下の座標がわかれば、元の長方形を再現することができます。これらの情報をキープして切り抜くときに使いましょう。
OpenCVの座標をTexture2Dの座標に直す
ここ、僕個人が滅茶苦茶詰まったところなんですけど、OpenCVで使われている座標系とUnityのTexture2Dで使われている座標系は原点と各軸の正の向きが違います。 しょうがないのでこれらを変換しましょう。
afterPos.x = beforePos.x;
afterPos.y = targetTexture.height - beforePos.y;
テクスチャを切り出す
Texture2Dには特定の範囲のpixelを転写する便利な関数 GetPixels(起点座標x,起点座標y,幅,高さ)
があり、これがそのままトリミングの機能を果たしてくれます。超便利。これを使って元画像からピクセル列を取り出しそれを新しいテクスチャに貼り付けることでトリミングが可能になります。この際にトリミング後の画像サイズの情報が必要になるのでそれも用意しましょう。実装例のTrimmingTexureです。右上と左下のポイントを指定するとそれらを頂点とした矩形範囲を切り取ってTexture2Dとして切り出してくれます。
public class TrimmingTexture
{
private Vector2Int pointRightTop;
private Vector2Int pointLeftBottom;
private Texture2D inputTex;
public TrimmingTexture(Vector2Int pointRightTop, Vector2Int pointLeftBottom,Texture2D inputTex)
{
this.pointRightTop = pointRightTop;
this.pointLeftBottom = pointLeftBottom;
this.inputTex = inputTex;
}
public Texture2D Trim ()
{
var tw = pointRightTop.x - pointLeftBottom.x;
var th = pointRightTop.y - pointLeftBottom.y;
var result = new Texture2D(tw,th);
var pixels = inputTex.GetPixels(pointLeftBottom.x, pointLeftBottom.y,tw,th);
result.SetPixels(pixels);
result.Apply();
return result;
}
}
トリミング成功!
コレで顔の部分を追いかけ続けるテクスチャが完成しました。後はmaterialの_MainTextureに貼り付けるなどすると好きなモデルに貼り付けたりできます。空きフレームの線形補完とかトラッキングが外れたときの処理とかを入れるともっとそれらしくなるかもしれません。綺麗にUV展開したモデルに貼り付けると、顔ハメパネルを作ることもできます
さいごに
コレで顔の切り抜きを取得することができたので、何か好きなものの顔ハメパネルでもつくれば良さそう。