3_1_1:MobileNetによる特徴抽出を使用した画像分類 ml5.js JavaScript

以下は、「ml5-examples/p5js/FeatureExtractor/FeatureExtractor_Image_Classification」で公開されているサンプルの解説です。

次のリンクをクリックすると、このサンプルの動作が確認できます。
MobileNetによる特徴抽出を使用した画像分類サンプル

上記ml5-examplesのFeatureExtractor_Image_Classificationサンプルでは、Webカメラで撮ったある決まったポーズAをcat、別のポーズBをdogというラベルを付け、その画像をカスタムモデルに訓練させていますが、かなり分かりづらい(catやdogとWebカメラの画像を関連付ける意味が理解できない)ので、下記サンプルでは、catとdogをじゃんけんのグーとパーに置き換えています。

簡単に言うと、Webカメラでグーとパーの画像を適量撮り、それをモデルに訓練させます。訓練が終わったら、カメラにグーかパーを見せてモデルに推測させます。この方法を取ることで、既存のモデルを自分の用途向けに改変することができます。

なぜこのようなことが可能になるかを下図で簡単に説明します。基本になるMobileNetモデルは相当な訓練を積んだ優秀なモデルです。そのモデルを利用して、グーの画像は”gu”で、パーの画像は”pa”だと学習させます。画像といっても実際はたくさんの数値の羅列です。ラベルと呼んでいるのは”正解”を意味します。つまり、モデルは、グーの数値は”gu”で、パーの数値は”pa”だと言われる、関係性を探るわけです。

モデルの訓練が進むと、損失値(モデルの推測と正解との差)がかなり小さくなります。これは精度が上がったことを意味します。チョキの画像では指が2本伸びていますが、グーの画像にはこれがありません。モデルはチョキの画像の特徴とグーの画像の特徴を見つけ、それらを分類できるようになります。

l5-examplesのFeatureExtractor_Image_Classificationサンプルをグーとパーが分類できるようにするには、サンプルで提供されているカスタムモデルに手を加える必要があります。サンプルのmodelフォルダにあるmodel.jsonファイルをテキストエディタで開き、最後のmapStringToIndexに対応する配列を次のように変更します。

{“mapStringToIndex”:[“gu”,”choki”]}

HTML

<body>
  ...>
  <div id="videoContainer"></div>
  <h6><span id="modelStatus">ベースモデルの読み込み中...</span> | <span id="videoStatus">ビデオの読み込み中...</span></h6>
  ...
  <p>
    <button id="guButton">グーの画像を追加</button>
    <p><span id="amountOfGuImages">0</span> グーの画像</p>
    <br>
    <button id="chokiButton">チョキの画像を追加</button>
    <p><span id="amountOfChokiImages">0</span> チョキの画像</p>
  </p>
  <br/>
  <button id="train">訓練</button><span id="loss"></span>
  <br/>
  <p>
    <button id="buttonPredict">推測を開始</button>
    <br> カスタムモデルはこれを
    <span id="result">...</span>と分類した。
  </p>
  <br/>
  <br>
  <button id="save">保存</button>
  <br>
  <label for="avatar">モデルを読み込む:</label>
  <input type="file" id="load" multiple/>
  <script src="sketch.js"></script>
</body>

sketch.js

let featureExtractor;
let classifier;
let video;
let loss;
let guImages = 0;
let chokiImages = 0;

function setup() {
    noCanvas();
    // video要素を作成
    video = createCapture(VIDEO);
    // video要素をvideoContainer DOM要素に追加
    // p5.Element.parent() - 要素を、指定された親に追加する。
    // https://p5js.org/reference/#/p5.Element/parent
    video.parent('videoContainer');
    // MobileNetから学習済みの特徴を抽出
    // ml5.featureExtractor(model, ?callback)
    // https://ml5js.org/docs/FeatureExtractor
    featureExtractor = ml5.featureExtractor('MobileNet', modelReady);
    // その特徴を使って新しい分類器を作成し、使用したいビデオを与える
    // .classification(?video, ?callback)
    // https://ml5js.org/docs/FeatureExtractor
    classifier = featureExtractor.classification(video, videoReady);
    // UIボタンを設定
    setupButtons();
}

// モデルがロードされたときに呼び出される関数
const modelReady = () => {
    select('#modelStatus').html('ベースモデル(MobileNet)を読み込んだ');
    // async load(filesOrPath = null, callback) {
    // https://github.com/ml5js/ml5-library/blob/master/src/FeatureExtractor/Mobilenet.js
    classifier.load('./model/model.json', function() {
        select('#modelStatus').html('カスタムモデルを読み込んだ');
    });
}

// ビデオがロードされたときに呼び出される関数
const videoReady = () => {
    select('#videoStatus').html('ビデオの準備完了');
}

// 現在のフレームを分類
const classify = () => {
    // .classify(?callback)
    // https://ml5js.org/docs/FeatureExtractor
    classifier.classify(gotResults);
}

// UIボタンを作成する便利関数
const setupButtons = () => {
    // グーのボタンが押されたら、ビデオの現在のフレームを、"gu"のラベルとともに分類器に追加する。
    const buttonA = select('#guButton');
    // https://p5js.org/reference/#/p5/mousePressed
    buttonA.mousePressed(function() {
        // .addImage(label, ?callback)
        // https://ml5js.org/docs/FeatureExtractor
        classifier.addImage('gu');
        select('#amountOfGuImages').html(guImages++);
    });

    // チョキのボタンが押されたら、ビデオの現在のフレームを、"choki"のラベルとともに分類器に追加する。
    const buttonB = select('#chokiButton');
    buttonB.mousePressed(function() {
        classifier.addImage('choki');
        select('#amountOfChokiImages').html(chokiImages++);
    });

    // 訓練ボタン
    const train = select('#train');
    train.mousePressed(function() {
        // .train(callback)
        // https://ml5js.org/docs/FeatureExtractor
        classifier.train(function(lossValue) {
            if (lossValue) {
                loss = lossValue;
                select('#loss').html('損失:' + loss);
            }
            else {
                select('#loss').html('訓練終了 最終的な損失: ' + loss);
            }
        });
    });

    // 推測ボタン
    const buttonPredict = select('#buttonPredict');
    buttonPredict.mousePressed(classify);

    // 保存ボタン
    const saveBtn = select('#save');
    saveBtn.mousePressed(function() {
        // async save(callback) {
        // https://github.com/ml5js/ml5-library/blob/master/src/FeatureExtractor/Mobilenet.js
        classifier.save();
    });

    // モデルを読み込むボタン
    const loadBtn = select('#load');
    loadBtn.changed(function() {
        // async load(filesOrPath = null, callback) {
        // https://github.com/ml5js/ml5-library/blob/master/src/FeatureExtractor/Mobilenet.js

        // p5.Element.elt p5.Elementの基盤となっているHTML要素。通常のHTMLメソッドはすべて、eltから呼び出せる。
        classifier.load(loadBtn.elt.files, function() {
            select('#modelStatus').html('カスタムモデルを読み込んだ');
        });
    });
}

// 結果を表示
const gotResults = (err, result) => {
    // エラーを示す
    if (err) {
        console.error(err);
    }
    select('#result').html(result);
    classify();
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA