画像認識モデルを組み込んだWebアプリケーションの作成
目次
概要
https://mydocument.atlassian.net/wiki/spaces/support4textbook/pages/1654390803 について、パソコンのWebカメラを用いて画像認識を行うWebアプリケーションを作る手順を紹介します。Teachable Machineようなノーコードによる機械学習を使ったアプリケーションでは、Line botへの応用が2018年頃から定番で、開発する人が多いです。
Teachable Machineで画像認識モデルを作っていない場合は、https://mydocument.atlassian.net/wiki/spaces/support4textbook/pages/1654390803 にアクセスし、先に作ってください。
Node-RED x Teachable Machine で画像認識を行う方法
IoTなどの機器やAIの定番ツールであるNode-RED、本教材では「https://mydocument.atlassian.net/wiki/spaces/support4textbook/pages/1658028161 」で扱いました。Node-REDで、Teachable Machineを利用することができます。Node-REDは、PaizaCloudの他、Github Codespacesなどでも利用することができます。
Node-REDでTeachable Machineを使用するために必要なノードを追加
の方法でNode-RED環境を構築する場合は、ノード追加の作業は不要です。 に記載の には、下記のノードを自動インストールするように「package.json」に設定済みです。
Node-REDでTeachable Machineを利用する場合は、下記のノードをNode-REDに追加する必要があります。
node-red-contrib-teachable-machine
node-red-node-base64
node-red-contrib-image-output
追記方法は、Node-REDの画面右上の「三」>>「パレットの管理」の順にクリックします。
「ノードを追加」タブをクリックし、追加したいノードで検索をかけ、「ノードを追加」>>「追加」の順にクリックします。インストールが行われます。
インストールが成功し、インストール済みとなったノードは、「現在のノード」タブをクリックすることで確認できます。エラーが出た場合は、英語などでエラーメッセージが表示されますので、他のプログラミング言語やアプリケーションのように、エラーメッセージをきちんと読んで問題解決を図りましょう。
Node-REDで、Teachable Machineによる画像認識を行う。
サンプルのフローを使って、Node-REDで、Teachable Machine による画像認識を行う手順を説明します。この作業を始める前に直前の「Node-REDで、Teachable Machine を呼び出す場合に追加する必要のあるノード」で紹介している3つのノードが追加済みであるかどうか確認してください。追加されていなければエラーになりますので、必ず追加してください。
例として、Node-REDのURLは、https://myapp.local/red/ としていますが、各自のNode-REDのURLに読みかえてください。Github Codespacesで起動したNode-REDの場合は、https://xxxxxxxxxxxxxxxx-1880.preview.app.github.dev/#flow/xxxxxxx のようになります。
サンプルフローの読み込み
Node-REDで、画面右上の「三」>>「読み込み」の順にクリックします。
後述のサンプルのフロー(JSON)を貼り付けます。
サンプルのフロー(JSON)
[
{
"id": "dfa2037f.3531e",
"type": "tab",
"label": "Teachable Machine 画像認識",
"disabled": false,
"info": ""
},
{
"id": "10b1d12d.50285f",
"type": "teachable machine",
"z": "dfa2037f.3531e",
"name": "",
"mode": "online",
"modelUrl": "https://teachablemachine.withgoogle.com/models/zTx7v43x4/",
"localModel": "teachable_model",
"output": "best",
"activeThreshold": false,
"threshold": 80,
"activeMaxResults": false,
"maxResults": 3,
"passThrough": true,
"x": 670,
"y": 180,
"wires": [
[
"544f370b.75a078"
]
]
},
{
"id": "5c8f9307.b3fbdc",
"type": "http in",
"z": "dfa2037f.3531e",
"name": "/form",
"url": "/form",
"method": "get",
"upload": false,
"swaggerDoc": "",
"x": 110,
"y": 80,
"wires": [
[
"35aaad1.5d51a52"
]
]
},
{
"id": "37e55a41.2528f6",
"type": "http response",
"z": "dfa2037f.3531e",
"name": "",
"x": 1010,
"y": 80,
"wires": []
},
{
"id": "35aaad1.5d51a52",
"type": "template",
"z": "dfa2037f.3531e",
"name": "画像アップロードフォーム",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html lang=\"ja\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\t<title>file upload</title>\n<head>\n<meta charset=\"utf-8\">\n<title>アップロード</title>\n</head>\n<body>\n<form action=\"/upload\" method=\"post\" enctype=\"multipart/form-data\">\n<input name=\"file\" type=\"file\" id=\"file\"><br>\n<input type=\"submit\" value=\"アップロード\">\n</form>\n</body>\n</html>",
"x": 380,
"y": 80,
"wires": [
[
"37e55a41.2528f6"
]
]
},
{
"id": "9f585d58.3016d",
"type": "http in",
"z": "dfa2037f.3531e",
"name": "/upload",
"url": "/upload",
"method": "post",
"upload": true,
"swaggerDoc": "",
"x": 110,
"y": 180,
"wires": [
[
"4c01b353.ae21dc"
]
]
},
{
"id": "66f1fbf6.7f92a4",
"type": "comment",
"z": "dfa2037f.3531e",
"name": "入力フォーム",
"info": "",
"x": 130,
"y": 40,
"wires": []
},
{
"id": "7856cb04.85cc04",
"type": "comment",
"z": "dfa2037f.3531e",
"name": "アップロードファイルを取得",
"info": "",
"x": 180,
"y": 140,
"wires": []
},
{
"id": "9d2a1b07.7d1f58",
"type": "comment",
"z": "dfa2037f.3531e",
"name": "結果の表示",
"info": "",
"x": 920,
"y": 200,
"wires": []
},
{
"id": "4c01b353.ae21dc",
"type": "function",
"z": "dfa2037f.3531e",
"name": "画像をpayloadへ",
"func": "msg.payload = msg.req.files[0].buffer;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 410,
"y": 180,
"wires": [
[
"10b1d12d.50285f",
"6a7fb108b9ce0826"
]
]
},
{
"id": "1a309205.e2043e",
"type": "template",
"z": "dfa2037f.3531e",
"name": "",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "<html>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>Result</title>\n<style type=\"text/css\">\ntable{\n width: 100%;\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntable th,table td{\n padding: 10px 0;\n text-align: center;\n}\n\ntable tr:nth-child(odd){\n background-color: #eee\n}\n</style>\n</head>\n<body>\n<center>\n<h4>Recognition result</h4>\n<table>\n <tr>\n <th>class</th>\n <th>score</th>\n <tr>\n <td>{{class}}</td>\n <td>{{score}}</td>\n </tr>\n </tr>\n</table>\n<hr />\n<p><button type=\"button\" onclick=\"history.back()\">Back</button></p>\n</center>\n</body>\n</html>",
"output": "str",
"x": 920,
"y": 240,
"wires": [
[
"37e55a41.2528f6"
]
]
},
{
"id": "4138701f.4eabe",
"type": "function",
"z": "dfa2037f.3531e",
"name": "クラス名とスコアの抽出",
"func": "msg.class = msg.payload[0].class;\nmsg.score = msg.payload[0].score;\nmsg.image = msg.image;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 810,
"y": 360,
"wires": [
[
"1a309205.e2043e"
]
]
},
{
"id": "5b8f3f47.36d11",
"type": "comment",
"z": "dfa2037f.3531e",
"name": "JPEG画像のみ画像認識",
"info": "",
"x": 420,
"y": 140,
"wires": []
},
{
"id": "544f370b.75a078",
"type": "base64",
"z": "dfa2037f.3531e",
"name": "",
"action": "",
"property": "image",
"x": 600,
"y": 360,
"wires": [
[
"4138701f.4eabe"
]
]
},
{
"id": "6a7fb108b9ce0826",
"type": "image",
"z": "dfa2037f.3531e",
"name": "",
"width": "128",
"data": "payload",
"dataType": "msg",
"thumbnail": true,
"active": true,
"pass": false,
"outputs": 0,
"x": 520,
"y": 420,
"wires": []
}
]
上記を貼りつけた様子
「読み込み」をクリックします。次のように表示されます。箱と線で成り立つものを、Node-REDではフローと言います。
箱をノードと言います。「teachable machine」ノードをダブルクリックします。
「Url」の欄の中身を、Teachable Machine で作成しアップロードしたモデルのURLに書き換えます。Teachable Machineで画像認識モデルを作り、インターネット接続上に公開する手順は「Teachable Machineによる画像認識モデルの作成」で実施したものになります。まだの人は作ってください。
書き換え後、「完了」をクリックします。
画面右上の「デプロイ」をクリックします。
動作確認
Webブラウザで新規にタブを用意してください。タブの作り方はWebブラウザの基本操作ですので、わからない場合はお使いのWebブラウザのマニュアルを見て下さい。
Node-REDのURLは、https://myapp.local/red/ の場合は、https://myapp.local/form にアクセスします。としていますが、https://myapp.local/ の部分は各自のNode-REDのURLにあわせて読みかえてアクセスします。Github Codespacesで起動したNode-REDの場合は、https://xxxxxxxxxxxxxxxx-1880.preview.app.github.dev/#flow/xxxxxxx のようになっているので、dev/ の後を dev/form に書き換え、https://xxxxxxxxxxxxxxxx-1880.preview.app.github.dev/form となるようにします。
「ファイルの選択」をクリックします。画像認識使う画像のサイズは使用するサーバーのスペックに依存します。無料のサーバーを使っている場合は、低スペックなので、大きくても1MB前後の画像を使うことが無難です。
ボールペンの画像を使う場合は、確認用としてこちらの画像を使って構いません。画像の上で右クリックし、「名前を付けて保存」でダウンロードできます。
「ファイルを選択」で、認識したい画像を選ぶと、次のようになります。
「アップロード」ボタンをクリックします。次のように画像認識結果が表示されます。これは、画像をTeachable Machineで作成した画像認識モデルに渡し、認識結果を受け取り、表示しています。別の画像で画像認識を行う場合は、「Back」をクリックします。
画像認識した模様をNode-REDのタブに戻ると、次のように表示されています。
Node-REDを使うことで、短時間に画像認識を行うWebアプリケーションを作り、動作を確認することができました。
参考資料