画像認識結果をさらに詳しく表示するには? ー 第1章❹ IF文が必要となる理由を実感しよう! ー 『JavaScriptでやる1分間プログラミング』
前回パート❸では手描き画像認識の結果を画面表示するところまでプログラミンをしました。
機械学習モデルの使用部分はこれだけなのですが、結果の表示をユーザーがもっと楽しめるようにしてみます。具体的にはこんな仕様はどうでしょう。
❶ 結果は一つだけではなく、トップ2を表示する
❷ ラベルと同時に信頼度(推測確率)も表示する
❸ 信頼度に応じて音声で推測結果をしゃべる
❸はオリジナルのCodeing Trainにはないのですが、ここでは特別に音声機能を加えてみます。このステップ❸は次回に取り上げます。
プロジェクトの用意
これまでと同様、新しいプロジェクトフォルダ、Oekaki5を作成して、前回のOekaki4からファイルをコピーしてきてください。
エラーの処理
まず現在のsketch.jsを見てみます。
let canvas; //絵を描くエリア
let clearButton; //消すボタン
//キャンバスの設定
function setup() {
//キャンバスを用意
canvas = createCanvas(400, 400);//Canvasを作成
canvas.parent('canvas-div'); //@@@ここを追加@@@
background(255); //Canvasの背景を白にする
//ボタンを用意
clearButton = createButton('消す');//ボタンを作成
clearButton.parent('canvas-div'); //@@@ここを追加@@@
clearButton.mousePressed(clearCanvas);//ボタンクリックの関数を指定
//モデルを読み込む
doodleClassifier = ml5.imageClassifier('DoodleNet', modelReady);
//ロード中のメッセージを表示
resultsDiv = createDiv("モデルをロードしています・・・");
resultsDiv.parent('results-div');
}
//モデルがロードされた時の処理
function modelReady() {
resultsDiv.html("モデル準備完了!");
//キャンバスの絵を認識する
doodleClassifier.classify(canvas, gotResults);
}
//認識結果を処理する
function gotResults(error, results) {
//認識結果の最初のラベルを表示する
resultsDiv.html(results[0].label);
//すかさずclassifyメソッドでまた認識してもらう
doodleClassifier.classify(canvas, gotResults);
}
//マウスで絵を描くための関数
function draw() {
if (mouseIsPressed) {
strokeWeight(18);
line(mouseX, mouseY, pmouseX, pmouseY);
}
}
//絵を全て消すボタンの動作
function clearCanvas() {
background(255);
}
そこで結果を表示するgotResults関数を見てください。
//認識結果を処理する
function gotResults(error, results) {
//認識結果の最初のラベルを表示する
resultsDiv.html(results[0].label);
//すかさずclassifyメソッドでまた認識してもらう
doodleClassifier.classify(canvas, gotResults);
}
前回説明した通り、このコールバック関数には決められたerrorとresultsというパラメータがあります。resultsはすでに結果データを取得するために使用していますが、errorの処理も必要です。認識にエラーが出るとresultsは中身のない結果になるので、そこからresults[0]などと中身を取り出そうとするとそこがエラーになります。
そこで、「もしエラーになったら〇〇してね」という処理がどうしても必要となってくるのです。この「もし」のところを実現するのがIF文です。
IF(条件文)はプログラミングの”キモ”!
それではエラーが発生した場合の対処をコーディングしてみます。
そこで次の「//まずはエラーの処理」とある箇所の3行のセクションを加えてください。
//認識結果を処理する
function gotResults(error, results) {
//まずはエラーの処理
if (error) {
console.error(error);
return;
}
//認識結果の最初のラベルを表示する
resultsDiv.html(results[0].label);
//すかさずclassifyメソッドでまた認識してもらう
doodleClassifier.classify(canvas, gotResults);
}
この関数に渡されるerrorというのは、Errorオブジェクトと言って、エラーのname(名前)とかmessage(エラー文)が属性として入っています(error.name, error.messageのようにして取得します)。
ここでやりたいのは、「もしエラーが発生したら」という処理です。これがIF文です。そこでコードに書いてあるのがこれです。
if (error) {
・・・エラーが出たときの処理・・・
}
これをそのまま読むと「もし(エラー)なら」のようになるので、プログラミングに慣れてない方には実にわかりやすいコードだと思います。
余談ですが、JavaScript以外のプログラミングをやっている人にとってはこのIF文の書き方は実に”気持ち悪い”のです。普通IF文はその中でBooleanという真偽(True or False)の値を入れます。でもこのerrorという変数にはErrorオブジェクトが入っていて、決してBoolean型ではありません。実はJavaScriptでは、IFの中で「オブジェクトが存在する」あるいは「値が存在する」というだけでTrueと判断されるのです。
初心者の方には何のことかさっぱりでしょうが、ここら辺、疑問に思うプログラミング経験者がいたら是非この文書を読んでJavaScriptの条件文のことをしっかり理解してください。
エラーが発生したらどうするか?
では、モデルがエラーに陥ったらどうするかですが、まず2つのことを考えないといけません。
❶ どんな問題が発生したかユーザーに知らせる
❷ エラー状況によって処理を止めるか続行するか、ユーザーにインパクトの少ない形で対処する
❶ エラーの内容を”記録する”
まずIF文でエラーをキャッチしたら、最初のコードがこれです。
console.error(error);
これはコンソールというブラウザの「検証」画面にエラーの内容を表示させます。もちろんこの画面はユーザーが意図して開かないとみることができません。ですからこれはあくまで「記録」が目的で、通常のユーザーに何かをしてもらうようなつもりは一切ないのです。
❷ 関数を”終了する”
画像認識モデルにキャンバスの絵を渡して何かエラーが出たということは、その絵が何であるかという推測はされないということです。認識結果が出ないということなら、ネコとかネズミとかの結果を表示することは無理ということ。そこで次のコードでこう書いています。
return;
returnは関数の返値を指定するものですが、それだけだと単に「関数をそのまま終了してください」という命令になります。つまりここでgotResults関数は終了。その後何もしなくなります。
実際にはこれはちょっと問題のある対処です。前のパートの後半に説明しましたが、このgotResults関数は自分で自分を呼ぶ(再帰と言います)ことで、絵を描き進むたびに画像認識をするようになっています。もし一つのエラーが発生して関数の”ループ”をストップさせると、その後は絵を描いても何も認識しません。つまりアプリ全体がストップするわけです。
ただ、Coding Trainでは画像認識のコードをやるのが主眼なので、ここでは単純な処理で済ませています。
本来、エラー処理というのはとても難しいものです。エラーが発生してもユーザーが自然にアプリで作業を続けることができるようにするのが理想です。もしユーザーに何かをしてもらう(エラー修正のため)ならその操作を明確に伝える必要があります。ここら辺は時間をかけて対処方法を身に付けていく必要があります。
トップ2の結果を表示させる
ちょっと詳しくエラー処理の話をしましたが、まずはIF文を理解してください。そしてそのIF文を利用して、結果の表示をもう少しスマートに行います。
❶ 認識の確率が50%以上でないと表示させない
❷ トップ2を、ラベル+確率で表示する
一つ目の処理をどうしてやるのかわかりますか?先に説明した通り、画像認識の呼び出しは回帰的に呼び出すために、何度も何度も認識が実行されることになります。キャンバスに何かがプロットされるとそれが何であれ推測が返ってきます。これは無駄なので、「自信がある時だけ教えてね」という指示を加えます。ここでもIF文を使うことになります。
ではまず、results配列の解説を思い出してください。この配列には複数の結果が入っていて、それぞれについて、.lableはラベル名、.confidenceは確率が入っていました。そこでまず、「トップ1の確率が50%以上の時だけ・・・」という条件を加えます。
if (results[0].confidence > 0.5) {
・・・結果表示のコードを書く・・・
}
こうすることで、結果の確率が50%を超えていないと表示処理はスキップされます。つまりその時に表示されたテキストがそのままスルーするということです。これで目まぐるしくパタパタ変わる結果テキストは少し落ち着くはずです。
そして予想確率が五割を超えたら、トップ1だけではなく2番目のものも表示されるようにします。しかもラベルに加えてその確率〇%という数字も表示させましょう。コードはこうなります。
if (results[0].confidence > 0.5) {
//表示テキストを用意する
let content = results[0].label + " " +
(100 * results[0].confidence).toFixed(1) + "%</br>" +
results[1].label + " " +
(100 * results[1].confidence).toFixed(1) + "%";
resultsDiv.html(content);
}
ここではcontent(内容)という変数を用意し、ここに結果表示のテキストを入れています。まずresults[0]とresults[1]の二つ(これがトップ2)を使い、そのラベルと確率を”足し合わせて”長い文字列を作っています。
ただ、コードがごちゃごちゃしているのは、確率の値が0.632345のように小数点以下がとても長いのです。そこで数値を次のように変換しています。
(100 * results[0].confidence).toFixed(1)
✅ "100 *"(100を掛ける)で 0.632345 が63.2345になる
✅".toFixed(1)"(小数点以下1桁表示)によって63.2になる
では確認してみましょう。gotResults関数はいまのところこんな感じになります。
//認識結果を処理する
function gotResults(error, results) {
//まずはエラーの処理
if (error) {
console.error(error);
return;
}
//確率50%を超えたときだけ結果表示を更新する
if (results[0].confidence > 0.5) {
//表示テキストを用意する
let content = results[0].label + " " +
(100 * results[0].confidence).toFixed(1) + "%</br>" +
results[1].label + " " +
(100 * results[1].confidence).toFixed(1) + "%";
resultsDiv.html(content);
}
//すかさずclassifyメソッドでまた認識してもらう
doodleClassifier.classify(canvas, gotResults);
}
これでファイルを保存し、ブラウザを更新すると、今度は以下のように2行表示され、しかも推測確率も表示されているはずです。
ちょっと表示を拡張してみる
コードをちょっといじって遊んでみるのもよいと思います。例えば、確率の数字は括弧付きで表示させるのはどうでしょう。
animal_migration (52.3%)
squiggle (32.1%)
あるいはトップ2ではなくトップ3を表示させるのはどうでしょう。
animal_migration (52.3%)
squiggle (32.1%)
cat (11.9%)
さらにIF文を活用してこんなのはどうでしょう?
● もし確率が80%以上なら「~ですね!」という表現にする
● もし確率が50%~80%なら「~かな?」という表示にする
次回は確率に応じて結果の表現を変え、しかもそれを音声でしゃべるようにしてみます。もうちょっと複雑なIF文を使用するのと、Web Speech APIを利用して音声合成をやってみます。
乞うご期待!
この記事が気に入ったらサポートをしてみませんか?