Amazon Rekognitionで幸せ写真から逃げられるか

この記事は[NIFTY Advent Calendar 2016](http://qiita.com/advent-calendar/2016/nifty)の24日目の記事です。

昨日の記事は@ysaotomeさんの[ニフティ株式会社にモダンな開発・運用環境を導入するために奮闘した(している)話](http://qiita.com/ysaotome/items/53b5cd4d2e1fc7e18855)でした。流れを変えるきっかけを作っていただいたので、十二分に活用すると共に、この流れを大きくするアクションを起こしていきたいですね。

# コトのはじまり
Advent Calendar今日が空いていたので、意気揚々と確保したんですが、**クリスマス・イヴ**だったんですね。（だから空いてたのかな。）
この時期は外に出ず、外界から隔離されていたいですが、何気なく開いたSNSで心温まる幸せな写真を目にすることでしょう。

いつもなら幸せ写真を見ると、こちらも幸せになりますが、
今日だけは･･･今だけは･･･逃げたい。


「そうだ。**幸せフィルタリング**を作ろう。」

という感じで、SNSのタイムラインから幸せ写真投稿を一時的にフィルタする機能を作ってみようかと。
# 結論
**逃げられなかった。**


先に述べると、**幸せ写真のフィルタはできた**が、InstagramやFacebookが広告ビジネスを始めてからAPI経由でタイムラインが取れなくなっており、連携できず。。

無念。

しかし、今後現れるであろう救世主のために活動の軌跡をここに記そうと思う。

# Amazon Rekognitionとは？
![FacialAnalysisDemo.png](https://qiita-image-store.s3.amazonaws.com/0/60915/423a30d9-e776-740b-80e9-5a521d3c0a7d.png)

今月頭に発表されたDeep Learningを利用した画像解析サービスです。
現在用意されている機能は以下の３つです

1. Detect Labels : 物体とシーンの検出（物体やコンセプト、シーンを検出しタグをつけてくれる）
1. Facial Analysis : 顔の分析（感情や、表情、顔パーツ位置とかを算出してくれる）
1. Compared Face : 顔の比較（顔の類似度の算出とか）

類似サービスとしては[Emotion API](https://www.microsoft.com/cognitive-services/en-us/emotion-api)、[Face API](https://www.microsoft.com/cognitive-services/en-us/face-api)あたりですかね。

また、Go,Java,Python等のSDKも準備されておりサクッと使えます。

## 対応リージョン
現在は利用できるリージョンは限られています。（2016年12月24日現在）

* EU (アイルランド) ： eu-west-1
* 米国東部 (バージニア北部) ： us-east-1
* 米国西部 (オレゴン) ： us-west-2


### [参考] リージョン別の実行時間
ちょっと興味があったのでリージョン別に[func (*Rekognition) DetectLabels](https://github.com/aws/aws-sdk-go/blob/master/service/rekognition/api.go#L618)実行時のレスポンスタイムを計測してみました。（外部からの画像の取得処理時間とかは含んでいないです）

![awsbentch.png](https://qiita-image-store.s3.amazonaws.com/0/60915/1b556b5b-91a2-f532-1d28-da740818fdc9.png)

一番早いus-east-1でも1画像あたり平均1.5秒かかる感じです。
今回は対象画像を[DetectLabelsInput](http://docs.aws.amazon.com/sdk-for-go/api/service/rekognition/#DetectLabelsInput)のImageフィールドにセットして実行しているので、事前に同一リージョンのS3に画像を配置してから、[S3Object](http://docs.aws.amazon.com/sdk-for-go/api/service/rekognition/#S3Object)で指定する方法をとるともう少し早くなるかもです。

## Facial Analysisで取得可能な要素
幸せフィルタリングはFacial Analysisを使えば実現できそうなので仕様をさらってみる。
### 1. 感情
感情は各Emotion Typeを信用度と共に返してくれます

| Emotion Type | 備考 |
|:-----------|:----------|
| HAPPY | 幸福 |
| SAD | 悲しみ |
| ANGRY | 怒 |
| CONFUSED | 困惑 |
| DISGUSTED | うんざり |
| SURPRISED | 驚き |
| CALM | 穏やか |
| UNKNOWN | 不明 |

サンプル)

```js
      Emotions: [{
          confidence: 99.92073059082031,
          Type: "HAPPY"
        },{
          Confidence: 2.370285987854004,
          Type: "SAD"
        },{
          Confidence: 1.2227296829223633,
          Type: "DISGUSTED"
        }],
```

### 2. 性別
算出された性別と信頼度と共に返してくれます

| Gender Type | 備考 |
|:-----------|:----------|
| Male | 男性 |
| Female | 女性 |

 サンプル)

```js
      Gender: {
        Confidence: 99.9145278930664,
        Value: "Male"
      },
```

### 3. 顔パーツ位置
何がどこにあるのかをx、y座標と共に返します

| LandMark Type | 備考 |
|:-----------|:----------|
| eyeLeft | 左目 |
| eyeRight | 右目 |
| nose | 鼻 |
| mouthLeft | 口の左端 |
| mouthRight | 口の右端 |
| leftEyebrowLeft | 左眉毛の左端 |
| leftEyebrowRight | 左眉毛の右端 |
| leftEyebrowUp | 左眉毛の上|
| rightEyebrowLeft | 右眉毛の左端 |
| rightEyebrowRight | 右眉毛の右端|
| rightEyebrowUp | 右眉毛の上端 |
| leftEyeLeft | 左目の左端 |
| leftEyeRight | 左目の右端|
| leftEyeUp | 左目の上端 |
| leftEyeDown | 左目の下端 |
| rightEyeLeft | 右目の左端 |
| rightEyeRight | 右目の右端|
| rightEyeUp | 右目の上端 |
| rightEyeDown | 右目の下端 |
| noseLeft | 鼻の左端 |
| noseRight | 鼻の右端 |
| mouthUp | 唇の上端 |
| mouthDown | 唇の下端 |
| leftPupil | 左目の瞳孔 |
| rightPupil | 右目の瞳孔|

サンプル)

```js
      Landmarks: [
        {
          Type: "eyeLeft",
          X: 0.6445090770721436,
          Y: 0.21687917411327362
        },
        {
          Type: "eyeRight",
          X: 0.6933053731918335,
          Y: 0.19673460721969604
        },
        {
          Type: "nose",
          X: 0.6678402423858643,
          Y: 0.2529497742652893
        }
        ...省略...
      ]
```

### 4. 画像補正回転度
検出した画像を認識する際に補正した回転度合いをどれかで返してくれます

| OrientationCorrection Value | 備考 |
|:-----------|:----------|
| ROTATE_0 | 補正無し |
| ROTATE_90 | 90度 |
| ROTATE_180 | 180度 |
| ROTATE_270 | 270度 |

サンプル)

```js
  OrientationCorrection: "ROTATE_0"
```

### 5. その他

| 要素名 | 備考 | 値 |
|:-----------|:----------|:----------|
| Beard | あごひげがあるか | Confidence(信頼度)とValue（bool） |
| BoundingBox | 顔検出した境界線 | Height、Left、Top、Width |
| Confidence | 全体の信頼度 | 0~100% |
| MouthOpen | 口が開いているか | Confidence(信頼度)とValue（bool） |
| Mustache | 口ひげがあるか | Confidence(信頼度)とValue（bool） |
| Pose | 顔ポーズ | Pitch（ピッチ軸）,Roll（ロール軸）,Yaw（ヨー軸）における検出した顔の回転を-180度から180度の間の角度で表現 |
| Quality | 質 | Brightness（顔の明るさ)とSharpness(鮮明度) |
| Smile | 笑っているか | Confidence(信頼度)とValue（bool） |
| Sunglasses | サングラスをかけているか | Confidence(信頼度)とValue（bool） |

サンプル）

```js
      Beard: {
        Confidence: 99.85322570800781,
        Value: false
      },
      BoundingBox: {
        Height: 0.20666666328907013,
        Left: 0.6066666841506958,
        Top: 0.12333333492279053,
        Width: 0.1388888955116272
      },
      Confidence: 99.99443054199219,
      MouthOpen: {
        Confidence: 99.85244750976562,
        Value: false
      },
      Mustache: {
        Confidence: 97.82624816894531,
        Value: false
      },
      Pose: {
        Pitch: -10.305879592895508,
        Roll: -15.276971817016602,
        Yaw: -14.620390892028809
      },
      Quality: {
        Brightness: 28.10620880126953,
        Sharpness: 0
      },
      Smile: {
        Confidence: 97.54458618164062,
        Value: true
      },
      Sunglasses: {
        Confidence: 99.909912109375,
        Value: false
      }
```

# Go SDKを利用して幸せフィルタを作る
## セットアップ
1. AWSのコンソールからユーザーとグループを作成
アクセスの種類を「プログラムによるアクセス」と設定して作成し、グループの権限は「AmazonRekognitionReadOnlyAccess」だけで事足ります。

2. アクセスキーとシークレットアクセスキーをメモ

3. [awsCLI](https://aws.amazon.com/jp/cli/)を利用して認証設定を追加する
`$ aws configure`

4. SDKの追加
`$ go get github.com/aws/aws-sdk-go`

## 幸せフィルタの実装
全体ソースは[こちら](https://github.com/Oktasuke/HomeAlone/blob/master/main.go)

### Rekognition clientの生成
* rekognition.Newの際、[example](http://docs.aws.amazon.com/sdk-for-go/api/service/rekognition/#example_Rekognition_DetectLabels)の様にリージョンを指定しないとエラーになります。`AWS_SDK_LOAD_CONFIG`に`~/.aws/config`とかawsCLIでの設定時に生成されたconfigのPATHを設定すれば指定しなくてもいけると思いますが試してないです。

```go
se, err := session.NewSession()
if err != nil {
	glog.Fatal("failed to create session,", err)
}
svc := rekognition.New(se,aws.NewConfig().WithRegion(REGION))
```

### Facial Analysisの実行
* fetchByteImage(url string)は内部で
[http.Get(url string)](https://golang.org/pkg/net/http/#Client.Get)で指定URLのレスポンスを取得してbodyを[io.ReadAll(r io.Reader)](https://golang.org/pkg/io/ioutil/#ReadAll)でbyte[]型に変換して返す様にしています。
* [Image](http://docs.aws.amazon.com/sdk-for-go/api/service/rekognition/#Image)に記載されている通りBytesにセットしてあげればBase64に変換してくれるみたいです。

> Bytes is automatically base64 encoded/decoded by the SDK.

```go
params := &rekognition.DetectFacesInput{
	Image: &rekognition.Image{
		Bytes: fetchByteImage(url),
	},
	Attributes: []*string{
		aws.String("ALL"),
	},
}
res, err := rec.DetectFaces(params)
if err != nil {
	glog.Error(err.Error())
}
```


### 幸せフィルタ判定
* Facial Analysisでは1つの画像から最大15の顔を検出できるみたいなので、前項で記載したレスポンス内容と掛け合わせて幸せそうな写真の判定軸として勘と経験で以下の様にしました。
* HAPPY度の閾値は**2人合計で100%**が一番良い結果だったので100で設定しています。（2人で100%ってなんかいいですね。）

```go
func IsHeartBreak(url string, rec *rekognition.Rekognition) bool{
    re := executeDetectFaces(url, rec) //DetectFaces
    // ２人であるか？
    if len(re.FaceDetails) == 2 {
        // 男女ペアであるか？
        if *re.FaceDetails[0].Gender.Value != *re.FaceDetails[1].Gender.Value {
            // HAPPY度の合計が閾値を超えているか?
            var gnh float64 = 0
            for i := 0; i < len(re.FaceDetails); i++ {
                for j := 0; j < len(re.FaceDetails[i].Emotions); j++{
                    if(*re.FaceDetails[i].Emotions[j].Type == "HAPPY"){
                        gnh += *re.FaceDetails[i].Emotions[j].Confidence
                    }
                }
            }
            if ENVY_LIMIT < gnh {
                return true
            }
        }
    }
    return false
}
```



## フィルタ精度
| 項目名 | 結果 |
|:---------|:---------|
| テスト画像数 | 20 |
| 正解数 | 16 |
| 正答率 | 80.0 % |
ランダムに写真をピックアップして目検で判定結果（正解値）を付与したCSVを作成して検証した結果です。正答率80％なのでまずまずのフィルタリングではないでしょうか？
改善点としてBoundingBoxの値を利用して**顔と顔が近い**とか算出したら精度あがりそうなので、バレンタインデー前に調整しようとおもいます。


# さいごに
現実からの逃げで試したAmazon Rekognitionですが、担当の[ニフティ不動産](http://myhome.nifty.com/)で、画像データのフォーマット違反（間取り図画像であるはずなのに、トイレ画像だったりする）が発生していたので、試しに今回作ったプログラムを少し改修して通してみたら意外といけそうだったりと、なんか役に立ちました。まさに、逃げるははじだが役に立つですね。

明日は大トリ、@muddydixonさんです！楽しみです！