OpenCV for Unity + Face APIで死神の目を手に入れる


はじめに

ARやVRを用いて漫画やアニメの再現をするのが好きで,OculusGoで進撃の巨人のVRゲームを作ったりしています.今回は僕が大好きな「DEATH NOTE」から,相手の本名と寿命を見ることができる死神の目を,寿命を半分捧げることなく手に入れたいと思いスマホアプリとして製作しました.

イメージは以下のとおりです.

画像元:https://vod-halloffame.com/anime/2660.html

出来上がったものがこちら.


本名と寿命を推定するのはさすがに難しいので,Microsoftが提供しているFaceAPIを使って年齢を推定して表示するようにしました.

[2019/4/27 追記]
画面タッチから年齢表示までラグがあるため,ちょっとした演出を加えました.
https://www.youtube.com/watch?v=tG7eG8N4YuM

システム概要

リアルタイムで年齢を推定,つまり毎フレームFaceAPIにデータを送信するとFree版の上限にすぐ達してしまうため,画面をタッチしたらデータを送って年齢推定を行うという形にしました.OpenCVでカスケード分類器による顔検出を毎フレーム行い,タッチされたときに顔を1つも検出していなければ「No Face」と表示し,FaceAPIには送りません.

FaceAPIから結果が返ってきたら,OpenCVで検出した顔の上に年齢を表示します.毎フレーム検出することでカメラや被写体がある程度動いても追従して表示できます.

顔の検出と画面を赤くするためにOpenCV for Unityを使用しました.
OpenCV for Unityでカメラ映像に処理をして表示する流れはこの記事に書いています.



実装方法

FaceAPIの利用設定

Azureのサブスクリプションとリソースの作成は以下の記事を参考にさせていただきました.

サブスクリプションに関しては,無料試用版を以前使い果たしたので従量課金制のものを取得しましたが,FaceAPIのリソースはFree版を選択しました.Free版のプラン内容はCognitive Services の価格 - Face APIに書いてあります.

FaceAPIにデータを送信する

Azure Face REST APIとC#のドキュメントのソースコードを参考にしました.
ウェブカメラの映像をQuadに映し,画面タッチされたらQuadのテクスチャをEncodeToJPG()でバイト配列に変換して送信します.
年齢と顔の位置だけ取れればいいので,requestParametersは以下のように設定しました.

string requestParameters = "returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=age"

JSONの解析

FaceAPIが顔検出した結果は以下のようなJSON形式のstringで返ってきます.

[
   {
      "faceId": "4ba77ed4-d523-4d97-871a-96856a1b72c8",
      "faceRectangle": {
         "top": 128,
         "left": 189,
         "width": 202,
         "height": 202
      },
      "faceAttributes": {
         "age": 22.0
      }
   }
]

解析にはUnityのJsonUtilityを使ったのですが,どうも解析に失敗するので調べたところ以下のサイトが参考になりました.

JsonUtilityはルートがArrayのJSONをパースできないようなので,上記サイトに倣ってJsonHelperクラスを作成しました.

解析結果を以下のFaceクラスのインスタンスに格納します.

[Serializable]
public class Face
{
    public string faceId;
    public FaceRectangle faceRectangle;
    public FaceAttributes faceAttributes;
}

[Serializable]
public class FaceRectangle
{
    public int top;
    public int left;
    public int width;
    public int height;
}

[Serializable]
public class FaceAttributes
{
    public int age;
}

年齢の表示

それっぽい雰囲気を出すために,こちらのサイトのDEATH NOTE風フォントを利用させていただきました.
OpenCVで画像に書き込むほうが楽なのですが,好きなフォントを使えないのでuGUIのTextを使用しました.

年齢を推定した後は,ある程度カメラが動いても年齢表示が追従するようにします.FaceAPIが返す結果は顔の位置と年齢がセットになっているので,
OpenCVで検出した各顔に対して,FaceAPIで検出した顔の中で重心が最も近いものを探し,それに対応する年齢を顔の上に表示します.

OpenCVで検出した顔の位置をもとに画像(Mat)上の表示位置を計算し,それをUnityのワールド座標に変換してから,RectTransformUtility.WorldToScreenPoint()でCanvas上に配置します(参考サイト).

あと,画面全体を赤くする処理はこちらに書きました.

おわりに

UnityとOpenCVの組み合わせはやはり強力です.赤い画面とフォントによってかなり雰囲気が出ていると思います.