YOLOをブラウザ実行する仕組みを解説
YOLOをONNX形式に変換し、ONNX Runtime Web,WebAssembly/WebGPUを用いてブラウザ上でリアルタイム物体検出を実行する仕組みを解説します。
Table of contents
author: Mami
1. ブラウザだけでYOLO物体検出を動かす
まずは完成イメージをご覧ください。
UnsplashのCthroughが撮影した写真
よくあるYOLOの物体検出結果ですが、以下は ブラウザ内だけでリアルタイム推論 しています。 サーバーは使っていません。画像も外部に送信していません。
- 🚗 車を検出
- 🚲 自転車を検出
- 🐶 犬を検出
- 👤 人を検出
2. なぜブラウザでYOLOを動かすのか?
従来、YOLOのような物体検出モデルはサーバー側で実行されるのが一般的でした。
ブラウザ → サーバー → Python推論 → 結果を返却
この構成は分かりやすいですが、いくつか課題があります。
- ネットワーク遅延が発生する
- サーバーGPUのコストがかかる
- 画像データを外部に送信する必要がある
- スケール時にインフラ負荷が増える
そこで注目されているのが、ブラウザ内で推論を完結させるアプローチです。
この構成にすることで、次のようなメリットがあります。
✅ 低遅延(リアルタイム向き)
ネットワーク往復が不要なため、Webカメラ映像にリアルタイムで検出結果を重ねる用途に最適です。
✅ サーバー不要
推論処理をクライアント側で行うため、バックエンドGPUが不要になります。
✅ プライバシー保護
画像データが端末外に送信されないため、センシティブなデータでも扱いやすくなります。
✅ コスト効率
アクセス数が増えても推論コストは増加しません。
3. なぜ ONNX・ONNX Runtime Web・WebAssembly が必要なのか?
ブラウザはそのままではPyTorchやPythonを実行できません。
そのため、YOLOモデルをブラウザ上で動かすには、 次の3つの役割が必要になります。
- モデル形式を変換する仕組み
- モデルを実行するランタイム
- 高速に数値計算する基盤
それぞれを担っているのが、以下の技術です。
■ ONNX
ONNX(Open Neural Network Exchange)は、機械学習モデルの共通フォーマットです。
PyTorchで学習したYOLOモデルをONNXに変換することで、 ブラウザでも読み込める形式になります。
通常は以下ですが、ONNXはそれらを統一します。
- PyTorch → .pt
- TensorFlow → .pb
ここでは、Ultralytics YOLO(例:YOLOv8)をONNXにエクスポートする方法を紹介します。 例としてYOLOv8n(軽量モデル)を使用します。
from ultralytics import YOLO
# モデルを読み込む
model = YOLO("yolov8n.pt") # 事前学習済みモデル、またはカスタムモデルのパス
# ONNX形式にエクスポート
model.export(format="onnx")
■ ONNX Runtime Web(実行エンジン)
本サンプルの中核となっているのが onnxruntime-web です。
ONNX Runtime Webは、ONNX形式のモデルをブラウザ上で実行するためのランタイムです。
Microsoftが提供しているONNX RuntimeのWeb版にあたります。
YOLOをONNX形式に変換しただけでは、まだブラウザで実行できません。
ブラウザでモデルを動かすには、以下の仕組みが必要です。
- モデルを読み込み
- テンソルを扱い
- 推論を実行し
- GPUやWASMを制御する
それをまとめて提供してくれるのが ONNX Runtime Web です。
■ WebAssembly(WASM)
WebAssembly(WASM)はブラウザ上で高速に動作するバイナリ形式です。
以下特徴があります。
- ネイティブに近い速度
- 数値計算に強い
- JavaScriptより高速
推論では以下う役割分担になります。
- 計算部分 → WASM
- 制御部分 → JavaScript
本サンプルでは、ブラウザの標準高速化技術であるWebAssembly (WASM) をベースに推論しますが、もしお使いのブラウザが最新のWebGPUに対応していれば、さらに高速な(GPUによる)リアルタイム推論が自動的に選択される仕組みになっています。
4. CodeSandbox で試す
CodeSandbox から実際に以下を実行できます。
- モデル読み込み
- 推論
- 各種依存ライブラリ
- UIをブラウザ上で即実行
5. コード解説
ここではサンプルコードのポイントとなる箇所を解説します。
ブラウザでYOLO推論する流れは、大きく分けて以下の5ステップです。
- モデル読み込み
- 入力画像の前処理
- 推論実行
- 後処理(NMSなど)
- Canvas描画
① モデル読み込み
- ONNXモデルを読み込み
- WebGPU → WASMの順で実行環境を選択
import * as ort from "onnxruntime-web";
const session = await ort.InferenceSession.create("/model/yolo.onnx", {
executionProviders: ["webgpu", "wasm"],
});
② 前処理(画像 → Tensor変換)
YOLOは決まったサイズの入力を必要とします。
- リサイズ
- 正規化(0〜1)
- NCHW形式へ変換
function preprocess(video) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 640;
canvas.height = 640;
ctx.drawImage(video, 0, 0, 640, 640);
const imageData = ctx.getImageData(0, 0, 640, 640);
const { data } = imageData;
const floatData = new Float32Array(1 * 3 * 640 * 640);
for (let i = 0; i < 640 * 640; i++) {
floatData[i] = data[i * 4] / 255;
floatData[i + 640 * 640] = data[i * 4 + 1] / 255;
floatData[i + 2 * 640 * 640] = data[i * 4 + 2] / 255;
}
return new ort.Tensor("float32", floatData, [1, 3, 640, 640]);
}
③ 推論実行
const results = await session.run({ images: inputTensor });
内部ではWebGPUまたはWASMで大量の数値演算が行われています。
④ 後処理(NMS)
YOLOの出力には重複検出が含まれるため、 Non-Maximum Suppression(NMS)で除去します。
- 信頼度フィルタリング
- IoU計算
- 重複削除
type Det = {
x1: number;
y1: number;
x2: number;
y2: number;
score: number;
classId: number;
};
function calcIoU(a: Det, b: Det): number {
const ix1 = Math.max(a.x1, b.x1);
const iy1 = Math.max(a.y1, b.y1);
const ix2 = Math.min(a.x2, b.x2);
const iy2 = Math.min(a.y2, b.y2);
const iw = Math.max(0, ix2 - ix1);
const ih = Math.max(0, iy2 - iy1);
const inter = iw * ih;
const areaA = Math.max(0, a.x2 - a.x1) * Math.max(0, a.y2 - a.y1);
const areaB = Math.max(0, b.x2 - b.x1) * Math.max(0, b.y2 - b.y1);
const union = areaA + areaB - inter;
return union > 0 ? inter / union : 0;
}
/**
* NMS(class-wise)
* - スコアの高い検出から採用
* - 同一クラスで IoU が閾値を超えるものを除外
*/
function nmsClassWise(dets: Det[], iouThreshold: number): Det[] {
const sorted = [...dets].sort((p, q) => q.score - p.score);
const keep: Det[] = [];
while (sorted.length) {
const best = sorted.shift()!;
keep.push(best);
for (let i = sorted.length - 1; i >= 0; i--) {
const cand = sorted[i];
// repoと同じ:別クラスは抑制しない
if (cand.classId !== best.classId) continue;
if (calcIoU(best, cand) > iouThreshold) {
sorted.splice(i, 1);
}
}
}
return keep;
}
⑤ 描画
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.strokeRect(x, y, width, height);
Canvas上へバウンディングボックスを描画します。
6. まとめ
- YOLOは高速物体検出モデル
- ONNXはモデルの共通フォーマット
- WebAssemblyはブラウザ高速実行技術
この3つを組み合わせることで、ブラウザ単体でリアルタイム物体検出が可能になります。 GPUサーバーを立てればもっと簡単に実装できますが、限られた環境の中でどうやって実現するかを考えることも、 エンジニアにとってとても大事な視点だと思っています。
そうやって“やりくり”する技術には、個人的にすごく面白さを感じます。
※本記事は、ジーアイクラウド株式会社の見解を述べたものであり、必要な調査・検討は行っているものの必ずしもその正確性や真実性を保証するものではありません。
※リンクを利用する際には、必ず出典がGIC dryaki-blogであることを明記してください。
リンクの利用によりトラブルが発生した場合、リンクを設置した方ご自身の責任で対応してください。
ジーアイクラウド株式会社はユーザーによるリンクの利用につき、如何なる責任を負うものではありません。