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은 픽셀 좌표를 사용하며, 모든 이미지에 대해 단일 JSON 파일에
[x_min, y_min, width, height]형식으로 저장합니다. - 클래스 ID가 다릅니다 — COCO는 임의의
category_id값을 사용하지만, YOLO에는 0부터 시작하는 클래스 ID가 필요합니다.
| 기능 | COCO JSON | YOLO TXT |
|---|---|---|
| 구조 | 모든 이미지가 포함된 단일 JSON 파일 | 이미지당 .txt 파일 하나 |
| BBox 형식 | 픽셀 단위 [x_min, y_min, width, height] | class x_center y_center width height 정규화됨 (0-1) |
| 클래스 ID | category_id (임의의 숫자에서 시작할 수 있음) | 0부터 시작하는 인덱스 |
| 세그멘테이션 | segmentation 필드의 폴리곤 배열 | 클래스 ID 다음에 오는 폴리곤 좌표 |
| 키포인트 | 픽셀 단위 [x, y, visibility, ...] | [x, y, visibility, ...] 정규화됨 |
빠른 시작#
COCO 어노테이션을 변환하고 학습을 시작하는 가장 빠른 방법:
from ultralytics.data.converter import convert_coco
convert_coco(
labels_dir="my_dataset/annotations/", # JSON 파일이 들어 있는 디렉터리
save_dir="my_dataset/converted/", # 변환된 레이블을 저장할 위치
cls91to80=False, # 사용자 지정 데이터셋의 경우 False로 설정하세요(아래 경고 참조).
)변환한 후 디렉터리 구조를 구성하고, dataset.yaml을 만들고, 학습을 시작하세요. 아래의 전체 단계별 가이드를 참조하세요.
기본 cls91to80=True은 80개의 객체 클래스를 사용하는 표준 COCO 데이터셋 전용으로 설계되었습니다. 이 설정은 연속되지 않는 카테고리 ID 91개를 연속된 클래스 ID 80개에 매핑합니다. 사용자 지정 데이터셋에서는 반드시 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. 어노테이션 변환#
COCO JSON 어노테이션을 YOLO .txt 형식으로 변환하려면 convert_coco() 함수를 사용하세요.
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 파일 이름에서 instances_ 접두사를 제거한 이름으로 labels/ 하위 디렉터리를 만듭니다(따라서 instances_train.json는 labels/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는 labels/ 디렉터리가 images/ 디렉터리의 구조를 그대로 반영하는 형식을 사용합니다.
import shutil
from pathlib import Path
converted_dir = Path("my_dataset/converted/labels")
dataset_dir = Path("my_dataset")
# convert_coco는 각 하위 디렉터리를 해당 JSON 파일 이름(“instances_” 접두사 제외)으로 지정하므로,
# “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.yaml4. dataset.yaml 만들기#
COCO 카테고리를 YOLO 클래스 이름에 매핑하는 dataset.yaml 설정 파일을 만드세요. 이 파일은 데이터의 위치와 탐지할 클래스를 YOLO에 알려줍니다.
import json
from pathlib import Path
import yaml
# COCO JSON에서 카테고리 읽기
with open("my_dataset/annotations/instances_train.json") as f:
coco = json.load(f)
# convert_coco 출력에 맞는 클래스 이름 구성(category_id - 1)
categories = sorted(coco["categories"], key=lambda x: x["id"])
names = {cat["id"] - 1: cat["name"] for cat in categories}
# 참고: convert_coco는 클래스 ID를 category_id - 1로 매핑하므로 category_id는
# 1부터 시작해야 합니다. 카테고리가 0부터 시작한다면 각 ID에 1을 더하세요.
# 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 모델을 학습시키세요.
from ultralytics import YOLO
model = YOLO("yolo26n.pt") # 사전 학습 모델 불러오기
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을 사용했을 가능성이 큽니다. 이 설정은 COCO 91-to-80 조회 테이블을 통해 category_id 값을 매핑하며, 이 방식은 표준 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)이 들어 있을 가능성이 큽니다.
해결 방법: 인스턴스 어노테이션 JSON 파일(예: instances_train2017.json)만 labels_dir에 넣으세요.
변환 후 빈 레이블 파일#
변환이 완료되었지만 .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 값이 없거나 좌표 쌍이 세 개 이상인 리스트가 아닌 값이 포함되어 있습니다. 일반적인 원인으로는 필드가 누락되거나 비어 있는 탐지 전용 내보내기와 비트마스크 내보내기 도구(예: SAM)가 기록하는 COCO 런 렝스 인코딩({"counts": ..., "size": ...})이 있습니다. 폴리곤 리스트로 둘러싸지 않은 평탄한 좌표 리스트, 점이 한두 개인 윤곽선, 그 밖의 잘못된 값도 같은 방식으로 처리됩니다. 어노테이션에는 유효한 폴리곤만 남고, 유효한 폴리곤이 없으면 바운딩 박스 모양의 세그먼트 행으로 대체됩니다. 따라서 레이블은 유효한 상태를 유지하지만 해당 행에는 마스크 정보가 포함되지 않습니다.
해결 방법: 어노테이션을 폴리곤 세그멘테이션 형식으로 다시 내보내거나, convert_coco()을 실행하기 전에 RLE 마스크를 폴리곤으로 디코딩하거나, 잘못된 segmentation 값을 수정하세요.
변환된 레이블에서 클래스 ID가 건너뜀#
레이블 파일의 클래스 ID가 연속되지 않는 경우(예: 0, 1, 2 대신 0, 4, 9), 어노테이션 도구에서 연속되지 않는 category_id 값을 사용하고 있는 것입니다.
해결 방법: .txt 파일의 클래스 ID가 dataset.yaml의 names 딕셔너리와 일치하는지 확인하세요. 필요한 경우 ID를 연속된 값으로 다시 매핑하세요.
전체 API 세부 정보와 매개변수 설명은 convert_coco API 레퍼런스를 참조하세요.
자주 묻는 질문#
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매개변수는 COCOcategory_id값을 YOLO 클래스 ID에 매핑하는 방식을 제어합니다.True(기본값)를 사용하면 80개 클래스와 연속되지 않는 ID(1-90)가 있는 표준 COCO 데이터셋용으로 설계된coco91_to_coco80_class()조회 테이블을 적용합니다. 사용자 지정 데이터셋에는 항상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)포즈 추정 학습용 COCO 키포인트 어노테이션을 변환하려면
use_keypoints=True을 사용하세요.from ultralytics.data.converter import convert_coco convert_coco(labels_dir="annotations/", save_dir="output/", use_keypoints=True, cls91to80=False)use_segments과use_keypoints을 모두True로 설정하면 키포인트만 레이블 파일에 기록되고 세그먼트는 알림 없이 무시됩니다.