Cloud Functions と Vision API を連携して画像解析を試してみる

## はじめに

[Google Cloud Platform(1) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/gcp) の8日目を担当します。
Google Cloud Functions か Cloud Vision API のどちらかの内容で書こうと思ったのですが、どうせなら組合せてしまえ、ということで試してみました。（Vision API単体で利用することも当然可能です）

## Cloud Vision API とは
https://cloud.google.com/vision/ より
> Google Cloud Vision API は、使いやすい REST API にパワフルな機械学習モデルが組み込まれており、画像の内容を認識するアプリケーションの開発を可能にします。Google Cloud Vision API は、膨大な数のカテゴリ（「ヨット」や「ライオン」、「エッフェル塔」など）に各画像を素早く分類する機能や、画像内の個々のモノや人の顔を検出する機能、画像内に含まれているテキストを検出して読み取る機能を備えています。 また、画像カタログのメタデータ作成、不適切なコンテンツの管理、画像の感情分析を通じた新しいマーケティング手法の導入が可能になります。リクエストでアップロードされた画像を分析することも、Google Cloud Storage の画像ストレージに統合することもできます。

つまりGoogleが提供している画像解析用のAPIで、画像から様々な情報を読み取ることができます。

**■主な機能**

- 画像に映っている様々な物体を検出し分類（例：花、動物、乗り物など）
- 不適切情報の検出（暴力的なもの、アダルトなものなど）
- 画像内の人物の感情分析（喜びや悲しみ、怒りなど）
- 画像に映っている文字の検出（光学式文字認識）

実際にどんな感じなのか、上記のURLから画像をアップロードすると簡単に試すことができます。
試しに私の顔写真をアップしてみると、このような情報が読み取れます。感情分析では"Joy"の評価値が高いので、喜んでいると判断されたようです。

![スクリーンショット 2016-12-09 0.09.26.png](https://qiita-image-store.s3.amazonaws.com/0/52142/85c1f80a-13ac-1c9e-45b1-c26f40a763ad.png)

## Cloud Functions とは
最近、サーバーレスアーキテクチャが何かとホットですが、AWS Lambda や Azure Functions のGoogle版という感じです。ファイルストレージにファイルがアップロードされたり、DBのデータが更新された、といったイベントをトリガーにして自動実行される処理を、サーバーの存在を意識することなく実装できるFaaSと呼ばれるサービスです。（もちろん実際には裏で物理サーバが動いているわけですが、開発者がそれを意識する必要がない）

この辺の説明をすると非常に長くなるので割愛しますが、AWS Lambda と Google Cloud Functions の違いについて書かれた記事を以前に翻訳したので、興味があればこちらもご覧ください。
[【翻訳記事】Google Cloud Functions 対 AWS Lambda: サーバーレスクラウドを制する闘いが始まる](http://qiita.com/bump_of_kiharu/items/355a73412445c5ff59a0)

ただし注意点として、2016年12月現在、Cloud Functions はアルファ版で利用には申請が必要です。（ちなみに私は申請が通るまでに1ヶ月以上かかりました）

## 試したこと

今回やりたかったのは以下の通りです。

🍀 Google Cloud ストレージ の特定のバケットにファイルをアップロードする
　　　↓
🍀 アップロードイベントをトリガーにして、Cloud Functionsのファンクションを自動起動する
　　　↓
🍀 ファンクション内でVision APIを呼び出し、アップロードした画像の解析結果をコンソールに出力

## 実装手順
※ Cloud Functionsの利用申請は通っている前提です。

1. GCP上に新規プロジェクトを作成する（または既存のプロジェクトを選択）
2. "API Manager" メニューから、Cloud Vision API を有効にする
3. "Cloud Functions" メニューから、ファンクションを登録する
4. 指定したバケットに画像をアップロードする
5. "ログ" メニューで実行結果を確認する

基本的にはこれだけで出来ました。
最終的にIAMロールは特に設定しなくても実行できたのですが、それはアルファ版だからなのか・・・？実行アカウントをセットする箇所も特に見当たらないので、この辺は分かり次第追記します。

## ファンクションの登録とサンプルコード
なんと、このブログを書いているうちに、GCPコンソール上にインラインエディタ（ブラウザ上でコードが書けるもの）が追加されていました！これでお手軽に試すことが出来ますね。

![スクリーンショット 2016-12-09 1.31.09.png](https://qiita-image-store.s3.amazonaws.com/0/52142/71640cfa-7cf9-c5e3-aabb-8a9fff184c84.png)

**割当メモリ:**
ファンクション実行時に必要とされるメモリ（今は適当で大丈夫です）

**タイムアウト:**
ファンクションの最大実行時間。もし万が一無限ループになったとしても、指定秒で終了します

**トリガー:**
今回は「Cloud Storage バケット」を選択します

**バケット:**
検知したい画像をアップロードするバケットを指定

**ソースコード:**
今回は「インラインエディタ」を指定

**ステージバケット**
"ZIPアップロード"を選択した時もそうですが、ソースファイルはここで指定したバケットにアップロードされます。インラインエディタの場合は、index.jsとpackage.jsonをzip化した状態で自動的にここに格納されます。
とりあえず、"staging.xxxxxx.appspot.com" みたいな名前のバケット（新規プロジェクト作成時に自動的に作られるバケット）でも指定しておけば大丈夫です。

**実行する関数**
以下のサンプルコードの場合は、"processFile" が実行関数名になります。

**サンプルコード**
今回、画像に書いてある文字を拾うサンプルを作成します。（そのままコピペで動きます）

```js:index.js
var gcloud = require('gcloud')({
  projectId: process.env.GCP_PROJECT  // GCPの環境変数
});

var vision = gcloud.vision(),
    gcs = gcloud.storage();

exports.processFile = function(event, callback) {
  var file = gcs.bucket(event.data.bucket).file(event.data.name);
  
  vision.detectText(file, function(err, text, apiResponse) {
    console.log('text:', text);
  });
  callback();
};
```

```package.json
{
  "name": "sample-cloud-storage",
  "version": "0.0.1",
  "dependencies": {
    "gcloud": "latest"
  }
}
```

## 実行結果

**検証:1**
![CbZA5E1UYAAf1sT.jpg](https://qiita-image-store.s3.amazonaws.com/0/52142/1ed2dd6f-4328-e1e8-2541-fb8301d6b65e.jpeg)

![スクリーンショット 2016-12-09 1.49.35.png](https://qiita-image-store.s3.amazonaws.com/0/52142/a1400c5f-7fcf-adfb-4df5-f64d63d2dc8b.png)

**検証2：**
![ABC.png](https://qiita-image-store.s3.amazonaws.com/0/52142/beaa67bc-2939-d2b3-e19a-fe4400cfc107.png)

![スクリーンショット 2016-12-09 1.44.07.png](https://qiita-image-store.s3.amazonaws.com/0/52142/a2c0843d-0a63-67ff-0934-bb1a4c117844.png)

かなり良い精度で文字情報が取得できていることが分かります。
検証１のような、手書き風の文字や視認性が悪い部分は多少精度が落ちますが、検証２のような、はっきりと文字が確認できるスクリーンショットであれば、ほぼ完璧に読み取れているようです。

## 注意事項（2016年12月時点）
Cloud Functionsの方はまだアルファ版ということもあり、これを書いている最中にも色々アップデートが入ったので注意が必要です。
特に一番大きな落とし穴が、公式ドキュメントに書かれているサンプルコードが、そのままだと動かないことです・・・

例えばこれ
https://cloud.google.com/functions/docs/tutorials/storage

```だめな例.js
exports.helloGCS = function helloGCS (context, data) {
  console.log('Hello ' + (data.name || 'World') + '!');
  context.success();
};
```

実行時ファンクションの引数が大幅に変わったようで、(context, data) ではなく イベント情報とコールバック関数が渡ってくるため、このままでは動きません。ドキュメントはまだ整備されていないようです。
Cloud Functions のサンプルコードを検索すると、この古い引数でのサンプルばかりヒットするので、気を付けてください。

## 所感

実は私が一番やりたかったのは、Firebase上のアプリケーションから画像がアップロードされたら、各サイズのサムネイル画像を自動作成する処理を実装することでした。AWS Lambdaの登場当時、やはりサムネイル画像の作成処理の事例が多く共有されていました。（Firebaseのストレージサービスは実はGCSなので、Cloud Functionsを利用することが可能なのです）

GCPは、FaaSの部分で他ベンダーに少し遅れを取っている感があるので、まずはCloud Functionsが1日も早く正式リリースされ、そして今後Firebaseとの連携ノウハウなども活発に共有されていくことを期待しています。