「写真に何が写っているかをAIに判定させたい」という相談は、この数年で一気に増えました。ところが、画像認識のシステムは何ができて、何が難しく、どこに費用がかかるのかが見えにくく、見積もりの根拠も分かりにくい分野です。
この連載では、画像認識(写真から「何が・どこに・いくつ」写っているかを見つける技術。物体検出と呼びます)の最小構成を、PythonとYOLO(ヨーロー。物体検出でよく使われるAIモデル)で実際に作ります。題材は、果物の写真から種類と個数を数える検品ツールです。
第0回では、全体像を説明したうえで、環境を作り、サンプルの写真1枚を認識するところまでを動かします。
この連載で作るもの:果物の検品・カウントツール
題材に果物を選んだ理由は3つあります。
- テストしやすい:果物の写真は誰でも手元で用意でき、「何が何個写っているか」の正解を人がすぐに確認できます
- 学習済みモデルがそのまま使える範囲がある:公開されている学習済みモデルは、りんご・バナナ・オレンジを最初から見分けられます
- 「学習済みモデルで足りない部分」が見える:レモンやキウイなど、学習済みモデルが知らない対象は、うまく識別できません。この失敗を見ることで、精度の測り方や追加学習が必要になる理由が分かります
連載は全12回の予定です(各回のタイトルは変更になることがあります)。
| 回 | 内容 |
|---|---|
| 0 | 全体像と環境づくり(この回) |
| 1 | 信頼度のしきい値とJSON出力 |
| 2 | 認識結果を画像に描いて確認する |
| 3 | 種類ごとの個数を数えてCSVに集計する |
| 4 | FastAPIで画像認識APIにする |
| 5 | ブラウザから画像をアップロードして結果を見る |
| 6 | 精度を測る(正解ラベルと適合率・再現率) |
| 7 | 学習済みモデルにない対象を覚えさせる(追加学習) |
| 8 | 自信の低い結果だけ人が確認するフロー |
| 9 | ONNXに変換してCPUで速く動かす |
| 10 | Dockerで動かす |
| 11 | 運用の注意点とライセンス(AGPL-3.0)・限界のまとめ |
外部サービスのAPIキーは不要です。AIモデルは自分のパソコンの中で動きます。
使う技術と、その選び方
| 役割 | 採用したもの | 補足 |
|---|---|---|
| 言語 | Python 3.11以上 | 画像認識の情報が最も多い言語 |
| 物体検出 | Ultralytics(YOLO26) | 推論・評価・追加学習・ONNX変換までが1つで済む |
| 深層学習の基盤 | PyTorch(CPU版) | GPU(画像処理用の高性能な部品)がなくても動く |
| テスト・検査 | pytest / ruff | 自動テストとコードの書き方の検査 |
バージョンは、本連載を書いた2026年6月時点で公開されている版に固定しました(Ultralyticsは8.4系、PyTorchは2系)。詳しい版は、サンプルコードの requirements.txt にあります。
技術選定は「一番実現性が高いもの」を基準にしました。学習済みモデルが配布されていて、APIキーも不要、パソコンのCPUだけで動く。この3点がそろっているため、まず試作(PoC)から始めたい場合に最も現実的です。クラウドの画像認識サービスという選択肢もあり、第11回で比較します。
発注者が知っておきたい注意:ライセンス
UltralyticsのYOLOは、オープンソースのライセンス「AGPL-3.0」で公開されています。公式のREADMEでは、商用の製品・サービスや社内ツールへの組み込みには「Ultralytics Enterprise License」を案内しています。
📰 出典:Ultralytics 公式リポジトリ README(ライセンスの項)
自社のシステムにどちらのライセンスが必要かは、使い方によって変わります。この連載では一般的な注意点の紹介にとどめ、個別の判断は専門家や提供元に確認してください。この論点は、第11回でもう一度まとめます。
環境を作る
サンプルコードは、リポジトリの blogs/it_hacchu/series/yolo-kensa/code/ にあります。まず、Pythonの仮想環境(プロジェクトごとに部品を分けて入れる仕組み)を作り、必要な部品を入れます。
cd blogs/it_hacchu/series/yolo-kensa/code
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements-dev.txt --extra-index-url https://download.pytorch.org/whl/cpu
requirements.txt には、動作を確認した版を固定して書いています。
# requirements.txt
ultralytics==8.4.58
torch==2.12.0
torchvision==0.27.0
opencv-python==4.13.0.92
numpy==2.4.6
pillow==12.2.0
ここで一つ、つまずきやすい点があります。PyTorchの本体(torch)だけ版を上げると、姉妹部品のtorchvisionと版が合わなくなり、operator torchvision::nms does not exist というエラーで止まります。torchとtorchvisionは、必ず対になる版で固定してください。
学習済みモデルとサンプル画像を取得する
次のスクリプトで、AIモデル(学習済みの重みファイル)とサンプル画像をダウンロードします。初回だけ実行します。
# tools/download_assets.py(抜粋)
ASSETS = {
ROOT / "models" / "yolo26n.pt": "https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo26n.pt",
ROOT / "samples" / "fruits.jpg": "https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/fruits.jpg",
}
python tools/download_assets.py
yolo26n.pt:80種類の物体で学習済みの、最も小さいモデル(約5MB)。「n」はnano(超小型)の頭文字ですfruits.jpg:画像処理ライブラリOpenCVのサンプル画像(Apache-2.0ライセンス)。切ったオレンジ・レモン・ライム・キウイ・バナナ・紫キャベツが写っています
モデルと画像は、リポジトリには含めない設定にしています(.gitignore)。
画像1枚を認識するコードを書く
認識の中心は、app/detector.py の Detector クラスです。「見つかった物体1つ分」を表す Detection と、画像から Detection の一覧を返す Detector の2つで構成します。
# app/detector.py(抜粋)
@dataclass(frozen=True)
class Detection:
"""見つかった物体1つ分。"""
label: str # 何が写っているか(例: "orange")
confidence: float # 信頼度 0〜1
box: tuple[int, int, int, int] # 画像上の四角(左, 上, 右, 下)をピクセルで
class Detector:
def __init__(self, model_path: Path = MODEL_PATH, confidence: float = DEFAULT_CONFIDENCE) -> None:
if not Path(model_path).exists():
raise FileNotFoundError(f"モデルが見つかりません: {model_path}(python tools/download_assets.py を実行してください)")
self._model = YOLO(str(model_path))
self.confidence = confidence
def detect(self, image_path: str | Path) -> list[Detection]:
"""画像ファイルを読み、しきい値以上の検出結果を返す。"""
if not Path(image_path).exists():
raise FileNotFoundError(f"画像が見つかりません: {image_path}")
result = self._model.predict(str(image_path), conf=self.confidence, verbose=False)[0]
detections: list[Detection] = []
for box in result.boxes:
left, top, right, bottom = (round(v) for v in box.xyxy[0].tolist())
detections.append(
Detection(
label=self._model.names[int(box.cls)],
confidence=float(box.conf),
box=(left, top, right, bottom),
)
)
return detections
ポイントは次の3つです。
- 信頼度(confidence):モデルが「これだ」と思う自信の度合いで、0〜1の数字です。しきい値(既定は0.25)より低いものは結果に出しません。この数字の意味と調整は、次の第1回で詳しく扱います
- 位置(box):物体を囲む四角の、左・上・右・下の座標です。単位はピクセル(画像の点の数)です
- ファイルが無いときは分かるエラーにする:モデルや画像が見つからないときに、次に何をすればよいかが分かる文言を出します
設定は app/config.py にまとめ、環境変数で上書きできるようにしています。
# app/config.py(抜粋)
MODEL_PATH = Path(os.environ.get("YOLO_MODEL_PATH", ROOT / "models" / "yolo26n.pt"))
DEFAULT_CONFIDENCE = float(os.environ.get("YOLO_CONFIDENCE", "0.25"))
コマンドラインから動かすための入口が app/main.py です。
# app/main.py(抜粋)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="画像1枚から物体を検出する")
parser.add_argument("image", help="認識する画像ファイル")
parser.add_argument("--conf", type=float, default=DEFAULT_CONFIDENCE, help="信頼度のしきい値(0〜1)")
args = parser.parse_args(argv)
detections = Detector(confidence=args.conf).detect(args.image)
print(f"{len(detections)} 件見つかりました(しきい値 {args.conf})")
for d in detections:
print(f"- {d.label} 信頼度 {d.confidence:.2f} 位置 {d.box}")
return 0
動かして確かめる
サンプル画像を認識してみます。
python -m app.main samples/fruits.jpg
執筆時の開発環境での実行結果は次のとおりです。
4 件見つかりました(しきい値 0.25)
- orange 信頼度 0.87 位置 (317, 247, 512, 477)
- orange 信頼度 0.72 位置 (69, 44, 348, 470)
- orange 信頼度 0.47 位置 (0, 277, 130, 477)
- orange 信頼度 0.40 位置 (321, 121, 512, 305)
学習済みモデルは、画像から4つの orange(オレンジ)を見つけました。処理時間は、開発環境のCPUで1枚あたり0.1秒弱でした(環境により異なります)。
結果をよく見ると「うまくいっていない」
サンプル画像には、切ったオレンジ、レモン、ライム、キウイ、バナナ、紫キャベツが写っています。ところが、AIの答えは「すべてorange」です。
- レモンやライムなど、学習済みモデルが知らない果物も、似た形の「orange」として答えてしまうことがあります
- バナナは、この写真では見つかりませんでした(見つからなかった=見逃し)
これは、AIが壊れているのではなく、学習済みモデルが「知っている80種類」の範囲でしか答えられないためです。この限界をどう扱うかが、画像認識のシステムを発注するときの一番大きな論点になります。第6回(精度の測り方)と第7回(追加学習)で、実際に確かめながら向き合います。
テストも用意しておく
コードには、自動テストも付けています。モデルを読み込まない偽物を使ったテストで、ロジックを速く確かめます。
# tests/test_detector.py(抜粋)
def test_detect_converts_result(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
...
monkeypatch.setattr(detector_module, "YOLO", _FakeYOLO)
result = Detector(model_path=model, confidence=0.4).detect(image)
assert result == [Detection(label="orange", confidence=pytest.approx(0.87), box=(10, 21, 110, 220))]
実際のモデルを使うテスト(@pytest.mark.integration)は、モデルとサンプル画像を取得済みの場合だけ走ります。
pytest -q # 4件成功
ruff check . # 問題なし
つまずきやすい点
- torchとtorchvisionの版がずれる:前述のとおり、対で固定します
- 初回に時間がかかる:PyTorchの導入とモデルの取得に数分かかります。社内の通信制限でGitHubに接続できないと、モデルを取得できません
- 写真に人が写っていないか:実運用では、顔や車のナンバーなど個人が特定できる情報を含む写真の扱いに注意が必要です。この連載のサンプルは果物だけを使います
発注者向けメモ:画像認識の相談で最初に決めること
この回で見えた「学習済みモデルはそのまま動くが、知らないものは間違える」という点は、発注の場面でそのまま論点になります。
発注者がやること チェックリスト
- ☐ 何を判定したいか(種類・個数・良否など)を、写真つきで具体的に書き出した
- ☐ 判定したい対象の写真を、実際の現場で撮影して数十枚以上集めた
- ☐ 見逃し(写っているのに見つからない)と誤検出(違うものを見つける)のどちらがより困るかを決めた
- ☐ AIが間違えたとき、人が確認・修正する運用を前提にすることを社内で合意した
- ☐ 写真に個人情報(顔・車のナンバー等)が写らないか確認した
開発会社への質問例
- 「私たちの対象は、学習済みモデルのままで判定できそうですか。それとも追加の学習が必要ですか」
- 「精度の目標(何%)は、どのように決め、どのテスト画像で確認しますか」
- 「学習や評価に使う写真は、こちらで何枚くらい用意する必要がありますか」
- 「使うAIモデルのライセンスは何ですか。商用で使う場合に追加の費用や条件はありますか」
- 「稼働後に、対象が変わったり精度が落ちたりしたときの再学習は、保守に含まれますか」
まとめと次回予告
第0回では、次のことを行いました。
- 果物の検品・カウントツールを題材に、画像認識の連載の全体像を整理した
- Python環境を作り、学習済みモデルとサンプル画像を取得した
- 画像1枚を認識して、種類・信頼度・位置を表示できた
- 学習済みモデルが知らない対象(レモンなど)を間違える、という限界を確認した
次回の第1回では、信頼度のしきい値を変えると結果がどう変わるかを調べ、結果をJSONの形で出力して「使える形」にします。
この連載の記事一覧
この記事は連載「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):精度の低下に気づく仕組みと、連載のまとめ

