GoogleCloudVisionAPIに画像を投げるエンドポイントをGAEに立ててアダルト判定しよう。

Google CloudVisionAPIにアダルト判定をさせる話です。


#そもそも何ができるのか。
以下を見ましょう。
[Vision API - 画像コンテンツ分析  |  Google Cloud Platform](https://cloud.google.com/vision/?hl=ja
)

これを見ると
ラベル検出：画像に写っているカテゴリの物体を検出する。
**不適切なコンテンツの検出：アダルトや暴力的コンテンツなどの検出**
ロゴ検出：商品ロゴ検出
ランドマーク検出：人口建造物を検出できる。
光学式文字認識：OCR。テキスト検出と抽出ができる。
顔検出：顔があることを検出(=個人を特定する顔検出はできない)
画像属性：ドミナントカラーや切り抜きのヒントなど検出。

面白そう。
たぶんGoogleのエンジニア的には不適切なコンテンツを検出して弾く用途で作ったんだと思います。
でも逆に言えば、エロ画像の過激度を判定して分類してくれるのが作れそう。

#料金
[料金  |  Google Cloud Vision API ドキュメント  |  Google Cloud Platform](https://cloud.google.com/vision/docs/pricing?hl=ja)

ここに詳しく書いてあります。明朗会計。


#なぜAppEngineを使うのか
別にAppEngineを使う必要があるのかというと別にそんなことないです。
[Detecting Safe Search Properties  |  Google Cloud Vision API Documentation  |  Google Cloud Platform](https://cloud.google.com/vision/docs/detecting-safe-search?hl=ja#vision-safe-search-detection-gcs-protocol)

ここを見ると、普通にCloud Vision用のAPIに対してPOSTすればいいですね。

以上をふまえて、なぜAppEngineを使うのかというと…特に深い意味はなかったですね…。
この記事を書いてるときに初めてREST APIあることに気づきました。

まぁでもAppEngineに立てておけば、CloudVisionAPIから返ってくるJSONの値を加工してクライアント側に渡せるという点だけでも意味はあるのかなと思います。
クライアント側にBase64エンコードさせる必要がないのも1つの利点かもしれません。

#コード

##コントローラ

```controller/safesearch.go
package safesearch

import (
	"bytes"
	"encoding/base64"
	"image"

	"image/jpeg"

	model "cvapi/model/safesearch"
	"log"

	"github.com/gin-gonic/gin"
)
func GetSafeSearchResult(g *gin.Context) {
	g.Request.ParseForm()
	//file="hogehoge.jpg"となっている場合
	file, _, err := g.Request.FormFile("file")
	if err != nil {
		g.AbortWithStatus(500)
	}
	//image.Image形式にDecodeする。
	img, _, err := image.Decode(file)
	if err != nil {
		g.AbortWithStatus(500)
	}

	buf := new(bytes.Buffer)
	//jpeg.Encodeでは、bufに対してByte配列が入る。
	err = jpeg.Encode(buf, img, nil)
	if err != nil {
		g.AbortWithStatus(500)
	}
	//base64にエンコードする。
	enc := base64.StdEncoding.EncodeToString(buf.Bytes())

	//base64エンコードされたものをmodel側に投げて判定のJSONを得る。
	json, err := model.CheckSafe(enc, g)
	if err != nil {
		g.AbortWithStatus(500)
	}
	//今回のデモでは特に加工せずJSONを出力する。
	g.JSON(200, json)
}
```

##モデル側
```model/safesearch.go
package safesearch

import (
	"io/ioutil"

	"github.com/gin-gonic/gin"
	"golang.org/x/oauth2/google"

	"google.golang.org/api/vision/v1"
	"google.golang.org/appengine"
)

func CheckSafe(enc string, g *gin.Context) (*vision.AnnotateImageResponse, error) {
	img := &vision.Image{Content: enc}
	ctx := appengine.NewContext(g.Request)

	//configFileはGCPのAPIManagerから認証情報のJSONファイルを発行しましょう
	confFile, err := ioutil.ReadFile("resources/{{認証情報.json}}")
	if err != nil {
		return nil, err
	}
	//GoogleDeveloperのサービスアカウントのJSONファイルを使って
	//認証情報を読んでリクエストに認可を与える
	cfg, err := google.JWTConfigFromJSON([]byte(confFile), vision.CloudPlatformScope)
	client := cfg.Client(ctx)

	svc, err := vision.New(client)
	if err != nil {
		return nil, err
	}

	//ここでVisionAPIの中で使用するサービスを指定する。
	//FACE_DETECTIONとかTEXT_DETECTIONとか。
	feature := &vision.Feature{
		Type:       "SAFE_SEARCH_DETECTION",
		MaxResults: 5,
	}
	//最初に作ったimg情報とFeatureを利用してリクエストを作成する。
	req := &vision.AnnotateImageRequest{
		Image:    img,
		Features: []*vision.Feature{feature},
	}

	batch := &vision.BatchAnnotateImagesRequest{
		Requests: []*vision.AnnotateImageRequest{req},
	}
	//リクエストを投げる。
	res, err := svc.Images.Annotate(batch).Do()
	if err != nil {
		return nil, err
	}
	//コントローラ側でJSON作成のために構造体を返す。
	return res.Responses[0], nil
}


```
###エンドポイントの使い方：
上で作ったエンドポイントにPOSTで画像(.jpg)を投げるだけ。

#参考
[GolangとGoogle Cloud Vision APIを使って牛の画像認識をする - Developers Note](
http://developersnote.jp/google-apps/google-cloud-vision-api.html)



#終わりに
勢いで作ってしまったけど、レスポンスは

```response.json
{
  "responses": [
    {
      "safeSearchAnnotation": {
        "adult": "VERY_UNLIKELY",
        "spoof": "VERY_LIKELY",
        "medical": "UNLIKELY",
        "violence": "UNLIKELY"
      }
    }
  ]
}
```
という感じに帰ってくる。
Adult成分がVERY_UNLIKELYやPOSSIBLE、LIKELYなど返ってくるのがわかったので
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">エロ画像をCloudVisionに突っ込むとセーフサーチにひっかかるかどうかの判定してPOSSIBLE,LIKELYとか段階付けて返ってくるようにしてるんだけど、自動でドスケベエロ画像、スケベエロ画像、微エロ画像でフォルダ分けして収集が可能になってしまいましたねこれは…。</p>&mdash; Negipoyoc (@CST_negi) <a href="https://twitter.com/CST_negi/status/884588932704354308">2017年7月11日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

こういうことができそうだなって思いました。
AppEngineで画像投げるだけでGCSに格納して、DataStoreにはAdult情報と画像を紐付けるデータを格納しておく、というところまではできるんじゃないでしょうか。
(多分作っても公開しませんが…)

7/15追記：作りました。(自動でストレージに分類して保存してくれて最高)

終わり。