OpenCV for Unityで顔の輪郭を検出する
開発準備
検出に使うカスケードファイルを用意します。
輪郭を検出するhaarcascade_frontalface_alt.xmlを用意しました。
カスケードファイルとは顔や目や口などの特定のものを画像から認識し位置を特定するのに使用します。自分でもわかるように訳すとパーツの特徴を学習した内容のファイルです。
OpenCVをダウンロードすることで標準的なカスケードを手に入れることができます。その他特殊なのは検索して探してみてください。 カスケードファイルの追加
顔検出で使えるカスケードをScriptから使えるようにプロジェクトに追加します。
Assets > StreamingAssets > Cascadeにhaarcascade_frontalface_alt.xmlを追加しました。
https://gyazo.com/973a61c06bb399ec4f15c01e5e1b8c88
Scriptの追加
SimpleScript.csをFaceScript.csに置き換えて進めます。
先にカメラ映像の描画のみで動作するかお試しください。
修正点まとめ
検出した顔の取得にListを使うため「using System.Collections.Generic;」を追加
取得したカスケードを保持するためのクラス変数 faceCascadeを用意
Start()メソッドでカスケードの読み込み
Update()メソッドにある「/* 画像加工開始 */」と「/* 画像加工終了 */」の間にカスケードを使用した顔検出の処理を追加
引数による精度の調整
顔検出には「detectMultiScale();」メソッドを使います。
第一引数:CV_8U型のMat、このMatから顔を検出する
第二引数:検出結果を格納するMatOfRect、検出した座標の配列になる。
第三引数:画像スケールにおける縮小量、顔検出の精度を指定。
第四引数:物体候補となる矩形が含まないといけない近傍矩形の数?
第五引数:処理モードを指定?
第六引数:物体として認識する最小サイズ
第七引数:物体として認識する最大サイズ
code:FaceScript.cs
using UnityEngine;
using UnityEngine.UI;
using OpenCVForUnity;
using System.Collections.Generic;
public class FaceScript : MonoBehaviour {
public RawImage rawImage;
private CVCameraMat cvCameraMat;
private Texture2D texture;
private bool startCVCam = false;
private CascadeClassifier faceCascade;
/*--------------------------------
: Default Method
--------------------------------*/
// Use this for initialization
void Start()
{
//カスケードファイルを読み込ませる
string faceCascadePath = Application.streamingAssetsPath + "/Cascade/haarcascade_frontalface_alt.xml";
faceCascade = new CascadeClassifier(faceCascadePath);
if (faceCascade.empty())
{
Debug.LogError("NotFound file:'" + faceCascadePath + "'");
}
//カメラの初期化
Init();
}
// Update is called once per frame
void Update()
{
//カメラ画像の表示先があるか確認
if (rawImage) {
//カメラの取得準備ができているか確認
if (startCVCam) {
//CVCameraMatの初期化が終了している
if (cvCameraMat) {
//CVCameraMatが撮影中か確認
if (cvCameraMat.isPlaying ()) {
//CVCameraMatのフレームが更新されているか確認
if (cvCameraMat.didUpdateThisFrame ()) {
//Texture2Dが初期化されているか確認
if (texture != null) {
//カメラのMat画像を取得
Mat cvCamMat = cvCameraMat.GetMat ();
//Matが取得できているか確認
if (cvCamMat != null) {
//Matが空のデータじゃないか確認
if (!cvCamMat.empty ()) {
/* 画像加工開始 */
if (!faceCascade.empty ()) {
//全体のグレースケールの作成
Mat grayMat = new Mat ();
Imgproc.cvtColor (cvCamMat, grayMat, Imgproc.COLOR_RGBA2GRAY);
//グレースケール画像のヒストグラムの均一化
Mat equalizeHistMat = new Mat ();
Imgproc.equalizeHist (grayMat, equalizeHistMat);
//カスケードで検出
MatOfRect faces = new MatOfRect ();
faceCascade.detectMultiScale (equalizeHistMat, faces, 1.1f, 2, 0 | Objdetect.CASCADE_SCALE_IMAGE, new Size (equalizeHistMat.cols () * 0.13, equalizeHistMat.cols () * 0.13), new Size ());
if (faces.rows () > 0) {
List<OpenCVForUnity.Rect> rectsList = faces.toList ();
for (int i = 0; i < rectsList.ToArray().Length; i++) {
//検出した顔の座標取得
OpenCVForUnity.Rect faceRect = rectsListi; /* 検出した顔に対する処理開始 */
/* 検出した顔に対する処理終了 */
}
}
}
/* 画像加工終了 */
//加工したMatが存在するか確認
if (cvCamMat != null) {
//Matが空のデータじゃないか確認
if (!cvCamMat.empty ()) {
try {
//cvCamMatをTexture2Dに変換して反映する
cvCameraMat.matToTexture2D (cvCamMat, texture);
} catch (System.ArgumentException e) {
Debug.Log (e.Message);
} catch (System.Exception e) {
Debug.Log ("OtherError");
}
}
}
}
cvCamMat = null;
}
}
}
}
}
}
} else {
Debug.LogError("NotFound:rawImage");
}
}
/* 以下SimpleScriptと同じのため省略 */
}
上のScriptに変更して実行、カメラで顔を撮影することで輪郭を検出します。
検出結果は座標(faceRect)として取得することができます。(複数可)
Update()メソッド内の「/* 検出した顔に対する処理開始 */」 と 「/* 検出した顔に対する処理終了 */」の間に描画処理などが発生します。
OpenCVでMatに対しての描画,加工処理は別でまとめてます。
faceRectはMatを基準にした距離が入っています。
顔座標に応じてGameObjectを移動したい場合は3D座標に変換する必要があります。
横や逆さまに映り込んだ顔は検出できません。また、顔の角度も取得できません。(以下2つ、未検証だが方法として記載)
入力Matを回転して検出させた時に一番適切に検出できているものを使用する。
適切の判断基準は検出した顔に対してさらに目や口の検出を行い個数や位置が顔に対して正しいもの。目は2個,口は1つなど...
顔の角度は入力Matを回転させた角度になる
回転した顔に対応したカスケードを使用する。