변환 없이 COCO YOLO 훈련하는 방법
왜 COCO 을 직접 학습해야 할까요?
주석 에서 COCO JSON 이 형식은 다음 용도로 직접 사용할 수 있습니다. Ultralytics YOLO 변환하지 않고 훈련하기 .txt 먼저 파일을 처리합니다. 이는 서브클래스를 생성하여 수행됩니다. YOLODataset COCO 실시간으로 파싱하고, 사용자 정의 트레이너를 통해 이를 훈련 파이프라인에 연결하는 것.
이 접근 방식은 COCO 유일한 신뢰할 수 있는 정보원으로 유지합니다 — 아니요 convert_coco() 호출, 디렉토리 재구성 없음, 중간 레이블 파일 없음. YOLO26 및 기타 모든 Ultralytics YOLO detect 모델이 지원됩니다. segment 및 포즈 모델은 추가 레이블 필드를 필요로 합니다 (참조: FAQ)입니다.
차라리 일회성 변환을 원하시나요?
다음을 참조하십시오. COCO에서 YOLO로 변환 가이드 표준에 따라 convert_coco() 워크플로.
아키텍처 개요
다음 두 가지 클래스가 필요합니다:
COCODataset— COCO 읽어들이고 변환합니다 bounding box 훈련 중에 메모리 내 YOLO 변환COCOTrainer— 재정의build_dataset()사용하려면COCODataset기본값 대신YOLODataset
이 구현은 내장된 기능과 동일한 방식을 따릅니다. GroundingDataset, 이는 JSON 주석도 직접 읽습니다. 세 가지 메서드가 재정의됩니다: get_img_files(), cache_labels()및 get_labels().
COCO JSON 데이터셋 클래스 구축
에 지정되어 있습니다. COCODataset 클래스는 다음으로부터 상속됩니다 YOLODataset 레이블 로딩 로직을 재정의합니다. 읽는 대신 .txt labels 디렉터리의 파일을 불러온 다음, COCO 파일을 열고, 이미지별로 그룹화된 어노테이션을 순차적으로 처리하며, 각 바운딩 박스를 COCO 형식에서 [x_min, y_min, width, height] YOLO 중심 좌표 형식으로 [x_center, y_center, width, height]. 크라우드 주석 (iscrowd: 1) 및 면적이 0인 박스는 자동으로 건너뛰어집니다.
에 지정되어 있습니다. get_img_files() 이 메서드는 이미지 경로가 JSON에서 해결되기 때문에 빈 목록을 반환합니다 file_name 내부 필드 cache_labels(). 카테고리 ID는 정렬되어 0-인덱스 클래스 인덱스로 다시 매핑되므로, 1-기반(표준 COCO) 및 비연속 ID 체계 모두 올바르게 작동합니다.
import json
from collections import defaultdict
from pathlib import Path
import numpy as np
from ultralytics.data.dataset import DATASET_CACHE_VERSION, YOLODataset
from ultralytics.data.utils import get_hash, load_dataset_cache_file, save_dataset_cache_file
from ultralytics.utils import TQDM
class COCODataset(YOLODataset):
"""Dataset that reads COCO JSON annotations directly without conversion to .txt files."""
def __init__(self, *args, json_file="", **kwargs):
self.json_file = json_file
super().__init__(*args, data={"channels": 3}, **kwargs)
def get_img_files(self, img_path):
"""Image paths are resolved from the JSON file, not from scanning a directory."""
return []
def cache_labels(self, path=Path("./labels.cache")):
"""Parse COCO JSON and convert annotations to YOLO format. Results are saved to a .cache file."""
x = {"labels": []}
with open(self.json_file) as f:
coco = json.load(f)
images = {img["id"]: img for img in coco["images"]}
# Sort categories by ID and map to 0-indexed classes
categories = {cat["id"]: i for i, cat in enumerate(sorted(coco["categories"], key=lambda c: c["id"]))}
img_to_anns = defaultdict(list)
for ann in coco["annotations"]:
img_to_anns[ann["image_id"]].append(ann)
for img_info in TQDM(coco["images"], desc="reading annotations"):
h, w = img_info["height"], img_info["width"]
im_file = Path(self.img_path) / img_info["file_name"]
if not im_file.exists():
continue
self.im_files.append(str(im_file))
bboxes = []
for ann in img_to_anns.get(img_info["id"], []):
if ann.get("iscrowd", False):
continue
# COCO: [x, y, w, h] top-left in pixels -> YOLO: [cx, cy, w, h] center normalized
box = np.array(ann["bbox"], dtype=np.float32)
box[:2] += box[2:] / 2 # top-left to center
box[[0, 2]] /= w # normalize x
box[[1, 3]] /= h # normalize y
if box[2] <= 0 or box[3] <= 0:
continue
cls = categories[ann["category_id"]]
bboxes.append([cls, *box.tolist()])
lb = np.array(bboxes, dtype=np.float32) if bboxes else np.zeros((0, 5), dtype=np.float32)
x["labels"].append(
{
"im_file": str(im_file),
"shape": (h, w),
"cls": lb[:, 0:1],
"bboxes": lb[:, 1:],
"segments": [],
"normalized": True,
"bbox_format": "xywh",
}
)
x["hash"] = get_hash([self.json_file, str(self.img_path)])
save_dataset_cache_file(self.prefix, path, x, DATASET_CACHE_VERSION)
return x
def get_labels(self):
"""Load labels from .cache file if available, otherwise parse JSON and create the cache."""
cache_path = Path(self.json_file).with_suffix(".cache")
try:
cache = load_dataset_cache_file(cache_path)
assert cache["version"] == DATASET_CACHE_VERSION
assert cache["hash"] == get_hash([self.json_file, str(self.img_path)])
self.im_files = [lb["im_file"] for lb in cache["labels"]]
except (FileNotFoundError, AssertionError, AttributeError, KeyError, ModuleNotFoundError):
cache = self.cache_labels(cache_path)
cache.pop("hash", None)
cache.pop("version", None)
return cache["labels"]
구문 분석된 레이블은 .cache JSON 파일 옆에 있는 파일 (예: instances_train.cache). 후속 훈련 실행 시 캐시가 직접 로드되어 JSON 파싱을 건너뜁니다. JSON 파일이 변경되면 해시 검사가 실패하고 캐시가 자동으로 재구축됩니다.
훈련 파이프라인에 데이터셋 연결
트레이너에서 필요한 유일한 변경 사항은 재정의하는 것입니다 build_dataset(). 기본 DetectionTrainer 구축합니다 YOLODataset ~를 검색하는 .txt 라벨 파일. 이를 다음으로 대체하여 COCODataset, 대신 트레이너는 COCO JSON에서 읽습니다.
JSON 파일 경로는 사용자 정의 train_json / val_json 데이터 구성의 해당 필드(3단계 참조). 훈련 중에, mode="train" ~하기로 결심한다 train_json; 검증 중, mode="val" ~하기로 결심한다 val_json. 만약 val_json 설정되어 있지 않으면, 다음으로 대체됩니다 train_json.
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import colorstr
class COCOTrainer(DetectionTrainer):
"""Trainer that uses COCODataset for direct COCO JSON training."""
def build_dataset(self, img_path, mode="train", batch=None):
json_file = self.data["train_json"] if mode == "train" else self.data.get("val_json", self.data["train_json"])
return COCODataset(
img_path=img_path,
json_file=json_file,
imgsz=self.args.imgsz,
batch_size=batch,
augment=mode == "train",
hyp=self.args,
rect=self.args.rect or mode == "val",
cache=self.args.cache or None,
single_cls=self.args.single_cls or False,
stride=int(self.model.stride.max()) if hasattr(self, "model") and self.model else 32,
pad=0.0 if mode == "train" else 0.5,
prefix=colorstr(f"{mode}: "),
task=self.args.task,
classes=self.args.classes,
fraction=self.args.fraction if mode == "train" else 1.0,
)
COCO JSON용 dataset.yaml 구성
에 지정되어 있습니다. dataset.yaml 표준을 사용합니다 path, train및 val 이미지 디렉터리를 지정하는 필드. 두 개의 추가 필드, train_json 및 val_json, COCO 주석 파일을 지정하여 COCOTrainer 라고 적혀 있다. nc 및 names 필드는 클래스의 수와 이름을 정의하며, 이는 다음의 정렬된 순서와 일치합니다. categories JSON에서.
path: /path/to/images # root directory with train/ and val/ subfolders
train: train
val: val
# COCO JSON annotation files
train_json: /path/to/annotations/instances_train.json
val_json: /path/to/annotations/instances_val.json
nc: 80
names:
0: person
1: bicycle
# ... remaining class names
예상 디렉터리 구조:
my_dataset/
images/
train/
img_001.jpg
...
val/
img_100.jpg
...
annotations/
instances_train.json
instances_val.json
dataset.yaml
COCO 활용한 러닝 트레이닝
데이터셋 클래스, 트레이너 클래스 및 YAML 구성 파일이 준비되면, 훈련은 표준 절차를 통해 진행됩니다. model.train() 호출합니다. 일반적인 훈련 실행과의 유일한 차이점은 trainer=COCOTrainer 인수이며, Ultralytics가 기본 데이터셋 로더 대신 사용자 지정 데이터셋 로더를 사용하도록 지시합니다.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)
검증, 체크포인트 저장, 메트릭 로깅을 포함한 전체 훈련 파이프라인이 예상대로 실행됩니다.
완전한 이행
편의를 위해 전체 구현 내용을 하나의 복사-붙여넣기용 스크립트로 아래에 제공합니다. 여기에는 사용자 정의 데이터셋, 사용자 정의 트레이너, 그리고 훈련 호출이 포함되어 있습니다. 이 파일을 여러분의 dataset.yaml 및 직접 실행합니다.
import json
from collections import defaultdict
from pathlib import Path
import numpy as np
from ultralytics import YOLO
from ultralytics.data.dataset import DATASET_CACHE_VERSION, YOLODataset
from ultralytics.data.utils import get_hash, load_dataset_cache_file, save_dataset_cache_file
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import TQDM, colorstr
class COCODataset(YOLODataset):
"""Dataset that reads COCO JSON annotations directly without conversion to .txt files."""
def __init__(self, *args, json_file="", **kwargs):
self.json_file = json_file
super().__init__(*args, data={"channels": 3}, **kwargs)
def get_img_files(self, img_path):
return []
def cache_labels(self, path=Path("./labels.cache")):
x = {"labels": []}
with open(self.json_file) as f:
coco = json.load(f)
images = {img["id"]: img for img in coco["images"]}
categories = {cat["id"]: i for i, cat in enumerate(sorted(coco["categories"], key=lambda c: c["id"]))}
img_to_anns = defaultdict(list)
for ann in coco["annotations"]:
img_to_anns[ann["image_id"]].append(ann)
for img_info in TQDM(coco["images"], desc="reading annotations"):
h, w = img_info["height"], img_info["width"]
im_file = Path(self.img_path) / img_info["file_name"]
if not im_file.exists():
continue
self.im_files.append(str(im_file))
bboxes = []
for ann in img_to_anns.get(img_info["id"], []):
if ann.get("iscrowd", False):
continue
box = np.array(ann["bbox"], dtype=np.float32)
box[:2] += box[2:] / 2
box[[0, 2]] /= w
box[[1, 3]] /= h
if box[2] <= 0 or box[3] <= 0:
continue
cls = categories[ann["category_id"]]
bboxes.append([cls, *box.tolist()])
lb = np.array(bboxes, dtype=np.float32) if bboxes else np.zeros((0, 5), dtype=np.float32)
x["labels"].append(
{
"im_file": str(im_file),
"shape": (h, w),
"cls": lb[:, 0:1],
"bboxes": lb[:, 1:],
"segments": [],
"normalized": True,
"bbox_format": "xywh",
}
)
x["hash"] = get_hash([self.json_file, str(self.img_path)])
save_dataset_cache_file(self.prefix, path, x, DATASET_CACHE_VERSION)
return x
def get_labels(self):
cache_path = Path(self.json_file).with_suffix(".cache")
try:
cache = load_dataset_cache_file(cache_path)
assert cache["version"] == DATASET_CACHE_VERSION
assert cache["hash"] == get_hash([self.json_file, str(self.img_path)])
self.im_files = [lb["im_file"] for lb in cache["labels"]]
except (FileNotFoundError, AssertionError, AttributeError, KeyError, ModuleNotFoundError):
cache = self.cache_labels(cache_path)
cache.pop("hash", None)
cache.pop("version", None)
return cache["labels"]
class COCOTrainer(DetectionTrainer):
"""Trainer that uses COCODataset for direct COCO JSON training."""
def build_dataset(self, img_path, mode="train", batch=None):
json_file = self.data["train_json"] if mode == "train" else self.data.get("val_json", self.data["train_json"])
return COCODataset(
img_path=img_path,
json_file=json_file,
imgsz=self.args.imgsz,
batch_size=batch,
augment=mode == "train",
hyp=self.args,
rect=self.args.rect or mode == "val",
cache=self.args.cache or None,
single_cls=self.args.single_cls or False,
stride=int(self.model.stride.max()) if hasattr(self, "model") and self.model else 32,
pad=0.0 if mode == "train" else 0.5,
prefix=colorstr(f"{mode}: "),
task=self.args.task,
classes=self.args.classes,
fraction=self.args.fraction if mode == "train" else 1.0,
)
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOTrainer)
하이퍼파라미터 권장 사항에 대해서는 ‘모델 훈련 팁’ 가이드를 참조하십시오.
FAQ
이 함수와 convert_coco()의 차이점은 무엇인가요?
convert_coco() 쓰다 .txt 라벨 파일을 디스크에 일회성 변환으로 저장합니다. 이 방식은 각 훈련 실행 시작 시 JSON을 파싱하고 메모리 내에서 주석을 변환합니다. 다음을 사용하십시오 convert_coco() 영구적인 YOLO 레이블을 선호하는 경우, 이 방법을 사용하여 추가 파일을 생성하지 않고 COCO 유일한 신뢰할 수 있는 데이터 소스로 유지하십시오.
사용자 지정 코드 없이 COCO JSON에서 YOLO를 훈련할 수 있나요?
현재 YOLO 사용하는 Ultralytics 에서는 그렇지 않습니다 .txt 기본적으로 레이블을 사용합니다. 이 가이드에서는 필요한 최소한의 사용자 정의 코드, 즉 데이터셋 클래스 하나와 트레이너 클래스 하나를 제공합니다. 일단 정의되면, 훈련에는 표준 model.train() 호출.
이것은 segmentation과 자세 추정을 지원합니까?
이 안내서에서는 다음 내용을 다룹니다 객체 감지. 추가하려면 인스턴스 분할 지원, 다음을 포함합니다 segmentation COCO 데이터의 다각형 데이터 segments 각 레이블 사전의 필드. 예를 들어 포즈 추정, 포함 keypoints. 다음 GroundingDataset 소스 코드 세그먼트 처리를 위한 참조 구현을 제공합니다.
이 사용자 정의 데이터셋에서 증강이 작동하나요?
네. COCODataset 확장하다 YOLODataset, 따라서 모든 내장된 데이터 증강 — 모자이크, mixup, 복사-붙여넣기, 기타 — 수정 없이 실행됩니다.
카테고리 ID는 클래스 인덱스에 어떻게 매핑되나요?
카테고리는 다음 기준으로 정렬됩니다. id 그리고 0부터 시작하는 순차적 인덱스에 매핑됩니다. 이는 1-기반 ID(표준 COCO), 0-기반 ID 및 비연속적 ID를 처리합니다. 해당 names 사전에서 dataset.yaml COCO 동일한 정렬 순서를 따라야 합니다 categories 배열입니다.
사전 변환된 레이블에 비해 성능상의 오버헤드가 발생하나요?
COCO 첫 번째 훈련 실행 시 한 번 파싱됩니다. 파싱된 레이블은 .cache 파일로 저장되므로, 이후 실행 시에는 다시 파싱할 필요 없이 즉시 로드됩니다. 주석이 메모리에 저장되므로 YOLO 속도는 표준 YOLO 동일합니다. JSON 파일이 변경되면 캐시가 자동으로 재구축됩니다.