고품질의 대표성 있는 데이터 수집
실제 배포 환경에서 이미지를 확보하고, 모든 파일명에 촬영 메타데이터를 포함하며, 명세에 따른 커버리지를 추적하고, 데이터셋이 불필요한 프레임으로 넘치지 않도록 동영상을 샘플링하세요.
대부분의 데이터셋 실패는 수집 단계에서 비롯됩니다. 한 팀이 한 대의 카메라로 한 주 동안 깨끗한 예시를 촬영하고, 검증 결과가 훌륭하게 나오지만 두 번째 현장에서는 모델의 성능이 급격히 떨어집니다. 데이터 수집이 성능의 상한선을 결정합니다. 라벨 품질, 데이터 증강, 하이퍼파라미터로도 데이터셋에 포함되지 않은 장면을 모델에 학습시킬 수는 없습니다.
실습형
수량보다 다양성이 중요합니다#
동일한 배포 환경을 목표로 하는 두 데이터셋을 비교해 보겠습니다.
| 데이터셋 | 검증 결과 | 프로덕션 환경 |
|---|---|---|
| 정오에 한 대의 카메라로 촬영한 거의 동일한 프레임 10,000장 | 높음: 검증 프레임이 학습 프레임과 비슷함 | 다른 카메라와 해 질 녘 환경에서는 성능 저하 |
| 명세의 시나리오 12개에 분산된 프레임 1,500장 | 상대적으로 낮음 | 프로덕션 환경이 데이터와 비슷하므로 성능이 유지됨 |
첫 번째 데이터셋의 검증 점수가 높은 것은 검증 과정에서 해당 데이터셋이 포함하는 한 가지 조건만 측정하기 때문입니다. 프로덕션 환경에서 모델 성능은 모델이 접하는 시나리오 중 커버리지가 가장 낮은 시나리오에 좌우됩니다. 먼저 명세의 커버리지를 높이는 데 집중하고, 그다음에 데이터 수량을 늘리세요.
올바른 순서로 데이터 소스 선택하기#
데이터셋의 대부분은 실제 프로덕션 환경에서 확보해야 합니다. 데이터 수집 및 어노테이션 가이드에서 각 데이터 소스를 자세히 설명합니다.
| 소스 | 유용한 경우 | 문제가 되는 경우 |
|---|---|---|
| 프로덕션 카메라, 실시간 | 배포 환경과 가장 유사함 | 느리며 데이터 사용 계약이 필요할 수 있음 |
| 프로덕션 카메라, 아카이브 영상 | 짧은 시간에 다양한 데이터를 많이 확보할 수 있음 | 오래된 영상이 현재 설정과 맞지 않을 수 있음 |
| 공개 데이터셋 (COCO, Open Images, VOC) | 일반적인 클래스로 초기 데이터 구축 | 사용자 환경과의 도메인 차이 |
| 연출 촬영 | 필요에 따라 특정 엣지 케이스 확보 | 너무 깔끔해 과도하게 의존하기 쉬움 |
| 합성 또는 생성 이미지 | 기다려서는 확보할 수 없는 희귀한 위험 요소와 결함 | 모델이 생성기의 스타일을 학습할 수 있음 |
| 웹 스크래핑 | 저렴하게 다양한 데이터 확보 | 품질이 일관되지 않고 라이선스 위험이 있음 |
연출 이미지, 공개 이미지, 합성 이미지는 명세에 명시된 부족분을 보완할 때만 사용하고, 도움이 되는지 측정할 수 있도록 데이터셋 내 비율을 추적하세요. Ultralytics Platform의 유사 이미지 생성 기능은 기존 이미지의 생성 변형을 데이터셋에 추가하며, 해당 이미지는 라벨링할 수 있도록 라벨 없이 제공됩니다.
파일명에 메타데이터 포함하기#
모든 이미지에는 최소한 카메라 ID, 촬영 날짜, 프레임 또는 클립 번호가 필요합니다. 이 정보가 없으면 나중에 그룹별로 분할하거나, 회귀의 원인을 추적하거나, 대표성이 부족한 시나리오의 데이터를 추가로 수집할 수 없습니다. 가장 안정적으로 메타데이터를 보존하는 방법은 파일명에 포함하는 것입니다. cam2_2026-05-01_000124.jpg은 모든 도구, 내보내기, 업로드 과정에서 유지됩니다. Ultralytics Platform 데이터셋 검색은 파일명과 사용자 지정 메타데이터를 대상으로 하므로, 업로드 후에도 이러한 키를 검색할 수 있습니다.
이 스크립트는 나머지 과정에서 사용할 연습용 데이터셋을 만듭니다. COCO128의 라벨링된 이미지를 복사한 뒤, 마치 네 대의 카메라가 나흘 동안 촬영한 것처럼 파일명을 변경합니다. 편리한 카메라에서 흔히 그렇듯 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%를 차지합니다. 해당 소스의 영상을 한 주 더 수집하면 데이터 수량은 늘지만 커버리지는 개선되지 않습니다.
데이터셋이 넘치지 않도록 동영상 샘플링하기#
동영상은 가장 저렴한 데이터 소스이자 잘못 활용하기 가장 쉬운 소스입니다. 초당 30프레임으로 1분 동안 거의 동일한 프레임 1,800장이 생성되며, 같은 순간을 두 번 라벨링하면 비용이 들고 지표도 부풀려집니다. 마지막으로 보존한 프레임 이후 장면이 달라졌을 때만 프레임을 남기세요.
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 kept2초짜리 클립에는 프레임이 62장 있지만, 시각적으로 구별되는 순간은 6개뿐입니다. 스크립트는 작은 회색조 썸네일을 비교하므로 몇 시간 분량의 영상도 빠르게 처리할 수 있습니다. 고정된 카메라에서 프레임을 더 적게 남기려면 10의 임계값을 높이고, 움직임이 빠른 장면에서는 낮추세요. 클립별로도 예산을 분산하세요. 클립 50개에서 각각 프레임 20장을 확보하는 편이 한 클립에서 프레임 1,000장을 확보하는 것보다 낫습니다.
1초 간격의 두 프레임은 조명과 배경을 공유하고, 대개 같은 객체도 포함합니다. 두 프레임이 학습 세트와 검증 세트에 나뉘어 들어가면, 모델이 사실상 이미 본 이미지로 검증 점수를 받게 됩니다. Split the Dataset Correctly에서 클립을 하나로 묶을 수 있도록 모든 파일 이름에 클립 ID를 넣으세요.
데이터 수집 시 흔히 발생하는 함정을 피하세요#
| 문제점 | 증상 | 해결 방법 |
|---|---|---|
| 한 주에만 수집 | 검증 mAP는 높지만 다른 계절이나 교대 시간대에서는 누락이 발생합니다 | 수집 시기를 여러 주 또는 여러 달에 분산하세요 |
| 카메라 한 대만 사용 | 두 번째 카메라에서는 제대로 작동하지 않습니다 | 배포할 모든 카메라 유형에서 데이터를 수집하세요 |
| 연출된 데이터만 사용 | 실제 현장의 복잡한 환경에서는 제대로 작동하지 않습니다 | 연출된 이미지는 소수로 유지하고 수량을 추적하세요 |
| 중복 프레임 | 지표가 부풀려지고 라벨링 작업이 낭비됩니다 | 장면 변화에 따라 샘플링하세요. Ultralytics Platform은 업로드 시 완전히 동일한 사본도 건너뜁니다 |
| 작업자 또는 교대 시간대가 하나뿐임 | 예상하지 못한 외관과 클래스 불균형이 발생합니다 | 교대 시간대, 현장, 작업자를 다양하게 포함해 샘플링하세요 |
| 희귀 사례 누락 | 가장 중요한 안전 사고를 놓칩니다 | 집중 수집, 연출 또는 합성 데이터 추가 |
편향 및 동의#
편향은 모델링 문제가 아니라 데이터 수집 문제입니다. 데이터에 애초에 포함되지 않은 그룹은 어떤 하이퍼파라미터로도 해결할 수 없습니다. 데이터 수집 가이드에서는 네 가지 방법을 설명합니다. 다양한 출처를 사용하고, 균형 있게 대표성을 확보하며, 포함 범위를 정기적으로 재감사하고, 대표성이 낮은 그룹을 오버샘플링하는 등의 완화 방법을 적용합니다.
이미지에 얼굴, 번호판 또는 기타 개인정보가 포함되어 있다면 수집을 시작하기 전에 동의와 보존 정책을 마련해야 합니다. 데이터 프라이버시 용어집 항목을 좋은 출발점으로 삼을 수 있으며, 개인정보 보호, 리전 및 규정 준수에서는 dataRegion를 사용해 데이터를 적절한 관할권 내에 보관하는 방법을 다룹니다.
정답 보기
연습용 세트의 경우 범위 보고서에서 다음과 같은 직접적인 계획을 얻을 수 있습니다.
| 소스 | 개수 / 할당량 | 조치 |
|---|---|---|
cam1 | 52 / 30 | 수집을 중단하세요. 이미 전체 이미지의 40%를 차지합니다 |
cam2 | 26 / 30 | 4개 더 수집 |
cam3 | 25 / 30 | 5개 더 수집 |
cam4 | 25 / 30 | 5개 더 수집 |
각 카메라에서 4일 모두를 촬영하므로 격차는 날짜별이 아니라 카메라별로 발생합니다. 새 이미지 14개를 최소 이틀 이상의 새로운 날짜에 분산해 같은 오후의 장면을 더 추가하는 대신 새로운 조건을 반영하도록 하세요. 격차만큼 데이터를 제공할 수 없는 카메라(예: 야간 장면만 촬영하는 카메라)의 경우 누락된 조건을 사양에 행으로 추가하고, 연출 이미지 또는 생성 이미지로 보완했는지 기록해 나중에 그 영향을 측정할 수 있도록 하세요.