LoginSignup
105
56

More than 5 years have passed since last update.

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

Posted at

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

105
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
56