YOLO Vision 2026:

COCO 주석을 YOLO 형식으로 변환하는 방법#

Ultralytics YOLO 모델을 학습하려면 YOLO 형식의 주석이 필요하지만, 널리 사용되는 많은 주석 도구는 대신 COCO JSON 형식으로 내보냅니다. 이 가이드에서는 COCO 주석을 YOLO 형식으로 변환하고 객체 검출, 인스턴스 세그멘테이션, 포즈 추정 모델 학습을 시작하는 방법을 설명합니다.

변환을 건너뛰고 싶으신가요?

.txt 파일을 생성하지 않고 COCO JSON으로 직접 학습하려면 변환 없이 COCO JSON으로 YOLO 학습하기를 참조하세요.

COCO를 YOLO로 변환하는 이유#

COCO JSON 형식은 모든 주석을 하나의 파일에 저장하는 반면, YOLO는 정규화된 좌표가 포함된 이미지별 텍스트 파일 하나를 사용합니다. 다음과 같은 이유로 변환이 필요합니다.

  • YOLO 모델에는 .txt 레이블 파일이 필요합니다. 이미지마다 파일 하나가 필요하며, 정규화된 좌표의 class x_center y_center width height이 포함됩니다.
  • COCO JSON은 픽셀 좌표를 사용합니다. [x_min, y_min, width, height] 형식으로 저장되며, 모든 이미지에 하나의 JSON 파일을 사용합니다.
  • 클래스 ID가 다릅니다 — COCO는 임의의 category_id 값을 사용하는 반면, YOLO는 0부터 시작하는 클래스 ID를 요구합니다.
기능COCO JSONYOLO TXT
구조모든 이미지에 하나의 JSON 파일이미지당 .txt 파일 하나
Bbox 형식픽셀 단위의 [x_min, y_min, width, height]정규화된 class x_center y_center width height (0-1)
클래스 IDcategory_id (어떤 숫자에서든 시작 가능)0부터 시작하는 인덱스 (0에서 시작)
세그멘테이션segmentation 필드의 폴리곤 배열클래스 ID 뒤에 오는 폴리곤 좌표
키포인트픽셀 단위의 [x, y, visibility, ...]정규화된 [x, y, visibility, ...]

빠른 시작#

COCO 주석을 변환하고 학습을 시작하는 가장 빠른 방법은 다음과 같습니다.

from ultralytics.data.converter import convert_coco

convert_coco(
    labels_dir="my_dataset/annotations/",  # directory containing your JSON files
    save_dir="my_dataset/converted/",  # where to save converted labels
    cls91to80=False,  # set False for custom datasets (see warning below)
)

변환 후 디렉터리 구조를 구성하고, dataset.yaml을 생성한 다음, 학습을 시작하세요. 아래의 전체 단계별 가이드를 참조하세요.

사용자 정의 데이터셋: 항상 `cls91to80=False` 사용

cls91to80=True 기본값은 80개의 객체 클래스를 포함하는 표준 COCO 데이터셋 전용으로 설계되었으며, 연속적이지 않은 91개의 카테고리 ID를 연속적인 80개의 클래스 ID로 매핑합니다. 사용자 정의 데이터셋에서는 반드시 cls91to80=False를 설정해야 합니다. 그렇지 않으면 클래스 ID가 조용히 잘못 매핑되어 모델이 잘못된 클래스를 학습하게 됩니다.

단계별 변환 가이드#

1. COCO 데이터셋 준비#

주석 도구에서 내보낸 일반적인 COCO 형식 데이터셋은 다음과 같은 구조를 가집니다.

my_dataset/
├── images/
│   ├── train/
│   │   ├── img_001.jpg
│   │   ├── img_002.jpg
│   │   └── ...
│   └── val/
│       ├── img_100.jpg
│       └── ...
└── annotations/
    ├── instances_train.json
    └── instances_val.json

각 JSON 파일은 세 가지 필수 필드인 images, annotations, categories이 포함된 COCO 데이터 형식 사양을 따릅니다.

{
    "images": [{ "id": 1, "file_name": "img_001.jpg", "width": 640, "height": 480 }],
    "annotations": [
        {
            "id": 1,
            "image_id": 1,
            "category_id": 1,
            "bbox": [100, 50, 200, 150],
            "area": 30000,
            "iscrowd": 0
        }
    ],
    "categories": [
        { "id": 1, "name": "helmet" },
        { "id": 2, "name": "vest" }
    ]
}

2. 주석 변환#

convert_coco() 함수를 사용하여 COCO JSON 주석을 YOLO .txt 형식으로 변환합니다.

COCO를 YOLO 형식으로 변환
from ultralytics.data.converter import convert_coco

convert_coco(
    labels_dir="my_dataset/annotations/",
    save_dir="my_dataset/converted/",
    cls91to80=False,
)

convert_coco()은 주석이 있는 이미지마다 .txt 파일 하나를 각 JSON 파일 이름을 따서 명명한 labels/ 하위 디렉터리에 기록하며, instances_ 접두사는 제거합니다(instances_train.jsonlabels/train/를 생성합니다). 주석이 없는 이미지는 건너뛰고 레이블 파일도 생성하지 않으므로 labels/ 트리는 모든 이미지를 그대로 반영하지 않을 수 있습니다.

my_dataset/converted/
├── images/      # created but left empty
└── labels/
    ├── train/   # from instances_train.json
    │   ├── img_001.txt
    │   └── ...
    └── val/     # from instances_val.json
        └── ...
다시 실행하면 새 출력 폴더가 생성됩니다

convert_coco()은 기존 save_dir을 덮어쓰지 않습니다. my_dataset/converted/가 이미 있으면 다시 실행할 때 대신 my_dataset/converted-2/에 기록합니다. 다시 실행하기 전에 이전 출력을 삭제하거나 save_dir를 변경하세요. 그렇지 않으면 다음 단계에서 오래된 레이블을 읽게 됩니다.

3. 디렉터리 구조 구성#

변환 후 레이블 파일을 이미지와 함께 배치해야 합니다. YOLO는 images/ 디렉터리를 그대로 반영하는 labels/ 디렉터리를 예상합니다.

import shutil
from pathlib import Path

converted_dir = Path("my_dataset/converted/labels")
dataset_dir = Path("my_dataset")

# convert_coco names each subdirectory after its JSON file (minus the "instances_" prefix),
# so iterate the actual subdirectories instead of assuming "train"/"val".
for src in converted_dir.iterdir():
    if not src.is_dir():
        continue
    dst = dataset_dir / "labels" / src.name
    dst.mkdir(parents=True, exist_ok=True)
    for f in src.glob("*.txt"):
        shutil.move(str(f), str(dst / f.name))

최종 데이터셋 구조는 다음과 같아야 합니다.

my_dataset/
├── images/
│   ├── train/
│   │   ├── img_001.jpg
│   │   └── ...
│   └── val/
│       └── ...
├── labels/
│   ├── train/
│   │   ├── img_001.txt
│   │   └── ...
│   └── val/
│       └── ...
└── dataset.yaml

4. dataset.yaml 생성#

COCO 카테고리를 YOLO 클래스 이름에 매핑하는 dataset.yaml 구성 파일을 생성합니다. 이 파일은 YOLO에 데이터 위치와 검출할 클래스를 알려줍니다.

import json
from pathlib import Path

import yaml

# Read categories from your COCO JSON
with open("my_dataset/annotations/instances_train.json") as f:
    coco = json.load(f)

# Build class names matching convert_coco output (category_id - 1)
categories = sorted(coco["categories"], key=lambda x: x["id"])
names = {cat["id"] - 1: cat["name"] for cat in categories}
# NOTE: convert_coco maps class IDs as category_id - 1, so category_id must
# start from 1. If your categories start from 0, add 1 to each ID first.

# Create dataset.yaml
dataset = {
    "path": str(Path("my_dataset").resolve()),
    "train": "images/train",
    "val": "images/val",
    "names": names,
}

with open("my_dataset/dataset.yaml", "w") as f:
    yaml.dump(dataset, f, default_flow_style=False)

생성된 YAML 파일:

path: /absolute/path/to/my_dataset
train: images/train
val: images/val
names:
    0: helmet
    1: vest

데이터셋 YAML 형식에 대한 자세한 내용은 데이터셋 구성 가이드를 참조하세요.

5. YOLO 모델 학습#

변환된 데이터셋을 준비했으면 YOLO 모델을 학습합니다.

변환된 COCO 데이터로 학습
from ultralytics import YOLO

model = YOLO("yolo26n.pt")  # load a pretrained model
results = model.train(data="my_dataset/dataset.yaml", epochs=100, imgsz=640)

학습 팁과 모범 사례는 모델 학습 가이드를 참조하세요.

6. 변환 확인#

학습 전에 몇 개의 레이블 파일을 무작위로 확인하여 클래스 ID와 좌표가 올바른지 검증합니다.

from pathlib import Path

label_file = Path("my_dataset/labels/train/img_001.txt")
for line in label_file.read_text().strip().splitlines():
    parts = line.split()
    cls_id = int(parts[0])
    coords = [float(v) for v in parts[1:5]]
    assert cls_id >= 0, f"Negative class ID {cls_id} — category_id in your JSON may start from 0"
    assert all(0 <= v <= 1 for v in coords), f"Coordinates out of [0, 1] range: {coords}"

음수 클래스 ID가 표시된다면 COCO JSON에서 category_id이 0부터 시작할 가능성이 높습니다. convert_coco()를 실행하기 전에 JSON의 모든 category_id 값에 1을 더하세요. 이 함수는 클래스 ID를 category_id - 1으로 매핑하기 때문입니다.

일반적인 문제 해결#

변환 후 잘못된 클래스 ID#

모델이 학습되지만 잘못된 객체 클래스를 검출한다면 사용자 정의 데이터셋에 cls91to80=True(기본값)을 사용하고 있을 가능성이 높습니다. 이 설정은 category_id 값을 COCO 91-to-80 조회 테이블을 통해 매핑하며, 이는 표준 COCO 데이터셋에만 올바릅니다. COCO-80에 대응하는 항목이 없는 category_id은 아무 항목에도 매핑되지 않으며, 잘못된 레이블을 생성하는 대신 변환 중에 TypeError: must be real number, not NoneType를 발생시킵니다.

해결 방법: 사용자 정의 데이터셋에는 항상 cls91to80=False을 사용하세요.

학습 중 레이블을 찾을 수 없음#

레이블 스캔에서 0 images, N backgrounds이 보고되고 이후 학습이 ValueError: train: No labels found in .../labels/train.cache과 함께 중단된다면 레이블 파일이 예상 디렉터리에 없는 것입니다. convert_coco()는 레이블을 별도의 출력 디렉터리(예: save_dir/labels/train/)에 저장하지만, YOLO는 데이터셋 디렉터리 내부에서 images/와 나란히 있는 labels/를 예상합니다.

해결 방법: 레이블 파일을 예상 디렉터리 구조에 맞게 이동하세요. labels/train/images/train/와 같은 수준에 있는지 확인하세요.

변환 중 KeyError#

convert_coco()을 실행할 때 KeyError: 'bbox' 또는 유사한 오류가 발생한다면 labels_dir에 서로 다른 주석 구조를 가진 인스턴스가 아닌 JSON 파일(예: captions_train2017.json)이 포함되어 있을 가능성이 높습니다.

해결 방법: labels_dir에는 인스턴스 주석 JSON 파일(예: instances_train2017.json)만 배치하세요.

변환 후 빈 레이블 파일#

변환이 완료되었지만 .txt 파일이 비어 있거나 누락된 경우, 모든 주석에 iscrowd: 1이 있을 수 있으며(SAM이 생성한 마스크에서 흔히 발생), 바운딩 박스의 너비 또는 높이가 0일 수 있습니다. 검출 전용 내보내기에 use_keypoints=True를 사용해도 동일한 결과가 발생합니다. keypoints 필드가 없는 주석은 완전히 건너뛰기 때문입니다.

해결 방법: JSON 주석에서 iscrowd 값을 검사하세요. SAM 마스크를 사용하는 경우 JSON을 사전 처리하여 iscrowd: 0을 설정하세요. use_keypoints=True를 전달했다면 주석에 실제로 keypoints이 포함되어 있는지 확인하세요.

마스크 주석에서 박스 형태의 폴리곤 생성#

use_segments=True에서 annotations without a usable polygon을 기록한다면 일부 주석에 segmentation 값이 없거나, 최소 3개의 좌표 쌍으로 구성된 목록이 아닌 값이 있는 것입니다. 일반적인 원인은 필드가 누락되거나 비어 있는 검출 전용 내보내기와, SAM 같은 비트마스크 내보내기가 기록하는 COCO 런-길이 인코딩({"counts": ..., "size": ...})입니다. 둘러싸는 폴리곤 목록이 없는 평면 좌표 목록, 1~2개의 점으로 구성된 윤곽선 및 기타 잘못된 값도 동일하게 처리됩니다. 주석에는 유효한 폴리곤만 유지되며, 유효한 폴리곤이 없으면 바운딩 박스와 같은 형태의 세그먼트 행으로 대체되므로 레이블은 유효하게 유지되지만 해당 행에는 마스크 세부 정보가 없습니다.

해결 방법: 폴리곤 세그멘테이션으로 주석을 다시 내보내거나, convert_coco()을 실행하기 전에 RLE 마스크를 폴리곤으로 디코딩하거나, 잘못된 segmentation 값을 수정하세요.

변환된 레이블의 클래스 ID 공백#

레이블 파일의 클래스 ID가 연속적이지 않다면(예: 0, 1, 2 대신 0, 4, 9), 주석 도구가 연속적이지 않은 category_id 값을 사용하는 것입니다.

해결 방법: .txt 파일의 클래스 ID가 dataset.yamlnames 딕셔너리와 일치하는지 확인하세요. 필요한 경우 ID를 연속적인 값으로 다시 매핑하세요.

전체 API 세부 정보와 매개변수 설명은 convert_coco API 참조를 참조하세요.

FAQ#

  • Ultralytics의 convert_coco() 함수를 사용하여 COCO JSON 주석을 YOLO .txt 형식으로 변환하세요. 사용자 정의 데이터셋에는 cls91to80=False를 설정합니다.

    from ultralytics.data.converter import convert_coco
    
    convert_coco(labels_dir="path/to/annotations/", save_dir="output/", cls91to80=False)

    변환 후 레이블 파일을 재구성하여 labels/images/ 디렉터리를 그대로 반영하도록 한 다음 dataset.yaml 파일을 생성하세요. 전체 워크플로는 단계별 가이드를 참조하세요.

  • 이는 convert_coco()이 레이블을 데이터셋의 labels/train/images/train/와 나란히 직접 저장하지 않고 save_dir/labels/ 내부의 하위 디렉터리(예: save_dir/labels/train/)에 저장하기 때문에 발생합니다. YOLO는 이미지와 같은 수준에 레이블이 있기를 예상합니다. 예를 들어 images/train/img.jpg에는 labels/train/img.txt이 필요합니다. 변환된 레이블을 이 구조에 맞게 이동하세요. 디렉터리 구조 수정을 참조하세요.

  • cls91to80 매개변수는 COCO category_id 값을 YOLO 클래스 ID에 매핑하는 방식을 제어합니다. True(기본값)인 경우 표준 COCO 데이터셋을 위해 설계된 coco91_to_coco80_class() 조회 테이블을 적용합니다. 이 데이터셋은 연속적이지 않은 ID(1-90)를 가진 80개 클래스로 구성됩니다. 사용자 정의 데이터셋에서는 항상 cls91to80=False을 설정하세요. 그러면 각 category_id에서 1을 빼서 0부터 시작하는 클래스 ID를 생성합니다.

  • 사용자 정의 코드 없이는 불가능합니다. 기본 학습 파이프라인은 이미지마다 하나의 파일로 구성된 YOLO .txt 레이블을 예상합니다. 따라서 convert_coco()을 실행하고 이 단계별 가이드를 따르거나, 데이터셋을 서브클래싱하여 COCO JSON을 즉시 파싱해야 합니다. 자세한 내용은 변환 없이 COCO JSON으로 YOLO 학습하기를 참조하세요. 지원되는 형식에 대한 자세한 내용은 데이터셋 형식을 참조하세요.

  • 예. convert_coco()을 호출할 때 use_segments=True을 사용하면 변환된 YOLO 레이블에 폴리곤 세그멘테이션 마스크를 포함할 수 있습니다. 이렇게 하면 YOLO 세그멘테이션 모델과 호환되는 레이블 파일이 생성됩니다.

    from ultralytics.data.converter import convert_coco
    
    convert_coco(labels_dir="annotations/", save_dir="output/", use_segments=True, cls91to80=False)
  • use_keypoints=True을 사용하여 포즈 추정 학습을 위한 COCO 키포인트 주석을 변환합니다.

    from ultralytics.data.converter import convert_coco
    
    convert_coco(labels_dir="annotations/", save_dir="output/", use_keypoints=True, cls91to80=False)

    use_segmentsuse_keypoints이 모두 True로 설정된 경우 키포인트만 레이블 파일에 기록되며 세그먼트는 자동으로 무시됩니다.

댓글