ローコードでマスクチェッカーを作ろう！

2020年10月27日にMicrosoft社が買収した [Lobe](https://lobe.ai/)のベータ版がリリースされていたので、
マスクをしているかどうか判定するツールをNode-REDを使ってローコードで作ってみました。

# 1. Lobeをインストールする
## Lobeをダウンロード
下記URLにアクセスしてLobeのアプリをダウンロードします。

https://lobe.ai/

諸々情報を入力して 「Download」ボタンをクリックします。

![s100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/7b1801f6-c6de-23d5-7220-e7481e8ffa26.png)

ダウンロードしたファイルをインストールしておきます。

# 2. 学習データを登録する

## 2-1. プロジェクト作成
①プロジェクト名を入力して、右上の ②「Import」をクリックし、画像、カメラ、データセットからインポートしたい形式を選びます。今回は③カメラを選択しました。

![s200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/f99be3aa-5fbd-925c-0609-31d9c6b10eee.png)

## 2-2. 学習データ登録
カメラ画像の左下に学習するラベル名を入力します。入力したら、画面中央にあるシャッターボタンをクリックします。
長押しすることで連続して撮影することも可能です。学習が終われば、右上の「Done」をクリックします。

![s201.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/01ccf219-3202-d543-6ccc-fe58175a3816.png)



同様にマスクを付けているタイプの写真を撮影して学習させます。

![s202.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/27987d8e-317e-df0e-318a-7b60d1033a79.png)



## 2-3. トレーニング
撮影したデータの学習はバックグラウンドでされます。100%になれば効果音で知らせてくれます。

![s203.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/06a1357d-e9df-8860-0f4c-751570b03f8f.png)



## 2-4. 確認する
学習したデータが正しくできているか確認します。左側メニューの「Play」から確認できます。判定ラベルはリアルタイムで切り替わります。

![s204.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/a8b6db95-c43f-c456-c0f6-c6232fcdc922.png)


判定がおかしい場合は左下のアイコンで判定して正しいものを学習させてください。

# 3. Node-REDで使う
学習したデータは様々な形でプログラムに組み込むことができます。サポートされているのは、`CoreML` `TensorFlow` `TensorFlow Lite` `Local API` の4つがありました。
今回はNode-REDで使うので、 `Local API` を使用します。

## 3-1. エクスポートする
メニューから「Export」をクリックします。

![s300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/9bed0acd-7e31-85be-71c9-55511bfd5904.png)


`Local API` をクリックします。

![s301.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/34b7bcb6-6058-8174-dbbf-d2dd5072e882.png)


APIのURLが発行されるので、メモしておきましょう。

![s302.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/73f5fe35-32e9-fa88-fe16-42ec8e7dec31.png)


## 3-2. Node-REDの設定
[Node-RED Desktop](https://sakazuki.github.io/node-red-desktop/ja/) アプリをインストールしてください。

ダウンロードしてアプリを起動して、右上のハンバーガーメニューにある「パレットの管理」をクリックします。

![s303.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/d6fc1605-cbdb-4239-7153-a1cc25ed9f7a.png)


①「ノードを追加」タブをクリックして、②検索窓に下記４つのノード名を入力して検索します。それぞれ③ノードを追加してください。

* [node-red-contrib-browser-utils](https://flows.nodered.org/node/node-red-contrib-browser-utils)
* [node-red-contrib-image-output](https://flows.nodered.org/node/node-red-contrib-image-output)
* [node-red-contrib-lobe-local-api](https://flows.nodered.org/node/node-red-contrib-lobe-local-api)
* [node-red-node-base64](https://flows.nodered.org/node/node-red-node-base64)


![s304.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/92a75ba8-1db5-f598-c595-26fa25df837c.png)



## 3-3. ノードを配置する
左側にあるパレットウィンドウから、 入力カテゴリにある `Camera` ノードをフローエディタにドラッグ・アンド・ドロップします。

![s305.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/ef395ab9-3910-9fbf-cab3-00da8f5f9935.png)


続いて、出力カテゴリにある `image` ノードをドラッグ・アンド・ドロップして、先程配置した `Camera` ノードと線でつなぎます。

![s306.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/28423b46-51fe-ac46-2aa4-1199275d5914.png)


パーサカテゴリにある、 `base64` ノードをドラッグ・アンド・ドロップして、 `Camera` ノードとつなぎます。

![s307.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/7e3d7410-8ffa-89a9-d2b5-c61d1439b54f.png)


分析カテゴリにある、 `lobe-local` ノードをドラッグ・アンド・ドロップして、 `base64` ノードとつなぎます。ノードをダブルクリックして、プロパティ画面を開きます。

URLにLobeで発行されたLocal APIのURLを貼り付けます。

![s308.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/a1e4e1c4-72f9-8655-4942-0ec28b3f1176.png)


共通カテゴリにある、 `debug` ノードをドラッグ・アンド・ドロップして、 `lobe-local` ノードとつなぎます。最後に右上にある 「デプロイ」ボタンをクリックします。

![s309.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/233a7947-d02f-e1e7-f3a0-08a0c8ebd465.png)


`Camera` ノードの左側にあるボタンをクリックするとPC内蔵カメラが起動します。デバッグタブをクリックして、結果が表示されていることを確認してください。

![s310.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/46ef0e96-0e88-31e1-7b30-8fd921bed85e.png)


マスクをつけると、「マスクあり」と返ってきます。

![s311.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/87a27da7-e511-02e1-357d-5ba6835d8c2b.png)


うまくいかない方は下記をお試しください。

```json
[
    {
        "id": "c74ee55b.52b928",
        "type": "tab",
        "label": "ハンズオン",
        "disabled": false,
        "info": ""
    },
    {
        "id": "dfcc243c.fb35d8",
        "type": "camera",
        "z": "c74ee55b.52b928",
        "name": "",
        "x": 120,
        "y": 240,
        "wires": [
            [
                "1fcd09cb.659fc6",
                "e0780598.440308"
            ]
        ]
    },
    {
        "id": "1fcd09cb.659fc6",
        "type": "image",
        "z": "c74ee55b.52b928",
        "name": "",
        "width": 160,
        "data": "payload",
        "dataType": "msg",
        "thumbnail": false,
        "active": true,
        "pass": false,
        "outputs": 0,
        "x": 170,
        "y": 340,
        "wires": []
    },
    {
        "id": "e0780598.440308",
        "type": "base64",
        "z": "c74ee55b.52b928",
        "name": "",
        "action": "",
        "property": "payload",
        "x": 300,
        "y": 240,
        "wires": [
            [
                "897476b5.cd05b8"
            ]
        ]
    },
    {
        "id": "897476b5.cd05b8",
        "type": "lobe-local",
        "z": "c74ee55b.52b928",
        "name": "",
        "modelUrl": "",
        "base64Image": "",
        "output": "best",
        "x": 480,
        "y": 240,
        "wires": [
            [
                "db2c90a0.b4824"
            ]
        ]
    },
    {
        "id": "db2c90a0.b4824",
        "type": "debug",
        "z": "c74ee55b.52b928",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 670,
        "y": 240,
        "wires": []
    }
]
```

# 4. LINEと連携しよう
## 4-1. 新規チャネルを作成する
LINE DeveloperページへアクセスしてLINEログインしてください。

https://developers.line.biz/ja/

まだプロバイダー設定してない方は「新規プロバイダー作成」をクリックします。

![s200](https://raw.githubusercontent.com/gaomar/katacoda-scenarios/master/paypay-liff-handson-playground/images/s200.png)

プロバイダー名はお好きなものを入力して、「作成」ボタンをクリックします。

![s201](https://raw.githubusercontent.com/gaomar/katacoda-scenarios/master/paypay-liff-handson-playground/images/s201.png)

`Messaging API` をクリックします。

![s400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/e6fcd69c-e086-65ff-38fa-ea7fb1ea7fd1.png)


各種項目を埋めていきます。

|項目|値|
|:--|:--|
|①チャネル名|Lobeハンズオン|
|②チャネル説明|Lobeハンズオン|
|③大業種|個人|
|④小業種|個人（その他）|
|⑤メールアドレス|あなたのメールアドレスを入力してください|

![s401.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/b7e30ace-d16a-f502-8165-afbe58bdee68.png)


2つのチェックを入れて、［作成］ボタンをクリックします。

![s402](https://raw.githubusercontent.com/gaomar/katacoda-scenarios/master/liff-v2-handson-playground/images/s402.png)

［同意する］ボタンをクリックします。

![s403](https://raw.githubusercontent.com/gaomar/katacoda-scenarios/master/liff-v2-handson-playground/images/s403.png)

## 4-2. ボットと友だちなる
作成したボットと友だちになっておきます。「Messaging API設定」タブにあるQRコードをLINEアプリから読み取って友だちになっておいてください。

![s402.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/54890b92-e59d-d70b-32d5-0a03081378b7.png)


## 4-3. アクセストークンを発行する
下の方にスクロールして、チャネルアクセストークンカテゴリにある「発行」ボタンをクリックして、チャネルアクセストークンを発行して、メモしておきましょう。終われば、応答メッセージにある「編集」をクリックします。

![s403.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/d9d6133b-ebf1-ab00-d842-6ff996df1b50.png)


## 4-4. Webhookの設定を行う
応答メッセージ `オフ` 、Webhookは `オン` にしてから「Messaging API設定」ボタンをクリックしてください。

![s404.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/0a172920-4ac4-5096-9b5e-784fd206b279.png)


WebhookのURLはNode-RED Desktopアプリのメニューバー①「エンドポイント - ngrokに接続する」で発行された②URLをコピーしておきます。Webhook URLはngrokのURLを貼り付けて、末尾に③「`/bot`」と入力して、④「保存」ボタンをクリックします。
そして、最後に記載されている ⑤`Channel secret` はメモしておきましょう。後ほど使います。

※例) https://xxxxxxxxxxxx.jp.ngrok.io/bot

![s405.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/7189d322-1087-7425-473d-3bbf576e3a51.png)


## 4-5. LINE用ノードを設定する
パレット管理から下記ノードを追加しておいてください。

* [node-red-contrib-line-messaging-api](https://flows.nodered.org/node/node-red-contrib-line-messaging-api)
* [node-red-contrib-line-image-api](https://flows.nodered.org/node/node-red-contrib-line-image-api)

ネットワークカテゴリにある `http in` ノードをドラッグ・アンド・ドロップします。メソッドは `POST` を選択して、URLに「`/bot`」と入力して、「完了」ボタンをクリックします。

|項目|値|
|:--|:--|
|メソッド|POST|
|URL|/bot <br>※「`/`」を付け忘れないよう気をつけてください|

![s406.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/7ddfdfd9-bcc0-51b7-c6c6-cde201abfedd.png)


機能カテゴリにある `function` ノードをドラッグ・アンド・ドロップします。 `http in` ノードとつないでおきましょう。下記コードを反映させてください。LINEから送られてくる返信用のトークンを取得しています。

```javascript:コード
msg.replyToken = msg.payload.events[0].replyToken;
return msg;
```

![s407.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/adf1809e-fa35-4126-c496-636d3dd125b7.png)


LINEカテゴリにある `line-image` ノードをドラッグ・アンド・ドロップします。 `function` ノードとつないでおきましょう。AccessTokenは `4-3` で発行したチャネルアクセストークンを貼り付けます。出力形式は `base64` を選択します。

|項目|値|
|:--|:--|
|AccessToken| 4-3で発行したチャネルアクセストークンを貼り付ける|
|出力形式|base64|

![s408.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/c14fad1b-f18c-b54c-814c-620d1c5cfd8f.png)


`line-image` ノードと `lobe-local` ノードを線でつないでおきましょう。

![s409.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/1dcb89fe-937a-7964-2ff9-c71780492276.png)


機能カテゴリにある `function` ノードをドラッグ・アンド・ドロップします。 `lobe-local` ノードとつないでおきましょう。コード部分に下記コードを反映してください。

```javascript:コード
const ret = msg.payload;
const replyToken = msg.replyToken;

msg.payload = {};
msg.payload.events = [
    {
        "type": "message",
        "replyToken": replyToken,
        "message": {
            "type": "text",
            "text": `これは「${ret}」かな`
        }
    }
];
return msg;
```

![s410.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/31a7bf5f-20a3-f943-144f-75837f63df35.png)


LINEカテゴリにある `ReplyMessage` ノード（※PushMessegeではないので気をつけてください）をドラッグ・アンド・ドロップします。 `function` ノードとつないでおきましょう。
Secret（※4-4でメモしています）とアクセストークン（※4-3でメモしています）をそれぞれ反映してください。

|項目|値|
|:--|:--|
|Secret|4-4でメモしたChannel Scret|
|AccessToken|4-3で発行したチャネルアクセストークン|

![s411.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/e7b13b96-5e53-6c9c-41e5-c4580d8901c8.png)


## 4-6. 動作確認する
LINEアプリで友だちになった「Lobeハンズオン」を開いてください。
トーク画面からマスクをつけていない画像とつけている画像をそれぞれ投稿して、結果が返ってくることを確認してみましょう。

![s412.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154355/2dfe8d4f-8d8e-4f18-98ed-e7f6dd493087.png)


## 4-7. 最終的なノード
どうしてもうまくいかない方は下記をNode-REDに適用してください。LINEのアクセストークンや、LobeのURLはご自身のものを設定してください。

```javascript:ハンズオン
[
    {
        "id": "c74ee55b.52b928",
        "type": "tab",
        "label": "ハンズオン",
        "disabled": false,
        "info": ""
    },
    {
        "id": "dfcc243c.fb35d8",
        "type": "camera",
        "z": "c74ee55b.52b928",
        "name": "",
        "x": 130,
        "y": 320,
        "wires": [
            [
                "1fcd09cb.659fc6",
                "e0780598.440308"
            ]
        ]
    },
    {
        "id": "1fcd09cb.659fc6",
        "type": "image",
        "z": "c74ee55b.52b928",
        "name": "",
        "width": 160,
        "data": "payload",
        "dataType": "msg",
        "thumbnail": false,
        "active": true,
        "pass": false,
        "outputs": 0,
        "x": 180,
        "y": 420,
        "wires": []
    },
    {
        "id": "e0780598.440308",
        "type": "base64",
        "z": "c74ee55b.52b928",
        "name": "",
        "action": "",
        "property": "payload",
        "x": 310,
        "y": 320,
        "wires": [
            [
                "897476b5.cd05b8"
            ]
        ]
    },
    {
        "id": "897476b5.cd05b8",
        "type": "lobe-local",
        "z": "c74ee55b.52b928",
        "name": "",
        "modelUrl": "",
        "base64Image": "",
        "output": "best",
        "x": 490,
        "y": 320,
        "wires": [
            [
                "db2c90a0.b4824",
                "e5b7608a.4514f"
            ]
        ]
    },
    {
        "id": "db2c90a0.b4824",
        "type": "debug",
        "z": "c74ee55b.52b928",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 680,
        "y": 320,
        "wires": []
    },
    {
        "id": "768e340b.3fa3fc",
        "type": "http in",
        "z": "c74ee55b.52b928",
        "name": "",
        "url": "/bot",
        "method": "post",
        "upload": false,
        "swaggerDoc": "",
        "x": 130,
        "y": 200,
        "wires": [
            [
                "78838a5f.9f3784"
            ]
        ]
    },
    {
        "id": "6404378b.892d18",
        "type": "line-image",
        "z": "c74ee55b.52b928",
        "name": "",
        "MessageId": "",
        "AccessToken": "",
        "output": "base64",
        "x": 570,
        "y": 200,
        "wires": [
            [
                "897476b5.cd05b8"
            ]
        ]
    },
    {
        "id": "e5b7608a.4514f",
        "type": "function",
        "z": "c74ee55b.52b928",
        "name": "結果取得",
        "func": "const ret = msg.payload;\nconst replyToken = msg.replyToken;\n\nmsg.payload = {};\nmsg.payload.events = [\n    {\n        \"type\": \"message\",\n        \"replyToken\": replyToken,\n        \"message\": {\n            \"type\": \"text\",\n            \"text\": `これは「${ret}」かな`\n        }\n    }\n];\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 570,
        "y": 420,
        "wires": [
            [
                "c48a597a.950508"
            ]
        ]
    },
    {
        "id": "c48a597a.950508",
        "type": "ReplyMessage",
        "z": "c74ee55b.52b928",
        "name": "",
        "channelSecret": "",
        "channelAccessToken": "",
        "replyMessage": "",
        "x": 770,
        "y": 420,
        "wires": []
    },
    {
        "id": "78838a5f.9f3784",
        "type": "function",
        "z": "c74ee55b.52b928",
        "name": "リプライトークン取得",
        "func": "msg.replyToken = msg.payload.events[0].replyToken;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 340,
        "y": 200,
        "wires": [
            [
                "6404378b.892d18"
            ]
        ]
    }
]
```