以下は「ml5-examples/p5js/ImageClassification/ImageClassification_VideoScavengerHunt」で公開されているサンプルのコード解説です。
次のリンクをクリックすると、このサンプルの動作が確認できます(パソコンにカメラを接続し、スピーカーをオンにしておきます)。
「MobileNetとp5.js、p5.speechを使った音声出力をともなうスカベンジャーハントゲーム」
このサンプルは、p5.speechを利用した前の「2_1_4:MobileNetとp5.js、p5.speechを使った音声出力をともなうWebカメラ画像分類」を進めたものです。コンピュータに探すよう求められた物を周辺から探して持ってきてカメラに見せると、モデルがそれが何かを推測します。
HTMLでは、libフォルダにあるp5.speech.jsを読み込みます。
<head>
...
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/addons/p5.dom.min.js"></script>
<script src="https://unpkg.com/ml5@0.1.3/dist/ml5.min.js" type="text/javascript"></script>
<script src="lib/p5.speech.js"></script>
</head>
<body>
<h1>MobileNetとp5.js、p5.speechを使った音声出力をともなうスカベンジャーハントゲーム</h1>
<p>コンピュータが指定した物を探し、それをWebカメラに見せる。するとモデルがそれを推測して結果をしゃべる</p>
<p id="status">モデルのロード中...</p>
<p>スピーカーをオンにしてください。</p>
<button id="start">スタート</button>
<p id="instruction"></p>
<p id="message"></p>
<button id="next">次の単語</button><br><br>
<script src="sketch.js"></script>
</body>
sketch.js
let classifier;
let video;
let currentWord;
let currentIndex = 0;
let isPlaying = false;
const words = ['banana', 'watch', 'shoe', 'book', 'cellphone', 'keyboard', 'shirt', 'pants', 'cup'];
// 新しいp5.Speechオブジェクトを作成する。
// 使用する言語や音声のレート、ピッチ、ボリュームを制御することもできる。
// 詳細は次のリンクを参照:http://ability.nyu.edu/p5.js-speech/
const myVoice = new p5.Speech();
function setup() {
noCanvas();
// カメラ入力を作成
video = createCapture(VIDEO);
// ml5.imageClassifier()メソッドを、MobileNetとビデオ(p5のvideo要素)で初期化
classifier = ml5.imageClassifier('MobileNet', video, modelReady);
// [スタート]ボタンのクリックからはplayNextWord()関数を呼び出す。
select('#start').mousePressed(function() {
playNextWord();
});
// [次の単語]ボタンがクリックされたら、
select('#next').mousePressed(function() {
// currentIndex変数を1だけ大きくする。
currentIndex++;
// 単語数をオーバーしたら最初に戻る
if (currentIndex >= words.length) {
currentIndex = 0;
}
playNextWord();
});
// 喋る言語を日本語に設定
myVoice.setLang('ja');
// speechEnded()関数は、発声が終わったときに呼び出される
// p5.SpeechのonEndプロパティについては、次のリンクを参照:http://ability.nyu.edu/p5.js-speech/
myVoice.onEnd = speechEnded;
}
const playNextWord = () => {
isPlaying = true;
currentWord = words[currentIndex];
select('#instruction').html(`${currentWord}を探せ!`);
// classifyVideo()関数を呼び出し、ビデオを分類を開始する。
classifyVideo();
}
const modelReady = () => {
// モデルの準備ができたらその状態を示す
select('#status').html('モデルの準備完了');
}
// 現在のビデオフレームに関して、推測を得る
const classifyVideo = () => {
classifier.predict(gotResult);
}
// 結果が得られたら、
function gotResult(err, results) {
// 結果は、確率の順に配列に入っている
// 最初の結果の文字列を得る
const result = results[0].className;
// 最初の文字列をコンマで分割し、最初の単語を得る
const oneWordRes = result.split(',')[0];
// 上位3つ(トップ3)の結果を、文字列の配列として得る。
const top3Res = results.map((r) => {
return r.className;
});
// トップ3のどれかに現在の単語が含まれているかどうかを調べる。
// Array.find() 配列の要素に指定されたテスト関数を適用していき、テストを満たす最初の要素の値を返す。
const ifFound = top3Res.find((r) => {
// String.includes() 1つの文字列を別の文字列の中に見出すことができるかどうかを判断し、必要に応じてtrueかfalse を返す。
return r.includes(currentWord);
});
// トップ3に現在の単語が含まれていれば、
if (ifFound) {
isPlaying = false;
select('#message').html(`${currentWord}を見つけた。`);
myVoice.speak(`${currentWord}を見つけた`);
}
else {
select('#message').html(`これは${oneWordRes}`);
myVoice.speak(`これは${oneWordRes}だと推測`);
}
}
function speechEnded() {
if (isPlaying) classifyVideo();
}
上位3つ(トップ3)の結果を文字列の配列として得る、とコメントされた次のコードで、なぜ上位3つと特定できるのかと不思議に思われるかもしれません。
const top3Res = results.map((r) => {
return r.className;
});
これはml5.imageClassifier()の仕様で、optionsオブジェクトを省略した場合、MobileNetについての{version:1,alpha:1.0,topk:3}というオブジェクトが渡されます。このtopkプロパティの値3というのが上位3つを意味します。たとえば上位5つにしたい場合には、topk:5を指定すればよい訳です。
ml5.imageClassifier(model, ?video, ?options, ?callback)