150
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Organization

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

娘も私も絵本の読み聞かせは毎日欠かせない

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

娘が使う目的なので、画面はできるだけシンプルにして、カメラに絵本をかざせば、すぐに読み聞かせが開始されるようにした。↓が実際の画面。
image.png

また、娘が使ってくれたことに気づけるように、LINE Notifyで通知する機能も追加した。(※「あ、今日も読んでくれたんだー」と仕事帰りの電車でニヤニヤするための自己満足)
図1.png

構成

1.Teachable Machineを使い、娘のお気に入りの絵本を学習させ、モデルの埋め込み用リンクを発行する。
image.png
2.学習モデルを、ml5を使って読み込み、フロントエンドで動くようにする。

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

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

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

sample.html
<audio id="sound-file1" preload="auto">
  <source src="https://dotup.org/uploda/dotup.org2302152.mp3" type="audio/mp3" controls>
</audio>
sample.js
function storytelling1(){
  //音声ファイルを再生する
  document.getElementById('sound-file1').play();
  //Webhookにアクセス
  sendWebhook('「Do you want a Hug?」を読んだよ');
}

4.axiosを使い、音源が再生された際にIntegromatのWebhookURLにアクセスされるようにする。

sample.html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
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);
}
}

5.Integromatを使ってWebhookURLとLINE Notificationを連携し、「読み聞かせ」が行われたことを通知する。
image.png

ソース

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などを使えば簡単に学習モデルを作ることができた。また、ml5などのライブラリからモデルを「使う」ことも、そこまで難しくなくできる、という感覚も得られた。ただ、モデルのタイプによっては、色々と制約などもありそうなので、使いながら覚えていきたいと思った。

おまけ

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

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
150
Help us understand the problem. What are the problem?