私は普段Goを書いているのですが、go fmt
でコードが整形される瞬間が異常に興奮します。
汚れた心が浄化される感覚と言いますか、脳汁が止まりません。
いつの頃からか「*.go
だけでなく、あらゆるものにgo fmt
をかけたい」という欲求が芽生え始めました。
まずは人間から始めてみようと思い、Webサービスを作ったので紹介します。
作ったもの
画像のURLを指定するかアップロードすると、顔を検出してGopherっぽくします。
これが
こうなる
上記のサンプルは@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
検出対象の画像は、いつものレナさんです。
顔検出のコード
特段難しいところも無く、読み込んだ画像を食わせるだけです。
詳細はコード中のコメントをご確認ください。
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
検出箇所が多すぎてよくわからない
どれがどこを指しているのか細かな違いが良くわからないので、プロットしてみました。
多分プロット箇所が潰れてよく見えないと思うので、画像クリックで拡大してください。
プロット箇所見るとわかりますが、左右の表現に注意が必要です。
例えば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