スマホで撮った写真をBluemixのNode-REDにアップロードし、Node-REDフローで写真データを利用できるようにします。
写真撮影にはモバイルアプリもJavaScriptも使わず、HTML記述とブラウザの機能でスマホのカメラを起動し、撮影した写真はjpegファイルとしてHTMLフォームを使って送信します。
手順は以下のとおりです。
- 写真アップロード用のNode-REDフローを作成する
- アップロードされたデータから写真データを抽出するNode-REDフローを作成する
それぞれのNode-REDフローのエクスポート・データも添付しておきます。
[{"id":"bf065758.c35d38","type":"template","z":"456ff1.fa06001","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":348,"y":108,"wires":[["c02f4753.6d8888"]]},{"id":"da2dbf9d.6d0bc","type":"http in","z":"456ff1.fa06001","name":"[GET] /vrimage3","url":"/vrimage3","method":"get","swaggerDoc":"","x":147.5,"y":74,"wires":[["bf065758.c35d38"]]},{"id":"c02f4753.6d8888","type":"http response","z":"456ff1.fa06001","name":"http response","x":557.5,"y":108,"wires":[]}]
[{"id":"f31e3df1.61dd5","type":"http in","z":"456ff1.fa06001","name":"[POST] /vrimage3","url":"/vrimage3","method":"post","swaggerDoc":"","x":150,"y":232,"wires":[["9faf39cb.85ed58"]]},{"id":"9faf39cb.85ed58","type":"function","z":"456ff1.fa06001","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":335,"y":264,"wires":[["6b5ec310.9fec0c"]]},{"id":"6b5ec310.9fec0c","type":"debug","z":"456ff1.fa06001","name":"","active":true,"console":"false","complete":"file","x":530.5,"y":264,"wires":[]}]
1. 写真アップロード用のNode-REDフローを作成する
作成したNode-REDフローとテンプレートノードの中味(HTML)、及びこのHTMLのブラウザ表示画面(iPhoneのSafariの例)を以下に示します。
<html>
<head><title>Watson Visual Recognition on Node-RED</title></head>
<body>
<h1>Watson Visual Recognition on Node-RED</h1>
<h2>Select an image file</h2>
<form action="/vrimage3" method="post" enctype="multipart/form-data">
<input type="file" name="imagedata" accept="image/*" />
<input type="submit" value="Analyze"/>
</form>
</body>
</html>
「ファイルを選択」をタップすると選択肢が表示されますが、この中からカメラを選択して写真を撮影します。撮影した写真のjpegデータはHTMLフォームの中でfileタイプのインプット要素として扱われます。
写真撮影後に「Analyze」という名前のボタンをタップすると、写真のjpegデータを含むフォームが/vrimage3というパス宛にPOSTメソッドで送付されます。
以下に「ファイルを選択」タップ後に選択肢が表示されている画面の例を、iPhoneのSafariとAndroidのChromeの二種類載せておきます。
2. アップロードされたデータから写真データを抽出するNode-REDフローを作成する
フォームのデータはマルチパートという形式でアップロードされるのですが、そこには写真のjpegデータ以外のデータも混ざっています。欲しいのはjpegデータだけなので、マルチパートのデータからjpegデータを抽出します。
Node-REDにはjpeg抽出のためのノードは残念ながら用意されていません。マルチパートを扱うためのJavaScriptのライブラリを利用する手もありますが、ライブラリがNode-REDに標準で組み込まれていないと面倒なので、マルチパートからjpegデータを取り出すプログラムを書くことにしました。もしかしたら標準で使える簡単な方法があるのかもしれませんが、プログラムが意外と簡単だったので真剣には調べていません。
jpegデータは仕様によりSOI(Start of Image, X'FFD8')で始まり、EOI(End of Image, X'FFD9')で終わると決められているので、マルチパートから該当部分を抽出すればjpegデータが得られるはずです。(下図はITU CCITT Recommendation T.81より抜粋)
もしかしたらマルチパートの中にたまたまSOI,EOIに一致してしまう部分があるかも知れませんが、その時は運が悪かったと諦めることにして、プログラミングは必要最小限に留めておきます。
jpegデータを抽出するプログラムは、functionノードを使ってJavaScriptで実装します。作成したNode-REDフローとfunctionノードの実装を以下に示します。
var buf = msg.req.body;
var SOI = new Buffer("FFD8","hex");
var EOI = new Buffer("FFD9","hex");
var iSOI = 0;
var file = "";
for (var i=0 ; i<=buf.length ; i++) {
if(SOI.equals(buf.slice(i,i+2))) {
iSOI = i;
}
if(EOI.equals(buf.slice(i,i+2))) {
file = buf.slice(iSOI,i+2);
break;
}
}
msg.file = file;
return msg;
debug出力(msg.fileの出力)は以下のようになります。jpegデータが正しく抽出できているかどうかは別途検証するとして、ここではSOI(X'FFD8')で始まるデータが抽出できていることを確認したので良しとしておきます。
以上です。
P.S. この記事は、「スマホで撮った写真をWatson Visual Recognitionに分析させる」の一部です。本編の方も御覧ください。
参照:
http://hp.vector.co.jp/authors/VA032610/JPEGFormat/StructureOfJPEG.htm
https://www.w3.org/Graphics/JPEG/
https://www.w3.org/Graphics/JPEG/itu-t81.pdf