Node-REDでGoogle Cloud Vision APIを使う

# イントロダクション

ここで紹介するのは、Googleがクラウドサービス「Google Cloud Platform」の1つとして提供するサービス「[Google Cloud Vision API](https://cloud.google.com/vision/)」を[Node-RED](http://nodered.org/)から使う方法の1つです。このAPIは、強力な機械学習モデルの能力を活用することで画像の内容を理解できるアプリケーションの開発を可能にするもので、Google Cloud Platformに登録すればすぐに利用を始めることができます。

![Screen Shot 2016-05-05 at 12.56.06.png](https://qiita-image-store.s3.amazonaws.com/0/92577/cd59376f-3f22-c868-0335-1c4bc601bd99.png)

## 動作確認を行った環境

* MacOS: v10.11.4
* node.js: v4.4.3
* Node-RED: v0.13.4

# サンプルで行っている処理の説明

まず、Twitterから指定したキーワード（このスクリーンショットの例では`#google`）で検索し、ツイートに画像が添付されているものが見つかったら、その画像のURLを取得します。そして、そのURLの画像を続くhttp requestノードでダウンロードします。

```js
// Ignore tweets without images
if (typeof msg.tweet.entities.media === 'undefined') {
    return null;
}

var image = msg.tweet.entities.media[0];
var newMessage = { payload: image.media_url_https };
newMessage.url = image.media_url_https;
return newMessage;
```

画像がダウンロードできたら、functionノードでBase64でエンコードした画像データおよび各種パラメータを含むGoogle Cloud Vision APIへのリクエストを生成します。ここでは、タイプとして`LABEL_DETECTION`を、最大数を3に指定しています。ここで指定している`LABEL_DETECTION`以外に、`TEXT_DETECTION`、`FACE_DETECTION`、`LANDMARK_DETECTION`、`LOGO_DETECTION`、`SAFE_SEARCH_DETECTION`、`IMAGE_PROPERTIES`が指定できます。

```js
var image = {content: msg.payload.toString('base64')};
var features = {type: 'LABEL_DETECTION', maxResults: 3};
var imageContext = {languageHints: 'ja'};
var request = {image: image, features: features, imageContext: imageContext};
var requests = {requests: request};
msg.payload = requests;

return msg;
```

次に、続くhttp requestノードに渡すために、msgのurlとheadersをGoogle Cloud Vision APIを使うためのものに置き換えます。ここで、`?key=`以下の文字列は自分で取得したAPIキーに置き換えます。http requestノードでは、指定したurlとheadersにmsg.payloadをボディとしてHTTPリクエストを発行します。

![Screen Shot 2016-05-05 at 13.40.42 masked.png](https://qiita-image-store.s3.amazonaws.com/0/92577/20b12ac2-3151-499c-298f-8e4a1f0aa354.png)

リクエストに対しては、通常は次のような回答が返ってきます。descriptionが説明で、scoreの高い順から指定した最大数まで（この例の場合には3個）が返ってきます。

```json
{
	"responses": [{
		"labelAnnotations": [{
			"mid": "/m/0215n",
			"description": "cartoon",
			"score": 0.88192624
		}, {
			"mid": "/m/012h24",
			"description": "comics",
			"score": 0.78700739
		}, {
			"mid": "/m/0bt_c3",
			"description": "book",
			"score": 0.64699644
		}]
	}]
}
```

しかしながら、何もラベルが見つからなかった場合には次のように空の回答が返ってくることもあります。

```json
{
	"responses": [{}]
}
```

そこで、受け取った回答を処理するfunctionノードでは、回答が空でなかった場合に限り、ラベルの説明とスコアを文字列として出力します。このサンプルではdebugノードでdebugタブに出力しているだけですが、Twitterに投稿する、メールで送信する、データベースに登録する、など様々な利用方法があるかと思います。

```js
// To skip messages like { "responses": [ {} ] }
if (Object.keys(msg.payload.responses[0]).length < 1) {
    return null;
}

var labels = 'labels: ';
var labelAnnotations = msg.payload.responses[0].labelAnnotations;
for (var i = 0; i < labelAnnotations.length; i++) {
    labels += labelAnnotations[i].description;
    labels +=' (' + labelAnnotations[i].score + '), ';
}

msg.payload = labels.slice(0, labels.length - 2);
return msg;
```

この例をNode-REDで試すには、以下のテキストをフローにコピー＆ペーストした上で、Google Cloud Platformで取得したAPIキーに置き換えてから試してみてください。

```json
[{"id":"38011ed4.7b0ad2","type":"twitter in","z":"46a7fc5.383dc04","twitter":"","tags":"#google","user":"false","name":"","topic":"tweets","x":80,"y":80,"wires":[["79175e4f.8f637"]]},{"id":"79175e4f.8f637","type":"function","z":"46a7fc5.383dc04","name":"If found a tweet with an image...","func":"// Ignore tweets without images\nif (typeof msg.tweet.entities.media === 'undefined') {\n    return null;\n}\n\nvar image = msg.tweet.entities.media[0];\nvar newMessage = { payload: image.media_url_https };\nnewMessage.url = image.media_url_https;\nreturn newMessage;","outputs":"1","noerr":0,"x":330,"y":80,"wires":[["53eadf47.4439c"]]},{"id":"53eadf47.4439c","type":"http request","z":"46a7fc5.383dc04","name":"Send a request to get the image","method":"GET","ret":"bin","url":"","x":330,"y":120,"wires":[["b600066.46b27f8"]]},{"id":"1789627e.253e5e","type":"http request","z":"46a7fc5.383dc04","name":"Send a request to get labels","method":"POST","ret":"obj","url":"","x":320,"y":240,"wires":[["1f6d8ae9.0fb8f5"]]},{"id":"b600066.46b27f8","type":"function","z":"46a7fc5.383dc04","name":"Make a request for Google Cloud Vision to get labels","func":"var image = {content: msg.payload.toString('base64')};\nvar features = {type: 'LABEL_DETECTION', maxResults: 3};\nvar imageContext = {languageHints: 'ja'};\nvar request = {image: image, features: features, imageContext: imageContext};\nvar requests = {requests: request};\nmsg.payload = requests;\n\nreturn msg;","outputs":1,"noerr":0,"x":400,"y":160,"wires":[["fe37ed4d.3f0e7"]]},{"id":"fe37ed4d.3f0e7","type":"change","z":"46a7fc5.383dc04","name":"Set url and headers","rules":[{"t":"set","p":"url","pt":"msg","to":"https://vision.googleapis.com/v1/images:annotate?key=API_KEY","tot":"str"},{"t":"set","p":"headers","pt":"msg","to":"Content-Type: application/json","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":200,"wires":[["1789627e.253e5e"]]},{"id":"b9d122fb.37122","type":"debug","z":"46a7fc5.383dc04","name":"","active":true,"console":"false","complete":"payload","x":530,"y":280,"wires":[]},{"id":"d7010e16.64272","type":"comment","z":"46a7fc5.383dc04","name":"Replace with your favorite user or hashtag","info":"","x":180,"y":40,"wires":[]},{"id":"1f6d8ae9.0fb8f5","type":"function","z":"46a7fc5.383dc04","name":"Report found labels","func":"// To skip messages like { \"responses\": [ {} ] }\nif (Object.keys(msg.payload.responses[0]).length < 1) {\n    return null;\n}\n\nvar labels = 'labels: ';\nvar labelAnnotations = msg.payload.responses[0].labelAnnotations;\nfor (var i = 0; i < labelAnnotations.length; i++) {\n    labels += labelAnnotations[i].description;\n    labels +=' (' + labelAnnotations[i].score + '), ';\n}\n\nmsg.payload = labels.slice(0, labels.length - 2);\nreturn msg;","outputs":1,"noerr":0,"x":290,"y":280,"wires":[["b9d122fb.37122"]]},{"id":"2da0f93a.083626","type":"comment","z":"46a7fc5.383dc04","name":"Replace with your API key","info":"","x":530,"y":200,"wires":[]}]
```


# 参考にしたドキュメント

* [Google Cloud Vision API Documentation](https://cloud.google.com/vision/docs/)
* [Node-RED | Documentation](http://nodered.org/docs/)
* [BluemixのNode-REDで簡単Webアプリ開発](http://qiita.com/KenichiSekine/items/7005df36093fabf00b41)
* [私が Node-RED を頻繁に利用するようになったきっかけとハッカソンでの Node-RED 利用について考察](http://qiita.com/ibmamnt/items/19ef94f085b129e60828)