GAE/GoとVision APIで人類をGopher化する

私は普段Goを書いているのですが、`go fmt`でコードが整形される瞬間が異常に興奮します。
汚れた心が浄化される感覚と言いますか、脳汁が止まりません。

いつの頃からか「`*.go`だけでなく、あらゆるものに`go fmt`をかけたい」という欲求が芽生え始めました。
まずは人間から始めてみようと思い、Webサービスを作ったので紹介します。

# 作ったもの

画像のURLを指定するかアップロードすると、顔を検出して[Gopher](https://github.com/golang-samples/gopher-vector)っぽくします。

__これが__

![tanksuzuki_before.png](https://qiita-image-store.s3.amazonaws.com/0/104608/78577d0d-862a-3593-7950-3999e19d99de.png)

__こうなる__

![tanksuzuki_after.png](https://qiita-image-store.s3.amazonaws.com/0/104608/eaf26da2-7585-59cb-9a03-f1d92f72c70a.png)


上記のサンプルは@tanksuzuki 1人ですが、複数人でもOKです。
何人でも、見つけ次第、一人残らずGopherizeします。

__↓こちらから遊べます。__
https://gopherize.com

__↓コードも公開してます。__
https://github.com/tanksuzuki/gopherize
ノリと勢いで書いたので、まだ汚いです。未ファクタリング。

また、[Gopherの画像](https://github.com/golang-samples/gopher-vector)は@tenntennさん作のものを使用させて頂きました。

# コンポーネント

## Google App Engine/Go

基盤はGAE/Goです。
運用が楽なのと、少しクセあるけど青天井スケールなDatastoreが使えるので、PureGoなサービスはGAEに乗せると幸せになると思います。

紹介記事がたくさんあるので、詳細は割愛します。

## Google Cloud Vision API

GCPが提供する機械学習系のサービスで、画像に含まれる顔、ランドマーク、文字認識等ができます。
今回は顔認識を使ってます。
https://cloud.google.com/vision/

__本記事ではGoにおけるVision APIの遊び方について書きます。__

# GoでVision API(顔認識)

RESTでもいけますが、Goの場合はVision API用のクライアントライブラリがあるので、これを使うと便利です。
https://cloud.google.com/vision/docs/reference/libraries#client-libraries-install-go

また、事前にGCPのコンソールでAPIを有効にしてサービスアカウントキーを発行してください。
https://cloud.google.com/vision/docs/before-you-begin

検出対象の画像は、いつものレナさんです。

![lenna_mini.png](https://qiita-image-store.s3.amazonaws.com/0/104608/1f291349-3989-c0ee-e51e-30ddabbef3fa.png)


## 顔検出のコード

特段難しいところも無く、読み込んだ画像を食わせるだけです。
詳細はコード中のコメントをご確認ください。

```go
package main

import (
	"context"
	"fmt"
	"os"

	"cloud.google.com/go/vision/apiv1"
	"google.golang.org/api/option"
)

func main() {
	ctx := context.Background()

	// コンソールで取得したサービスアカウントキーを読み込む
	client, err := vision.NewImageAnnotatorClient(ctx, option.WithCredentialsFile("./service_account.json"))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	// 解析対象の画像ファイルを開く
	file, err := os.Open("lenna.png")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	defer file.Close()

	// Vision API用の画像に変換
	img, err := vision.NewImageFromReader(file)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	// 顔検出を行う
	// 検出した顔の情報がスライス(人数分)で返る
	// 顔が検出できなかった場合はfaces=nil, err=nilになる
	faces, err := client.DetectFaces(ctx, img, nil, 0)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	fmt.Println("検出した顔の情報を表示")
	for _, face := range faces {
		for _, landmark := range face.Landmarks {
			// パーツ名と座標を表示
			fmt.Printf("Type: %s, Position: %+v\n", landmark.Type.String(), landmark.Position)
		}
	}

	fmt.Println("face.Landmarksを階層化された構造体に変換")
	for _, face := range faces {
		faceLandmarks := vision.FaceFromLandmarks(face.Landmarks)
		fmt.Println(faceLandmarks.Eyes.Left.Center.String())
	}
}
```

## 実行結果

全部で34種類検出されます。
また、画像では確認できない耳の位置も推測で出してくれてます。

```
検出した顔の情報を表示
Type: LEFT_EYE, Position: x:1031.5101 y:1047.2147 z:0.0030202179
Type: RIGHT_EYE, Position: x:1268.2346 y:1049.2048 z:150.27559
Type: LEFT_OF_LEFT_EYEBROW, Position: x:954.9276 y:989.3008 z:-34.32128
Type: RIGHT_OF_LEFT_EYEBROW, Position: x:1138.0808 y:1004.2999 z:-12.161362
Type: LEFT_OF_RIGHT_EYEBROW, Position: x:1252.9858 y:1001.98145 z:60.611076
Type: RIGHT_OF_RIGHT_EYEBROW, Position: x:1350.7546 y:985.4259 z:216.2247
Type: MIDPOINT_BETWEEN_EYES, Position: x:1196.5642 y:1056.3239 z:30.015688
Type: NOSE_TIP, Position: x:1218.5188 y:1236.4507 z:-9.081103
Type: UPPER_LIP, Position: x:1173.3488 y:1326.7092 z:63.479485
Type: LOWER_LIP, Position: x:1153.4204 y:1404.1895 z:95.16259
Type: MOUTH_LEFT, Position: x:1035.6951 y:1350.1532 z:73.79502
Type: MOUTH_RIGHT, Position: x:1220.6986 y:1348.6553 z:190.47676
Type: MOUTH_CENTER, Position: x:1159.4535 y:1360.9813 z:88.02694
Type: NOSE_BOTTOM_RIGHT, Position: x:1224.9163 y:1234.2664 z:124.771126
Type: NOSE_BOTTOM_LEFT, Position: x:1107.1759 y:1245.0901 z:44.69591
Type: NOSE_BOTTOM_CENTER, Position: x:1178.1892 y:1269.0527 z:52.52708
Type: LEFT_EYE_TOP_BOUNDARY, Position: x:1052.9135 y:1043.2208 z:-12.859723
Type: LEFT_EYE_RIGHT_CORNER, Position: x:1096.7885 y:1062.7416 z:39.157196
Type: LEFT_EYE_BOTTOM_BOUNDARY, Position: x:1034.957 y:1075.8553 z:6.84032
Type: LEFT_EYE_LEFT_CORNER, Position: x:980.4265 y:1054.7437 z:-2.2765164
Type: LEFT_EYE_PUPIL, Position: x:1041.6069 y:1060.5155 z:-1.8535404
Type: RIGHT_EYE_TOP_BOUNDARY, Position: x:1289.9827 y:1040.9736 z:137.23383
Type: RIGHT_EYE_RIGHT_CORNER, Position: x:1311.72 y:1051.614 z:207.39917
Type: RIGHT_EYE_BOTTOM_BOUNDARY, Position: x:1277.3224 y:1075.3264 z:157.48947
Type: RIGHT_EYE_LEFT_CORNER, Position: x:1232.6814 y:1061.701 z:128.17969
Type: RIGHT_EYE_PUPIL, Position: x:1284.7712 y:1058.293 z:152.14508
Type: LEFT_EYEBROW_UPPER_MIDPOINT, Position: x:1059.0701 y:966.94434 z:-45.04433
Type: RIGHT_EYEBROW_UPPER_MIDPOINT, Position: x:1316.0935 y:964.3993 z:117.82972
Type: LEFT_EAR_TRAGION, Position: x:695.56396 y:1126.4656 z:220.7455
Type: RIGHT_EAR_TRAGION, Position: x:1233.4604 y:1121.3889 z:561.2823
Type: FOREHEAD_GLABELLA, Position: x:1201.843 y:1001.74365 z:14.844043
Type: CHIN_GNATHION, Position: x:1119.3358 y:1526.7256 z:151.99123
Type: CHIN_LEFT_GONION, Position: x:765.1258 y:1326.3259 z:173.84352
Type: CHIN_RIGHT_GONION, Position: x:1249.9426 y:1320.6958 z:481.17767

face.Landmarksを階層化された構造体に変換
x:267.96326 y:267.1449 z:-0.0016142273
```

# 検出箇所が多すぎてよくわからない

どれがどこを指しているのか細かな違いが良くわからないので、プロットしてみました。
多分プロット箇所が潰れてよく見えないと思うので、画像クリックで拡大してください。

![out-min.png](https://qiita-image-store.s3.amazonaws.com/0/104608/c8687424-a52b-52c7-7074-1f441a20d4bd.png)

プロット箇所見るとわかりますが、左右の表現に注意が必要です。
例えば`LEFT_EYE`は左目ではなく、__向かって左側の目__という意味になります。

## 向かって左目

| Type | Marker |
| --- | --- |
| LEFT_EYE | a |
| LEFT_EYE_RIGHT_CORNER | b |
| LEFT_EYE_LEFT_CORNER | c |
| LEFT_EYE_BOTTOM_BOUNDARY | d |
| LEFT_EYE_TOP_BOUNDARY | e |
| LEFT_EYE_PUPIL | f |

## 向かって左眉毛

| Type | Marker |
| --- | --- |
| LEFT_OF_LEFT_EYEBROW | g |
| LEFT_OF_RIGHT_EYEBROW | h |
| LEFT_EYEBROW_UPPER_MIDPOINT | i |

## 向かって右目

| Type | Marker |
| --- | --- |
| RIGHT_EYE | j |
| RIGHT_EYE_TOP_BOUNDARY | k |
| RIGHT_EYE_RIGHT_CORNER | l |
| RIGHT_EYE_BOTTOM_BOUNDARY | m |
| RIGHT_EYE_LEFT_CORNER | n |
| RIGHT_EYE_PUPIL | o |

## 向かって右眉毛

| Type | Marker |
| --- | --- |
| RIGHT_OF_LEFT_EYEBROW | p |
| RIGHT_OF_RIGHT_EYEBROW | q |
| RIGHT_EYEBROW_UPPER_MIDPOINT | r |

## 目と目の間（眉間の少し下）

| Type | Marker |
| --- | --- |
| MIDPOINT_BETWEEN_EYES | s |

## 鼻

| Type | Marker |
| --- | --- |
| NOSE_TIP | t |
| NOSE_BOTTOM_RIGHT | u |
| NOSE_BOTTOM_LEFT | v |
| NOSE_BOTTOM_CENTER | w |

## 唇

| Type | Marker |
| --- | --- |
| UPPER_LIP | x |
| LOWER_LIP | y |

## 口

| Type | Marker |
| --- | --- |
| MOUTH_LEFT | z |
| MOUTH_RIGHT | A |
| MOUTH_CENTER | B |

## 耳

| Type | Marker |
| --- | --- |
| LEFT_EAR_TRAGION | C |
| RIGHT_EAR_TRAGION | D |

## 額

| Type | Marker |
| --- | --- |
| FOREHEAD_GLABELLA | E |

## 顎

| Type | Marker |
| --- | --- |
| CHIN_GNATHION | F |
| CHIN_LEFT_GONION | G |
| CHIN_RIGHT_GONION | H |

プロット用に使ったコードはこちらに置きました。
https://gist.github.com/tanksuzuki/8e9deb7a302e47408af066d6f1aba8d5

# まとめ

Vision APIを使えば顔検出用の外部ライブラリ使わなくて良いので、ピュアなGoで書ける＝GAEに乗せられる点が嬉しいところです。

使い方も特に難しいところはありませんが、[画像サイズに制限(最大で4MB)](https://cloud.google.com/vision/docs/limits)があるので、大きな画像を扱う場合には注意が必要です。GAEの場合はメモリの許容量超えてコンテナが落ちないように、ある程度余裕を持ったインスタンス使うのが良いと思います。

あと、みんなでGopherになって遊びましょう。
https://gopherize.com

![tanksuzuki_after.png](https://qiita-image-store.s3.amazonaws.com/0/104608/eaf26da2-7585-59cb-9a03-f1d92f72c70a.png)