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 필드의 폴리곤 배열 또는 RLE 마스크 | 클래스 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
from ultralytics.utils 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,
}
YAML.save("my_dataset/dataset.yaml", dataset)생성된 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(기본값)을 사용하고 있을 가능성이 큽니다. 이 설정은 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)이 들어 있을 가능성이 큽니다.
해결 방법: 인스턴스 어노테이션 JSON 파일(예: instances_train2017.json)만 labels_dir에 넣으세요.
변환 후 빈 레이블 파일#
변환은 완료되었지만 .txt 파일이 비어 있거나 누락된 경우, 모든 어노테이션에 iscrowd: 1이 포함되어 있을 수 있으며, convert_coco()는 이를 건너뜁니다(CVAT은 예를 들어 마스크 어노테이션을 군중 영역으로 내보냅니다). 또는 바운딩 박스의 너비나 높이가 0일 수 있습니다. 탐지 전용 내보내기에 use_keypoints=True를 지정해 실행해도 같은 결과가 발생합니다. keypoints 필드가 없는 어노테이션은 완전히 건너뛰기 때문입니다.
해결 방법: JSON 어노테이션에서 iscrowd 값을 확인합니다. 군중 플래그가 설정된 각 마스크가 CVAT 마스크 내보내기의 경우처럼 단일 객체의 윤곽을 나타내면, iscrowd: 0을 설정하여 use_segments=True가 해당 RLE 마스크를 변환하도록 합니다. 여러 객체를 포함하는 실제 군중 영역은 플래그를 설정한 상태로 둡니다. use_keypoints=True을 전달했다면 어노테이션에 실제로 keypoints가 있는지 확인합니다.
마스크 어노테이션에서 상자 형태의 폴리곤이 생성됨#
use_segments=True에서 annotations without a usable polygon을 기록하면, 일부 어노테이션에 segmentation 값이 없거나, COCO 런렝스 인코딩({"counts": ..., "size": ...})의 size가 이미지의 [height, width]와 일치하지 않거나, 마스크가 비어 있거나 세 개의 점으로 윤곽을 나타내기에는 너무 작거나 얇은 영역만 포함하거나, RLE도 아니고 좌표 쌍이 세 개 이상인 목록도 아닌 값이 포함되어 있다는 뜻입니다. 일반적인 원인은 탐지 전용 내보내기로, 이 경우 해당 필드가 누락되거나 비어 있습니다. 바깥쪽 폴리곤 목록 없이 평탄화된 좌표 목록, 점이 한 개 또는 두 개인 윤곽선, 그 밖의 잘못된 값도 같은 방식으로 처리됩니다. 어노테이션에는 유효한 폴리곤이 남아 있으면 해당 폴리곤이 유지되고, 유효한 폴리곤이 하나도 없으면 바운딩 박스 형태의 세그먼트 행으로 대체되므로 레이블은 유효한 상태로 유지되지만 해당 행에는 마스크 세부 정보가 포함되지 않습니다.
counts이 누락되었거나 디코딩할 수 없는 RLE 값(예: 잘린 문자열 또는 합계가 size과 일치하지 않는 런)이 있으면 대체 처리로 넘어가는 대신 오류와 함께 변환이 중단됩니다.
해결 방법: 폴리곤 또는 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 학습하기를 참조하세요. 지원되는 형식에 대한 자세한 내용은 데이터셋 형식을 참조하세요.예, 변환된 YOLO 레이블에 세그멘테이션 마스크를 포함하려면
convert_coco()을 호출할 때use_segments=True을 사용합니다. 폴리곤 세그멘테이션은 직접 변환되며, 압축 또는 비압축 런렝스 인코딩(RLE) 마스크는faster-coco-eval(JSON 파일에 해당 마스크가 있으면 자동으로 설치됨)로 디코딩되어 영역의 바깥쪽 윤곽으로 변환됩니다. 여러 폴리곤 또는 마스크 영역으로 나뉜 객체는 하나의 폴리곤 행으로 결합되며, YOLO 폴리곤에는 구멍을 표현할 수 없으므로 마스크의 구멍은 채워집니다. 군중 어노테이션(iscrowd: 1)은 건너뜁니다. 이렇게 생성된 레이블 파일은 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로 설정하면 키포인트만 레이블 파일에 기록되고 세그먼트는 알림 없이 무시됩니다.
댓글
질문을 하거나 결과를 공유하거나 다른 사람을 도와주세요.