如何不转换格式直接用 Ultralytics YOLO 在 COCO JSON 标注上训练
2026/9/9 13:27:17 网站建设 项目流程

如何不转换格式直接用 Ultralytics YOLO 在 COCO JSON 标注上训练

【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics

如果你的数据集标注以 COCO JSON 格式存在(如 Labelme、COCO 官方工具或 SAM 导出的instances_train.json),而 Ultralytics YOLO 默认训练管线只识别 YOLO.txt标签,常规做法是先跑一次convert_coco()把标注转成.txt文件再训练。项目文档提供了一条不转换的路径:通过一个自定义数据集类在训练时直接解析 COCO JSON,并用一个自定义 trainer 把它接入标准训练流程。完成本文后,你可以用一个标准的model.train()调用直接在自己的 COCO JSON 标注上训练检测模型(文档以 YOLO26 为例,其他 Ultralytics YOLO 检测模型同样适用),标注文件保持为唯一的真值来源,不产生任何中间标签文件。

这条路径适用于对象检测任务;实例分割和姿态估计需要在cache_labels()中额外写入segmentskeypoints字段,本文最后会说明扩展方向。

方案原理:两个类替换默认数据加载

Ultralytics 的训练管线默认构建YOLODataset,它会扫描标签目录中的.txt文件。直接读 COCO JSON 的做法是替换这条数据通路,只需要两个类:

  1. COCODataset— 继承YOLODataset,重写标签加载逻辑:打开 COCO JSON,把每个边界框从 COCO 像素格式[x_min, y_min, width, height]转换为 YOLO 归一化中心点格式[x_center, y_center, width, height],全部在内存中完成。iscrowd: 1的众包标注和零面积框会被自动跳过。
  2. COCOTrainer— 继承DetectionTrainer,只重写build_dataset()方法,让训练器构建COCODataset而不是默认的YOLODataset

这个实现是内置GroundingDataset的简化版——GroundingDataset同样直接读取 JSON 标注,可参考其 源码实现 处理 segments 等更复杂的场景。COCODataset重写了三个方法:get_img_files()cache_labels()get_labels()。其中get_img_files()返回空列表,因为图片路径从 JSON 的file_name字段解析,而不是扫描目录;category_id会按 ID 排序后重映射为从 0 开始的类别索引,所以 1-based(标准 COCO)、0-based 或非连续 ID 体系都能正确处理。

与一次性转换(convert_coco() 工作流)的区别:convert_coco().txt标签写入磁盘,适合需要永久保留 YOLO 格式标签的场景;本文方案在训练时解析 JSON、内存中转换,适合希望以 COCO JSON 为唯一真值来源、不生成额外文件的场景。

准备数据集

目录结构按如下方式组织,images/下按 train/val 分开放图片,JSON 标注文件单独存放:

my_dataset/ images/ train/ img_001.jpg ... val/ img_100.jpg ... annotations/ instances_train.json instances_val.json dataset.yaml

JSON 文件需符合 COCO 数据格式,包含imagesannotationscategories三个字段,其中images中每条记录的file_name是相对于图片根目录的文件名,解析时通过Path(self.img_path) / img_info["file_name"]定位图片,找不到的图片会被跳过。

编写训练脚本

下面是项目文档提供的完整脚本,包含数据集类、训练器和训练调用。把它保存在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): """Initialize the dataset with a COCO JSON annotation file.""" 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.""" self.fraction = 1.0 # fraction is applied while scanning a directory, which this dataset skips 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) 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", } ) if not x["labels"]: raise RuntimeError(f"No images listed in {self.json_file} were found in {self.img_path}") 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"] class COCOTrainer(DetectionTrainer): """Trainer that uses COCODataset for direct COCO JSON training.""" def build_dataset(self, img_path, mode="train", batch=None): """Build a COCODataset for the given split using the JSON file from the data config.""" json_file = self.data["train_json"] if mode == "train" else self.data["val_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)

代码中的两个关键点:

  • build_dataset()只改一件事:训练时用train_json、验证时用val_json取 JSON 路径。这两个键在 data 配置中都是必填的——训练和验证读取不同的图片目录,训练 JSON 不能代替缺失的val_json
  • 解析结果会写缓存:标签解析完成后保存到 JSON 同目录的.cache文件(例如instances_train.cache),后续训练直接加载缓存,跳过 JSON 解析。

在 Windows 上以脚本方式启动训练时,需要在训练调用前加if __name__ == "__main__":代码块,否则会触发RuntimeError(这是 Ultralytics 训练脚本的通用要求)。

配置 dataset.yaml

dataset.yaml使用标准的pathtrainval字段定位图片目录,再新增train_jsonval_json两个字段指向 COCO 标注文件。注意与 转换指南 中的写法不同:这里的path指向图片根目录,所以trainval是裸的分片名;而两个 JSON 路径字段不与path拼接,必须写绝对路径。下例中的/path/to/my_dataset需替换为你数据集的实际绝对路径:

path: /path/to/my_dataset/images # root with train/ and val/ image subfolders train: train val: val # COCO JSON annotation files (use absolute paths; these custom keys are not resolved against `path`) train_json: /path/to/my_dataset/annotations/instances_train.json val_json: /path/to/my_dataset/annotations/instances_val.json names: 0: person 1: bicycle # ... remaining class names

names必须按 JSONcategories数组按 ID 排序后的顺序列出类别名(与代码中categories的重映射逻辑一致),类别数量从names推导,不需要单独设置nc

启动训练

运行上面保存的脚本即可。与普通训练相比,唯一的区别是model.train()中的trainer=COCOTrainer参数,它告诉 Ultralytics 使用自定义数据集加载器。epochs=100imgsz=640是文档示例中的取值,可按需调整;完整训练管线按标准流程运行,包括训练中的验证、checkpoint 保存和指标记录,详见 训练模式文档。

验证结果与常见失败现象

检查缓存文件。首次运行时,JSON 同目录会生成instances_train.cache/instances_val.cache。后续运行直接加载该缓存,说明解析结果已被复用。

确认训练与验证都在正常跑指标。训练中的验证会走COCOTrainer.build_datasetmode="val"解析val_json),所以验证阶段能读到标签、正常计算指标。训练结束后按 训练文档 中描述的方式查看保存的 checkpoint 和记录的训练/验证指标即可。

两个文档明确给出的失败现象:

  • 如果 JSON 里列出的图片在img_path下一个都找不到,cache_labels()会抛出RuntimeError: No images listed in <json> were found in <img_path>。此时检查path是否指向了包含train/val/的图片根目录,以及 JSON 中file_name的相对路径是否与该目录一致。
  • 独立的model.val()不走自定义 trainer:只有训练中的验证经过COCOTrainer.build_dataset;单独调用model.val()会构建标准YOLODataset,扫描图片旁的.txt标签而找不到——它不会报错,而是把图片全部计为背景,验证跑完但所有指标为0,并给出No labels found in ...no labels found in detect set, cannot compute metrics without labels警告。如果你在训练之外单独验证模型,需要按同样的build_dataset覆盖方式子类化 validator,并通过model.val(validator=...)传入。

缓存陈旧陷阱。缓存的哈希基于 JSON 的文件大小和路径,而不是内容。任何"保持字节数不变"的编辑——微调坐标、翻转iscrowd、替换两个等长类别名——都会让陈旧缓存原样保留,训练会静默使用旧标注且无警告;原地替换某张图片同理不可见。编辑标注或原地替换图片后,删除对应的.cache文件。

限制与扩展

  • 仅覆盖对象检测。需要实例分割时,把 COCO 标注中的segmentation多边形数据写入每个标签字典的segments字段;姿态估计则写入keypoints。处理 segments 的参考实现见内置GroundingDataset的 源码。
  • fraction参数不生效。fraction在扫描图片目录时才应用,COCODataset跳过了这一步,代码里把它重置为1.0,即该数据集只接受完整数据集,不能按比例采样。
  • 无额外性能开销。JSON 只在首次训练时解析一次,之后从.cache文件加载;标注驻留内存,训练速度与标准 YOLO 训练一致。

如果之后需要永久性的 YOLO 格式标签(例如换用其他框架),改走 COCO to YOLO 转换指南中的convert_coco()一次性转换流程即可,自定义数据集代码不再需要。

下一步

  • cache_labels()扩展segmentskeypoints以支持分割与姿态任务;
  • 调参方面参考 Model Training Tips 中的超参数建议;
  • 更多训练参数与多 GPU 配置见 训练模式文档,数据集 API 细节见 YOLODataset 参考。

【免费下载链接】ultralyticsUltralytics YOLO26, YOLO11, YOLOv8 — object detection, instance segmentation, semantic segmentation, image classification, pose estimation, object tracking项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询