スマホで撮った写真をWatson Visual Recognitionに分析させる

スマホで撮った写真をBluemixのNode-REDにアップロードし、Node-REDフローでWatson Visual Recognitionの顔識別機能を呼び出して、年齢・性別を判定するアプリケーションを作成します。

写真を撮影してアップロードするところまでの手順は別記事「[スマホで撮った写真をNode-REDにアップロードする](http://qiita.com/takeyan/items/ccd74797937dc51959f6)」で紹介します。本記事では以下の手順を紹介します。

1. 写真データを圧縮する
2. Watson Visual Recognitionの顔識別機能を呼び出す
3. Watson Visual Recognitionの応答データをmustacheで整形してブラウザに返す

手順の説明に入る前に、写真のアップロードも含めたNode-REDフローの全体像を以下に示しておきます。
<img width="936" alt="Screen Shot 2016-12-16 at 10.15.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/75685d8d-c790-3ea4-86a5-a8d917d148e0.png">

またNode-REDフローのエクスポート・データも添付します。

```json
[{"id":"cf327624.6a5458","type":"visual-recognition-v3","z":"be2b435.a0f1b4","name":"Visual Recognition","apikey":"","image-feature":"detectFaces","x":311.3666687011719,"y":384,"wires":[["d7a95cc8.af6a98","b0c22b36.a7a388"]]},{"id":"6917833b.7470d4","type":"template","z":"be2b435.a0f1b4","name":"写真アップロード","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<html>\n<head><title>Watson Visual Recognition on Node-RED</title></head>\n<body>\n<h1>Watson Visual Recognition on Node-RED</h1>\n<h2>Select an image file</h2>\n<form  action=\"/vrimage3\" method=\"post\" enctype=\"multipart/form-data\">\n    <input type=\"file\" name=\"imagedata\" accept=\"image/*\"  />\n    <input type=\"submit\" value=\"Analyze\"/>\n</form>\n</body>\n</html>","x":333.3666687011719,"y":72,"wires":[["e4bf8aec.e044d"]]},{"id":"ab43f30d.9ec7f","type":"http in","z":"be2b435.a0f1b4","name":"[GET] /vrimage3","url":"/vrimage3","method":"get","swaggerDoc":"","x":132.86666870117188,"y":38,"wires":[["6917833b.7470d4"]]},{"id":"e4bf8aec.e044d","type":"http response","z":"be2b435.a0f1b4","name":"http response","x":542.8666687011719,"y":72,"wires":[]},{"id":"b00efb52.29892","type":"http in","z":"be2b435.a0f1b4","name":"[POST] /vrimage3","url":"/vrimage3","method":"post","swaggerDoc":"","x":128.36666870117188,"y":147,"wires":[["441fe1c3.ee57a8"]]},{"id":"fafc7840.148d48","type":"http response","z":"be2b435.a0f1b4","name":"http response","x":322.8666687011719,"y":577.9999694824219,"wires":[]},{"id":"b0c22b36.a7a388","type":"function","z":"be2b435.a0f1b4","name":"add HTTP header","func":"msg.headers = {\"content-type\" : \"text/html\" };\nreturn msg;","outputs":1,"noerr":0,"x":320.8666687011719,"y":474.9999694824219,"wires":[["7b2dd0.8167923"]]},{"id":"d7a95cc8.af6a98","type":"debug","z":"be2b435.a0f1b4","name":"","active":true,"console":"false","complete":"result","x":615.8666687011719,"y":382,"wires":[]},{"id":"8d9d5d07.9137d8","type":"function","z":"be2b435.a0f1b4","name":"Calling TinyPNG (POST) ","func":"var auth = new Buffer(\"api:XXX-XXX-XXXXXXXXXXXXXXXXXXXXXXXX\") ;\nauth = \"Basic \" + auth.toString(\"base64\");\nmsg.url = \"https://api.tinify.com/shrink\";\n\nmsg.method= \"POST\";\nmsg.headers = { \"Authorization\" : auth , \"Content-Type\" : \"image/jpeg\"};\nmsg.payload = msg.file;\n\nreturn msg;\n","outputs":1,"noerr":0,"x":313.3666687011719,"y":229,"wires":[["593685e0.be74cc","57a6a770.706c4"]]},{"id":"593685e0.be74cc","type":"debug","z":"be2b435.a0f1b4","name":"","active":false,"console":"false","complete":"payload","x":613.3666687011719,"y":229,"wires":[]},{"id":"57a6a770.706c4","type":"http request","z":"be2b435.a0f1b4","name":"TinyPNG","method":"use","ret":"txt","url":"","x":317.8666687011719,"y":271,"wires":[["f1085d67.43453","e28c4546.58987"]]},{"id":"f1085d67.43453","type":"debug","z":"be2b435.a0f1b4","name":"","active":true,"console":"false","complete":"payload","x":611.3666687011719,"y":273,"wires":[]},{"id":"e28c4546.58987","type":"function","z":"be2b435.a0f1b4","name":"parse","func":"msg.payload = JSON.parse(msg.payload).output.url;\nreturn msg;","outputs":1,"noerr":0,"x":320.3666687011719,"y":343,"wires":[["cf327624.6a5458","bc2227ff.c28c1"]]},{"id":"bc2227ff.c28c1","type":"debug","z":"be2b435.a0f1b4","name":"","active":false,"console":"false","complete":"payload","x":615.3666687011719,"y":341,"wires":[]},{"id":"7b2dd0.8167923","type":"template","z":"be2b435.a0f1b4","name":"Display Face Detection Result","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<h1>Visual Recognition on Bluemix Node-RED</h1>\n{{#result}}\n    {{#images}}\n        <div><img src=\"{{source_url}}\" height='300'/></div><br>\n    {{#faces}}\n        <br><b>年齢</b>\n        <table border='1'>\n            <thead>\n                <tr><th>Max</th><th>Min</th><th>Score</th></tr>\n            </thead>\n            <tbody>\n                {{#age}}\n                    <tr><td>{{max}}</td><td>{{min}}</td><td>{{score}}</td></tr>\n                {{/age}} \n            </tbody>\n        </table>\n        <br><b>性別</b>\n        <table border='1'>\n            <thead>\n                <tr><th>Gender</th><th>Score</th></tr>\n            </thead>\n            <tbody>\n                {{#gender}}\n                    <tr><td>{{gender}}</td><td>{{score}}</td></tr>\n                {{/gender}} \n            </tbody>\n        </table>\n        <br><b>該当者</b>\n        <table border='1'>\n            <thead>\n                <tr><th>Name</th><th>Score</th></tr>\n            </thead>\n            <tbody>\n                {{#identity}}\n                    <tr><td>{{name}}</td><td>{{score}}</td></tr>\n                {{/identity}} \n            </tbody>\n        </table>\n    {{/faces}}\n    {{/images}}    \n{{/result}}","x":315.3666687011719,"y":527.9999694824219,"wires":[["fafc7840.148d48"]]},{"id":"441fe1c3.ee57a8","type":"function","z":"be2b435.a0f1b4","name":"jpeg抽出","func":"var buf = msg.req.body;\nvar SOI = new Buffer(\"FFD8\",\"hex\");\nvar EOI = new Buffer(\"FFD9\",\"hex\");\nvar iSOI = 0;\nvar file = \"\";\n\nfor (var i=0 ; i<=buf.length ; i++) {\n    if(SOI.equals(buf.slice(i,i+2))) {\n        iSOI = i;\n        }\n    if(EOI.equals(buf.slice(i,i+2))) {\n        file = buf.slice(iSOI,i+2);\n        break;\n        }\n    }\n\nmsg.file = file;\nreturn msg;","outputs":1,"noerr":0,"x":313.3666687011719,"y":179,"wires":[["abdac4cd.ac2ff","8d9d5d07.9137d8"]]},{"id":"abdac4cd.ac2ff","type":"debug","z":"be2b435.a0f1b4","name":"","active":false,"console":"false","complete":"file","x":598.8666687011719,"y":179,"wires":[]}]
```

## 1. 写真データを圧縮する
Node-REDのWatson Visual Recognitonノードの説明（ノードをクリックして、Node-RED画面右側のinfoタブに表示される情報）をよく見ると、"Maximum image size is 2MB"という記述があります。
<img width="888" alt="Screen Shot 2016-12-15 at 19.35.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/43d3248b-ea31-f44d-1b52-df05ed31ff7e.png">
昨今のスマホのカメラは高解像度のため2MBにおさまらないことが多いので、Watson Visual Recognitionに写真データを入力する前にサイズを圧縮しておきます。
圧縮機能は手っ取り早く外部のサービスに頼ることにして、今回はREST APIで呼び出せて無料枠（API呼び出し500件／月まで無料）のあるTinyPNGというサービスを利用しました。

### 1-1. TinyPNGのAPIキーを取得する 
まずTinyPNG Developer APIのページ（https://tinypng.com/developers ）を開きます。
<img width="1054" alt="Screen Shot 2016-12-16 at 9.41.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/b6e21b77-e096-015e-5ce3-2f57c4b11e84.png">


ここに名前とメールアドレスを入力して"Get your API key"をクリックすると、まもなく通知のメールが届きます。
<img width="737" alt="Screen Shot 2016-12-16 at 9.42.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/5bae4d48-0dcb-9dcf-f457-bade7d4fc82d.png">

メールの中にDashboardへのリンクがあるので、これをクリックしてDashboardを開きます。そこにAPIキーが書かれているので、後ほどNode-REDにコピペして使用します。

### 1-2. TinyPNGのAPIをコールする
Node-REDフローの以下の部分でTinyPNGのAPIをコールしています。
<img width="331" alt="Screen Shot 2016-12-16 at 10.20.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/e4d22e93-bbac-46da-2c63-aee3761eafed.png">

functionノードの中味は以下のとおりです。TinyPNGのDashboardで入手したAPIキーを1行目に記入します。7行目のmsg.fileは、直前のfunctionノード（jpeg抽出）でjpegファイルをmsg.fileに格納してreturnしているので、これに合わせてあります。jpegファイルを後続のhttp requestノードに渡すためにmsg.payloadに格納してreturnしています。
<img width="720" alt="Screen Shot 2016-12-16 at 10.28.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/f904f1c5-1e5e-9030-d452-a43d6c473ca0.png">

```js:Calling&nbsp;TinyPNG&nbsp;(POST) 
var auth = new Buffer("api:XXX-XXX-XXXXXXXXXXXXXXXXXXXXXXXX") ;
auth = "Basic " + auth.toString("base64");
msg.url = "https://api.tinify.com/shrink";

msg.method= "POST";
msg.headers = { "Authorization" : auth , "Content-Type" : "image/jpeg"};
msg.payload = msg.file;

return msg;
```

http requestノードの中味は以下のとおりです。名前を指定しただけで他はデフォルトのままです。
<img width="497" alt="Screen Shot 2016-12-16 at 11.14.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/c4eceb28-a638-dcac-f9d8-2da8e8b37efa.png">


## 2. Watson Visual Recognitionの顔識別機能を呼び出す
ここではNode-REDフローの以下の部分を説明します。
<img width="411" alt="Screen Shot 2016-12-16 at 12.30.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/b6d780d9-9c86-5f47-b6a1-2155db8c6ff3.png">


TinyPNGをコールしたhttp requestノードは、TinyPNGからの応答をJSON形式の文字列として、以下の例のようにmsg.payloadにセットして返します。
<img width="644" alt="Screen Shot 2016-12-16 at 12.43.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/e64d0697-5ae9-88a3-5fdb-024efc2f4e8d.png">


```json:msg.payload
{"input":{"size":5856,"type":"image/jpeg"},"output":{"size":5722,"type":"image/jpeg","width":115,"height":115,"ratio":0.9771,"url":"https://api.tinify.com/output/ibuikute0r6t7p27.jpg"}}
```

圧縮した画像そのものは応答には含まれていませんが、画像のURLが応答の一部として渡ってきます。これを抜き出してWatson Visual Recognitionに渡すのですが、ここではJSON.parseでJSON形式の文字列をJSONオブジェクトに変換し、オブジェクトのプロパティとしてurlの値を取り出します。
以下にfunctionノードの中味を示します。
<img width="624" alt="Screen Shot 2016-12-18 at 20.11.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/b6fc2c24-a447-6659-4136-6743de1ff721.png">

```js:parse
msg.payload = JSON.parse(msg.payload).output.url;
return msg;
```


次にWatson Visual Recognitionノードの中味を以下に示します。
<img width="648" alt="Screen Shot 2016-12-16 at 12.31.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/58818959-14d8-3310-a482-a019dca38248.png">

API Keyのフィールドは、表示される場合とされない場合があります。Node-REDのランタイムにWatson Visual Recognitionインスタンスが接続済の場合には、このフィールドは表示されません。API Keyは内部で自動的に連携されます。
Watson Visual Recognitionインスタンスが接続されていない場合にはこのフィールドが表示されるので、Watson Visual Recognitionインスタンスのcredentialsのapi_keyの値をここにコピペします。

"Detect"では"Detect Faces"（顔識別）を選択しておきます。


## 3. Watson Visual Recognitionの応答データをmustacheで整形してブラウザに返す
ここではNode-REDフローの以下の部分を説明します。
<img width="452" alt="Screen Shot 2016-12-16 at 12.45.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/283bf05e-c567-7f7e-9c1d-01adf255af6c.png">

Watson Visual Recognitionからの応答直後は、msg.headersにセットされているcontent-typeの値が"application/json"になっているので、ブラウザにHTMLを返すために"text/html"に置き換えておきます。functionノードの中味を以下に示します。
<img width="698" alt="Screen Shot 2016-12-16 at 13.14.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/f3795a2a-4d22-c1a5-5d09-9ead41a0b3d3.png">


Watson Visual Recognitionは顔識別の結果をJSONオブジェクトとして、以下の例のようにmsg.resultにセットして返します。
<img width="645" alt="Screen Shot 2016-12-16 at 12.48.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/46cb8897-f3cb-33d8-e818-4cf050277114.png">

このJSONオブジェクトとmustacheというテンプレートを使って、ブラウザに出力するHTMLを組み立てます。mustacheは{{}}でJSON要素の名前（key）をくくってやると、対応するJSON要素の値（value）に置き換えてくれます。
興味のある方は解説記事や書籍を探してみて下さい。今回作成したtemplateノードの中味を以下に示します。
<img width="771" alt="Screen Shot 2016-12-18 at 21.42.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/da76581d-9aa1-a2e4-e79b-62dae79c29d2.png">

テンプレートの全文をあらためて以下に掲載します。

```html:Display&nbsp;Face&nbsp;Detection&nbsp;Result
<h1>Visual Recognition on Bluemix Node-RED</h1>
{{#result}}
    {{#images}}
        <div><img src="{{source_url}}" height='300'/></div><br>
    {{#faces}}
        <br><b>年齢</b>
        <table border='1'>
            <thead>
                <tr><th>Max</th><th>Min</th><th>Score</th></tr>
            </thead>
            <tbody>
                {{#age}}
                    <tr><td>{{max}}</td><td>{{min}}</td><td>{{score}}</td></tr>
                {{/age}} 
            </tbody>
        </table>
        <br><b>性別</b>
        <table border='1'>
            <thead>
                <tr><th>Gender</th><th>Score</th></tr>
            </thead>
            <tbody>
                {{#gender}}
                    <tr><td>{{gender}}</td><td>{{score}}</td></tr>
                {{/gender}} 
            </tbody>
        </table>
        <br><b>該当者</b>
        <table border='1'>
            <thead>
                <tr><th>Name</th><th>Score</th></tr>
            </thead>
            <tbody>
                {{#identity}}
                    <tr><td>{{name}}</td><td>{{score}}</td></tr>
                {{/identity}} 
            </tbody>
        </table>
    {{/faces}}
    {{/images}}    
{{/result}}
```

最後にブラウザの応答画面例を示します。mustacheテンプレートのレイアウトに従って、スマホで撮影した写真とWatson Visual Recognitionの判定結果が表示されています。
<img width="403" alt="Screen Shot 2016-12-16 at 17.53.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/32083/4967c0e1-2789-e0f7-d9d1-4204e5381dc4.png">
（※イラスト部分は合成です。ちなみにこのイラストはWatson Visual Recognitionには顔と認識されませんでした。）

以上です。