Teachable MachineをNoodlで使ってみたメモ

# Teachable Machineとは
Googleが開発した、ブラウザ上で超カンタンに機械学習できるサービスです。
くわしくは[こちら](https://teachablemachine.withgoogle.com/)

## 学習できるもの
すでに色々試されています！
- 画像 [参考: 爆速で画像認識ができる！？Teachable Machineでカピバラとビーバー判別をしてみた](https://qiita.com/kmaepu/items/541db5c320dd10b87f21)
- 音 [参考： toio を音で制御してみた（Audio用の Teachable Machine でベルやタンバリンの音を機械学習）](https://qiita.com/youtoy/items/37f70bb4ce630e6cbd92)
- ポーズ [「うちでヨガしよう」ヨガポーズがあっているか判定してくれるLINE bot～Node.jsに読み込む～](https://qiita.com/kmaepu/items/11480ef4edeb850e2508)

# ポーズを学習させて、モデルデータを作る
## まずはブラウザで学習させてみよう
こちらの記事が参考になります！
[Teachable Machine を使った音声からの任意のキーワードの検出（ブラウザ上で機械学習）](https://qiita.com/youtoy/items/9096836e5d77274500ea)
ここでは音声を学習させていますが、今回はポーズを選択します。
<img width="1381" alt="スクリーンショット 2020-04-24 21.10.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/9303880f-0dfc-4060-173f-c9aee96906e5.png">

**注意！学習中にタブの切り替えをすると止まってしまいます！そのまま、何もせず待ちましょう！**

動画入れる

ブラウザ上で、うまく動作することが確認できたら次に進みます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Teachable Machine（つまり有言実行でQiita書いてるナウ） <a href="https://t.co/Bd8mGhjwyV">pic.twitter.com/Bd8mGhjwyV</a></p>&mdash; はりねずみ麺 (@hedgehog_noodl) <a href="https://twitter.com/hedgehog_noodl/status/1253663238635450368?ref_src=twsrc%5Etfw">April 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ちなみに、今回私が学習した、ガニ股検出モデルは[コチラ](https://teachablemachine.withgoogle.com/models/--Vx-Td0Y/)で試せます。

## モデルデータとScriptのダウンロード
<img width="852" alt="スクリーンショット 2020-04-24 21.19.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/4003a79d-8e4d-4897-0423-144d7052ae47.png">

- Export Modelボタンを押す
- Export your modelのDownloadを選択
- Download My modelを押してダウンロード。これが学習したモデルデータです。

# 作成したモデルデータをNoodlで使う
学習させるのが面倒くさい！という方は[コチラ](https://github.com/mizuki-kisa/teachablemachine)からモデルデータをダウンロードしてください！

## モデルデータを、Noodlプロジェクトに格納する
ダウンロードしたモデルデータを解凍しフォルダごとNoodlプロジェクトに、格納。
Noodlのプロジェクトデータの場所は、歯車アイコン→Open project folderで開くことが出来ます。

格納するデータは3つ
my-pose-model/
- metadata.json
- model.json
- weights.bin

## Script Downloaderノードを作成し、ライブラリを読み込む
ScriptDownloaderノードに、`<script src="...` で読み込んでいるjsライブラリのURLを入れる
この２つのライブラリを、ScriptdownloaderノードのExternal Scriptsに入力
`https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.3.1/dist/tf.min.js`
`https://cdn.jsdelivr.net/npm/@teachablemachine/pose@0.8/dist/teachablemachine-pose.min.js`

## Javascriptノードを作成し、下記スクリプトをコピペ
元のJSを、NoodlのJavascriptノードの一番下にコピペします。

```javascript:Javascriptノード

define({
	// The input ports of the Javascript node, name of input and type
	inputs:{
	    // ExampleInput:'number',
	    // Available types are 'number', 'string', 'boolean', 'color' and 'signal',
	    loaded:'signal'
	},
	
	// The output ports of the Javascript node, name of output and type
	outputs:{
	    // ExampleOutput:'string',
	    classLabel:'string',
	    class1:'number',
	    class2:'number'
	},
	
	// All signal inputs need their own function with the corresponding name that
	// will be run when a signal is received on the input.
	loaded:function(inputs,outputs) {
        init();
	},
	
	// This function will be called when any of the inputs have changed
	change:function(inputs,outputs) {
	    outputs.class1 = class1;
	    outputs.class2 = class2;
	    this.flagOutputDirty("class1")
	    this.flagOutputDirty("class2")
	    this.runNextFrame();
	}
})
let model, webcam, maxPredictions, class1, class2;
async function init() {
    console.log('init');
    const modelURL = "my-pose-model/model.json";
    const metadataURL = "my-pose-model/metadata.json";
    model = model = await tmPose.load(modelURL, metadataURL);
    maxPredictions = model.getTotalClasses();
    const size = 200;
    const flip = true; // whether to flip the webcam
    webcam = new tmPose.Webcam(size, size, flip); // width, height, flip
    await webcam.setup(); // request access to the webcam
    await webcam.play();
    window.requestAnimationFrame(loop);
}

async function loop(timestamp) {
        webcam.update(); // update the webcam frame
        await predict();
        window.requestAnimationFrame(loop);
    }

// run the webcam image through the image model
async function predict() {
    const { pose, posenetOutput } = await model.estimatePose(webcam.canvas);
        // Prediction 2: run input through teachable machine classification model
    const prediction = await model.predict(posenetOutput);
    class1 = prediction[0].probability.toFixed(2);
    class2 = prediction[1].probability.toFixed(2);
}
```

## ノードをつなぐ
図のようにノードを作成してつなぎ、数字が出ていれば成功です！
<img width="1514" alt="スクリーンショット 2020-04-24 22.05.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/425b004c-2af5-7742-1967-8d3fdcc4df0e.png">

## WebカメラをNoodl画面に表示する
自分の体がきちんと写っているかどうか、わかりにくいためカメラの映像をNoodl画面に表示します。
- 図のように`Web Camera`ノードと`Video`ノードを作成してつなぐ
- Videoノードの`Autoplay`にチェックを入れ、`Size Mode`をContent Heightに変更する。
<img width="1143" alt="スクリーンショット 2020-04-24 22.12.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/bdf90465-a392-cc2f-95ad-1973ee8847db.png">

これで、クリックするとWebカメラの映像がストリーム再生されます。

## 自分がガニ股すると、キャラクターもガニ股するように
下記2枚のイラストをダウンロードし、Noodlプロジェクトに格納してください。（画像をNoodlにドラッグ＆ドロップ）
今度は、自分がガニ股したときにガニ股している画像に切り替えるようにしていきます。
![c-naruto02500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/7d792041-de3c-f22d-451e-e9c420dc12d6.png)
![gani500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/60fe427f-7cae-068f-1357-ddd92501c0b3.png)
*※このキャラクターはナルトの成瀬といいます。*

`Image`ノードを作成し、プロパティを図のようにセットします。
画像の選択は、`Source`プロパティをクリックすると格納されている画像一覧が出てきて直感的に選ぶことが出来ます。
<img width="1141" alt="スクリーンショット 2020-04-24 22.15.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/2a41fbf6-064f-6fc9-9403-93e988206cc3.png">

先程作った`Image`ノードをコピペし、`Source`だけガニ股している画像に変更します。
2つの画像が重なっていればOKです。
<img width="1183" alt="スクリーンショット 2020-04-24 22.16.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/cdbb08a0-f3e1-73af-1652-bb1b28c6ad74.png">

さらに、`Javascript`ノードと`Imageノード`をつなぎます。
ポーズの判定結果は、各クラスが0-1.0の数値で出力されます。それをImageノードの`Opacity`につなぐことで、
ポーズに合わせて画像の切り替えを行います。
<img width="1184" alt="スクリーンショット 2020-04-24 22.17.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/d76007b2-2dc9-15a7-aa0e-190d5698c9b6.png">

## 完成〜〜〜！
いい感じです。少し処理が遅れますね。
<blockquote class="twitter-tweet"><p lang="und" dir="ltr">OK <a href="https://t.co/jNomCzcxpq">pic.twitter.com/jNomCzcxpq</a></p>&mdash; はりねずみ麺 (@hedgehog_noodl) <a href="https://twitter.com/hedgehog_noodl/status/1253681689206722561?ref_src=twsrc%5Etfw">April 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>