Ultralytics YOLO27: ウェイトリストに登録する。
Get Started
高性能YOLOデータセットの構築·収集·レッスン 3/10
レッスン初級

高品質で代表性のあるデータを収集する

実際の導入環境から画像を取得し、すべてのファイル名に撮影メタデータを含め、仕様書に対するカバー率を追跡し、データセットを過剰に膨らませないよう動画をサンプリングします。

データセットの失敗の大半は、収集段階で起きます。あるチームが1週間に1台のカメラから鮮明な画像を集め、検証では良好な結果が出ても、2つ目の拠点ではモデルの性能が大きく低下します。収集の質が性能の上限を決めます。データセットに含まれないシーンを、ラベルの品質、データ拡張、ハイパーパラメーターでモデルに学習させることはできません。

成果

ファイル名にカメラ、日付、フレームの情報を含むデータセットを作成し、仕様書の割当数に対するカバー率を報告し、動画を特徴の異なるフレームに絞ってサンプリングします。

短期集中コース
すでに使い方をご存じの方は、こちらをご覧ください。
  1. モデルを実際に動作させる本番カメラで撮影します。

  2. 後の工程でグループ化や監査ができるよう、すべてのファイルに camera_day_frame という名前を付けます。

  3. 仕様書に照らしてカメラ、日付、シナリオごとの画像数を数え、不足分を収集します。

  4. シーンに変化があったフレームだけが残るよう、動画をサンプリングします。

  5. 公開画像、演出して撮影した画像、合成画像は、明示された不足分の補完にのみ使用します。

実践的

量よりもばらつき#

同じ導入先を想定した2つのデータセットを比較します。

データセット検証結果本番環境での結果
ほぼ同じフレームが10,000枚、カメラ1台、正午のみ高い:検証フレームが学習フレームに似ている別のカメラや夕暮れ時には性能が低下する
仕様書の12シナリオに分散した1,500フレームやや控えめ本番環境がデータに似ているため、性能を維持できる

最初のデータセットの検証スコアが高いのは、検証で評価される条件が、データセットがカバーする1つの条件だけだからです。本番環境では、モデルの性能は遭遇するシナリオのうち、最もカバー率が低いものによって決まります。まず仕様書のカバー率を高めることを優先し、データ量の拡大はその次に行います。

適切な順序でデータソースを選ぶ#

データセットの大半は、実際の本番環境から取得してください。データの収集とアノテーションのガイドでは、各データソースについて詳しく解説しています。

データソース有効な場合問題となる場合
稼働中の本番カメラ導入環境に最も近い時間がかかる。データ利用契約が必要な場合がある
本番カメラのアーカイブ映像短時間で多様なデータを大量に取得できる古い映像は現在の環境と一致しない場合がある
公開データセット(COCO、Open Images、VOC)一般的なクラスの初期データを確保できる自分のシーンとのドメインシフトがある
演出して撮影した画像特定のエッジケースを必要に応じて取得できるきれいすぎて、過度に依存しやすい
合成画像または生成画像発生を待てないまれな危険や故障を補えるモデルが生成器のスタイルを学習する可能性がある
Webスクレイピング低コストで多様性を確保できる品質にばらつきがあり、ライセンス上のリスクがある

演出画像、公開画像、合成画像は、仕様書で特定した不足分の補完に使用し、効果を測定できるようデータセットに占める割合を追跡します。Ultralytics Platformの類似画像を生成機能では、既存画像のバリエーションを生成してデータセットに追加できます。生成画像にはラベルが付いておらず、アノテーションをすぐに開始できます。

メタデータをファイル名に含める#

すべての画像には、少なくともカメラID、撮影日、フレーム番号またはクリップ番号が必要です。これらがないと、後からグループ単位で分割したり、回帰の原因を特定したり、カバー率の低いシナリオのデータを追加収集したりできません。最も長く使える保存先はファイル名です。cam2_2026-05-01_000124.jpg は、あらゆるツール、エクスポート、アップロード後も保持されます。Ultralytics Platformのデータセット検索では、ファイル名とカスタムメタデータを検索できるため、アップロード後もこれらのキーを検索できます。

このスクリプトでは、以降のコースで使用する練習用データセットを作成します。COCO128のラベル付き画像をコピーし、4台のカメラで4日間にわたって撮影したように名前を変更します。扱いやすいカメラの映像が過剰に集まるのと同じように、cam1 の映像も多めに含めます。

import shutil
from pathlib import Path

from ultralytics.data.utils import check_det_dataset, img2label_paths

data = check_det_dataset("coco128.yaml")  # 128 COCO images stand in for your camera captures
out = Path("datasets/practice")
(out / "images").mkdir(parents=True, exist_ok=True)
(out / "labels").mkdir(parents=True, exist_ok=True)

for i, image in enumerate(sorted(Path(data["train"]).glob("*.jpg"))):
    camera = ("cam1", "cam1", "cam2", "cam3", "cam4")[i % 5]  # cam1 over-collected, like a real site
    name = f"{camera}_2026-05-0{i // 32 + 1}_{i:06d}"  # camera, capture day, frame number
    shutil.copy(image, out / "images" / f"{name}.jpg")
    label = Path(img2label_paths([str(image)])[0])
    if label.exists():
        shutil.copy(label, out / "labels" / f"{name}.txt")

print(len(list((out / "images").glob("*.jpg"))), "images written to", out)
128 images written to datasets/practice

Ultralytics Platformの新規データセットダイアログ:YOLO、COCO、またはNDJSON形式の画像、動画、データセットをドラッグし、データセット名、URL、説明、タスクの種類、ライセンス、顔のぼかし、公開範囲を設定します

Ultralytics Platformにアップロードしても、これらのファイル名は保持されます。アップロードダイアログでは、動画やデータセット全体も受け付けており、設定にはプライバシー保護のための顔のぼかしも含まれます。

仕様書に対するカバー率を追跡する#

ファイル名にメタデータが含まれていれば、カバー率は数えるだけで把握できます。仕様書をもとに割当数を設定すると、スクリプトが次に収集すべき内容を示します。

from collections import Counter
from pathlib import Path

QUOTA = {"cam1": 30, "cam2": 30, "cam3": 30, "cam4": 30}  # images per camera, from your spec

images = sorted(Path("datasets/practice/images").glob("*.jpg"))
cameras = Counter(p.stem.split("_")[0] for p in images)
days = Counter(p.stem.split("_")[1] for p in images)

for camera, quota in QUOTA.items():
    gap = quota - cameras[camera]
    print(f"{camera}: {cameras[camera]:3d} / {quota}  {'ok' if gap <= 0 else f'collect {gap} more'}")
print("days:", dict(sorted(days.items())))
cam1:  52 / 30  ok
cam2:  26 / 30  collect 4 more
cam3:  25 / 30  collect 5 more
cam4:  25 / 30  collect 5 more
days: {'2026-05-01': 32, '2026-05-02': 32, '2026-05-03': 32, '2026-05-04': 32}

判断基準は、割当数を超えたデータソースからの収集を止め、不足分に労力を充てることです。 ここでは、cam1 だけですでに画像の40%を占めています。さらに1週間分の映像を追加してもデータ量が増えるだけで、新たなカバー範囲は増えません。

データセットを過剰に膨らませずに動画をサンプリングする#

動画は最も低コストなデータソースである一方、最も誤用しやすいソースでもあります。毎秒30フレームでは、1分間でほぼ同じフレームが1,800枚生成され、同じ瞬間に2回ラベルを付けると費用がかさみ、指標も水増しされます。最後に保持したフレームからシーンが変化した場合にのみ、フレームを残します。

from pathlib import Path

import cv2

from ultralytics.utils.downloads import safe_download

video = "solutions_ci_demo.mp4"  # a short public demo clip; swap in your own footage
safe_download(f"https://github.com/ultralytics/assets/releases/download/v0.0.0/{video}")
Path("frames").mkdir(exist_ok=True)

cap = cv2.VideoCapture(video)
fps = cap.get(cv2.CAP_PROP_FPS)
read = kept = 0
last = None
while True:
    ok, frame = cap.read()
    if not ok:
        break
    thumb = cv2.cvtColor(cv2.resize(frame, (64, 64)), cv2.COLOR_BGR2GRAY).astype(float)
    if last is None or abs(thumb - last).mean() > 10:  # keep a frame only when the scene has changed
        cv2.imwrite(f"frames/clip01_{read:06d}.jpg", frame)  # the clip ID stays in the filename
        kept, last = kept + 1, thumb
    read += 1

print(f"{read} frames at {fps:.0f} FPS -> {kept} kept")
62 frames at 30 FPS -> 6 kept

この2秒のクリップには62フレームありますが、視覚的に異なる場面は6つだけです。スクリプトは小さなグレースケールのサムネイルを比較するため、長時間の映像でも高速に処理できます。しきい値 10 を上げると、固定カメラから保持するフレームが少なくなります。動きの速いシーンではしきい値を下げてください。また、クリップ間で予算を分散します。1つのクリップから1,000フレームを取得するより、50本のクリップからそれぞれ20フレームを取得する方が効果的です。

1つのクリップのフレームは、複数のサンプルではなく1つのサンプル

1秒間隔の2つのフレームは、照明や背景を共有し、通常は同じ物体が写っています。両方が学習用と検証用に分かれると、モデルが実質的に見たことのある画像で検証を行うことになります。データセットを正しく分割するでクリップを同じ分割にまとめられるよう、すべてのファイル名にクリップIDを含めます。

データ収集でよくある落とし穴を避ける#

落とし穴症状対策
1週間だけの収集検証mAPは高くても、別の季節やシフトでは見逃しが発生する収集を数週間または数か月に分散させる
カメラが1台だけ2台目のカメラでは機能しない導入するすべての種類のカメラで撮影する
演出画像だけのデータ実環境の雑然とした状況に対応できない演出画像は少数にとどめ、割合を記録する
重複フレーム指標が水増しされ、ラベリングも無駄になるシーンの変化に基づいてサンプリングする。Ultralytics Platformではアップロード時に完全一致するコピーも除外されます
オペレーターまたはシフトが1つだけ想定外の外観やクラス不均衡が生じるシフト、拠点、担当者をまたいでサンプリングする
まれなケースの不足最も重要な安全上の事象を見逃す対象を絞った撮影、演出、または合成データによる補完

バイアスはモデリングではなく、データ収集の問題です。データに一度も含まれていないグループを、ハイパーパラメーターで補うことはできません。データ収集ガイドでは、4つの対策として、多様なソース、バランスの取れた代表性、カバレッジの定期的な再監査、過小に代表されるグループのオーバーサンプリングなどの緩和策を紹介しています。

画像に顔、ナンバープレート、その他の個人データが含まれる場合は、収集を始める前に同意を得て、保持ポリシーを定める必要があります。データプライバシーの用語集項目が出発点として役立ちます。また、プライバシー、リージョン、コンプライアンスでは、dataRegionを使ってデータを適切な管轄区域内に保管する方法を説明しています。

試してみる

make_practice_set.pyを実行し、coverage.pyの割り当て数を独自の仕様に合わせて編集するか、練習用の割り当て数をそのまま使って実行します。実際のプロジェクトでは、最近の画像50枚の名前をcamera_day_frameのパターンに変更し、同じカバレッジ集計を実行します。成果物はカバレッジレポートと収集計画です。計画には、割り当て数を超過しているすべてのソースと、不足量を含むすべてのギャップを記載します。

完了条件
以下のすべてを満たすと、レッスンは完了です。
  • すべての画像ファイル名に、カメラ(またはソース)、撮影日、フレーム番号またはクリップ番号を含めます。

  • カバレッジレポートでは、カメラ、日、シナリオごとの画像数を仕様の割り当て数と比較します。

  • 動画はシーンの変化に基づいてサンプリングし、採用したフレームにはクリップIDを保持します。

解答を表示

練習用データセットでは、カバレッジレポートから具体的な計画を立てられます。

データソース件数 / 割り当て数対応
cam152 / 30収集を停止します。すでに全画像の40%を占めています
cam226 / 30あと4枚収集する
cam325 / 30あと5枚収集する
cam425 / 30あと5枚収集する

各カメラは4日すべてをカバーしているため、不足は日ごとではなくカメラごとに生じています。同じ日の午後の画像を増やすのではなく、新しい条件を追加できるよう、新たに収集する14枚を少なくとも2つの新しい日に分散させます。不足分を用意できないカメラ(夜しか撮影できないカメラなど)については、不足している条件の仕様行を追加し、それを演出画像または生成画像のどちらで補うかを記録します。そうすることで、後からその影響を測定できます。

次のステップ

画像の収集が終わったら、次のレッスンでは画像に一貫性のあるラベルを初めて付与する方法を学びます。