この記事でについて
今回は、スマホのカメラに映った物体を、画像認識AIで検出してみます。
画像認識の仕組みやWebブラウザーで動かす方法、さらに、自分でAIモデルを作成する方法について紹介します。

1. 物体検出を体験してみる
まずは以下のURLを開いて、画像認識を体験してみましょう。
[YOLO物体検出デモ]
https://ykoba079.github.io/test_prj/yolo-camera/detectsample_camera_yolo.html
ページを開くと以下のような画面が表示されます。
データの読込で少し待たされますので、画面中央のメッセージが「準備完了」となるまでお待ちください。

カメラで試してみる
スマホなどでカメラが使える場合は、「カメラで試す」ボタンを押してください。ブラウザからカメラの使用許可を求める確認メッセージが表示されたら許可をしてください。許可すると画面中央に、カメラ画像が表示されます。
写真ファイルで試してみる
デスクトップPCなどでカメラが使えない場合は、「画像で試す」ボタンを押すと、画像ファイルを対象に同様の物体検出が試せますので、日常品や人物が写っている写真の画像を選択してみてください。
物体検出で表示されているもの
検出した物体は枠で囲んでいます。枠の左上には、検出した物体の名前と、モデルがその検出にどのくらい確信を持っているかを表す「信頼度」のパーセントが表示されます。
まずは物体に枠が付くところまで確認できました。
次は、Webブラウザーの中で何をしているのか、処理の流れを見てみましょう。
2. 画像認識とYOLO
先ほどのデモ画面は、Webブラウザーがカメラの映像を取得し、その画像を画像認識AIに処理させ、計算結果の物体の名前と位置を画面に表示しています。
今回使うYOLOは、画像の中に「どこに、何があるか」を検出する仕組みです。
例えば、先ほどのサンプル画像であれば、写っている物体の種類が「Bird」であることと、画像のどの範囲に写っているかを求められます。
何が写っているかを判断するために、大量の画像で学習した「学習済みモデル」を使用します。
大まかな処理の流れは以下の通りです。

今回は、おおよそ80種類の物体を学習した公開モデルを利用しています。
AIはどう物体を見分けるか
人間は写真を見るとき、色や形状から写っているものが鳥やミカンだと判断できます。(胸に手を当てて考えてください。そうですよね?)
AIも似たように、画像から輪郭や色、模様などの特徴を取り出して何が写っているかを判断します。
YOLOでは、畳み込みニューラルネットワークを使って画像から色や輪郭、形などの特徴を取り出します。
ニューラルネットワークは、ニューロンと呼ばれる計算単位が多数つながった仕組みで、それぞれのニューロンが数値を計算していきます。
この計算に使う情報・重みを、大量の画像を使って調整するのがAIの学習です。
学習した結果を使って、鳥や車など、画像に写っている物体を見分けられるようになります。
3. 画像認識の用途
このような物体検出は、身の回りのさまざまな用途に利用されています。
設備の監視
例えば稼働中のプレス機など、工場の危険な場所に人が立ち入ったら、自動でアラートを出したり機材を緊急停止させる、といった使い方ができます。

また、畑に監視カメラを設置して、イノシシやシカが入り込んだら知らせることも考えられます。
カメラ付きドローンで農地を見回れば、広範囲の監視もできそうです。

不良品の検品
工場などでは、製品をカメラで撮影して、キズや汚れを見つける用途にも利用できます。
例えば、ベルトコンベアを流れてくる部品を撮影して、キズがあるものを自動で取り除く、といった用途にも使われています。

4. 実装のポイントを見る
全体のソースはGitHubに置いています。HTMLとJavaScriptのみで、特別なソフトなどは必要ありません。
ここでは、主要な部分を抜粋して説明します。
カメラの映像を取得する
カメラの映像は、ブラウザーの getUserMedia という機能で取得できます。
const video = document.getElementById('video');
const facing = document.querySelector('input[name="facing"]:checked').value;
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: { ideal: facing },
width: { ideal: 640 }, height: { ideal: 480 }
},
audio: false
});
video.srcObject = stream;
await video.play();
facingMode はカメラの向きで、スマホの「背面」「前面」どちらのカメラを使うかの設定です。
取得した映像はstreamに入れ、HTMLのvideo 要素へ設定してから画像として取り出しています。
学習済みモデルを読み込む
モデルのファイル名や入力画像のサイズなどは、設定ファイル model.json に記載しています。
その設定からONNXファイルを読み込み、ONNX Runtime Webで実行する準備をします。
// モデルの場所などの設定ファイルを読み込む
const configUrl = new URL('model.json', location.href);
const response = await fetch(configUrl);
const config = await response.json();
// モデルに渡す画像を用意するためのCanvasを作成
const inputCanvas = document.createElement('canvas');
inputCanvas.width = inputCanvas.height = config.inputSize;
// WASMの実行用ファイルの場所と、使用するスレッド数を指定する
ort.env.wasm.wasmPaths = new URL('vendor/', location.href).href;
ort.env.wasm.numThreads = 1;
// 設定に記載された学習済みモデルをバイナリデータとして読み込む
const modelResponse = await fetch(new URL(config.url, configUrl));
const modelBytes = await modelResponse.arrayBuffer();
// WASMでモデルを実行する準備をする
const session = await ort.InferenceSession.create(modelBytes, {
executionProviders: ['wasm']
});
あらかじめInferenceSession.create でモデルを計算するための準備をする必要があります。画面を表示してしばらくすると「準備完了」になるのが、この処理が終わったところです。
画像を渡して検出する
準備ができたら、画像をモデルへ渡します。
モデルが必要とする画像のサイズや、色の数値の並びに合わせるため、先に画像を変換しています。
// カメラ画像をモデルの入力サイズやデータ形式に合わせて変換する
const prepared = prepare(video);
const tensor = prepared.tensor;
canvas.width = prepared.geometry.width;
canvas.height = prepared.geometry.height;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
// 画像をモデルへ渡し、物体検出を実行する
const result = await session.run({ [session.inputNames[0]]: tensor });
// モデルの計算結果
const out = result[session.outputNames[0]];
// 元画像上の枠の位置・物体名・信頼度へ変換する
const boxes = YoloDetection.decode(
out.data, out.dims, config.classes,
Number(document.getElementById('threshold').value) / 100,
prepared.geometry, config.boxFormat
);
session.run が、モデルによる計算を実行する部分です。計算を待つ前に画像をCanvasへ描き、計算結果はresultで受け取ります。
検出した場所に枠を描く
最後は、Canvasへ描いておいた画像に、検出した枠と名前を重ねます。
Canvasは、JavaScriptから画像や図形を描けるHTMLの要素です。名前を表示するところまで含めても、基本の処理は以下のようになります。
ctx.font = '20px sans-serif';
ctx.lineWidth = 2;
for (const b of boxes) {
ctx.strokeStyle = '#70ffc0';
ctx.strokeRect(b.x1, b.y1, b.x2 - b.x1, b.y2 - b.y1);
ctx.fillStyle = 'white';
ctx.fillText(`${b.label} ${(b.score * 100).toFixed(0)}%`, b.x1, b.y1);
}
ctx はCanvasに描くためのオブジェクトです。strokeRect で四角い枠、fillText で名前と信頼度を描いています。
カメラを使う間は、この検出と描画を繰り返して表示を更新しつづけます。
画像ファイルを選んだ場合も、選択した画像に対して処理をするだけで基本的には同じです。
物体を表示する処理のコア部分はこれだけです。
物体の名前と場所が簡単に取れるのが解りますね。先ほど挙げた、危険な範囲に人が入ったら~といったカスタマイズもイメージできるのではないでしょうか。
5. 自分の扱いたい物体を検出する
公開モデルで、自分が使いたいデータが無い場合もあります。上記の製品の不良品を見つける場合などですね。
ここでは、ミカンを見つける為の専用モデルを作成してみます。
専用モデルを作成するのに必要な作業は、以下の4つです。
- 対象が写った画像を用意する。
- 画像の中の対象を枠で囲み、名前を付ける。
- 画像と正解を使ってモデルを学習させる。
- 学習に使っていない画像で結果を確認する。
学習に使う画像は、以下のように分けておきます。
| 用途 | 使い方 |
|---|---|
| 学習用 | モデルに画像と正解を学習させる |
| 検証用 | 学習途中の成績を確認し、採用するモデルを選ぶ |
| 評価用 | 選んだモデルを、最後に未使用の画像で試す |
学習に使用した画像そのままだと正解も確定しているので、評価用の画像を別に用意しておきます。
今回は複数のミカンの写真を撮る代わりに、3Dでミカンを作って画像を用意してみました。(面倒なので!)
6. 3Dで作ったミカンを学習させてみる
ブラウザーでミカンを作る
3Dの表示には、JavaScriptのライブラリのBabylon.jsを使用しています。
形を作り、色や表面の模様を付けたミカンを作成しました。
3Dで作ったのは、ミカンの向きやカメラの位置を変えて簡単に画像を量産できるためです。横や底面など、違う方向から見たり、照明や背景、個数、トマトなどのほかの物体との組み合わせも変え、画像も作りました。

他の物体もいれたのは、ミカンと異なるものも学習しないと、オレンジ色や丸っぽければ何でもミカンと判断してしまうためです。
それでは画像に枠を付けてモデルを作ってみましょう。
ミカンを指定して学習させる
学習作業には今回は、オープンソースの画像のラベル付けクラウドサービス「CVAT」を例に使います。
URL: https://app.cvat.ai/
※初回利用時は用途などのアンケート入力を求められます。
手順は以下の通り。
1.[+]をクリックしCreate a new taskで新しいタスクを作り、名前を付ける。

2.必要項目を入力していきます。Labelsの欄に学習させたい物体名 mikan を入力して、Continueボタンを押します。学習させたい物体が複数ある場合は繰り返します。
Select filesの Click or drag files to this area に学習させる画像をドラッグして登録し、Submit & Open を押してしばらく待ちます。

3.画面が切り替わります。左下の Job #~~~~~ をクリックします。

4.画像が表示されるので、左側のツールから範囲選択ツールと、学習させる物体(今回はmikan)を選択します。

5.今回は四角形を選択しました。ミカンを囲むように、左上と右下を指定します。指定が終わったらSaveします。

ここで学習させるのは、「ここにミカンがある」という正解の情報です。
画像の中からミカンだけを囲んでいきます。ミカンがない画像は枠なしのままにします。
6.登録した画像に対してこれを繰り返します。
終わったら、Exportします。Yoloで使うので、Export formatはYOLOを指定しました。

ZIPには画像と同じ名前の .txt が入っています。例えば mikan001.png の正解は mikan001.txt です。
画像と対応するテキストを、次のように配置します。以下の labeled-manual が、手動で作った学習データのフォルダです。
画像は images、正解は labels へ入れ、学習用を train、検証用を val、評価用を test にします。
以下のようにファイルを置いていきます。imagesフォルダに画像ファイルを、labelsフォルダにCVATで作成したて画像と対で作成されるデータ(txt)を保存します。
yolo-camera/training/data/labeled-manual/
├─ images/
│ ├─ train/mikan001.png 学習用ファイル
│ ├─ val/mikan081.png 検証用ファイル
│ └─ test/mikan091.png テスト用ファイル
└─ labels/
├─ train/mikan001.txt
├─ val/mikan081.txt
└─ test/mikan091.txt
学習の準備をする
以下はWindowsのPowerShellとPython 3.10を使った場合です。
デモのソース一式と、学習用のファイル一式を yolo-camera/training/ へ用意しておきます。
まず、Python 3.10をインストールしたPCで、学習用の環境と必要なライブラリを用意します。機械学習に定番のPyTorchとかですね。
py -3.10 -m venv yolo-camera/training/.venv
$trainPython = "yolo-camera/training/.venv/Scripts/python.exe"
& $trainPython -m pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cpu
& $trainPython -m pip install -r yolo-camera/training/requirements.txt
& $trainPython yolo-camera/training/setup_upstream.py
最後の setup_upstream.py は、今回使うMIT版YOLOv9と、追加学習の土台になる学習済みデータを取得します。例はGPUを使わずCPUで学習する設定です。
画像と正解を検査して、学習を実行する
次のコマンドで、画像とラベルの対応を検査し、学習用のデータへ変換します。
上から順に、作業フォルダへ移動し、Pythonの場所の設定と、データ変換コマンドです。
Set-Location '「作業ルートにするファイルパス」'
$trainPython = (Resolve-Path 'yolo-camera/training/.venv/Scripts/python.exe').Path
& $trainPython 'yolo-camera/training/prepare_dataset.py' 'yolo-camera/ミカン学習画像/yolo-cameratrainingdatalabeled-manual/mikan_test_yolo11' 'yolo-camera/training/data/prepared-manual-7'
処理が終わると、prepared-manual に学習用データと枚数などの集計ができます。
続いて、学習を開始します。
上から順に、ベースの設定ファイルのコピー、CPUの仕様設定、YOLOフォルダへ移動、学習処理の実行 です。学習処理は少し時間がかかります。
Copy-Item 'yolo-camera/training/data/prepared-manual-7/mikan.yaml' 'yolo-camera/training/upstream/yolo/config/dataset/article-manual-7.yaml'
$env:OMP_NUM_THREADS = '4'
$env:MKL_NUM_THREADS = '4'
Set-Location 'yolo-camera/training/upstream'
& $trainPython yolo/lazy.py task=train model=v9-t dataset=article-manual-7 accelerator=cpu device=1 cpu_num=0 image_size=[320,320] task.epoch=1 task.data.batch_size=2 task.data.equivalent_batch_size=2 task.validation.data.batch_size=2 task.ema.enable=false use_wandb=false use_tensorboard=false +quiet=true name=mikan-manual-7-01
ブラウザー用のモデルファイルを作る
/training/upstream/runs/train/の下の方に、epoch=0-step=1.ckpt のような学習ファイルが作られているはずなので、これをブラウザーで使えるONNX形式へ変換します。
上から順に、作業パスの設定、Pythonと学習結果の場所の指定、変換処理です。
Set-Location '「作業ルートにするファイルパス」'
$trainPython = (Resolve-Path 'yolo-camera/training/.venv/Scripts/python.exe').Path
$run = 'yolo-camera/training/upstream/runs/train/mikan-manual-7-01'
& $trainPython yolo-camera/training/export_onnx.py "$run/checkpoints/epoch=0-step=1.ckpt" --official-config "$run/.hydra/config.yaml" --output yolo-camera/models/mikan-manual-7
これで yolo-camera/models/mikan-manual/ に、以下のファイルが作成されます。
| ファイル | 用途 |
|---|---|
mikan.onnx |
ブラウザーで計算する学習済みモデル |
model.json |
モデルの場所、入力サイズ、検出する名前などの設定 |
YOLOv9-LICENSE.txt |
モデルのライセンス文 |
export-report.json |
変換前後の比較などの確認記録 |
評価用画像で確認し、デモへ読み込む
最後に、学習とモデル選びに使わなかった test の画像で検出します。
& $trainPython yolo-camera/training/evaluate.py `
yolo-camera/models/mikan-manual/model.json `
yolo-camera/training/data/prepared-manual `
--output yolo-camera/training/runs/mikan-manual-320/test --confidence 0.4
以上が、画像を集め、人が正解を登録して、モデルファイルを作るまでの手順です。
この手順で作成したモデルで画像認識するのが、画面右上のミカン検出モデルです。
汎用モデルから切り替えて、ミカン検出モデルをアクティブにすると、ミカンしか検出しなくなっているのがわかるかと思います。学習枚数が少ないので精度はお察しください。
まとめ
今回は「JavaScriptによる画像認識と、ミカン専用モデルを作りブラウザーでミカンを検出する」流れまでを確認しました。
次回は今回作ったミカン検出モデルで、デジタルツインっぽく分別機を作成してみます。



