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

機械学習による推論をJavaScript上で簡単に実装できるml5.js

機械学習については解説記事をかじった程度の知識しかない僕でも、物体検出プログラムを簡単に作れたので紹介します。

ml5.js

機械学習プラットフォームでお馴染みのTensorFlowをJavaScriptで実装した TensorFlow.js というライブラリがあります。

WebGLを利用したGPGPUで計算を行うことによりネイティブを肉薄するパフォーマンスを得た、ブラウザで動く機械学習ライブラリです。
そしてTensorFlow.jsを、専門知識がなくても使えるよう至れり尽くせりしてくれるラッパーがml5.jsというわけです。

使い方

学習済モデルやアルゴリズムを選択してデータを入力することで、推論結果を得ることができます。

学習済モデルはデータ量が大きいのでライブラリ本体とは別に存在しており、選択した時点で配信サーバーへAjaxする挙動になります。
巨大なものだと100MB近くにまで達するので、スマホで利用する際は注意したほうが良いかもしれません。

入力可能なデータはHTMLMediaElementImageDataなどがあります。
MediaStream API でカメラやマイクなどのデバイスからデータストリームを取得しHTMLMediaElementに紐付けることで、リアルタイム検出が可能です。

利用可能なモデル

MobileNetやDarknetなどのニューラルネットワークモデルが使えます。
(ここら辺は付け焼き刃なので深い言及は避けておきます)

物体検出プログラム

早速、実際のプログラムを作ってみます。

物体検出には YOLO というアルゴリズムを使用し、映像はウェブカメラやスマホカメラから取得します。

HTML
<div class="overlay">
    <video id="capture" width="640" height="480"></video>
    <canvas id="detect" width="640" height="480"></canvas>
</div>
CSS
.overlay{
    position: relative;
}
.overlay > *{
    display: block;
    position: absolute;
}

まずHTML/CSSです。

いたって単純で、カメラから取得したリアルタイム映像を表示させる<video>と、検出結果をオーバーレイで表示させる<canvas>から成ります。

JavaScript
function getStream(video){
    return navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
            facingMode: "environment"
        }
    })
    .then((stream)=>{
        video.srcObject = stream;
        return video;
    });
}

最初にMediaStream APIを初期化します。

navigator.mediaDevices.getUserMedia()でデバイスのデータストリームを取得し、実際に表示させるHTMLMediaElementへ紐付けます。
初期化オプションは以下となります。

  • audio: 音声を拾うか否か
  • video: 映像関連の設定
  • facingMode: リアカメラorフロントカメラ

environmentがリアカメラでuserがフロントカメラとなります。

なお、設定値が無効だった場合は有効値を順々に探索していきますが、明示した設定値のみに限定したい場合はexactプロパティ経由で設定します。

JavaScript
function detectObject(video, canvas){
    const render = canvas.getContext("2d");

    render.beginPath();
    render.lineWidth = 2;
    render.strokeStyle = "#2fad09";

    render.font = "16px consolas";

    render.fillStyle = "#ffffff";
    render.fillRect(0, 0, canvas.width, canvas.height);
    render.fillStyle = "#000000";
    render.fillText("Model Loading...", 4, 14);
    render.fillStyle = "#2fad09";

    return ml5.YOLO({
        filterBoxesThreshold: 0.01,
        IOUThreshold: 0.2,
        classProbThreshold: 0.5
    }).ready
    .then((model)=>{
        render.clearRect(0, 0, canvas.width, canvas.height);
        video.play();

        return setInterval(()=>{
            if(!model.isPredicting){
                model.detect(video)
                .then((results)=>{
                    render.clearRect(0, 0, canvas.width, canvas.height);

                    for(const result of results){
                        render.strokeRect(result.x * canvas.width, result.y * canvas.height, result.w * canvas.width, result.h * canvas.height);
                        render.fillText(`${result.label}: ${Math.round(result.confidence * 100)} %`, result.x * canvas.width + 4, result.y * canvas.height + 14);
                    }
                });
            }
        }, 67);
    });
}

物体検出プログラム本体です。

ml5.YOLO().readyでYOLOモデルを取得しmodel.detect()で検出処理を実行します。
モデル取得時に閾値オプションを渡せます。

なお検出処理は入力された映像のスナップショットに対して実行されるので、物体をトラッキングするにはループさせる必要があります。
単純な実装であればsetInterval()でひたすら回せば良いのですが、検出処理はヘビータスクゆえ、もし前コンテキストが実行中のまま次ループへ入ってしまうと、リソースの奪い合いで時間経過とともにどんどん重くなってしまいます。

その対策として、モデルオブジェクトには "推論を実行中か" をBooleanで返すisPredictingというプロパティがあります。
これを使い、確実に処理が終了してから次のループに入るためのロック処理を入れました。
この例だと67ms毎(≒15fps)に検出処理中でないか確認してから実行しています。

結果は、検出された物体の "座標" と "ラベル" と "確率" が検出数の分だけ配列で返されます。

JavaScript
getStream(document.getElementById("capture"))
.then(video => detectObject(video, document.getElementById("detect")))
.catch(error => alert(error.message));

最終的にこのようなかたちで実行します。

完成

作ったサンプルはGitHubPagesに上げておきました。

Screenshot_20200115-155718_Chrome.jpg

この通り、スマホでも問題なく実行できます。
が、前述の通りヘビータスクなので結構重くなります。

おわりに

「機械学習で色々やってみたいけど、そもそも入口が複雑すぎてお手上げ...」
みたいな敷居の高さを取り払ってくれる、とても良いライブラリだと思いました。

もちろん、理解を深めるにはより低レベルで高度な知識が必要になると思いますが、そのきっかけ作りには丁度良いなと感じました。

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
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