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

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

イメージは以下のとおりです．
![eye.jpg](https://qiita-image-store.s3.amazonaws.com/0/239014/d34ca0b7-0308-9d57-e255-7c2510e91d98.jpeg)
画像元：[https://vod-halloffame.com/anime/2660.html](https://vod-halloffame.com/anime/2660.html)

出来上がったものがこちら．
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">死神の目を手に入れました．寿命半分になりました． <a href="https://t.co/7fZYWO9o4F">pic.twitter.com/7fZYWO9o4F</a></p>&mdash; 敬也 (@takaya901) <a href="https://twitter.com/takaya901/status/1083333785519677440?ref_src=twsrc%5Etfw">January 10, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

本名と寿命を推定するのはさすがに難しいので，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でカメラ映像に処理をして表示する流れは[この記事](https://qiita.com/takaya901/items/daf777d0dc290b6ead4f)に書いています．
<div align="center">
<img width="400" alt="Screen Shot 2019-01-10 at 20.43.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/239014/828bd762-5852-eb58-a889-b52f40179a33.png">
</div>

#実装方法

##FaceAPIの利用設定
Azureのサブスクリプションとリソースの作成は以下の記事を参考にさせていただきました．

- [Azure の従量課金制のサブスクリプションを作ってみた！](https://qiita.com/m_maru76/items/fd462324cfec623eef3e#23-azure-サブスクリプション-契約)
- [夏季休暇だったのでAzureのFaceAPIを使ってみた](https://qiita.com/y4u0t2a1r0/items/b1a69de8104fa23f19a2)

サブスクリプションに関しては，無料試用版を以前使い果たしたので従量課金制のものを取得しましたが，FaceAPIのリソースはFree版を選択しました．Free版のプラン内容は[Cognitive Services の価格 - Face API](https://azure.microsoft.com/ja-jp/pricing/details/cognitive-services/face-api/)に書いてあります．

##FaceAPIにデータを送信する
[Azure Face REST APIとC#のドキュメント](https://docs.microsoft.com/ja-jp/azure/cognitive-services/face/quickstarts/csharp)のソースコードを参考にしました．
ウェブカメラの映像をQuadに映し，画面タッチされたらQuadのテクスチャをEncodeToJPG()でバイト配列に変換して送信します．
年齢と顔の位置だけ取れればいいので，requestParametersは以下のように設定しました．

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

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

```json
[
   {
      "faceId": "4ba77ed4-d523-4d97-871a-96856a1b72c8",
      "faceRectangle": {
         "top": 128,
         "left": 189,
         "width": 202,
         "height": 202
      },
      "faceAttributes": {
         "age": 22.0
      }
   }
]
```
解析にはUnityのJsonUtilityを使ったのですが，どうも解析に失敗するので調べたところ以下のサイトが参考になりました．

- [LitJSONからUnityのJsonUtilityに移行したときに気になったこと](http://blog.cluster.mu/2016/08/jsonutility_instead_of_litjson/)

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

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

```cs
[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;
}
```

##年齢の表示
それっぽい雰囲気を出すために，[こちらのサイト](http://mnlab.sakura.ne.jp/garden/death.html)のDEATH NOTE風フォントを利用させていただきました．
OpenCVで画像に書き込むほうが楽なのですが，好きなフォントを使えないのでuGUIのTextを使用しました．

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

OpenCVで検出した顔の位置をもとに画像（Mat）上の表示位置を計算し，それをUnityのワールド座標に変換してから，RectTransformUtility.WorldToScreenPoint()でCanvas上に配置します（[参考サイト](http://tsubakit1.hateblo.jp/entry/2016/03/01/020510)）．

あと，画面全体を赤くする処理は[こちら](https://qiita.com/takaya901/items/d9e1da34b7a9dbfeca4c)に書きました．

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