Help us understand the problem. What is going on with this article?

toio を音で制御してみた(Audio用の Teachable Machine でベルやタンバリンの音を機械学習)

【2020/2/16 追記】 2/15・16(土・日)に開催されたつくばのミニメーカーフェアに出展して、来場者の方に作品を試してもらった際の話やツイートを追加
【2020/2/25 追記】 楽器の違いではなく、同じ楽器での音階の違いを学習させて試した動画を追加掲載

はじめに

今月はじめに書いた以下の記事で取り上げた「音を対象とした Teachable Machine」に関する話で、別の要素と続きの両方の内容が含まれた記事です

●Teachable Machine を使った音声からの任意のキーワードの検出(ブラウザ上で機械学習) - Qiita
 https://qiita.com/youtoy/items/9096836e5d77274500ea

別要素の部分は機械学習で学習させた対象が声から音になっているところで、続きになる部分というのは Teachable Machine で作成したモデルを JavaScript から扱うところです。

今回の記事の内容

概要とデモ動画

今回の記事では、音を対象とした機械学習によるモデル作成と、そのモデルを JavaScript のプログラムで用いる部分について書こうと思います。

以前書いた記事では、内容が異なる4パターンの音声(自分がしゃべった声)を学習させ、今回は 100円ショップで買ってきたタンバリンや卓上ベル等の 3つの音を学習させています。
84015758_2698069563639552_7208915693654245376_n.jpg

音を鳴らすのに用いた物は 3つですが、卓上ベルに関しては鳴らし方を「普通に鳴らすのと、連打する形の 2パターン」を学習させたため、学習させた音の種類は 4つ(背景雑音も含めると 5つ)になります。
そして、作成したモデルを使ってロボットトイの toio を制御するプログラムをJavaScriptで書き、実際に動作させてみました(動作させた際の様子は下記のとおりです)。


プログラムはブラウザ上(Google Chrome)で動かしていて、toio と通信する部分の処理には Web Bluetooth API を用いています。

なお今回の内容は、2/15・16(土・日)に開催されるイベント「Tsukuba Mini Maker Faire 2020」で、下記の「toio™で作ってみた!友の会」の展示ブースでの展示のために作ったものになります。
toio™で作ってみた!友の会_–_Tsukuba_Mini_Maker_Faire_2020.jpg

ソースコード等

未実装部分や整理されていない部分はありますが、ソースコード全体は以下から参照可能です。
(整理してからとか、仕上がってからとか、考えているとしばらく公開しない状態が続きそうな気がするため、公開してしまおうかと思います)

●API_Test/test02_c.html at master · yo-to/API_Test
 https://github.com/yo-to/API_Test/blob/master/WebAPI/test10_test09_and_AudioRecognition/test02_c.html

上記のリポジトリは GitHub Pages の設定をしているので、下記の URL にアクセスするとお試しも可能です(試すためには toio が必要になりますが・・・)。
 https://yo-to.github.io/API_Test/WebAPI/test10_test09_and_AudioRecognition/test02_c.html

補足などは、以下で書いていきます。

音で toio を制御するためにやったこと

音を Teachable Machine で学習させる

基本的な進め方は、冒頭でもご紹介した以下の記事で書いた流れと同じです。

●Teachable Machine を使った音声からの任意のキーワードの検出(ブラウザ上で機械学習) - Qiita
 https://qiita.com/youtoy/items/9096836e5d77274500ea

上記との進め方の違いは「音を学習させる際に、自分が何かしゃべるのではなくタンバリンやベルを鳴らしたりした」こと、「録音時間の設定をデフォルトの 2秒から 10秒(または 15秒)に変更した」ことと、「各クラスの名称をデフォルトの名前(Class2、Class3 など)ではなく、タンバリンや銀ベルなどに変更した」ことくらいです。
また、最終的に選択した音のパターンは「タンバリンと2種類のベルの音(うち1種類は連打するかしないかの2パターン)」の 4パターンでしたが、学習させる過程では下記のような音の鳴らし方も学習させて区別できるか(十分な精度が得られそうか)を試していました。

  • 人が聴くと音色が違う色違いの卓上ベル(形状は同じ)のそれぞれを単独で鳴らしたときの音色
  • 同じく色違いの卓上ベルをそれぞれ単独で鳴らしたときと、同時に鳴らしたときの音色
  • ハンドベル(銀ベルという名称にしているもの)を振るスピードを変えたときの音色

上記を採用しなかったのは、音源となるベルなどと PC本体の位置(マイクの位置)との位置関係などの条件が変わったときに誤認識がわりとでるように思われたり、うまく区別できかったりということが見られたりしたためです。もしかしたら、何か工夫をすると区別できるようになるかもしれないですが、特殊な対応を行うことなく実現できるものを採用したかったため、デモ動画で使っている内容を採用することとしました。

作成したモデルを JavaScript で動かす(まずはサンプルを動作させる)

モデルの作成を行った次のステップは、作成したモデルをプログラムから利用するステップです。
ブラウザ上で作成したモデルをダウンロードする際に、そのモデルを使うプログラムの例は JavaScript のプログラムと、p5.js を使ったプログラムが提示されます(以下の画像の赤と緑の矢印で示した部分のタブで切り替え)。
Audio_Model_-_Teachable_Machines.jpg
今回は、JavaScript のプログラムを採用しました。そして、まずはサンプルを自分の環境で動作させられるよう試しました。

サンプルプログラムは以下の URL でも閲覧できるようです。

●teachablemachine-community/libraries/audio at master · googlecreativelab/teachablemachine-community
 https://github.com/googlecreativelab/teachablemachine-community/tree/master/libraries/audio

Sample snippet と書かれた部分に以下のプログラムが掲載されています。

<div>Teachable Machine Audio Model</div>
<button type='button' onclick='init()'>Start</button>
<div id='label-container'></div>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.3.1/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/speech-commands@0.4.0/dist/speech-commands.min.js"></script>

<script type="text/javascript">
    // more documentation available at
    // https://github.com/tensorflow/tfjs-models/tree/master/speech-commands

    // the link to your model provided by Teachable Machine export panel
    const URL = '{{URL}}';

    async function createModel() {
        const checkpointURL = URL + 'model.json'; // model topology
        const metadataURL = URL + 'metadata.json'; // model metadata

        const recognizer = speechCommands.create(
            'BROWSER_FFT', // fourier transform type, not useful to change
            undefined, // speech commands vocabulary feature, not useful for your models
            checkpointURL,
            metadataURL);

        // check that model and metadata are loaded via HTTPS requests.
        await recognizer.ensureModelLoaded();

        return recognizer;
    }

    async function init() {
        const recognizer = await createModel();
        const classLabels = recognizer.wordLabels(); // get class labels
        const labelContainer = document.getElementById('label-container');
        for (let i = 0; i < classLabels.length; i++) {
            labelContainer.appendChild(document.createElement('div'));
        }

        // listen() takes two arguments:
        // 1. A callback function that is invoked anytime a word is recognized.
        // 2. A configuration object with adjustable fields
        recognizer.listen(result => {
            const scores = result.scores; // probability of prediction for each class
            // render the probability scores per class
            for (let i = 0; i < classLabels.length; i++) {
                const classPrediction = classLabels[i] + ': ' + result.scores[i].toFixed(2);
                labelContainer.childNodes[i].innerHTML = classPrediction;
            }
        }, {
            includeSpectrogram: true, // in case listen should return result.spectrogram
            probabilityThreshold: 0.75,
            invokeCallbackOnNoiseAndUnknown: true,
            overlapFactor: 0.50 // probably want between 0.5 and 0.75. More info in README
        });

        // Stop the recognition in 5 seconds.
        // setTimeout(() => recognizer.stopListening(), 5000);
    }
</script>

変更する必要があるのは const URL = '{{URL}}'; の部分くらいで、こちらは、ご自身がブラウザ上で作成したモデルデータが置かれたフォルダを指定する箇所です。
Teachable Machine で作成したモデルは、ダウンロードするかクラウドに置くかを選択できるのですが、下記のように「Export your model:」の部分で「Upload(shareable link)」を選択してWeb上に置く形をとった場合は、その URL (以下の画像の赤枠で囲った部分)を指定してください。
https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_50868_a252e5fa-b9f1-40fc-0ab0-cfe541f597a5.jpeg

自分は、モデルをダウンロードするパターンも試してみたので、その話も書いておきます。
モデルをダウンロードした後、JavaScript のプログラムで当初は相対パスでの指定を行ったのですが、下記のエラーが出てしまいました・・・。
Web_Bluetooth_API_や音声認識のテスト_と_タスクメモ_20200212_txt.jpg
エラーメッセージを読むと、どうやらモデルの置かれたフォルダの指定は「http:// や https:// で始まる絶対パス(node.js の場合のみ file:// で始まる絶対パスも)」を記載する必要があるようです。

ローカルで開発をする場合で、モデルをローカルに置いている場合は、ローカルでサーバを立ち上げて絶対パスで指定するようにしてください(自分はお手軽に行える、ワンライナーでサーバを立ち上げる方法を使いました)。

サンプルを実行すると以下のようなシンプルな画面で、リアルタイムに認識した音が背景雑音かそれ以外の学習させた音かを表すスコアが表示されます。このとき、マイクの利用の許可を求めるポップアップが表示されますので、マイク利用を許可してください。
Web_Bluetooth_API_のテスト_と_tm-my-audio-model.jpg
プログラムを動作せている状態で学習させた音を鳴らすと、表示されているスコアが変わるのが確認できます。

JavaScript のプログラムに toio を動かす処理などを加える

それでは、上記のサンプルにさらに手を加えて、toio を動かすための処理などを加えた下記のプログラムについて、Teachable Machine の処理の周りを説明します。基本的な方針として、元のプログラムはあまり改変せず作業量を最小限にする、という方向で作業しました。

●API_Test/test02_c.html at master · yo-to/API_Test
 https://github.com/yo-to/API_Test/blob/master/WebAPI/test10_test09_and_AudioRecognition/test02_c.html

Teachable Machine の処理まわりで手を加えた部分は、基本的に関数「init()」の部分です。

実現したい内容は「特定の音が鳴ったときに toio を動かす処理を実行する」というものです。そのうちの「特定の音が鳴った」ことを特定する処理を加えていきます。
サンプルのプログラムは、一定の時間間隔でスコアを表示しており、下記の 127〜130行目の処理でスコアを更新していました。

        for (let i = 0; i < classLabels.length; i++) {
            const classPrediction = classLabels[i] + ": " + result.scores[i].toFixed(2);
            labelContainer.childNodes[i].innerHTML = classPrediction;
        }

この処理の直後に、算出されたスコアが最大のものを見つければ、どの音が鳴ったと推定されるか特定できそうです。具体的には以下のような処理を追加しました。

        indexMax = result.scores.indexOf(Math.max(...result.scores)); // スコアが最大のもののインデックスを取得
        count += 1;
        console.log(indexMax + ''); // スコアが最大のもののインデックスをデバッグ用に出力
        if (count > 0) { // toio を動かす処理を毎回実行するか、X回に1回だけ動作させるようにするかを設定
            count = 0;

            switch (indexMax) {
            case 0:
                console.log('0番'); // 背景雑音
                break;
            case 1:
                console.log('1番'); // タンバリンの音
                controlMotor(cube1, motor_buf_01);
                break;
            case 2:
                console.log('2番'); // 銀ベルの音
                controlMotor(cube2, motor_buf_01);
                break;
            case 3:
                console.log('3番'); // 青白の卓上ベルを普通に鳴らしたとき
                controlMotor(cube1, motor_buf_02);
                controlMotor(cube2, motor_buf_02);
                break;
            case 4:
                console.log('4番'); // 青白の卓上ベルを連打して鳴らしたとき
                controlMotor(cube1, motor_buf_03);
                controlMotor(cube2, motor_buf_03);
                break;
            default:
                console.log('default');
            }
        } else {
            ;
        }

サンプルに実装されていた処理を見ると result.scores にどの音が鳴ったと推定されるかのスコアが格納されているようだったため、 indexMax = result.scores.indexOf(Math.max(...result.scores)); という処理で、スコアの配列の中で最大値となるインデックスを取り出しました。そして、背景雑音と 4つの音の 5つに対応したインデックス(0番から 4番までの 5つのインデックス)のどれであるかによって、 toio を動かすための関数「controlMotor」をパラメータを変えて呼び出しています。
上記のコメントにも書いたとおり「count」という変数は、この switch分で判定する処理を行うタイミングを、元のプログラムに手を入れることなく変更するために使っているものです。上記では、0 になっているので Teachable Machine のサンプルプログラムのスコア更新処理が行われるたびに、追加実装した判定処理が毎回実行されます。

GitHub のソースコードを見ると他にもいろいろ処理が書かれていますが、他の部分は「toio を制御する部分」と「 Web Speech API の SpeechSynthesis インターフェイスを使った音声合成処理を行う部分」になっています。今回は、この部分の説明は割愛します。

おわりに

今回、デモ動画の中の Teachable Machine で作成したモデルを JavaScript で処理し、音の認識を行う部分について記事で説明しました。
Web Bluetooth API を使った toio の制御(+Web Speech API の SpeechSynthesis インターフェイスによる音声合成処理)については説明を割愛しましたが、別途、それらについて説明した記事とを書ければと思います。

【2020/2/16 追記】 作った作品を試してもらって

2/15・16(土・日)に開催されたつくばのミニメーカーフェアで下記のブースで出展をして、こちらの作品を来場者の方に体験していただきました。

 ●toio™で作ってみた!友の会 – Tsukuba Mini Maker Faire 2020

小さい子でも楽しめる・子ども達が触りたくなる、というのを考えて作ったつもりではあるものの、試してもらうまでどんな反応が返ってくるかはドキドキでした。
最終的に、想定したよりも低年齢のお子さんから、大人の方までたくさんの方に楽しんでいただけました!(休みをとる暇がないくらいにw)

また、展示ブースでの様子をツイートいただいていました。

【2020/2/25 追記】 同じ楽器で音階違いのもの

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした