高品質で代表性のあるデータを収集する
実際の導入環境から画像を取得し、すべてのファイル名に撮影メタデータを含め、仕様書に対するカバー率を追跡し、データセットを過剰に膨らませないよう動画をサンプリングします。
データセットの失敗の大半は、収集段階で起きます。あるチームが1週間に1台のカメラから鮮明な画像を集め、検証では良好な結果が出ても、2つ目の拠点ではモデルの性能が大きく低下します。収集の質が性能の上限を決めます。データセットに含まれないシーンを、ラベルの品質、データ拡張、ハイパーパラメーターでモデルに学習させることはできません。
実践的
量よりもばらつき#
同じ導入先を想定した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にアップロードしても、これらのファイル名は保持されます。アップロードダイアログでは、動画やデータセット全体も受け付けており、設定にはプライバシー保護のための顔のぼかしも含まれます。
仕様書に対するカバー率を追跡する#
ファイル名にメタデータが含まれていれば、カバー率は数えるだけで把握できます。仕様書をもとに割当数を設定すると、スクリプトが次に収集すべき内容を示します。
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秒間隔の2つのフレームは、照明や背景を共有し、通常は同じ物体が写っています。両方が学習用と検証用に分かれると、モデルが実質的に見たことのある画像で検証を行うことになります。データセットを正しく分割するでクリップを同じ分割にまとめられるよう、すべてのファイル名にクリップIDを含めます。
データ収集でよくある落とし穴を避ける#
| 落とし穴 | 症状 | 対策 |
|---|---|---|
| 1週間だけの収集 | 検証mAPは高くても、別の季節やシフトでは見逃しが発生する | 収集を数週間または数か月に分散させる |
| カメラが1台だけ | 2台目のカメラでは機能しない | 導入するすべての種類のカメラで撮影する |
| 演出画像だけのデータ | 実環境の雑然とした状況に対応できない | 演出画像は少数にとどめ、割合を記録する |
| 重複フレーム | 指標が水増しされ、ラベリングも無駄になる | シーンの変化に基づいてサンプリングする。Ultralytics Platformではアップロード時に完全一致するコピーも除外されます |
| オペレーターまたはシフトが1つだけ | 想定外の外観やクラス不均衡が生じる | シフト、拠点、担当者をまたいでサンプリングする |
| まれなケースの不足 | 最も重要な安全上の事象を見逃す | 対象を絞った撮影、演出、または合成データによる補完 |
バイアスと同意#
バイアスはモデリングではなく、データ収集の問題です。データに一度も含まれていないグループを、ハイパーパラメーターで補うことはできません。データ収集ガイドでは、4つの対策として、多様なソース、バランスの取れた代表性、カバレッジの定期的な再監査、過小に代表されるグループのオーバーサンプリングなどの緩和策を紹介しています。
画像に顔、ナンバープレート、その他の個人データが含まれる場合は、収集を始める前に同意を得て、保持ポリシーを定める必要があります。データプライバシーの用語集項目が出発点として役立ちます。また、プライバシー、リージョン、コンプライアンスでは、dataRegionを使ってデータを適切な管轄区域内に保管する方法を説明しています。
解答を表示
練習用データセットでは、カバレッジレポートから具体的な計画を立てられます。
| データソース | 件数 / 割り当て数 | 対応 |
|---|---|---|
cam1 | 52 / 30 | 収集を停止します。すでに全画像の40%を占めています |
cam2 | 26 / 30 | あと4枚収集する |
cam3 | 25 / 30 | あと5枚収集する |
cam4 | 25 / 30 | あと5枚収集する |
各カメラは4日すべてをカバーしているため、不足は日ごとではなくカメラごとに生じています。同じ日の午後の画像を増やすのではなく、新しい条件を追加できるよう、新たに収集する14枚を少なくとも2つの新しい日に分散させます。不足分を用意できないカメラ(夜しか撮影できないカメラなど)については、不足している条件の仕様行を追加し、それを演出画像または生成画像のどちらで補うかを記録します。そうすることで、後からその影響を測定できます。