画像認識モデルを組み込んだ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アプリケーションを作り、動作を確認することができました。

参考資料