結局はOpenCVが最強だった話

こんちゃ。洋梨🍐です。

以前こんな記事を書いたのですが、結局はOpenCVを使ったほうが正確かつ楽であるという事が分かったという事を書きたいと思います。

Unityで動かしている様子
(なお、眼鏡付けてみても動作しました👼)

UnityでOpenCVを使うには

UnityでOpenCVの使い方を調べるとまず一番多く出てくるのはきっと「OpenCV For Unity」を使う方法だと思いますが、お金がかかってしまうので今回は別の方法でやります。

NuGetでOpenCV Sharp を取得

まずNuGetで “OpenCV Sharp” を取得します。

とは言ってもUnityでNuGetは使えないのでインストールできるプラグインを用いるか直接DLLをAssetsに入れるかします。

ソースコード作成

OpenCVの使い方はここを見ればかなりわかりますのでこのサイトを見てから進めると楽々です。

すべて書くとキリがないので抽象的に書いていきます。

基本

・Using OpenCvSharp;

・OpenCVで画像を扱う形式はMat形式

・今回例として使うMatTypeは8UC3,8UC1(グレー スケール用)

Webカメラの映像をUnityで読み込む

ここは全く難しくなく、普通にWebCamTextureを使います。

// 例 //
 WebCamTexture wTex;
 wTex = new WebCamTexture(WebCamTexture.devices[0].name,320,240);
 wTex.Play();

こんな感じです。ここが既にわからない人はこちらを参考にどうぞ。

Unity Texture を OpenCV Mat に変換

public static Mat ToMat8UC3(WebCamTexture tex)
{
    int x = tex.width;
    int y = tex.height;

    Mat mat = new Mat(y, x, MatType.CV_8UC3);
    Color32[] c32 = tex.GetPixels32();
    int x_ = 0, y_ = y - 1;
    for (int i = 0; i < x * y; i++, x_++;
    {
        if (x_ >= x)
        {
            x_ = 0;
            y_--;
        }
        Vec3b v3b = new Vec3b(c32[i].r, c32[i].g, c32[i].b);
        mat.Set<Vec3b>(y_, x_, v3b);
    }
    return mat;
}

OpenCVで扱える形式はMat形式になるので変換します。

なお、今回Alpha情報は不要の為 RGB(8*3byte) が保存できる 8UC3形式に変換してます。※Texture側でGrayScaleに変換してから8UC1でもいいかも

【注意】ここで気をつけないといけないことはUnity Textureでの座標系とOpenCV Matでの座標系が異なる点です。

座標系の違い

また、Unityでよく使われるColorは使わないほうがいいです。キャストに時間がかかりかなーーーーり動作が遅くなります。(なりました。) Color32 を使いましょう。

GrayScaleに変換

今回顔の特徴を認識するのに使うフィルターはグレースケールの画像を使うのでまずグレースケールに変換します

Mat baseMat;
Mat grayMat = baseMat.CvtColor(ColorConversionCodes.BGR2GRAY);

フィルターを取得・保存

ここからがメインです。まずフィルターのデーターを入手します。

フィルターのデーター(xml)はOpenCV公式のGithubから入手できます。

入手後、保存したファイルのパスを指定します。

CascadeClassifier cFace, cEye;

cFace = new CascadeClassifier("D:\\haarcascade_frontalface_alt.xml");
cEye = new CascadeClassifier("D:\\haarcascade_eye.xml");         

上例では(D:\\)に保存した例です。各自環境に合わせてください。

アプリに取り組みたい場合は起動時サーバーからローカルストレージにダウンロードするとかするといいかもしれませんね。

※また読み込むのは一度で良いので動画での使用などでループ文に入れないように注意です。if (cFace == null) などで読み込みましょう。

フィルターの適用

その後先ほど読み込んだフィルターを適用します。

    const double FACTOR_SCALE_FACE = 1.01; // 顔:しきい値
    const double FACTOR_SCALE_EYE = 1.01; // 目:しきい値
    const int MIN_SIZE = 5;

    public void Run()
    {
        foreach (OpenCvSharp.Rect rect in cFace.DetectMultiScale(grayMat, FACTOR_SCALE_FACE, MIN_SIZE)){

            var baseMatIn = grayMat[rect.Y, rect.Y + rect.Height, rect.X, rect.X + rect.Width]; // [Row_ , Col_]
            grayMat.Rectangle(rect, Scalar.White, 2); // 描画 デバック用

            foreach (OpenCvSharp.Rect inRect in cEye.DetectMultiScale(baseMatIn, FACTOR_SCALE_FACE, MIN_SIZE))  // Eye Rect
            {
                Point fPoint = new Point(rect.X + inRect.X, rect.Y + inRect.Y);
                Point ePoint = new Point(rect.X + inRect.X + inRect.Width, rect.Y + inRect.Y + inRect.Height);

                grayMat.Rectangle(fPoint, ePoint, Scalar.White, 1); // 描画2 デバック用
            }
        }
    }

取得した座標を表示

先ほどのフィルターで取得した情報を後は表示するだけです。

表示するだけならMatに対してRectangle(rect,Scalar.color)で囲みを描画し、Unity Texture に戻し表示するか座標を元に Unity Texture2D に描画し重ねて表示するのが手っ取り早いと思います。

まとめ

OpenCVをUnityで使ってみてまず思ったのは「考えてたより画像処理が早い」ということだった。320×240で処理してもカメラのフレームレート30fps がしっかり出せる(実行環境:core i7 7700k)のはいいのではないか?と感じました。

結構面白かったのでまた時間がある時 OpenCVを使った何かを作ろうと思います~^


ホームページでは他にも

・様々な記事や作った作品および過程
・ソースコード、素材ファイル
・あらゆる”モノ”の作り方

などなど随時、記事や作品を新規公開・更新していますので是非見ていってくださいね!見ていただけると本当に嬉しいです!