콘텐츠로 건너뛰기

변환 없이 COCO YOLO 훈련하는 방법

왜 COCO 을 직접 학습해야 할까요?

주석 에서 COCO JSON 이 형식은 다음 용도로 직접 사용할 수 있습니다. Ultralytics YOLO 변환하지 않고 훈련하기 .txt 먼저 파일을 처리합니다. 이는 서브클래스를 생성하여 수행됩니다. YOLODataset COCO 실시간으로 파싱하고, 사용자 정의 트레이너를 통해 이를 훈련 파이프라인에 연결하는 것.

이 접근 방식은 COCO 유일한 신뢰할 수 있는 정보원으로 유지합니다 — 아니요 convert_coco() 호출, 디렉토리 재구성 없음, 중간 레이블 파일 없음. YOLO26 및 기타 모든 Ultralytics YOLO detect 모델이 지원됩니다. segment 및 포즈 모델은 추가 레이블 필드를 필요로 합니다 (참조: FAQ)입니다.

차라리 일회성 변환을 원하시나요?

다음을 참조하십시오. COCO에서 YOLO로 변환 가이드 표준에 따라 convert_coco() 워크플로.

아키텍처 개요

다음 두 가지 클래스가 필요합니다:

  1. COCODataset — COCO 읽어들이고 변환합니다 bounding box 훈련 중에 메모리 내 YOLO 변환
  2. 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, trainval 이미지 디렉터리를 지정하는 필드. 두 개의 추가 필드, train_jsonval_json, COCO 주석 파일을 지정하여 COCOTrainer 라고 적혀 있다. ncnames 필드는 클래스의 수와 이름을 정의하며, 이는 다음의 정렬된 순서와 일치합니다. 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 파일이 변경되면 캐시가 자동으로 재구축됩니다.



📅 7일 전 생성 ✏️ 7일 전 업데이트
raimbekovmglenn-jocher

댓글