Unity+OpenCVに挑戦してみた
今回はUnityとOpenCVを利用して、WebCameraで顔を検出して、その位置に指定した画像を移動させるということをやってみた。もう少し雑に言うと攻殻機動隊の笑い男のようなことがUnity+OpenCVで出来ないか挑戦。
実行環境
Windows 11 Pro
Unity 2022.3.7f1 LTS
OpenCV plus Unity 1.7.1
OpenCV plus Unityのインストール
Unity Asset StoreよりOpenCV plus Unityをインストール
メニューよりEdit > Project Settings > Player > Other Settings → Script ComplilationのAllow 'unsafe' Codeをチェック
実際のシーンを作成する
新規で作成し、まずはHierarchyからUI > RawImageとUI > Imageを作成。
任意の画像をプロジェクトフォルダにコピーし、InspectorタブからTexture TypeをSprite (2D and UI)を選択。笑い男の画像を利用するのはあんまり良くない気がしたので、フリー素材のキツネのアイコン画像を使用している。
こちらの画像を先ほど追加したImageのSource Imageに指定。
WebCameraから顔を検出して画像を移動させるコードがこちら。一番苦労したのは画像を顔が検出された位置に移動させる箇所で、Unityの座標系の扱いに慣れていないため画面のサイズや比率によって画像が顔の位置からズレてしまっていた。色々と総当たり的に試して現在のコードになって、位置ズレの問題は解消したが、まだ完璧にUnityの座標系は理解出来ていないのが実情。
using OpenCvSharp;
using OpenCvSharp.Demo;
using UnityEngine;
using UnityEngine.UI;
public class CascadeRecognizer : WebCamera
{
public TextAsset faces;
public Image mask;
public RectTransform canvasRectTransform;
private CascadeClassifier cascadeFaces;
protected override void Awake()
{
base.Awake();
// classifier
FileStorage storageFaces = new FileStorage(faces.text, FileStorage.Mode.Read | FileStorage.Mode.Memory);
cascadeFaces = new CascadeClassifier();
if (!cascadeFaces.Read(storageFaces.GetFirstTopLevelNode()))
{
throw new System.Exception("FaceProcessor.Initialize: Failed to load faces cascade classifier");
}
}
protected override bool ProcessTexture(WebCamTexture input, ref Texture2D output)
{
Mat image = OpenCvSharp.Unity.TextureToMat(input);
Mat gray = image.CvtColor(ColorConversionCodes.BGR2GRAY);
Mat equalizeHistMat = new Mat();
Cv2.EqualizeHist(gray, equalizeHistMat);
OpenCvSharp.Rect[] rawFaces = cascadeFaces.DetectMultiScale(gray, 1.1, 6);
for (int i = 0; i < rawFaces.Length; i++)
{
//Cv2.Rectangle((InputOutputArray)image, rawFaces[i], Scalar.LightGreen, 2);
//顔検出位置の座標の計算
var cx = rawFaces[i].TopLeft.X + (rawFaces[i].Width / 2f);
var cy = rawFaces[i].TopLeft.Y + (rawFaces[i].Height / 2f);
//顔検出された座標をカメラ画像のViewport座標系に変換
Vector2 viewportPos = new Vector2(cx / gray.Width, 1 - cy / gray.Height);
//Viewport座標をCanvas内の座標に変換
Vector2 canvasPos = new Vector2(viewportPos.x * canvasRectTransform.sizeDelta.x, viewportPos.y * canvasRectTransform.sizeDelta.y);
mask.GetComponent<RectTransform>().position = canvasPos;
}
output = OpenCvSharp.Unity.MatToTexture(image);
return true;
}
}
上記のコードをRawImageのオブジェクトにアタッチして、Inspetorよりpublic変数の値をそれぞれ設定する。
実際に動かしてみたデモ動画がこちら。