변환 없이 COCO YOLO 훈련하는 방법
왜 COCO 을 직접 학습해야 할까요?
주석 에서 COCO 이 형식은 다음 용도로 직접 사용할 수 있습니다. Ultralytics YOLO 변환하지 않고 훈련하기 .txt 먼저 파일을 처리합니다. 이는 서브클래스를 생성하여 수행됩니다. YOLODataset COCO 실시간으로 파싱하고, 사용자 정의 트레이너를 통해 이를 훈련 파이프라인에 연결하는 것.
이 접근 방식은 COCO 유일한 신뢰할 수 있는 정보원으로 유지합니다 — 아니요 convert_coco() 호출, 디렉터리 재구성 없음, 중간 라벨 파일 없음. YOLO26 그리고 다른 모든 Ultralytics YOLO 모델도 지원됩니다. 분할 및 자세 모델의 경우 추가 라벨 필드가 필요합니다(자세한 내용은 FAQ)입니다.
차라리 일회성 변환을 원하시나요?
다음을 참조하십시오. COCO YOLO COCO YOLO 방법 표준에 따라 convert_coco() 워크플로.
아키텍처 개요
다음 두 가지 클래스가 필요합니다:
COCOJSONDataset— COCO 읽어들이고 변환합니다 bounding box 훈련 중에 메모리 내 YOLO 변환COCOJSONTrainer— 재정의build_dataset()사용하려면COCOJSONDataset기본값 대신YOLODataset
이 구현은 내장된 기능과 동일한 방식을 따릅니다. GroundingDataset, 또한 JSON 주석을 직접 읽어들입니다. 다음 세 가지 메서드가 재정의됩니다: get_img_files(), cache_labels()및 get_labels().
COCO 데이터셋 클래스 생성하기
에 지정되어 있습니다. COCOJSONDataset 클래스는 ~로부터 상속받습니다 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 COCOJSONDataset(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 라벨 파일. 이를 다음으로 대체하여 COCOJSONDataset대신 트레이너는 COCO 파일을 읽어옵니다.
JSON 파일 경로는 사용자 정의 train_json / val_json field in the data config (see Step 3). During training, 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 COCOJSONTrainer(DetectionTrainer):
"""Trainer that uses COCOJSONDataset 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 COCOJSONDataset(
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 yaml dataset.yaml 구성
에 지정되어 있습니다. dataset.yaml 표준을 사용합니다 path, train및 val 이미지 디렉터리를 지정하는 필드. 두 개의 추가 필드, train_json 및 val_json, 다음 COCO 파일을 지정하십시오. COCOJSONTrainer 라고 적혀 있다. 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=COCOJSONTrainer 이 인수는 Ultralytics 기본 데이터셋 로더 대신 사용자 정의 데이터셋 로더를 Ultralytics 지시합니다.
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
model.train(data="dataset.yaml", epochs=100, imgsz=640, trainer=COCOJSONTrainer)
검증, 체크포인트 저장, 메트릭 로깅을 포함한 전체 훈련 파이프라인이 예상대로 실행됩니다.
완전한 이행
편의를 위해 전체 구현 내용을 하나의 복사-붙여넣기용 스크립트로 아래에 제공합니다. 여기에는 사용자 정의 데이터셋, 사용자 정의 트레이너, 그리고 훈련 호출이 포함되어 있습니다. 이 파일을 여러분의 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 COCOJSONDataset(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 COCOJSONTrainer(DetectionTrainer):
"""Trainer that uses COCOJSONDataset 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 COCOJSONDataset(
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=COCOJSONTrainer)
하이퍼파라미터 권장 사항에 대해서는 ‘모델 훈련 팁’ 가이드를 참조하십시오.
FAQ
이 함수와 convert_coco()의 차이점은 무엇인가요?
convert_coco() 쓰다 .txt 라벨 파일을 디스크에 일회성 변환으로 저장합니다. 이 방식은 각 훈련 실행 시작 시 JSON을 파싱하고 메모리 내에서 주석을 변환합니다. 다음을 사용하십시오 convert_coco() 영구적인 YOLO 레이블을 선호하는 경우, 이 방법을 사용하여 추가 파일을 생성하지 않고 COCO 유일한 신뢰할 수 있는 데이터 소스로 유지하십시오.
YOLO 별도의 코드 없이 COCO 데이터로 YOLO 수 있나요?
현재 YOLO 사용하는 Ultralytics 에서는 그렇지 않습니다 .txt 기본적으로 레이블을 사용합니다. 이 가이드에서는 필요한 최소한의 사용자 정의 코드, 즉 데이터셋 클래스 하나와 트레이너 클래스 하나를 제공합니다. 일단 정의되면, 훈련에는 표준 model.train() 전화.
이 기능은 세그멘테이션과 자세 추정을 지원하나요?
이 안내서에서는 다음 내용을 다룹니다 객체 감지. 추가하려면 인스턴스 분할 지원, 다음을 포함합니다 segmentation COCO 데이터의 다각형 데이터 segments 각 레이블 사전의 필드. 예를 들어 포즈 추정, 포함 keypoints. 다음 GroundingDataset 소스 코드 세그먼트 처리를 위한 참조 구현을 제공합니다.
이 사용자 정의 데이터셋에서도 보강 학습이 적용되나요?
네. COCOJSONDataset 확장하다 YOLODataset, 따라서 모든 내장 데이터 증강 — 모자이크, mixup, 복사-붙여넣기, 등 — 수정 없이 실행됩니다.
카테고리 ID는 클래스 인덱스에 어떻게 매핑되나요?
카테고리는 다음 기준으로 정렬됩니다 id 그리고 0부터 시작하는 순차적 인덱스로 매핑됩니다. 이를 통해 1을 기준으로 하는 ID(표준 COCO), 0을 기준으로 하는 ID, 그리고 비연속적인 ID를 모두 처리할 수 있습니다. 이 names 사전 dataset.yaml COCO 동일한 정렬 순서를 따라야 합니다 categories 배열.
사전 변환된 레이블에 비해 성능상의 오버헤드가 발생하나요?
COCO 첫 번째 훈련 실행 시 한 번 파싱됩니다. 파싱된 레이블은 .cache 파일로 저장되므로, 이후 실행 시에는 다시 파싱할 필요 없이 즉시 로드됩니다. 주석이 메모리에 저장되므로 YOLO 속도는 표준 YOLO 동일합니다. JSON 파일이 변경되면 캐시가 자동으로 재구축됩니다.