Cloud Vision APIをAndroidで使って感情検知する。

この記事は[Monstar Lab, Inc. Advent Calendar 2016](http://qiita.com/advent-calendar/2016/monstar-lab) 12日目の記事です。

遅まきながらCloud Vision APIです。
`Type=FACE_DETECTION`(顔認識)でやってみます。

## Google Cloud Vision API
[公式](https://cloud.google.com/vision/?hl=ja)
[Google Cloud Vision APIを使ってみた](http://dev.classmethod.jp/cloud/gcp/google-cloud-vision-api-get-start/)

### お値段
[料金表](https://cloud.google.com/vision/pricing?hl=ja#prices)
`ユニット`単位での請求となる。
1000ユニットまでなら無料で使える。

ユニットは画像の枚数のことではない。
1画像に対して"顔認識とラベル検知"としてAPI利用した場合、2ユニット利用となる。


## APIキーの発行
実装前の準備です。

Google Developers Consoleに請求先情報を未登録の方はクレジットカードを握りしめて作業を開始してください。
※無料トライアルでも登録必須です。

1. [公式](https://cloud.google.com/vision/?hl=ja)から`コンソールを開く`
2. 請求先情報などの入力を行う
3. 新規にプロジェクトを作成
4. `Google Cloud API` -> `Vision API`を選択
5. `有効にする` -> 課金を求められるのでOK
5. `認証情報` -> `認証情報を作成` -> `APIキー`を選択
6. とりあえず`制限なし`でAPIキーを作成


## 実装
[公式サンプル](https://github.com/GoogleCloudPlatform/cloud-vision/tree/master/android/CloudVision)があります。
`Type=LABEL_DETECTION`(物体の分類)になっているので、[リファレンス](https://cloud.google.com/vision/reference/rest/v1/images/annotate)を見ながら、必要なところを顔認識に書き換えます。

簡単に、1人だけ写ってる画像から表情(感情)を抽出する、という内容にします。

### Feature typeの書き換え
#### before
```Java:MainActivity.java
annotateImageRequest.setFeatures(new ArrayList<Feature>() {{
    Feature labelDetection = new Feature();
    labelDetection.setType("LABEL_DETECTION");
    labelDetection.setMaxResults(10);
    add(labelDetection);
}});
```

#### after
```Java:MainActivity.java
annotateImageRequest.setFeatures(new ArrayList<Feature>() {{
    Feature labelDetection = new Feature();
    labelDetection.setType("FACE_DETECTION");
    labelDetection.setMaxResults(1);
    add(labelDetection);
}});
```
結果は1件だけ取得するようにします。

### 結果取得対象の書き換え
#### before
```Java:MainActivity.java
private String convertResponseToString(BatchAnnotateImagesResponse response) {
    String message = "I found these things:\n\n";

    List<EntityAnnotation> labels = response.getResponses().get(0).getLabelAnnotations();
    if (labels != null) {
        for (EntityAnnotation label : labels) {
            message += String.format("%.3f: %s", label.getScore(), label.getDescription());
            message += "\n";
        }
    } else {
        message += "nothing";
    }

    return message;
}
```

#### after
```Java:MainActivity.java
private String convertResponseToString(BatchAnnotateImagesResponse response) {
    String message = "I found these facial expressions:\n\n";
    List<FaceAnnotation> labels = response.getResponses().get(0).getFaceAnnotations();
    if (labels != null) {
        for (FaceAnnotation label : labels) {
            message += String.format("Joy：%s", label.getJoyLikelihood());
            message += "\n";
            message += String.format("Sorrow：%s", label.getSorrowLikelihood());
            message += "\n";
            message += String.format("Anger：%s", label.getAngerLikelihood());
            message += "\n";
            message += String.format("Surprise：%s", label.getSurpriseLikelihood());
        }
    } else {
        message += "nothing";
    }

    return message;
}
```
(とても雑で申し訳ないです。。。)

## 結果
`Likelihood`の言葉が示すとおり、「喜んでいる(楽しそうな)表情である可能性」という形で返ってきます。
値の意味は[こちら](https://cloud.google.com/vision/reference/rest/v1/images/annotate#Likelihood)にある通りです。
`UNKNOWN`を除いて5段階評価となっています。

なお、そもそも"人でない場合"は結果は0件となります。

### 精度(の感想)
そんなに細かく検証したわけではないので、感想レベルに留まります。

- 正面向きでハッキリした表情なら"VERY_LIKELY"となる。
- 帽子をかぶっていても大丈夫だが、目元が暗くなると判別できなくなる。
- 横向きでもハッキリした笑顔なら大丈夫。
- "くしゃっとした笑顔"はJoyとSorrowの両方が"LIKELY"や"POSSIBLE"となり、曖昧。
- 真顔は全て"VERY_UNLIKELY"。
- 笑顔は精度出るが、他は微妙な気がする。
- モノクロ画像は結果が曖昧になる。
  - 元は結果の得られた画像も、モノクロにすると全て"VERY_UNLIKELY"になる、など。
- 写実的なら絵でも銅像でも"顔"と判断される。
  - モナリザ：OK(無表情あつかい)
  - 阿修羅像：OK(無表情あつかい)
  - 三億円事件のモンタージュ写真：OK(機械には悲しんでる様に映るらしい)
  - 森気楼氏の絵：OK(感情も結構取れる！)
  - 西村キヌ氏の絵：NG
  - アニメのキャラクター：NG


## まとめ
20年前から好きでしたが、改めて森気楼氏の絵は素晴らしいと思いました。
それが言いたかっただけです。