娘に会えない日も自分の声で絵本を読み聞かせられるようにした。機械学習入門。

#娘も私も絵本の読み聞かせは毎日欠かせない
もうすぐ2歳になる娘は絵本を読んでもらうことが好きで、寝る前に必ず自分で選んだ絵本を持ってくる。<B>仕事で娘に会えない日も、自分の声で読み聞かせてあげる</B>ことができれば、明日も私を指名して絵本を持ってきてくれるかもしれない。<B>娘のお気に入りの絵本を機械学習が判断し、読み聞かせの音声を再生するWebアプリケーション</B>を作った。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">家にいない日も、娘に自分の声で読み聞かせできるようにした。<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92?src=hash&amp;ref_src=twsrc%5Etfw">#機械学習</a> <a href="https://twitter.com/hashtag/%E7%B5%B5%E6%9C%AC%E8%AA%AD%E3%81%BF%E8%81%9E%E3%81%8B%E3%81%9B?src=hash&amp;ref_src=twsrc%5Etfw">#絵本読み聞かせ</a> <a href="https://t.co/RHQl2OvDRS">pic.twitter.com/RHQl2OvDRS</a></p>&mdash; 3do. (@3doHi) <a href="https://twitter.com/3doHi/status/1325425425196015616?ref_src=twsrc%5Etfw">November 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<b>娘が使う目的なので</b>、画面は<b>できるだけシンプルに</b>して、カメラに絵本をかざせば、すぐに読み聞かせが開始されるようにした。↓が実際の画面。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737339/e1350565-00c8-ff14-f6ff-9bfeba471cd5.png)

また、娘が使ってくれたことに気づけるように、LINE Notifyで通知する機能も追加した。（※「あ、今日も読んでくれたんだー」と仕事帰りの電車でニヤニヤするための自己満足）
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737339/236f3dae-74cf-fb4a-cbc5-d6653e3d6b60.png)

#構成

１．Teachable Machineを使い、娘のお気に入りの絵本を学習させ、モデルの埋め込み用リンクを発行する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737339/da6954eb-3a24-fc65-0088-9bbc6503273f.png)
２．学習モデルを、ml5を使って読み込み、フロントエンドで動くようにする。

```html:sample.html
<script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
```
```js:sample.js
// 作成したモデルのURL
const imageModelURL = 'https://teachablemachine.withgoogle.com/models/XXXXX/';

// 自作モデルのロード
classifier = ml5.imageClassifier(imageModelURL + 'model.json', video, () => {
// ロード完了
console.log('Model Loaded!');
});
```

３．収録した読み聞かせ音源が、学習モデルの判断結果によって再生されるように実装する。

```html:sample.html
<audio id="sound-file1" preload="auto">
  <source src="https://dotup.org/uploda/dotup.org2302152.mp3" type="audio/mp3" controls>
</audio>
```

```js:sample.js
function storytelling1(){
  //音声ファイルを再生する
  document.getElementById('sound-file1').play();
  //Webhookにアクセス
  sendWebhook('「Do you want a Hug?」を読んだよ');
}
```
４．axiosを使い、音源が再生された際にIntegromatのWebhookURLにアクセスされるようにする。

```html:sample.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
```
```js:sample.js
// 引数に送りたいメッセージを入れる
async function sendWebhook(message) {
// Integromatに送る
try {
// 取得したIntegromatのWebhookURL
const res = await axios.get(`https://hook.integromat.com/XXXXXXXXXXXXXXXXXXXXXXXXXX?message=${message}`);
console.log(res.data);
} catch (err) {
console.error(err);
}
}
```

５．Integromatを使ってWebhookURLとLINE Notificationを連携し、「読み聞かせ」が行われたことを通知する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737339/2ab4a9e4-b011-95cc-c097-9acfe9d248b8.png)

#ソース
```html:storytelling.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Storytelling</title>
  </head>

  <body>
    <h1>Storytelling</h1>
    <div id="console_log"></div>
    <video id="myvideo" width="640" height="480" muted autoplay playsinline></video>
    <audio id="sound-file1" preload="auto">
        <source src="https://dotup.org/uploda/dotup.org2302152.mp3" type="audio/mp3" controls>
    </audio>
    <audio id="sound-file2" preload="auto">
        <source src="https://dotup.org/uploda/dotup.org2302153.mp3" type="audio/mp3" controls>
    </audio>
    <audio id="sound-file3" preload="auto">
        <source src="https://dotup.org/uploda/dotup.org2302151.mp3" type="audio/mp3" controls>
    </audio>

    <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    
    <script>
      // 作成したモデルのURL
      const imageModelURL = 'https://teachablemachine.withgoogle.com/models/XXXXXXX/';
      
      console.log = function (log) {
      document.getElementById('console_log').innerHTML = log;
      }
      
      async function main() {
        // カメラからの映像取得
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: false,
          video: true,
        });

        // IDが"myvideo"であるDOMを取得
        const video = document.getElementById('myvideo');

        // videoにカメラ映像をセット
        video.srcObject = stream;

        // 自作モデルのロード
        classifier = ml5.imageClassifier(imageModelURL + 'model.json', video, () => {
          // ロード完了
          console.log('Model Loaded!');
        });

        // 分類処理を連続的に行う
        function onDetect(err, results) {
          if (results[0]) {
              console.log(results[0].label);
              //読み聞かせ音を出す
              if (results[0].label === 'Do you want a Hug?') {
              // storytelling 関数実行
                 storytelling1();
              }
              if (results[0].label === 'おつきさまこんばんは') {
                 storytelling2();
              }
              if (results[0].label === 'だるまさんと') {
                 storytelling3();
              }
            }
          classifier.classify(onDetect);
        }
        classifier.classify(onDetect);
      }

      // 引数に送りたいメッセージを入れる
      async function sendWebhook(message) {
      // Integromatに送る
      try {
      // 取得したIntegromatのWebhookURL
      const res = await axios.get(`https://hook.integromat.com/XXXXXXXXXXXXXXXXXXXXXXXXXX?message=${message}`);
      console.log(res.data);
        } catch (err) {
      console.error(err);
       }
      }

      function storytelling1(){
            //音声ファイルを再生する
            document.getElementById('sound-file1').play();
            sendWebhook('「Do you want a Hug?」を読んだよ');
      }

      function storytelling2(){
            //音声ファイルを再生する
            document.getElementById('sound-file2').play();
            sendWebhook('「おつきさまこんばんは」を読んだよ');
      }

      function storytelling3(){
            //音声ファイルを再生する
            document.getElementById('sound-file3').play();
            sendWebhook('「だるまさんと」を読んだよ');
      }
      // 実行
      main();

    </script>
  </body>
</html>
```
#機械学習入門として作ってみた感想
機械学習ってキーワードから入ると、勉強しなければならないことも多いと思うが、TeachableMachineなどを使えば<b>簡単に学習モデルを作ることができた。</b>また、ml5などのライブラリから<b>モデルを「使う」ことも、そこまで難しくなく</b>できる、という感覚も得られた。ただ、モデルのタイプによっては、色々と制約などもありそうなので、使いながら覚えていきたいと思った。

#おまけ
・絵本の各ページを撮影し、声を吹き込んだ動画を流す案も考えましたが、娘が絵本を読むのは、寝る前であることが多いので、<b>明るい画面を見続けて眠れなくならないように、「音声」での読み聞かせを選びました。</b>
・私にとって、絵本の読み聞かせは、娘と過ごす時間の中でもトップ３に入る好きな時間です。より良い時間にできるアイデアを、また何か考えてみたいものです。