Ultralytics YOLO27: 加入候补名单。
开始使用

如何将 COCO 标注转换为 YOLO 格式

训练 Ultralytics YOLO 模型需要使用 YOLO 格式的标注,但许多常用的标注工具导出的是 COCO JSON 格式。本指南将介绍如何把 COCO 标注转换为 YOLO 格式,并开始训练目标检测、实例分割和姿态估计模型。

想跳过转换?

如需直接使用 COCO JSON 训练而不生成 .txt 文件,请参阅无需转换,直接使用 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 要求类别 ID 从 0 开始编号。
特性COCO JSONYOLO TXT
结构所有图像共用一个 JSON 文件每张图像对应一个 .txt 文件
边界框格式像素坐标中的 [x_min, y_min, width, height]class x_center y_center width height 归一化(0-1)
类别 IDcategory_id(可以从任意数字开始)从 0 开始索引(从 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=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 文件都遵循 COCO 数据格式规范,包含三个必需字段——images、annotations 和 categories:

{
    "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 文件命名且移除了 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.yaml

4. 创建 dataset.yaml#

创建一个 dataset.yaml 配置文件,将 COCO 类别映射到 YOLO 类别名称。此文件会告知 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 模型:

使用转换后的 COCO 数据进行训练
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 可能使用了从 0 开始的 category_id。运行 convert_coco() 前,请将 JSON 中所有 category_id 值加 1,因为它会按 category_id - 1 映射类别 ID。

常见问题排查#

转换后类别 ID 错误#

如果模型能够训练,但检测出的物体类别有误,你很可能在自定义数据集上使用了 cls91to80=True(默认值)。该设置会通过 COCO 91 到 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 要求 labels/ 位于数据集目录中,并与 images/ 平行。

解决方案:移动标签文件,使其符合预期的目录结构。确保 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 会将掩码标注导出为 crowd 区域);也可能是边界框的宽度或高度为零。对仅包含检测标注的导出数据运行 use_keypoints=True 也会得到相同结果,因为不含 keypoints 字段的标注会被完全跳过。

解决方案:检查 JSON 标注中的 iscrowd 值。如果每个带 crowd 标记的掩码都只勾勒出一个对象(如 CVAT 掩码导出数据中的情况),请设置 iscrowd: 0,这样 use_segments=True 就会转换其 RLE 掩码;对于确实覆盖多个对象的 crowd 区域,则保留其标记。如果你传入了 use_keypoints=True,请确认标注中确实包含 keypoints。

掩码标注生成框状多边形#

如果 use_segments=True 记录了 annotations without a usable polygon,说明部分标注没有 segmentation 值;该值可能是 COCO 游程编码({"counts": ..., "size": ...}),但其 size 与图像的 [height, width] 不符,掩码为空,或仅包含太小或太细、无法用三个点勾勒的区域;也可能是既非 RLE、也不是至少包含三对坐标的列表的值。通常这是因为导出数据仅包含检测标注,导致该字段缺失或为空;没有外层多边形列表的平坦坐标列表、只有一两个点的轮廓,以及其他格式错误的值也会按相同方式处理。标注会保留剩余的多边形;如果没有剩余多边形,则会回退到形状与其边界框相同的分割行,因此标签仍然有效,但这些行不包含掩码细节。

如果 RLE 值的 counts 缺失或无法解码(例如字符串被截断,或各段长度之和与 size 不符),转换会报错并停止,而不会回退处理。

解决方案:重新导出带有多边形或 RLE 分割标注的数据,或修正格式错误的 segmentation 值。

转换后的标签中类别 ID 不连续#

如果标签文件中的类别 ID 不连续(例如 0、4、9,而不是 0、1、2),说明你的标注工具使用了不连续的 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() 会将标签保存到 save_dir/labels/ 内的子目录(例如 save_dir/labels/train/),而不是直接保存到数据集的 labels/train/ 中,与 images/train/ 放在一起。YOLO 要求标签与图像目录平行——例如,images/train/img.jpg 需要对应 labels/train/img.txt。请移动转换后的标签,使其符合此结构。请参阅修正目录结构。

  • cls91to80 参数控制 COCO category_id 值如何映射到 YOLO 类别 ID。当设置为 True(默认值)时,它会应用为标准 COCO 数据集设计的 coco91_to_coco80_class() 查找表;该数据集有 80 个类别,ID 不连续(1-90)。对于自定义数据集,始终设置 cls91to80=False——它只需将每个 category_id 减 1,即可生成从 0 开始索引的类别 ID。

  • 不能直接使用默认流程,需要编写自定义代码。默认训练流程要求 YOLO .txt 标签,每张图像对应一个文件。因此,你可以运行 convert_coco() 并按照此分步指南操作,也可以对数据集类进行子类化,让它即时解析 COCO JSON——请参阅不转换 COCO JSON,直接训练 YOLO。有关支持格式的更多信息,请参阅数据集格式。

  • 可以,在调用 convert_coco() 时使用 use_segments=True,即可在转换后的 YOLO 标签中包含分割掩码。多边形分割会直接转换;游程编码(RLE)掩码无论经过压缩还是未压缩,都会使用 faster-coco-eval 解码(当 JSON 文件中包含此类掩码时会自动安装),并转换为其区域的外轮廓。被拆分为多个多边形或掩码区域的对象会合并为一行多边形;由于 YOLO 多边形无法表示孔洞,因此掩码中的孔洞会被填充。Crowd 标注(iscrowd: 1)会被跳过。这样生成的标签文件与 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_segments 和 use_keypoints 都设置为 True,标签文件中只会写入关键点,分割区域会被静默忽略。

贡献者
glenn-jocherglenn-jocher5raimbekovmraimbekovm5DNDevulapalli Naga Sri Vaishnavi1pderrengerpderrenger1

评论

提出问题、分享结果,或帮助其他人。