Posted at

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

More than 1 year has passed since last update.

私は普段Goを書いているのですが、go fmtでコードが整形される瞬間が異常に興奮します。

汚れた心が浄化される感覚と言いますか、脳汁が止まりません。

いつの頃からか「*.goだけでなく、あらゆるものにgo fmtをかけたい」という欲求が芽生え始めました。

まずは人間から始めてみようと思い、Webサービスを作ったので紹介します。


作ったもの

画像のURLを指定するかアップロードすると、顔を検出してGopherっぽくします。

これが

tanksuzuki_before.png

こうなる

tanksuzuki_after.png

上記のサンプルは@tanksuzuki 1人ですが、複数人でもOKです。

何人でも、見つけ次第、一人残らずGopherizeします。

↓こちらから遊べます。

https://gopherize.com

↓コードも公開してます。

https://github.com/tanksuzuki/gopherize

ノリと勢いで書いたので、まだ汚いです。未ファクタリング。

また、Gopherの画像@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


顔検出のコード

特段難しいところも無く、読み込んだ画像を食わせるだけです。

詳細はコード中のコメントをご確認ください。

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

プロット箇所見るとわかりますが、左右の表現に注意が必要です。

例えば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)があるので、大きな画像を扱う場合には注意が必要です。GAEの場合はメモリの許容量超えてコンテナが落ちないように、ある程度余裕を持ったインスタンス使うのが良いと思います。

あと、みんなでGopherになって遊びましょう。

https://gopherize.com

tanksuzuki_after.png