記事の目次

    こんにちは、Codelight株式会社の高田です。最近デザイナーの梅原 真さんの「おいしいデ」を読みました。とてもおいしいのに知名度がなく、なかなか売れない地方の商品(主に高知県の食料品)にデザインを加えることで商品に力が加わり売れ行きが良くなる、生産者に自信が付き好循環が生まれる。梅原さんが手がけた「おいしいデ」ザインを紹介しています。ここでいうデザインとはパッケージの図案を考えるだけではなくコピー、商品名、ストーリーづくりの全てです。綺麗な見た目だけではなく、本質を見極めることが大切だと改めて感じた本でした。

    TEXT_高田稔則 / Toshinori Takata(Codelight
    EDIT_小村仁美 / Hitomi Komura(CGWORLD)

    <1>OpenCVとは?

    さて、前回の予告通り、OpenCVを使って顔検出を使ったコンテンツのサンプルをつくっていきたいと思います。今回使うのは下記のアセットです(有料)。Webカメラも接続しておいてください。内蔵カメラでも問題ありません。

    ●OpenCV for Unity
    https://assetstore.unity.com/packages/tools/integration/opencv-for-unity-21088

    FaceTracker Example using OpenCV for Unity

    OpenCV(Open Source Computer Vision Library)はコンピュータで画像処理を行うときに使われるデファクトスタンダードのオープンソースライブラリです。1999年にプロジェクトが開始され、20年近い歴史があります。Linux、Android、iOS、Windows、MacOSなど様々な環境で動かすことができます。

    OpenCVは巨大で複雑なライブラリです。Unityで利用できるといってもC#で使うことができるだけなので、ライブラリの利用方法自体は学習が必要ですし、コンピュータビジョンに対する知識も必要になります。日本語のサイトとしてはOpenCV.jpがあるようですが、現在更新がほぼ止まっているようです。

    OpenCVで何ができるのかをつかむためには公式サイトのドキュメントを参照するのもひとつの手ですが、いきなりこれを見てもわからないと思います。ライブラリの実際の使用例やコードのサンプルなどはQiitaのOpenCVコミュニティなどの記事を見る方がわかりやすいでしょう。

    また、OpenCVに関してはたくさんの書籍が出ていますので、できれば「詳解 OpenCV 3 ―コンピュータビジョンライブラリを使った画像処理・認識」(オライリー・ジャパン)や、画像処理に関しては最近出版された「コンピュータビジョン―広がる要素技術と応用―」(共立出版)などに目を通すことをオススメします。書籍を見てみるとかなり難しいと感じる人も多いかもしれません。しかしどのような理論の上に成り立っているものなのかを押さえておくことで、表現の幅が広がると思います。

    <2>OpenCV for Unityを使ってみる

    ではOpenCV for Unityを利用してみましょう。今回使用するUnityのバージョンは2018.2.5.f1です。まずは、以下のように新しいプロジェクトを2Dテンプレートで作成してください。

    2018年8月27日現在、OpenCV for UnityはOpenCV 3.4.1がベースとなっています。OpenCV本体は3.4.2が最新なので、かなり早く追従してくれていると言えます。ここで、OpenCV for Unityを提供しているENOX SOFTWAREが提供している他のアセットも見てみましょう。

    上段右から2番目の「CV VTuber Example」が気になりませんか? これはC++で書かれた機械学習などの問題を解くためのDlibというライブラリを使ってフェイストラッキングを行い、VTuber風にユニティちゃんを動かすサンプルです。

    CVVTuberExample using OpenCV for Unity and Dlib FaceLandmark Detector

    このサンプルのベースとなるのが先ほどの一覧画像の上段左から2番目の「Dlib Face Landmark Detector」アセットです。Dlibのサイトではこちらなどで解説があります。しかし、「Dlib Face Landmark Detector」は有料アセットなので、今回こちらは触れずにOpenCVの基本アセットでできることを進めていきます。興味のある人は調べてみてください。

    ではOpenCV for Unityのライセンスをおもちの方はアセットストアからダウンロードしてインポートを行なってください。Assetsの下にOpenCVForUnityフォルダが作成されます。Examplesフォルダの下にサンプルシーンがありますので、「OpenCVForUnity Example」を実行してみてください。いくつかのサンプル名が表示されますが、何を選択しても何も起こらないと思います。

    これはUnityのシーン管理の問題です。[Build Settings]メニューにビルドするシーンを登録する必要があります。

    Examplesフォルダ内にさらにBasic、Advancedなどのフォルダがあり、その中にそれぞれサンプルシーンが入っています。このシーンを[Build Settings]の[Scene In Build]に追加してください。ドラッグ&ドロップで追加できます。試したいシーンを登録するとビルドに含まれてサンプルが実行できるようになります。個別にサンプルシーンを開いて実行しても結果は同じですが、手軽に切り替えられるので登録して見比べてみてください。

    <3>顔検出のサンプルシーンを実行する

    では改めて「OpenCVForUnity Example」を起ち上げ、Object detectカテゴリの「Face Detection WebCamTexture Example」を実行してみてください。おそらくカメラが起動するだけで何も起こらないと思います。Consoleを見ると以下のエラーが出ているはずです。

    cascade file is not loaded.Please copy from "OpenCVForUnity/StreamingAssets/" to "Assets/StreamingAssets/" folder. 
    UnityEngine.Debug:LogError(Object)
    OpenCVForUnityExample.FaceDetectionWebCamTextureExample:Start() (at Assets/OpenCVForUnity/Examples/MainModules/objdetect/FaceDetectionExample/FaceDetectionWebCamTextureExample.cs:87)
    

    エラー文の指示の通りにOpenCVForUnityフォルダ以下のStreamingAssetsフォルダをAssetsフォルダ直下に移動させ、再度「Face Detection WebCamTexture Example」を実行します。



    • OpenCVForUnityフォルダ以下のStreamingAssetsフォルダ(左)をAssetsフォルダ直下に移動(右)

    カメラが起動し、顔の部分に赤枠が現れたら、顔検出を利用したコンテンツの準備は完了です。

    次ページ:
    <4>顔検出のしくみを探る

    [[SplitPage]]

    <4>顔検出のしくみを探る

    次にこの「Face Detection Example」の中身を見てみます。OpenCVForUnity / Examples / MainModules / objdetect / FaceDetectionExampleフォルダを開いてみてください。以下の3つのスクリプトとシーンがそれぞれあるはずです。

    ・Asynchronous Face Detection Web Cam Texture Example
    ・Face Detection Web Cam Texture Example
    ・Face Detection Example

    「Face Detection Example」は「レナ画像」を使っての顔検出のサンプルです。レナ画像(下記)は画像処理のサンプルとして広く使われている画像です。

    「Asynchronous Face Detection Web Cam Texture Example」は顔検出処理を非同期で行うサンプルです。非同期処理はパフォーマンスを出すために重要な要素なので、今後の記事で説明していきます。

    シーンを細かく見ていくために、「Face Detection Web Cam Texture Example」シーンを開いてください。Quadゲームオブジェクトにアタッチされている「Face Detection Web Cam Texture Example.cs」を開いてください。ダブルクリックで開きます。

    ソースのコメントを見ると、OpenCVの「Cascade Classifier(カスケード型分類器)」サンプルのUnity実装のようで、cv::CascadeClassifierというクラスを使って顔検出を行なっています。

    では、Cascade Classifierとは何でしょう? 「Cascade(カスケード)」とは「数珠つなぎになったもの」を意味します。このしくみについては、先ほど紹介した書籍「コンピュータビジョン―広がる要素技術と応用―」内の9.2.1「顔検出」で解説されています。私ではひと言で説明することができないので解説を引用します。

    Haar-Like特徴量を用いた顔識別器の概要、図中の白丸は弱識別子を表し、上の画像はその弱識別子が用いるHarr-Like特徴量を示す。入力された画像は左の識別器から順に特徴量抽出、識別がされ、顔と識別されたものは次の弱識別器の処理に進む、顔でないと識別されたものはその時点で棄却される。

    一番左の場合は、顔の中心より少し上に、上が明るくて下が暗い横長の領域があればそれを目として認識します。このように目、鼻、口などを調べる検出器を数珠つなぎに繋げて、それが顔かそうでないかを最終的に決定します。この方法は人間の顔の特徴をみていくだけですので、個人を識別することはできません。

    FaceDetectionWebCamTextureExample .csに戻ります。Start関数でhaarcascade_frontalface_alt.xmlを読み込んでいます。これはOpenCVが提供している標準の特徴量のファイルです。細かく見ていくと大変なのでキーとなる部分だけ見てみます。Update関数を見てください(コメントは消してあります)。

    void Update ()
    {
    	if (webCamTextureToMatHelper.IsPlaying () &&
    		webCamTextureToMatHelper.DidUpdateThisFrame ()) {
    
    		Mat rgbaMat = webCamTextureToMatHelper.GetMat();
    		Imgproc.cvtColor(rgbaMat, grayMat, Imgproc.COLOR_RGBA2GRAY);
    		Imgproc.equalizeHist(grayMat, grayMat);
    		if (cascade != null) {
    			cascade.detectMultiScale (grayMat, faces, 1.1, 2, 2, 
    				new Size (grayMat.cols() * 0.2, grayMat.rows() * 0.2), new Size ());
    
    		OpenCVForUnity.Rect[] rects = faces.toArray ();
    		for (int i = 0; i < rects.Length; i++) { 
    			Imgproc.rectangle (rgbaMat, 
    				new Point(rects[i].x, rects[i].y), 
    				new Point(rects [i].x + rects[i].width, rects[i].y + rects[i].height), 
    				new Scalar(255, 0, 0, 255), 2);
    		}
    		Utils.fastMatToTexture2D (rgbaMat, texture);
    	}
    }
    

    上から5行目に下記のような記述があると思います。

    Mat rgbaMat = webCamTextureToMatHelper.GetMat();
    

    ここでは、カメラ画像を取得してOpenCVの画像フォーマットに変換しています。OpenCVで処理を行うためにはMat型で画像をもつことが必要なのです。

    次の2行はモノクロ画像に変換して、明るさを調整しています。

    Imgproc.cvtColor(rgbaMat, grayMat, Imgproc.COLOR_RGBA2GRAY);
    Imgproc.equalizeHist(grayMat, grayMat);
    

    このように、ゼロから実装すると面倒な画像処理が、OpenCVを使うことで簡単に実現できるのです。最後に顔検出の処理に画像を渡すと、facesに結果が返ってきます。

    cascade.detectMultiScale (
    	grayMat, // 入力画像
    	faces, // 検出結果(長方形領域)
    	1.1, // 縮尺比の係数
    	2, 2, // 隣接数の最小
    	new Size(grayMat.cols()*0.2, grayMat.rows()*0.2), // 考慮する最小サイズ
    	new Size ()); //考慮する最大サイズ
    

    ここでは細かなパラメータの説明は省きます。facesはMatOfRect型です。MatOfRect型はOpenCVがもつ型ではありません。OpenCV for Unityで実装されている、矩形を表す型です。Javaのラッパーなども同様の実装があるようです。これで検出した矩形が配列で取得できます。そして以下のループで検出した矩形領域を表示しています。

    for (int i = 0; i < rects.Length; i++) {
    	Imgproc.rectangle(
    		rgbaMat, 
    		new Point(rects[i].x, rects[i].y), 
    		new Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height), 
    		new Scalar (255, 0, 0, 255), 2);
    }
    

    rectsのx、yは検出範囲の左上を表し、widthとheightは幅と高さを示します。画像処理の実装の詳細はわからなくても簡単に顔検出を行うことができました。さらに、Unityで使えるということはそのまま様々な演出を容易に加えていくことが可能なわけです。

    <5>認識した顔に画像をかぶせてみる

    では顔にお面をかぶせてみます。時期的にちょっと早いですが、ジャック・オランタンをかぶってみましょう。Hierarchyを見るとメニューなどはuGUIで作られていますので、uGUIでお面を作っていきます。

    お面用のImageゲームオブジェクトをCanvasの中につくってください。素材はフリー素材配布サイト「Frame illust」の「[ハロウィン]カボチャランタン(ジャックランタン)のイラスト」を使わせてもらいます。

    ProjectのAssetsの下にImageフォルダを作り画像をインポートします。Assetsの下に直接ゲームオブジェクトを置いていくと整理がつかなくなるので、フォルダ構成や命名規則を考えて作業していってください。インポートした画像を選択して、インスペクタのTexture TypeがSprite(2D and UI)になっていることを確認してください。Unityのテンプレートには2Dと3Dがあり、インポート時の挙動がそれぞれちがいます。このプロジェクトは2Dで作っているので、uGUIで使いやすいフォーマットでインポートされます。インポートした画像を、先ほど作ったImageゲームオブジェクトのImageコンポーネントにアタッチします。Set Native Sizeボタンを押して元のサイズにセットしておいてください。

    FaceDetectionWebCamTextureExample.csを少し書き換えます。以下のようにクラスの先頭に2行追加してください。

    public class FaceDetectionWebCamTextureExample : MonoBehaviour
    {
    	[SerializeField] private UnityEngine.UI.Image _mask;
    	[SerializeField] private Vector2 _pos = new Vector2(320, 240);
    

    [SerializeField]属性を指定するとメンバーをprivateのままコンポーネントに表示してくれます。意味なくpublicなメンバーを作りたくないのでこの方法を採ります。

    次にUpdate関数内の結果を表示する部分を以下のように変更します。

    for (int i = 0; i < rects.Length; i++) {
    	var cx = rects[i].x + rects[i].width / 2f;
    	var cy = rects[i].y + rects[i].height / 2f;
    	_mask.transform.localPosition = new Vector3(cx-_pos.x, cy-_pos.y, 0); 
    	break;
    }
    

    これでジャックオランタンを検出領域の中心に移動できます。ここでは検出結果に対して1つだけ描きたいのでBreakで無条件にループを抜けています。エディタに戻るとFaceDetectionWebCamTextureExampleコンポーネントにアトリビュートが追加されます。最後にMaskにImageゲームオブジェクトをアタッチしてください。

    Posの値はジャック・オランタンの表示位置をオフセットするためのものです。それでは実行してみましょう。

    検出された顔にジャック・オランタンが重なりました。OpenCV for Unityのおかげで、Unityそのものの作業自体は非常に簡単だったと思います。このような素晴らしいアセットを使うことができるのもUnityの大きな魅力です。しかし、このままでは検出結果が暴れているのでもう少し動きを押さえ、さらに複数人にも対応できれば表現の幅が広がりそうです。次回では検出ノイズを押さえる方法と複数人に対応する方法を解説したいと思います。



    Profile.

    高田稔則/Toshinori Takata(Codelight)
    Codelight株式会社 代表取締役・インタラクションエンジニア
    フリーランス、株式会社TBSテレビ等で映画CG制作、株式会社ソニー・コンピュータエンタテインメント(現 ソニー・インタラクティブエンタテインメント)でPS4のOSD開発などを経て2006年にCodelight株式会社を設立。インタラクティブコンテンツの制作を中核として、製造業向けのプロトタイプ開発なども行う
    www.codelight.co.jp