前回の第4回では、画像認識をFastAPIのAPIにしました。ただ、curl のようなコマンドは、現場の担当者には使えません。今回は、ブラウザで写真を選ぶだけで結果が見られる画面を作ります。
画面は、HTMLファイル1枚だけで作ります。専用のフレームワークは使いません。「画像認識の機能」と「画面」を分けておくと、画面だけを後から作り替えやすくなります。
この回で作るもの
- 写真を選び、しきい値をスライダーで変えて「認識する」を押す画面
- 認識結果の枠とラベルを、ブラウザ上で写真に重ねて表示する機能
- 種類ごとの個数を表にして表示する機能
- APIサーバーが、この画面を配る設定(
/にアクセスすると画面が開く)
仕組み:画面は「APIを呼び出して、結果を描く」だけ
[ブラウザ] 写真を選ぶ → /detect に送る
[API] 認識して、JSON(種類・信頼度・位置)を返す
[ブラウザ] 受け取ったJSONをもとに、写真の上に枠を描き、個数を表に出す
第2回では、サーバー側(Python)で枠を描いた画像を作りました。今回は、ブラウザ側(JavaScript)で枠を描きます。サーバーは数字(JSON)を返すだけなので、画像を作り直す負荷がなく、写真の元データもそのまま残ります。
実装
APIサーバーから画面を配る
# app/api.py(追加部分)
STATIC_DIR = Path(__file__).parent / "static"
...
@app.get("/", include_in_schema=False)
def index() -> FileResponse:
return FileResponse(STATIC_DIR / "index.html")
/ にアクセスすると app/static/index.html が返ります。画面とAPIが同じサーバーにあるため、通信先の設定(CORSなど)が不要です。
画面 app/static/index.html
写真を送って結果を受け取る部分です。
// app/static/index.html(抜粋)
$("run").addEventListener("click", async () => {
const file = $("file").files[0];
$("message").textContent = "";
if (!file) { $("message").textContent = "先に写真を選んでください。"; return; }
const form = new FormData();
form.append("file", file);
$("run").disabled = true;
try {
const res = await fetch(`/detect?conf=${$("conf").value}`, { method: "POST", body: form });
const body = await res.json();
if (!res.ok) { throw new Error(typeof body.detail === "string" ? body.detail : "入力を確認してください。"); }
await draw(file, body.detections);
summarize(body.detections);
} catch (err) {
$("message").textContent = `認識できませんでした: ${err.message}`;
} finally {
$("run").disabled = false;
}
});
- 押している間はボタンを無効にする:連打して何度も送信されるのを防ぎます
- エラーは画面に文章で出す:写真を選んでいないときや、APIがエラーを返したときに、次にすることが分かる文言を表示します
写真の上に枠を描く部分です。
// app/static/index.html(抜粋)
async function draw(file, detections) {
const bitmap = await createImageBitmap(file);
const canvas = $("canvas");
canvas.width = bitmap.width; canvas.height = bitmap.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(bitmap, 0, 0);
for (const d of detections) {
const [l, t, r, b] = d.box;
ctx.strokeStyle = ctx.fillStyle = colorFor(d.label);
ctx.strokeRect(l, t, r - l, b - t);
...
}
}
canvas(ブラウザ上のお絵かきの領域)に、選んだ写真をそのまま描き、その上に枠とラベルを重ねます。結果の表を作るときは、textContent で文字を入れています。innerHTML を使わないのは、APIの結果に予期しない文字が混ざっても、それがHTMLとして実行されないようにするためです。
動かして確かめる
uvicorn app.api:app --port 8000
# ブラウザで http://127.0.0.1:8000/ を開き、samples/fruits.jpg を選んで「認識する」
開発環境(Linux上のChromium)で操作した画面が次のとおりです。ブラウザやOSにより、見た目は異なります。

- 写真の上に、第2回と同じ枠とラベルが表示されました
- 下の表に「orange 4」と個数が出ています
- 写真を選ばずに押すと「先に写真を選んでください。」と表示されました
- 前回に続き、レモンやキウイを「orange」と答えている点は変わりません。画面にすると、この問題が現場の人にも一目で伝わります
テスト
pytest -q # 21件成功
ruff check . # 問題なし
画面が配られることは、自動テストで確認しています。ボタンの操作や描画は、この回では自動テストではなく、実際のブラウザ(Chromium)を操作して確認しました。
つまずきやすい点
- 大きな写真が横にはみ出す:
canvasの最大幅を画面幅に合わせています(max-width: 100%) - スマホで撮った写真の向き:写真の向き情報(EXIF)の扱いで、横向き・縦向きが変わって見える場合があります。実際の端末で確認が必要です
- 認証がない:前回と同じく、この画面には認証がありません。社内ネットワークの外に公開する前に、認証を付けます
- 画面の作り込みは別:この画面は最小限です。ログイン、履歴、権限管理などは、要件が増えると費用が大きく変わります
発注者向けメモ:画面の範囲を先に決める
画像認識のシステムでは、AIの部分に注目が集まりがちですが、現場で使われるかどうかは画面の使いやすさで決まることが多くあります。一方で、画面の作り込みは費用が大きく変わる部分でもあります。
発注者がやること チェックリスト
- ☐ 誰が・どの端末(PC・スマホ・タブレット)で使うかを決めた
- ☐ 写真をどう入れるか(ファイル選択・カメラ撮影・フォルダ監視)を決めた
- ☐ 結果画面に必要な情報(枠・個数・履歴・修正機能)を、優先順位をつけて書き出した
- ☐ ログインや権限(誰が何を見られるか)が必要かを決めた
開発会社への質問例
- 「画面は、どの端末・ブラウザまで動作確認の対象ですか」
- 「最初のリリースに含める画面と、後から追加する画面は、どう分けますか」
- 「ログインや、利用者ごとの権限は、見積もりに含まれていますか」
- 「画面の変更(項目追加など)は、どの範囲まで保守に含まれますか」
まとめと次回予告
第5回では、次のことを行いました。
- ブラウザで写真を選び、認識結果を写真の上に表示する画面を作った
- 種類ごとの個数を、表で見られるようにした
- APIサーバーが画面も配るようにした
- 画面の操作を、実際のブラウザで確認した
次回の第6回では、ここまで「見た目で」確認してきた精度を、数字で測る方法(正解ラベルと、適合率・再現率)に進みます。
この連載の記事一覧
この記事は連載「YOLOで作る画像認識ツール」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【YOLOで作る画像認識ツール 第0回】全体像と環境づくり:YOLOで最初の1枚を認識する
- 【YOLOで作る画像認識ツール 第1回】信頼度のしきい値とJSON出力:認識結果を「使える形」にする
- 【YOLOで作る画像認識ツール 第2回】認識結果を画像に描いて確認する:枠とラベルで「間違い」を見つけやすくする
- 【YOLOで作る画像認識ツール 第3回】種類ごとに何個あるか数えて、CSVに集計する
- 【YOLOで作る画像認識ツール 第4回】FastAPIで画像認識を「API」にして、ほかのシステムから使えるようにする
- 【YOLOで作る画像認識ツール 第5回】ブラウザから写真をアップロードして、認識結果をその場で見る(この記事)
- 【YOLOで作る画像認識ツール 第6回】精度を数字で測る:正解データと、適合率・再現率
- 【YOLOで作る画像認識ツール 第7回】学習済みモデルにない対象を覚えさせる:追加学習の手順と、結果の正しい読み方
- 【YOLOで作る画像認識ツール 第8回】自信の低い結果だけ人が確認する:AIと人の役割分担を設計する
- 【YOLOで作る画像認識ツール 第9回】速く・軽くする:ONNXに変換してCPUで動かし、変換前後を比べる
- 【YOLOで作る画像認識ツール 第10回】Dockerで動かす:設定・ログ・ヘルスチェックを運用に近い形にする
- 【YOLOで作る画像認識ツール 第11回(最終回)】運用の注意点とライセンス(AGPL-3.0):精度の低下に気づく仕組みと、連載のまとめ

