news 2026/5/28 2:03:36

008、YOLO 数据标注格式详解:YOLO txt、COCO JSON、VOC XML 互转工程方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
008、YOLO 数据标注格式详解:YOLO txt、COCO JSON、VOC XML 互转工程方案

008、YOLO 数据标注格式详解:YOLO txt、COCO JSON、VOC XML 互转工程方案

一个让我熬夜到凌晨三点的标注格式问题

去年做工业缺陷检测项目,甲方给了5000张PCB板图像,标注格式是VOC XML。我习惯用YOLOv5训练,直接拿脚本转成YOLO txt。训练完mAP只有0.23,我以为是模型问题,调了三天学习率、换backbone,毫无起色。第四天我随手打开一张标注图,发现标注框全偏了——坐标转换时忘记归一化,框都跑到图像外面去了。那一刻我对着屏幕骂了十分钟。

从那以后,我养成了一个习惯:拿到任何数据集,第一件事不是训练,而是可视化标注。今天这篇笔记,就把我踩过的坑、写过的脚本、总结的工程方案,一次性说清楚。

三种格式的核心差异(别被名字骗了)

YOLO txt:最“反直觉”的格式

YOLO的txt格式看起来简单,但坑最多。每行五个数字:class_id x_center y_center width height。注意,这四个坐标值全部是相对于图像宽高的归一化值,范围0~1。

我第一次写转换脚本时,直接用了像素坐标除以图像尺寸,结果发现YOLO的x_center是框中心点的相对坐标,不是左上角。这个细节让我多花了两个小时debug。

实际工程中容易翻车的地方

  • 坐标值必须归一化,但很多人忘记除以图像宽高
  • 类别ID从0开始,不是1
  • 一个txt文件对应一张图,文件名必须和图像名一致(不含扩展名)

COCO JSON:大而全但容易“撑死”

COCO格式用JSON存储,结构是imagesannotationscategories三个数组。每个标注对象包含bbox(x, y, width, height,像素坐标)、segmentation(多边形点集)、areaiscrowd等字段。

最坑爹的地方:COCO的bbox是[x_min, y_min, width, height],而很多人误以为是[x_center, y_center, w, h]。我见过一个开源项目,作者把COCO转YOLO时直接拿bbox前两个值当中心点,结果框全偏了。

VOC XML:最“啰嗦”但最直观

VOC用XML文件存储,每个文件对应一张图。结构清晰:<filename><size>(width, height, depth)、<object>(name, bndbox: xmin, ymin, xmax, ymax)。

容易忽略的点:VOC的坐标是绝对像素坐标,且是左上角和右下角。如果你要转成YOLO,需要先算中心点:x_center = (xmin + xmax) / 2 / widthy_center = (ymin + ymax) / 2 / height

互转工程方案(直接可用的代码逻辑)

方案一:VOC XML → YOLO txt(最常用)

importxml.etree.ElementTreeasETimportosdefvoc_to_yolo(xml_path,output_dir,class_names):""" xml_path: 单个VOC XML文件路径 output_dir: 输出txt目录 class_names: 类别名称列表,如['person', 'car', ...] """tree=ET.parse(xml_path)root=tree.getroot()# 获取图像尺寸——这里踩过坑,有的XML没有size字段size=root.find('size')ifsizeisNone:print(f"警告:{xml_path}缺少size信息,跳过")returnimg_w=int(size.find('width').text)img_h=int(size.find('height').text)# 生成对应的txt文件名base_name=os.path.splitext(os.path.basename(xml_path))[0]txt_path=os.path.join(output_dir,base_name+'.txt')withopen(txt_path,'w')asf:forobjinroot.findall('object'):name=obj.find('name').textifnamenotinclass_names:# 别这样写:直接跳过未知类别,会导致漏标# 建议打印日志并手动检查print(f"未知类别:{name},文件:{xml_path}")continueclass_id=class_names.index(name)bbox=obj.find('bndbox')xmin=float(bbox.find('xmin').text)ymin=float(bbox.find('ymin').text)xmax=float(bbox.find('xmax').text)ymax=float(bbox.find('ymax').text)# 计算YOLO格式坐标x_center=(xmin+xmax)/2/img_w y_center=(ymin+ymax)/2/img_h width=(xmax-xmin)/img_w height=(ymax-ymin)/img_h# 边界检查——防止坐标越界x_center=max(0,min(1,x_center))y_center=max(0,min(1,y_center))width=max(0,min(1,width))height=max(0,min(1,height))f.write(f"{class_id}{x_center:.6f}{y_center:.6f}{width:.6f}{height:.6f}\n")

踩坑记录:有些VOC标注的坐标是整数,但计算时一定要用float,否则除法会截断。另外,如果标注框超出图像边界(比如xmin < 0),一定要做clip处理,否则YOLO训练时会报错。

方案二:COCO JSON → YOLO txt(批量处理)

importjsonimportosdefcoco_to_yolo(coco_json_path,output_dir):""" coco_json_path: COCO格式的JSON文件 output_dir: 输出txt目录 """withopen(coco_json_path,'r')asf:coco_data=json.load(f)# 建立类别ID到类别名称的映射categories={cat['id']:cat['name']forcatincoco_data['categories']}# 建立图像ID到图像信息的映射images={img['id']:imgforimgincoco_data['images']}# 按图像ID分组标注annotations_by_image={}forannincoco_data['annotations']:img_id=ann['image_id']ifimg_idnotinannotations_by_image:annotations_by_image[img_id]=[]annotations_by_image[img_id].append(ann)forimg_id,annsinannotations_by_image.items():img_info=images[img_id]img_w=img_info['width']img_h=img_info['height']file_name=os.path.splitext(img_info['file_name'])[0]txt_path=os.path.join(output_dir,file_name+'.txt')withopen(txt_path,'w')asf:foranninanns:# COCO的bbox是[x, y, width, height]——别搞错顺序x,y,w,h=ann['bbox']# 计算中心点x_center=(x+w/2)/img_w y_center=(y+h/2)/img_h width=w/img_w height=h/img_h# 类别ID需要从COCO的category_id映射到0开始的索引# 这里假设你的类别列表和COCO的categories顺序一致# 如果不一致,需要手动建立映射表class_id=ann['category_id']-1# 常见做法,但别盲目用f.write(f"{class_id}{x_center:.6f}{y_center:.6f}{width:.6f}{height:.6f}\n")

重要提醒:COCO的category_id通常从1开始,而YOLO从0开始。但有些数据集category_id不是连续的,比如只有1、3、5。这时候直接减1会出错。正确做法:先建立category_id → 连续索引的映射字典。

方案三:YOLO txt → COCO JSON(反向转换)

importjsonimportosfromPILimportImagedefyolo_to_coco(txt_dir,image_dir,output_json_path,class_names):""" txt_dir: YOLO txt文件目录 image_dir: 对应图像目录 output_json_path: 输出COCO JSON路径 class_names: 类别名称列表 """coco_output={"images":[],"annotations":[],"categories":[{"id":i,"name":name}fori,nameinenumerate(class_names)]}annotation_id=1image_id=1fortxt_fileinos.listdir(txt_dir):ifnottxt_file.endswith('.txt'):continuebase_name=os.path.splitext(txt_file)[0]# 查找对应图像——这里假设图像扩展名是.jpg,实际需要遍历常见格式img_path=Noneforextin['.jpg','.jpeg','.png','.bmp']:candidate=os.path.join(image_dir,base_name+ext)ifos.path.exists(candidate):img_path=candidatebreakifimg_pathisNone:print(f"找不到图像:{base_name}")continue# 获取图像尺寸——用PIL读取,别用OpenCV,因为OpenCV读出来是BGRwithImage.open(img_path)asimg:img_w,img_h=img.size coco_output["images"].append({"id":image_id,"file_name":os.path.basename(img_path),"width":img_w,"height":img_h})withopen(os.path.join(txt_dir,txt_file),'r')asf:forlineinf:parts=line.strip().split()iflen(parts)!=5:continueclass_id=int(parts[0])x_center=float(parts[1])y_center=float(parts[2])width=float(parts[3])height=float(parts[4])# 反归一化到像素坐标abs_x=x_center*img_w abs_y=y_center*img_h abs_w=width*img_w abs_h=height*img_h# COCO的bbox是[x_min, y_min, width, height]x_min=abs_x-abs_w/2y_min=abs_y-abs_h/2coco_output["annotations"].append({"id":annotation_id,"image_id":image_id,"category_id":class_id,# 注意:这里直接用了YOLO的class_id"bbox":[x_min,y_min,abs_w,abs_h],"area":abs_w*abs_h,"iscrowd":0})annotation_id+=1image_id+=1withopen(output_json_path,'w')asf:json.dump(coco_output,f,indent=2)

这个脚本有个隐藏问题:YOLO的class_id可能和COCO的category_id不对应。比如你的YOLO类别是[‘cat’, ‘dog’],但COCO标准数据集里cat的id是1,dog是2。如果你直接拿class_id当category_id,转出来的JSON别人用COCO API读会报错。建议:在转换时加一个映射参数。

工程化建议(血泪教训)

1. 永远先做可视化验证

写一个简单的可视化脚本,把标注框画到图像上。不要相信任何转换脚本的输出,除非你亲眼看到框的位置是对的。

defvisualize_yolo_annotation(image_path,txt_path,class_names):importcv2 img=cv2.imread(image_path)h,w=img.shape[:2]withopen(txt_path,'r')asf:forlineinf:parts=line.strip().split()class_id=int(parts[0])x_center=float(parts[1])*w y_center=float(parts[2])*h box_w=float(parts[3])*w box_h=float(parts[4])*h x1=int(x_center-box_w/2)y1=int(y_center-box_h/2)x2=int(x_center+box_w/2)y2=int(y_center+box_h/2)cv2.rectangle(img,(x1,y1),(x2,y2),(0,255,0),2)cv2.putText(img,class_names[class_id],(x1,y1-5),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,255,0),1)cv2.imshow('Visualization',img)cv2.waitKey(0)

2. 建立统一的中间格式

如果你经常需要在不同格式间转换,建议定义一个中间格式(比如一个Python字典),所有转换都经过这个中间格式。这样新增一种格式时,只需要写两个转换函数(输入→中间,中间→输出),而不是N个两两转换。

3. 处理缺失和异常数据

实际项目中,你可能会遇到:

  • 标注文件存在但图像缺失
  • 图像存在但标注文件缺失
  • 标注框坐标超出图像边界
  • 类别名称拼写错误(比如’person’写成’persn’)

我的做法:写一个数据完整性检查脚本,先跑一遍,生成报告,再手动修复。不要指望一次性转换成功。

4. 版本控制你的转换脚本

我见过太多人:今天用脚本A转格式,明天用脚本B转回来,结果两个脚本的坐标定义不一致。把转换脚本放在项目仓库里,加上README说明,这样半年后你回来维护时,不会对着代码发呆。

个人经验总结

  1. 不要相信任何标注格式的“标准”。我遇到过COCO格式的bbox是[x_center, y_center, w, h]的变种,也见过VOC XML里坐标是归一化值的奇葩数据集。拿到数据先看几个样本,确认坐标含义。

  2. 转换脚本一定要做单元测试。写一个简单的测试:用已知坐标生成一个标注,转换后再转回来,看坐标是否一致。这个测试能帮你发现90%的bug。

  3. 批量转换时加进度条。用tqdm库,不然5000张图转完你都不知道卡在哪一步。

  4. 保留原始标注文件。转换后的文件只是副本,原始数据永远不要动。我见过有人直接覆盖了原始VOC XML,结果发现转换脚本有bug,数据全废了。

  5. 最后一条,也是最重要的:标注格式转换只是工具,不是目的。花太多时间在格式上,不如花时间在数据质量上。一个标注准确的VOC数据集,比一个格式完美但标注错误的数据集强一百倍。


下期预告:009、YOLOv8/v11 数据增强策略深度解析——从Mosaic到MixUp,哪些增强真的有用?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 2:03:05

手把手教你配置鼎捷T100二次开发环境:从模块、表格到函数的命名规则全解析(附实战示例)

鼎捷T100二次开发实战指南&#xff1a;从环境搭建到命名规范全解析1. 开发环境准备与基础概念鼎捷T100作为企业级ERP系统&#xff0c;其二次开发环境与传统软件开发有显著差异。初次接触的开发者在配置环境时往往会遇到各种"水土不服"的情况。我们先从最基础的开发工…

作者头像 李华
网站建设 2026/5/28 2:02:04

品牌推广怎么少走弯路:这 10 个误区别踩

很多人一提品牌问题&#xff0c;第一反应是 Logo 不够高级、颜色没选对、海报不够好看。但真实情况往往不是这样。多数品牌做不好&#xff0c;不是某一个设计细节翻车&#xff0c;而是定位、视觉、文案、体验和业务状态对不上。你说自己专业&#xff0c;页面却很乱&#xff1b;…

作者头像 李华
网站建设 2026/5/28 1:59:52

车规MCU功能安全设计全解析 | 全网独家复现篇 | 三种安全状态机制、SBC协同深度防御、助力ASIL-D最高安全合规、EPS/BMS/AEB全场景量产落地与工程化代码实现

目录 一、前言 二、车规MCU功能安全核心体系与ASIL-D合规刚需 2.1 车规MCU安全设计核心定位 2.2 传统MCU安全设计核心短板 2.3 MCU+SBC协同防御核心价值 三、车规MCU三大安全状态机制全解(核心涨点) 3.1 状态一:正常运行态 (NORMAL_STATE) 3.1.1 状态定义 3.1.2 核…

作者头像 李华
网站建设 2026/5/28 1:57:47

RAG系列:#5 RAG中的11种分块策略

原文&#xff1a;https://mp.weixin.qq.com/s/Yax05qsVj1tXi77za8Wm2g 欢迎关注公zh: AI-Frontiers RAG往期文章推荐 RAG效果差&#xff1f;7个指标让你的准确率大幅提升 RAG评测完整指南&#xff1a;指标、测试和最佳实践 收藏&#xff01;RAG核心工具大全: 7大解析工具…

作者头像 李华