Go
golang
GoogleAppEngine
GoogleCloudPlatform
GoogleCloudVisionAPI

私は普段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