LoginSignup
7
13

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-18

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

写真を撮影してアップロードするところまでの手順は別記事「スマホで撮った写真をNode-REDにアップロードする」で紹介します。本記事では以下の手順を紹介します。

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

手順の説明に入る前に、写真のアップロードも含めたNode-REDフローの全体像を以下に示しておきます。
Screen Shot 2016-12-16 at 10.15.26.png

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

[{"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"という記述があります。
Screen Shot 2016-12-15 at 19.35.48.png
昨今のスマホのカメラは高解像度のため2MBにおさまらないことが多いので、Watson Visual Recognitionに写真データを入力する前にサイズを圧縮しておきます。
圧縮機能は手っ取り早く外部のサービスに頼ることにして、今回はREST APIで呼び出せて無料枠(API呼び出し500件/月まで無料)のあるTinyPNGというサービスを利用しました。

1-1. TinyPNGのAPIキーを取得する

まずTinyPNG Developer APIのページ(https://tinypng.com/developers )を開きます。
Screen Shot 2016-12-16 at 9.41.10.png

ここに名前とメールアドレスを入力して"Get your API key"をクリックすると、まもなく通知のメールが届きます。
Screen Shot 2016-12-16 at 9.42.10.png

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

1-2. TinyPNGのAPIをコールする

Node-REDフローの以下の部分でTinyPNGのAPIをコールしています。
Screen Shot 2016-12-16 at 10.20.56.png

functionノードの中味は以下のとおりです。TinyPNGのDashboardで入手したAPIキーを1行目に記入します。7行目のmsg.fileは、直前のfunctionノード(jpeg抽出)でjpegファイルをmsg.fileに格納してreturnしているので、これに合わせてあります。jpegファイルを後続のhttp requestノードに渡すためにmsg.payloadに格納してreturnしています。
Screen Shot 2016-12-16 at 10.28.25.png

Calling TinyPNG (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ノードの中味は以下のとおりです。名前を指定しただけで他はデフォルトのままです。
Screen Shot 2016-12-16 at 11.14.09.png

2. Watson Visual Recognitionの顔識別機能を呼び出す

ここではNode-REDフローの以下の部分を説明します。
Screen Shot 2016-12-16 at 12.30.18.png

TinyPNGをコールしたhttp requestノードは、TinyPNGからの応答をJSON形式の文字列として、以下の例のようにmsg.payloadにセットして返します。
Screen Shot 2016-12-16 at 12.43.51.png

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ノードの中味を示します。
Screen Shot 2016-12-18 at 20.11.31.png

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

次にWatson Visual Recognitionノードの中味を以下に示します。
Screen Shot 2016-12-16 at 12.31.39.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フローの以下の部分を説明します。
Screen Shot 2016-12-16 at 12.45.52.png

Watson Visual Recognitionからの応答直後は、msg.headersにセットされているcontent-typeの値が"application/json"になっているので、ブラウザにHTMLを返すために"text/html"に置き換えておきます。functionノードの中味を以下に示します。
Screen Shot 2016-12-16 at 13.14.45.png

Watson Visual Recognitionは顔識別の結果をJSONオブジェクトとして、以下の例のようにmsg.resultにセットして返します。
Screen Shot 2016-12-16 at 12.48.10.png

このJSONオブジェクトとmustacheというテンプレートを使って、ブラウザに出力するHTMLを組み立てます。mustacheは{{}}でJSON要素の名前(key)をくくってやると、対応するJSON要素の値(value)に置き換えてくれます。
興味のある方は解説記事や書籍を探してみて下さい。今回作成したtemplateノードの中味を以下に示します。
Screen Shot 2016-12-18 at 21.42.28.png

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

Display Face Detection 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の判定結果が表示されています。
Screen Shot 2016-12-16 at 17.53.11.png
(※イラスト部分は合成です。ちなみにこのイラストはWatson Visual Recognitionには顔と認識されませんでした。)

以上です。

7
13
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
7
13