HoloLensで始めるCognitive Services（Emotion APIサンプル編）

2017/6/15 update
サンプルプログラムで使用しているエンドポイント
api.projectoxford.ai
は2017/6/12より順次廃止となります。
実装の際には
https://westus.api.cognitive.microsoft.com/emotion/v1.0/recognize
といった新たなエンドポイントの使用を行ってください。
エンドポイントはAzureポータルから確認できます。

---

HoloLensの登場により、外部環境のすべてがシステムのアウトプット領域になりました。
これは同時に外部環境のすべてがインプット領域になったということでもあります。

外部環境を認識・解析し、アプリケーション側のINPUTとすれば、よりユーザーに寄り添ったアプリケーションの作成が可能となるでしょう。

この外部環境解析を行うために、Microsoft AzureのCognitive ServicesをHoloLensで使ってみたいと思います。
今回はEmotion APIを使用してみます。
Unity側のみで実装されていますので、UWP力の無い方でも実装可能です。

##環境
OS：Windows 10 CreatersUpdate
Unity：5.6.0f3
HoloToolkit：1.5.5.0

##完成イメージ
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://t.co/eeZIjoWxT1">https://t.co/eeZIjoWxT1</a><br>のunity-emotion-demoをHoloLens用に手直しして実行した様子。 <a href="https://t.co/lbLB2oa4Jz">pic.twitter.com/lbLB2oa4Jz</a></p>&mdash; morio@6/25～米国 (@morio36) <a href="https://twitter.com/morio36/status/874813274369384448">2017年6月14日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

##０．事前準備
本サンプルを試すためには、Azureのアカウントが必要です。
また、Emotion APIのサブスクリプションも必要となります。
2017年6月時点では無償プランがありますので、
下記の方の記事を参考にアカウント作成、サブスクリプション作成を行ってください。

[人工知能パーツ Microsoft Cognitive Services を使った表情分析アプリを作ろう！[サブスクリプション準備編] 2017年6月版](http://qiita.com/annie/items/251d4b85c86aa9321665)


##１．サンプルを取得～解凍～配置
下記のgithubからサンプルをダウンロードしてください。
https://github.com/misslivirose/unity-cognitive-services-demos

そして\unity-cognitive-services-demos-master
の中の
unity-emotion-demo
を切り取ってローカルドライブの上層のほうに移動してください。
そうしないとパスが長すぎてVisualStudioでのビルド時に高確率でエラーが出ます。

サンプルのスクリプトはおおよそ下記のような責務で実装されています。
- FaceObject.cs　Emotion APIが返してくるFace情報を取り扱うための構造体です。顔の位置や表情の分析値を保持します。
- ImageToEmotionAPI.cs AzureのAPIをコールするメインロジックです。今回はここにHoloLens用のロジックを追記していきます。
- ParseEmotionResponse.cs FaceObjectのリストを保持する構造体です。
- ShowImageOnPanel.cs Unity上のパネルに対してStreamingAssetsから画像を貼り付けるスクリプトです。

##２．実装
**※当記事の最後にgithubのサンプルコードもつけていますので、そちらもご覧ください。**

プロジェクトを開くとコンパイルエラーが出てますが、Build SettingsからWindows StoreにSwitch Pratformすると直ります。
![2.png](https://qiita-image-store.s3.amazonaws.com/0/114630/3037c5dd-7063-ccc9-0020-3e7bf21ad1e4.png)

<ol>
<li>APIキーの設定
まず、Emotion APIのキーをEMOTIONKEYに設定する必要があります。
０．事前準備で作成したEmotion APIのアクセスキーをImageToEmotionAPI.csの10行目にコピペしてください。
</li>
<li>Input/Output実装
で、どういう意図なのかはわからないのですが、
このgithub上のサンプルは「for HoloLens」と謳われているのに、なぜかHoloLens用のInput/Outputなど一切実装されていません。
なのでエアタップの設定周りから整備していく必要があります。
**※キーボードでPキー押下するとサンプル画像を読み込み、Spaceキー押下でAPIコールしますのでCognitive Servicesとの接続確認はこの時点で可能です。ただし、コンソールにしか結果は出力されません。**
</li>
<li>HoloToolkit入れ替え
さらに、このサンプルのHoloToolkitはバージョンが古く、我々がよく知っているHoloToolkitではありません。。
なので、思い切って一度Asset/HoloToolkitを削除してしまいましょう。
![3.png](https://qiita-image-store.s3.amazonaws.com/0/114630/5fc061c9-58f4-fe96-a5f7-022a8cbf7f94.png)
そして代わりにHoloToolkit-Unity-v1.5.5.0.unitypackageをインポートしてください。
![4.png](https://qiita-image-store.s3.amazonaws.com/0/114630/fa07ed73-d760-bd09-e631-422c2b37cc4f.png)
＜HoloToolkit-Unity-v1.5.5.0.unitypackageのありか＞
https://github.com/Microsoft/HoloToolkit-Unity/blob/v1.5.5.0/External/Unitypackages/HoloToolkit-Unity-v1.5.5.0.unitypackage
※2017年6月時点で1.5.7.0までバージョンが進んでいますが、個人的に動作確認が取れていないため1.5.5.0を使用します。
</li>
<li>Input追加
InputManagerプレファブとBasicCursolプレファブをヒエラルキーに追加します。
![13 - コピー.PNG](https://qiita-image-store.s3.amazonaws.com/0/114630/47987332-6579-64c3-6a71-6bfb8219cd39.png)

![14 - コピー.PNG](https://qiita-image-store.s3.amazonaws.com/0/114630/4d90545d-dce2-9d40-269b-5e07caa0969b.png)
</li>
<li>Output再配置
EmotionManagersのフィールド「Image Frame Object」の参照がはがれているので、再度ヒエラルキーからアタッチします。
![15.PNG](https://qiita-image-store.s3.amazonaws.com/0/114630/d90e811f-e47a-6f29-3f28-f5a965f33c67.png)
</li>
<li>エアタップ実装
エアタップを検知し、画像を読み込んでAPIをコールする部分を実装します。
ImageToEmotionAPI.csの継承元にIInputClickHandlerを追加し、using句を追加、さらにOnInputClickedを実装してください。panelやtextも操作できるようフィールドに追加します。

```ImageToEmotionAPI.cs
using UnityEngine;
using System.Collections;
using System.IO;
using UnityEngine.Networking;
using System;
using System.Collections.Generic;
using HoloToolkit.Unity.InputModule;
using UnityEngine.UI;

public class ImageToEmotionAPI : MonoBehaviour, IInputClickHandler
{

    string EMOTIONKEY = "EMOTIONKEY"; // replace with your Emotion API Key

    string emotionURL = "https://api.projectoxford.ai/emotion/v1.0/recognize";

    public string fileName { get; private set; }
    string responseData;

    private ShowImageOnPanel panel; //サンプルに対して追加
    public Text text;               //サンプルに対して追加

    // Use this for initialization
    void Start () {
      fileName = Path.Combine(Application.streamingAssetsPath, "myphoto.jpeg"); // Replace with your file

        InputManager.Instance.PushFallbackInputHandler(gameObject); //サンプルに対して追加
        panel = gameObject.GetComponent<ShowImageOnPanel>();        //サンプルに対して追加
    }
  
  // Update is called once per frame
  void Update () {
  
        // This will be called with your specific input mechanism
        if(Input.GetKeyDown(KeyCode.Space))
        {
            StartCoroutine(GetEmotionFromImages());
        }

  }
    /// <summary>
    /// Get emotion data from the Cognitive Services Emotion API
    /// Stores the response into the responseData string
    /// </summary>
    /// <returns> IEnumerator - needs to be called in a Coroutine </returns>
    IEnumerator GetEmotionFromImages()
    {
        byte[] bytes = UnityEngine.Windows.File.ReadAllBytes(fileName);

        var headers = new Dictionary<string, string>() {
            { "Ocp-Apim-Subscription-Key", EMOTIONKEY },
            { "Content-Type", "application/octet-stream" }
        };

        WWW www = new WWW(emotionURL, bytes, headers);

        yield return www;
        responseData = www.text; // Save the response as JSON string
        Debug.Log(responseData);
        GetComponent<ParseEmotionResponse>().ParseJSONData(responseData);

        text.text = responseData; //mori
    }

    public void OnInputClicked(InputEventData eventData) //サンプルに対して追加
    {
        panel.DisplayImage();

        StartCoroutine(GetEmotionFromImages());

    }
}
```
</li>
<li>画像表示部分の実装
ShowImageOnPanel.csのDisplayImage()のアクセスレベルをpublicに修正します。
  ※正直、このへんかなり無理やり感が拭えません。。

```ShowImageOnPanel.cs
    public void DisplayImage() //サンプルに対して修正
    {
        Texture2D imageTxtr = new Texture2D(2, 2);
        string fileName = gameObject.GetComponent<ImageToEmotionAPI>().fileName;
        byte[] fileData = File.ReadAllBytes(fileName);
        imageTxtr.LoadImage(fileData);
        ImageFrameObject.GetComponent<Renderer>().material.mainTexture = imageTxtr;
    }
```
</li>
<li>テキスト出力実装
今のままだとコンソールにしか結果が出力されませんので、HoloLens上でテキスト出力できるよう修正します。ヒエラルキーからCanvas→Image→Textを追加し、下記のようなイメージで配置してください。同時にImageFrameもCanvas配下に配置しなおします。
![16.png](https://qiita-image-store.s3.amazonaws.com/0/114630/de7f8e9b-e855-8a2b-d82d-95289eeeff26.png)

CanvasにはBillBoradとTagalongをアタッチしておきましょう。
追加したTextオブジェクトはImageToEmotionAPIから操作できるよう、publicフィールドにアタッチしておきます。
</li>
<li>Bulid Settings
HoloLens用のビルド設定を行います。Scene、Project、Capabilityを設定してください。
InterNetClientのCapabilityは必須です。
![17.png](https://qiita-image-store.s3.amazonaws.com/0/114630/2c79269d-029e-d184-e4fe-fc0451002bf9.png)
</li>
<li>コンパイルエラー修正
ビルドしてみると、JSONTemplates.csでコンパイルエラーが出るので明示的にFieldInfo[]にキャストします。

```cs:JSONTemplates
public static partial class JSONTemplates {
  static readonly HashSet<object> touched = new HashSet<object>();

  public static JSONObject TOJSON(object obj) {   //For a generic guess
    if(touched.Add(obj)) {
      JSONObject result = JSONObject.obj;
      //Fields
      FieldInfo[] fieldinfo = (FieldInfo[])obj.GetType().GetFields(); //サンプルに対して修正
```
</li>

以上でUnity側の設定は完了です。Visual Studioでビルド＆実機へデプロイし、動作を確認してください。
画像を別の物に差し替えたり、戻り値のJSONを変換してオブジェクト配置したり、いろいろな派生形が考えられます！
トライしてみてください！

##ソースコード
下記にアップしていますので、必要に応じてご参照ください。
https://github.com/haveagit/HoloLens-Emotion-Demo

次回はComputer Vision APIのサンプルを実装します！