AngelHackOsaka2018(https://angelhackosaka2018.peatix.com )というハッカソンイベントに運営スタッフとして参加しました.深夜にかけて時間の余裕があったので”ひとりハッカソン”と称して,協賛企業さんのAPIを使ってサービスを作ってみたのでご紹介します.
構想
- 画像処理のエンジニア → AIやりたい
- 香川県出身 → うどん
- モバイル → LINEをプラットホームに.
目標
- LINEでBotを作り,画像を投稿すると,AI処理した結果を返す (ミニマムサクセス)
- Webで適当に集めた画像でクラス分類問題を学習し,Webサービスとして提供(ミドルサクセス)
- うどん画像から店名と値段を推定 (フルサクセス)
結果
要素技術
- IBM Visual Recognition ・・・ 画像を送るとクラブ分類結果とその確信度を教えてくれる.30日無料
- IBM Node-Red Starter ・・・ Webブラウザ上でブロックを配置するだけでWebアプリサービスを実装&ローンチできるサービス.30日無料
- LINE Messaging API ・・・ LINE上にBotを作成する.ライトユーザは基本無料.
手順
参考にすべき情報
先駆者の情報がいろいろネットに上がっているので,まずはそちらを参考に環境構築.
- LINE Messaging API → IBM Node-Red Starter ... https://simple-it-life.com/2017/08/20/line-bot/
- IBM Node-Red Starter → IBM Visual Recognition ... http://wp.tech-style.info/archives/1081
おおまかな流れ
- とりあえず↑の2つをやる
- IBM Node-Red Starterにて,ブロック図を編集.
- BlueMixのダッシュボードへ移動 https://console.bluemix.net/dashboard/apps/
- Cloud Foundry アプリケーションの箇所にNode-Redが動いているレコードがあるのでクリック → ページ遷移.
- Cloud Foundry アプリケーションの詳細,っていう画面が出るので「経路」ボタンwをクリックし,ドロップダウンメニューの一番上にあるURLを選択 → ページ遷移
- Node-RED on IBM Cloud ,っていう画面になる.「Go to your Node-RED flow editor」をクリック.
- Node-REDのエディター画面になる.画面右上のメニューから,読み込み→クリップボード
- この記事末尾のコードをコピペ
- Node-REDのエディター画面にて,それっぽいブロックのフローが表示されていればOK
- LINEのほうは変更不要
- スマホからBotに画像を投げつけると,クラス分類判定第一位の名前と確信度が返ってくるはず・・・!
はまった点
- Node-RED
- 画像を扱う例は多数あるものの,Web上に画像単体がUPされている例(http://test.com/test.jpg みたいな)とかブラウザからPOSTされた画像を扱う例が多数派で,LINEから画像をとってきてそれを処理する例が見当たらなかった.
- LINE Messaging APIからPOSTされた画像の回収と,Visual RecognitionへのPOSTとを,シーケンシャルにやろうとすると,LINE Messaging APIへの応答文を作るのが難しい.2本のパラレルなルーチンを実行し,最後にmerge(=力技で結合)するという形で実現.
- Visual Recognition
- Web上に画像単体がUPされている例の画像を扱う,あるいは,サーバ内に画像がある場合を扱う(?)のが多数派
- Node-Redのコード内で,画像データをgetしてmsg変数に格納し,っていう処理はあまりなかった
- LINE Messaging API
- 画像を扱うドキュメントが少なく情報がわからん...Node-Redとなるとなおさら.
感想など
- モバイルプラットホーム⇔AIのapi,をあっさりと繋げることができて,さくっと面白いアプリを作れた印象
- 今回はデフォルトのVisualRecognitionを使ったので,一般物体が対象になった.自分で収集した画像でAIをスクラッチ(?)作成する機能も提供されているらしい.ぜひやりたい.
- Node-RedはWebサービスをさくっと作りたいときにとっても有用そうだ.
- 国内だとやっぱLINE最強.どんなユーザでも持っているので敷居が低くて◎
Node-REDのエディター画面
Node-REDのコード
[{"id":"e2e8c7c9.3d94d8","type":"http in","z":"f47f9794.9e13","name":"post /image_recog","url":"/image_recog","method":"post","upload":true,"swaggerDoc":"","x":130,"y":93.99999237060547,"wires":[["794accf4.28039c"]]},{"id":"6819ac4f.32c0bc","type":"visual-recognition-v3","z":"f47f9794.9e13","name":"","apikey":"__PWRD__","vr-service-endpoint":"https://gateway.watsonplatform.net/visual-recognition/api","image-feature":"classifyImage","lang":"en","x":874.9999389648438,"y":92.99999237060547,"wires":[["eda37ad.22d5a08","3772cebc.660e02"]]},{"id":"eda37ad.22d5a08","type":"debug","z":"f47f9794.9e13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"result","x":1037.9999389648438,"y":180,"wires":[]},{"id":"5ee73f81.59e6b8","type":"function","z":"f47f9794.9e13","name":"reserve headers","func":"msg.url = \"https://api.line.me/v2/bot/message/\" + msg.payload.events[0].message.id +'/content'\nmsg.headers ={\"Content-Type\": \"application/json\", \"Authorization\": \"Bearer YYm5YOlPsfTC2bzXFJwe/nhAkNuUeDaPhhxxp2+KThyOlkn4XbbftDzZ5URIf2VUIkP2umeUvEHq6UIxJDig3L/sqQSJCMmx5+NKeLfDpKlAwPaHISh6qdx4G4n+TxkuoduHvNT+2RBexJnhQ8VzpwdB04t89/1O/w1cDnyilFU=\"};\n\nreturn msg","outputs":1,"noerr":0,"x":487.86663818359375,"y":94.48332977294922,"wires":[["e648faa6.d4292"]]},{"id":"e648faa6.d4292","type":"http request","z":"f47f9794.9e13","name":"","method":"GET","ret":"bin","url":"","tls":"","x":665.8666381835938,"y":92.64995574951172,"wires":[["4d36507b.e9ce7","6819ac4f.32c0bc"]]},{"id":"4d36507b.e9ce7","type":"debug","z":"f47f9794.9e13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":803.8666381835938,"y":181.433349609375,"wires":[]},{"id":"53d54378.3a798c","type":"function","z":"f47f9794.9e13","name":"generate header","func":"var event = msg.payload[\"events\"][0];\n//if(event[\"message\"][\"type\"] != \"text\"){\n// return msg;\n//}\nvar message = event[\"message\"][\"text\"];\nvar replyToken = event[\"replyToken\"];\nvar replyMessage = {\"type\": \"text\", \"text\": message}\nmsg.payload = {\"messages\": [replyMessage], \"replyToken\": replyToken};\nmsg.headers ={\"Content-Type\": \"application/json\", \"Authorization\": \"Bearer YYm5YOlPsfTC2bzXFJwe/nhAkNuUeDaPhhxxp2+KThyOlkn4XbbftDzZ5URIf2VUIkP2umeUvEHq6UIxJDig3L/sqQSJCMmx5+NKeLfDpKlAwPaHISh6qdx4G4n+TxkuoduHvNT+2RBexJnhQ8VzpwdB04t89/1O/w1cDnyilFU=\"};\n\ncontext.global.payloadGenerated = msg.payload;\ncontext.global.headersGenerated = msg.headers;\ncontext.global.msg = msg;\n\nreturn msg","outputs":1,"noerr":0,"x":263.8666534423828,"y":281.3166809082031,"wires":[["da3949da.d249b8","33455b2a.45919c"]]},{"id":"da3949da.d249b8","type":"function","z":"f47f9794.9e13","name":"merge","func":"if(\n (context.global.resultGenerated !== null) && (context.global.headersGenerated !== null) && (context.global.payloadGenerated !== null)\n){\n //msg.url = null;\n //msg.payload = context.global.payloadGenerated;\n targetText = context.global.resultGenerated.images[0].classifiers[0].classes[0].class + \", score:\" + context.global.resultGenerated.images[0].classifiers[0].classes[0].score;\n //msg.payload[\"messages\"] = [\"test\"];\n //msg.headers = context.global.headersGenerated;\n //var replyMessage = {\"type\": \"text\", \"text\": \"test test\"}\n //msg.payload = {\"messages\": [replyMessage], \"replyToken\": replyToken};\n //msg.payload = {\"messages\": [replyMessage], \"replyToken\": replyToken};\n //msg.payload = context.global.payloadGenerated;\n //msg.payload = {\"messages\": [replyMessage], \"replyToken\": msg.payload[\"replyToken\"]};\n //msg.payload = {\"messages\": [replyMessage], \"replyToken\": msg.payload[\"replyToken\"]};\n //msg.headers ={\"Content-Type\": \"application/json\", \"Authorization\": \"Bearer YYm5YOlPsfTC2bzXFJwe/nhAkNuUeDaPhhxxp2+KThyOlkn4XbbftDzZ5URIf2VUIkP2umeUvEHq6UIxJDig3L/sqQSJCMmx5+NKeLfDpKlAwPaHISh6qdx4G4n+TxkuoduHvNT+2RBexJnhQ8VzpwdB04t89/1O/w1cDnyilFU=\"};\n msg = context.global.msg;\n var replyMessage = {\"type\": \"text\", \"text\": targetText}\n msg.payload = {\"messages\": [replyMessage], \"replyToken\": msg.payload[\"replyToken\"]};\n\n return msg;\n \n}else{\n //return msg;\n}\n\n\n\n\n","outputs":1,"noerr":0,"x":661.8666381835938,"y":445.3166809082031,"wires":[["ccb7c49.4b08a38","9ca59f7e.5be838"]]},{"id":"3772cebc.660e02","type":"function","z":"f47f9794.9e13","name":"reserve msg.result","func":"context.global.resultGenerated = msg.result;\nreturn msg;","outputs":1,"noerr":0,"x":1106.86669921875,"y":95.31664276123047,"wires":[["fee60f45.d04228","da3949da.d249b8"]]},{"id":"ccb7c49.4b08a38","type":"http request","z":"f47f9794.9e13","name":"LINE REPLY API実行","method":"POST","ret":"txt","url":"https://api.line.me/v2/bot/message/reply","tls":"","x":874.8666229248047,"y":445.4833679199219,"wires":[["ee18b3b6.a93838"]]},{"id":"ee18b3b6.a93838","type":"debug","z":"f47f9794.9e13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1035.8666229248047,"y":515.1832885742188,"wires":[]},{"id":"9ca59f7e.5be838","type":"debug","z":"f47f9794.9e13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":778.8666381835938,"y":514.1832885742188,"wires":[]},{"id":"fee60f45.d04228","type":"debug","z":"f47f9794.9e13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":1144.86669921875,"y":249.183349609375,"wires":[]},{"id":"794accf4.28039c","type":"function","z":"f47f9794.9e13","name":"global","func":"context.global.resultGenerated = null; \ncontext.global.headersGenerated = null; \ncontext.global.payloadGenerated = null;\n\nreturn msg;","outputs":1,"noerr":0,"x":301.86663818359375,"y":93.73332977294922,"wires":[["5ee73f81.59e6b8","53d54378.3a798c"]]},{"id":"33455b2a.45919c","type":"debug","z":"f47f9794.9e13","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":397.86663818359375,"y":347.183349609375,"wires":[]}]