以下は、「ml5-examples/p5js/ImageClassification/ImageClassification_MultipleImages」で公開されているサンプルのコード解説です。
次のリンクをクリックすると、このサンプルの動作が確認できます。
「MobileNetとp5.jsを使った複数画像の分類サンプル」
このサンプルは、画像を読み込みそれを推測して結果を表示するという作業を複数回、連続して実行するので、推測する画像の情報をJSONファイルから得る方法が取られています。またすべての推測が終わったら、その全部の結果をJSONファイルに保存します。これらはml5.jsによる機械学習とは直接関係しないテクニックですが、別のWebアプリを作成するときの参考になるかもしれません。
このサンプルは下図のフォルダ構造が取られています。
assetsフォルダにあるdata.jsonは次の構造で作成されています。
{“images”: [“image_0004.jpg”, “image_0005.jpg”, …,image_0013.jpg”]}
index.htmlの<body>要素内
<body>
<h1>MobileNetとp5.jsを使った複数画像の分類</h1>
<h2>結果は、完了時にJSONファイルとして保存される。</h2>
<p id='status'>モデルのロード中...</p>
<p>MobileNetモデルは<span id="probability">...</span>の信頼度で<span id="result">...</span>と分類しました。 </p>
<script src="sketch.js"></script>
</body>
sketch.js
let classifier = null;
let img = null;
let currentIndex = 0;
let allImages = [];
let display = true;
let displayTime = 750;
let predictions = [];
const modelReady = () => {
console.log('モデル準備完了');
select('#status').html('モデルのロード完了');
// <img>要素を作成
// createImg()で指定したコールバック関数は、その後も、
// createImg()で作成したp5.Element(img)のsrcの変更時に、何度も呼び出される。
// => img.attribute('src', allImages[currentIndex]); によってimageReady()が自動的に呼び出される。
img = createImg(allImages[0], imageReady);
console.log('画像準備開始');
}
// 画像の読み込みが完了したら、その画像に関する推測を得る。
const imageReady = (e) => {
console.log('画像準備完了');
console.log('画像の推測実行');
classifier.predict(img, gotResult);
}
// https://p5js.org/reference/#/p5/preload
// setup()の直前に呼び出される。外部ファイルの非同期読み込みを処理するために使用される。
// setup()は、preload()が完了してから呼び出される。
function preload() {
console.log('preload');
console.log('JSONデータを先読み込み');
// https://p5js.org/reference/#/p5/loadJSON
// JSONファイルをファイルやURLから読み込み、オブジェクトを返す。
data = loadJSON('assets/data.json');
// console.log(data);
}
// allImages配列に画像へのパスを入れる
const appendImages = () => {
console.log('画像ファイルへのパスを得る');
for (i = 0; i < data.images.length; i++) {
imgPath = data.images[i];
//console.log(imgPath);
allImages.push('images/dataset/' + imgPath);
}
}
function setup() {
console.log('setup');
// p5スケッチ用のデフォルトキャンバスを削除
noCanvas();
appendImages();
// MobileNetの訓練済み画像分類器をロードする。
classifier = ml5.imageClassifier('MobileNet', modelReady);
}
function drawNextImage() {
console.log('次の画像');
// https://p5js.org/reference/#/p5.Element/attribute
// 指定された要素に、新しい属性を追加するか、既存の属性の値を変更する
img.attribute('src', allImages[currentIndex]);
// imageReady()が自動的に呼び出される。
}
const savePredictions = () => {
predictionsJSON = {
"predictions": predictions
};
// https://p5js.org/reference/#/p5/saveJSON
// 配列やJSONオブジェクトの内容を.jsonファイルに書き込む。
// ファイルを保存する過程や保存する場所はWebブラウザによって異なる。
saveJSON(predictionsJSON, 'predictions.json');
console.log('JSONファイルに保存');
}
const removeImage = () => {
// currentIndex値を1大きくする
currentIndex++;
// 処理する画像が存在する間は
if (currentIndex <= allImages.length - 1) {
// 次の画像を推測
drawNextImage();
}
else {
// 処理する画像がなくなったら、推測結果をまとめて保存する
savePredictions();
}
}
// 結果を得たら、
function gotResult(err, results) {
if (err) {
console.error(err);
}
// currentIndexはremoveImage()で1ずつ足される
const information = {
"name": allImages[currentIndex],
"result": results,
}
// predictions配列にinformationオブジェクトを追加
predictions.push(information);
if (display) {
// 結果は、確率(probability)順に並んだ配列に保持されている
select('#result').html(results[0].className);
select('#probability').html(nf(results[0].probability, 0, 2));
// 画像の表示時間間隔は変数displayTimeで変更できる
// 0.75秒たったらremoveImageを呼び出す。
setTimeout(removeImage, displayTime);
}
else {
// displayがtrueでない場合
removeImage();
}
}
このJavaScriptコードでも、関数を連鎖的に呼び出す方法が取られています。一番最初に呼び出されるのはpreload()です。これは、p5.jsによって、setup()より前に呼び出される関数で、外部ファイルの非同期読み込みの処理に使用されます。preload()関数内では、p5.jsのloadJSON()関数を使ってJSONデータを読み込み、変数dataに代入しています。
以降は、前の「2_1_1:MobileNetとp5.jsを使った画像分類サンプル」と同様に、setup()、modelReady()、imageReady()、gotResult()と連鎖的に呼び出され、処理する画像がなくなったらsavePrediction()関数が呼び出されます。
p5.dom.jsのcreateImg()のリファレンスには書かれていないのですが、createImg()に指定したコールバック関数(imageReady)は、createImg()を呼び出したときだけでなく、createImg()で作成したp5.Elementオブジェクトのsrcプロパティを変更する(つまり表示する画像を変更する)ときも、何度でも呼び出されるようです。gotResult()からremoveImage()を経由して呼び出されるdrawNextImage()関数では、img.attribute(‘src’, allImages[currentIndex])を実行しているので、このときimageReady()が自動的に呼び出され、classifier.predict(img, gotResult)が実行されます。
目次
[Caltech 101]や自分の画像を使用する方法
このサンプルに付属するREADME.mdファイルには、サンプルに付属している以外の画像を、JavaScriptコードの変更なしで使用できるようにする方法が書かれています。ただしLinux環境が必要で、Pythonのバージョンは2.xにします。以下ではWindows 10のWindows Subsystem for Linux機能を使っています。
- 使用したい画像を用意するか、Caltech 101から画像をダウンロードする。
- 画像ファイルはimages/datasetフォルダに配置する。
- ターミナルでdatasetフォルダに移動する。
cd images/dataset - 次のコマンドを実行する。するとmake_jsonフォルダに、データの構造を記述したtree.txtが作成される。
- make_jsonフォルダに移動する。
cd ../../make_json - 次のコマンドを実行する。するとtree.txtに書かれた構造をJSONファイルに変換するプログラムによって、data.jsonが作成される。
python main.py - data.jsonをassetsフォルダに移す。
- data.jsonを変更するだけで、推測する画像を変えることができた。