HoloLensでCognitive Service APIを使ってみた

#Cognitive Serviceについて
とても便利な世の中になりました。MicrosoftからAzureのサービスの１つとしてCognitive Service APIがプレビュー版で公開されています。これらを使って感情などをスカウター（？）風に表示するHoloLensアプリを作ってみました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/152548/94e6b821-0a46-d205-0b11-49a17d70afb5.png)

顔検出している部分の左側に年齢と性別（実は情報とれていない）と右下付近に８つの感情値、左下に画像からのタグ情報が返っています。
タグ情報は「室内」、「コンピュータ」、「ラップトップ」などなどが出てきています。

## Cognitive Service
APIは３種類。それぞれ無料で利用できますが単位時間当あたりのトラフィック量が決まっています。それでも十分遊べますよ。

### Face API (preview)
顔の輪郭や目の位置など特徴点をデータとして抽出することができるAPI。
無料で利用可能なリソースは以下の通りです。

* 20トランザクション / 分
* 30,000トランザクション / 月

おもにとれる情報。

* 年齢
* 性別
* 顔の傾き
* 顔の位置
* 髪型（もみあげ等）
* 眼鏡かけているか

### Emotion API (preview)
顔の表情から感情値を返すことができるAPI。
無料で利用可能なリソースは以下の通りです。

* 画像：30,000トランザクション / 月 
* ビデオ：300 ビデオ アップロード / 分 
* クエリ：3,000 / 5分 

感情は以下の8種類取得可能。数値0.0～1.0です。

* Anger
* Contempt
* Disgust
* Fear
* Hapinness
* Nuetral
* Sadness
* Surprise

なおHoloLensハッカソンで稼働させたときはおおむねみんなHappinessでしたが。。。

### Computer Vision API (preview)
画像からその中に写っている情報に対してタグ付けを行うAPI。例えば、「机」「ラップトップ」「食べ物」等を返してくれます。
無料で利用可能なリソースは以下の通りです。

* 5,000 トランザクション / 月

## 環境
今回使用した環境は以下の通りです。
Windows 10 Pro
Visual Studio 2015 Community Edition update 3
Unity 5.5.0P2 Personal
HoloToolkit 1.5.5.0

## サンプルソース
ソースコードは以前作った「HoloFaceSamples」を拡張して作りました。最終的なソースコードは後日アップロードします。
一番重要になりそうなCognitive Service APIで処理する部分以外は解説を省きます。

### Azureサービスの有効化とキーの取得
まずは何よりCognitive Service APIを有効化してください。大まかな手順は以下の通りです。
1. Azureにアカウントを追加
2. メニューより「追加」を選択し「Cognitive Service API」で検索して
3. 「Face API」「Emotion API」「Computer Vision API」のサービスを追加
4. 追加したサービスを選択し「Keys」から２個のうち１個の値を控えておいてください。

### Unity側の実装
Unity側の実装は写真のようなデータを出す部分を作成します。出し方はお好みですがアプリではCanvasを使って作っています。各サービスを呼び出す方法は[前回の顔検出](http://qiita.com/miyaura/items/85f53b13c71983e8ced2)とほぼ同じです。Unity側からUWPのライブラリを呼ぶ必要があるので、サービスクラスを使った方法で必要なクラスを準備します。

### UWP側の実装
UWP側ではCognitve Service APIを呼び出します。と言っても呼び出しは非常に簡単で、それぞれのAPI用のクラスに、Azureの各サービスのキー値をセットして画像データを送るだけです。これだけでサーバにデータを送信し解析した結果を返してくれます。ただし、UnityでUWPのライブラリを使う場合のコツがあるのでいろいろ試行錯誤しています。
簡単にそのあたりを説明したいと思います。

#### HoloLensのカメラの準備
UWPでは画像のキャプチャなどにMediaCaptureクラスを用います。このクラスを利用するための準備としてHoloLensのカメラをMediaCaptureクラスで使えるように以下の実装を行います。

```csharp
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
var device = devices[0];
var capture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings
{
    VideoDeviceId = device.Id
};
MediaCapture capture = await capture.InitializeAsync(settings);
```

#### Cognitive Service APIの準備
次にCognitive　Service　APIの準備を行います。ここは先のキー値を引数にインスタンス化するだけです。

```csharp
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
var _faceClient = new FaceServiceClient("[取得したKey]");
var _emotionClient = new EmotionServiceClient("[取得したKey]");
var _visionClient = new VisionServiceClient("[取得したKey]");
```

#### 画像のキャプチャ
最も困ったところがここです。UWPでは従来の.NET Frameworkのリソース（Streamクラスなど）とは異なる体系に代わっています。よって画像をキャプチャしてからそれぞれのAPIに渡すまでがなかなか厄介です。
まずキャプチャする画像の情報をプロパティとして設定します。

```csharp
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
//使用するカメラの最大画素数を取得するためのプロパティ
var properties = capture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties;
if (properties == null) return;

//Jpeg形式でガメラの最大解像度で取得する。
var property = ImageEncodingProperties.CreateJpeg();
property.Width = properties.Width;
property.Height = properties.Height;
```
次にカメラでキャプチャして各APIに画像を送ります。

```csharp
// Copyright(c) 2017 Takahiro Miyaura
// Released under the MIT license
// http://opensource.org/licenses/mit-license.php
using (var stream = new InMemoryRandomAccessStream())
{
    //カメラから画像をキャプチャする。
    //注：この処理はawaitが使えない。今のところ原因は不明ですが、スレッド関連の問題だと思われる。
    capture.CapturePhotoToStreamAsync(property, stream).GetAwaiter().GetResult();
    //検索位置を戻す
    stream.Seek(0);
    //Face APIの呼び出し
    Face[] face = await _faceClient.DetectAsync(stream.AsStreamForRead());

}
using (var stream = new InMemoryRandomAccessStream())
{
    //カメラから画像をキャプチャする。
    //注：この処理はawaitが使えない。今のところ原因は不明ですが、スレッド関連の問題だと思われる。
    capture.CapturePhotoToStreamAsync(property, stream).GetAwaiter().GetResult();
    //検索位置を戻す
    stream.Seek(0);
    //Emotion APIの呼び出し
    Emotion[] emotion = await _emotionClient.RecognizeAsync(stream.AsStreamForRead());

}
using (var stream = new InMemoryRandomAccessStream())
{
    //カメラから画像をキャプチャする。
    //注：この処理はawaitが使えない。今のところ原因は不明ですが、スレッド関連の問題だと思われる。    capture.CapturePhotoToStreamAsync(property, stream).GetAwaiter().GetResult();
    //検索位置を戻す
    stream.Seek(0);
    //Computer Vision APIの呼び出し
    AnalysisResult vision = await _visionClient.GetTagsAsync(stream.AsStreamForRead());
}
```

それぞれの戻り値の型の中に解析後の情報がいろいろ入っているので好みの情報を取り出してUnity側で表示させます。

## 困ったこと 
試していて一番はまったのがcapture.CapturePhotoToStreamAsync(property, stream).GetAwaiter().GetResult();の部分です。最初awaitを使って待たせていたのですが、その場合例外が発生してうまく動かない事態に。どうもスレッド制御の問題なのかと思います。この辺り、整理がまだまだいりそうで次回以降で自分の理解している範囲の整理をしてみようと思います。また、今回UWP側でリソースを扱っているので実行するスレッドについてはUIスレッドで動作させてください。具体的にはAppCallBacks.Instance.InvokeUIThreadメソッドにて呼び出します。

## その他所感など
実はこれ自体、何もHoloLens用の要素は一切ないです。ですのでちょっと加工すれば素のUWPアプリ、Unityのアプリにも利用できます。いろいろなアプリで活用してみてください。拍子抜けするくらいの簡単さです！