ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

目标检测数据集制作全指南:从标注到VOC/COCO/YOLO格式转换避坑

2026/10/1 4:15:39 拓冰建站 浏览量
目标检测数据集制作全指南:从标注到VOC/COCO/YOLO格式转换避坑 做目标检测项目真正消耗时间的事情往往不是调模型而是做检测数据集。图片收集、标注、格式转换这三步每一个都能让人踩坑标了一周发现训练框架只认COCO手里全是VOC的XML转换脚本跑完训练直接loss爆炸一查是类别ID从0还是从1开始弄错了再要么就是框全部偏移半截看着像原图坐标没归一化。这篇把整条流程从头到尾捋一遍数据收集、标注工具选型、VOC/COCO/YOLO三种格式的底层差异以及它们之间的互转脚本和验证方法。给准备做自定义检测任务的开发者一份能直接照着干的操作参考。1. 为什么明明有现成脚本格式转换还是总出错先说一个很多人没想通的问题VOC、COCO、YOLO都是目标检测的标注格式网上转换脚本一搜一大把为什么自己做还是会翻车因为多数脚本只告诉你“转过去”没告诉你背后三种格式的“思维模式”完全不同。不理解差异转过去也是错着用。1.1 三种格式到底差在哪不是只有“坐标表示”不同VOC格式来自PASCAL VOC挑战赛是XML文件每个目标一个object节点框用xmin, ymin, xmax, ymax四个像素坐标表示。这种格式最接近人类的自然直觉一个矩形的左上角和右下角。COCO格式来自微软的COCO数据集用单个JSON文件管理所有图片和标注框用x, y, width, height表示即左上角坐标加宽高。YOLO格式更特殊每个图片对应一个txt文本文件框用归一化后的中心点坐标加宽高表示。我经常用生活里的东西来类比VOC像手写的物品清单每个物品的摆放位置写得清清楚楚COCO像一套标准档案柜所有图片是索引卡片标注按条目归档找东西效率高YOLO像贴在快递箱上的标签简短、归一化、专门给特定流程用的。三种格式没有绝对的谁好谁坏只有合不合适。VOC适合人工阅读和检查COCO适合大规模数据集管理YOLO适合Ultralytics系训练框架直接加载。这个差异看起来只是表示形式不同实际影响很大。比如VOC里一眼能看出某个框是不是超出图像边界COCO的JSON要写脚本统计才能发现而YOLO的txt文件里全是0到1之间的小数光看文件根本没法判断框具体在哪。格式转换从来不只是一个字段映射问题背后是“坐标系”、“类别ID规则”、“文件组织方式”三重转换。1.2 一套流程打通的正确顺序先定主格式再谈互转做数据集最忌讳一上来就“我最终要训练YOLO所以直接用YOLO格式标注”。YOLO的txt文件不好人工检查漏标错标很难发现。更合理的流程是先用一种方便检查和编辑的格式完成标注再把它作为主格式统一转换成训练需要的格式。我自己习惯用VOC或者COCO作为主格式。如果是个人小项目推荐VOCXML结构直观LabelImg这类工具直接导出出了问题可以用文本编辑器打开看。如果是团队协作或者标注数据量大推荐COCO一个JSON文件管理全部标注写脚本批量修改、统计、合并都方便。确定主格式后所有训练用的格式都从主格式派生这样能保证“一个事实来源”不会出现改了一版标注忘了同步的情况。整个流程的顺序很关键收集图片、清洗筛选、划分训练集验证集、统一标注、导出主格式、转换成训练格式、可视化验证。顺序一旦乱掉后期返工成本极高。比如先划分数据集再标注可以避免训练集和验证集出现重复图片先统一标注规范再开工可以避免多人标注口径不一致导致的返工。1.3 从几个真实场景看数据集的来源差异我最近关注到的热词里鸟类目标检测、开关闭合检测、电力红外数据集、传送带异物检测、车辆检测BDD100这些都属于典型的自定义检测场景。它们的共同点是公开数据集很难完全覆盖业务场景。鸟类检测要面对复杂的野外背景和极大的目标尺寸差异开关闭合检测要覆盖不同光照、不同开关面板型号电力红外数据集的单通道图像和普通RGB差异很大传送带异物检测则要处理运动模糊、遮挡和实时性要求。这些场景下自采数据往往比网上找公开数据更关键。不同场景的数据收集侧重点也不同。室外场景优先考虑天气、光照、季节覆盖工业场景优先考虑不同产线、不同机位角度、不同型号红外场景要确认标注时看的图像和推理时输入的一致性。这些差异直接影响标注规范和后续模型泛化能力。理解自己场景的数据特点比拿着通用流程硬套重要得多。2. 数据收集好数据集是一半工程一半运气很多人做数据集第一步就错了下载几个公开数据集随便挑一部分图片就开始标注。结果训练出来在测试集上指标还行一上真实环境就崩。数据收集阶段的核心目标是让样本分布尽量接近真实推理环境。2.1 业务实拍数据优先公开数据做补充我的经验法则很简单尽量用真实业务场景下采集的数据。所谓真实场景指的是模型实际部署时会遇到的摄像头位置、角度、分辨率、画质、环境复杂度和目标形态。公开数据集适合做预训练、补充背景多样性、增加罕见角度样本但永远不要让它成为主力。举个例子做一个传送带异物检测数据集如果只用公开的工业检测图片训练部署到实际产线上会发现光照变化、振动模糊和不同异物形态让模型完全失效。正确的做法是架设一台与实际部署位姿一致的相机采集不同班次、不同物料、不同光照下的视频再抽帧成图片。鸟类检测同理网图大多是清晰大图但实际监控画面里的鸟可能只有几十像素不专门采集远距离场景模型根本学不会小目标。这不是说公开数据没用。公开数据可以用来做“底座”比如先用大规模公开数据集做预训练再用自采数据微调。但如果目标是特定场景的检测器自采数据必须占主导。2.2 质量筛选的标准宁缺毋滥采集回来的图片不能全进标注流程。我一般按以下几个标准筛选清晰度运动模糊、失焦、严重压缩噪点的图片直接淘汰。重叠度视频抽帧时相邻帧高度相似需要去重否则训练集和验证集之间会出现“近重复图像”评估结果虚高。完整性图片里目标主体严重残缺、几乎不可辨识的单独处理或删除。代表性如果场景的摄像头是俯视的就不要用大量平视角度的图片填充样本比例差异过大会误导模型学习。空图处理完全没有目标的“背景图”也有价值可以作为负样本或者背景多样性的补充但不要混在正常标注文件里。YOLO里一张没有目标的图对应的txt空文件训练时就是纯背景样本可以用但要在data.yaml或者训练配置里确认是否包含。这个阶段我还会统一图片格式和文件名全部转成jpg、按语义命名加序号。文件名里不要带空格和中文这个习惯能省下之后做格式转换时的大量路径问题。2.3 类别分布、训练验证划分和负样本怎么处理数据收集阶段就要想清楚类别分布。检测任务的类别不平衡非常常见传送带异物检测里“正常物料”的负样本可能占90%真正的异物类别只占很小比例。这种情况下有两个选择一是尽量扩大异物类别的采样提高少数类占比二是在训练时设置类别权重或者使用focal loss这类损失函数。做数据集时能做的就是尽量让每个类别都有足够的正样本。单类目标检测我的经验是起步至少几百个标注目标目标形态多样、背景复杂的情况下最好上千。多类检测每类尽量不少于几百个实例更稳妥。训练集、验证集的划分要在标注前完成。先在图片层面划分再分别标注。给每个类别做一份“标注清单”清单里包含图片ID、目标数量、大致目标大小这样划分训练集和验证集时能保证类别分布基本一致。负样本单独建目录或者用空标注文件标记不要直接塞进某个类别的目录。一个容易忽略的细节训练集和验证集不能有来自同一段视频连续帧的图片否则验证集算出来的mAP会虚高。视频抽帧时先按时间窗口粗筛把连续帧放在一起再按场景断开后划分。3. 标注工具怎么选标注规范怎么写标注工具选型取决于项目规模、团队协作方式、以及你是否需要实例分割或者多边形标注。工具选对了效率差非常多。3.1 主流标注工具横向对比我实际用过、也看团队用过的工具主要有下面这几个各有优势工具适用场景导出格式核心优势LabelImg个人单机快速标注VOC XML、YOLO txt轻量、上手快、VOC格式兼容最好Labelme需要多边形/实例分割标注JSON可转COCO/VOC支持多边形、适合分割任务CVAT团队协作、Web平台VOC、COCO、YOLO、CVAT自有格式多人并行、项目管理、自动标注插件X-AnyLabeling个人自动辅助标注VOC、COCO、YOLO等集成检测/分割模型可以预标注后人工修正Roboflow在线协作平台YOLO、COCO、TFRecord等内置增强、格式转换、版本管理个人项目如果只是画矩形做检测LabelImg是最省心的选择。它界面简单打开图片画框选类别能直接导出VOC XML或者YOLO txt。要注意LabelImg默认输出XML时图片路径是绝对路径多人协作时容易出问题建议导出后统一用脚本处理成相对路径。团队协作场景我更推荐CVAT。它做完一轮标注后能自动把同一样本分配给不同人抽检还支持用已有模型做预标注人工只负责修正。这套流程对工业场景的数据集制作非常有用等于是先用一个粗糙模型跑一遍再人工精修标注成本能降一半以上。3.2 标注规范5条铁律标注规范必须在开工前写清楚尤其是多人团队。我自己的标注手册里固定写这几条框要贴合目标边缘不要包含过多背景。目标占画面很小的时候宁可框得紧一点。遮挡目标标注可见部分不要硬框一个包含大量遮挡物的框。如果大部分都看不见可以漏标不要制造污染框。边界截断目标照常标注框与图像边缘对齐即可。类别名严格统一大小写敏感。Bird和bird在代码里是两个类别这种错误在标注阶段就能杜绝。每个目标只标一次一个对象出现两个重叠框是最常见的低级错误。还有一个经验标注的时候给每个类别分配一个固定快捷键减少鼠标点击。当天标完批量检查一遍别攒到最后一起看。3.3 标注质量抽检方案标注完不是直接进入格式转换必须先做质量抽检。抽检不是随机看几张我建议按类别分层抽样每个类别至少抽20张图标注者之间交换检查。重点看框的贴合度、漏标比例、类别误标比例。漏标比错标更致命。模型会把漏标的目标当成背景去学直接影响召回率。抽检时把标注框隐藏先人工点一遍图里的目标再打开标注框对比看漏了谁。这个操作比较费时间但值得做。还可以写一个统计脚本检查每张图的标注数量分布、框面积分布、框是否越界。发现异常的图单独拉出来人工复核。4. 三种格式的详细拆解VOC、COCO、YOLO理解了格式底层结构转换脚本才能写得稳。这一章把三种格式彻底讲透后面的代码才能看得明白。4.1 VOCXML、一种容易“看穿”的格式VOC的目录结构一般是VOCdevkit/ ├── VOC2007/ │ ├── JPEGImages/ │ ├── Annotations/ │ ├── ImageSets/ │ │ └── Main/ │ │ ├── train.txt │ │ └── val.txtJPEGImages放图片Annotations放XML标注文件ImageSets/Main放划分文件里面每行是一个不带扩展名的图片文件名。XML的核心结构长这样annotation folderJPEGImages/folder filename000001.jpg/filename size width1920/width height1080/height depth3/depth /size object namecat/name difficult0/difficult bndbox xmin100/xmin ymin120/ymin xmax380/xmax ymax420/ymax /bndbox /object /annotationVOC XML里最容易记错的就是size必须和实际图片分辨率一致。如果标注时用的是缩放后的预览图写进XML的坐标和尺寸就不匹配转换出来的YOLO格式全是带偏移的。另一个点是difficult字段1表示这个目标很难辨识很多转换脚本会直接跳过。是否保留difficult目标要在转换前想清楚我一般选择丢弃训练时更干净。4.2 COCOJSON的灵活性和它的3个易错点COCO格式把一个数据集的所有信息塞进一个JSON文件最外层通常包含info、licenses、images、annotations、categories五个字段。核心是后面三个{ images: [ {id: 1, file_name: 000001.jpg, width: 1920, height: 1080} ], annotations: [ {id: 1, image_id: 1, category_id: 3, bbox: [100, 120, 280, 300], area: 84000, iscrowd: 0} ], categories: [ {id: 1, name: cat, supercategory: none} ] }COCO有三个特别容易踩的坑。第一category_id从1开始不是从0开始因为0常被当作背景。第二bbox的格式是[x, y, width, height]其中x, y是左上角坐标不是中心点。第三annotations里每条的id要求全局唯一且通常从1开始递增如果重复或者从0开始pycocotools加载时行为不可预期。另外COCO的area字段在标准数据集里是分割区域的真实面积但如果只用矩形框做检测直接用width * height算也可以。做实例分割再需要精细的area就得从polygon计算了。4.3 YOLO看起来最简单坑在归一化和类别IDYOLO格式的标签是每个图片对应一个txt文件文件名和图片名相同扩展名是.txt。每一行表示一个目标class_id x_center y_center width height其中坐标全部是相对于图片宽高的归一化数值取值在0到1之间。中心点坐标是(xmin xmax) / 2 / image_width这样的形式。同时Ultralytics的YOLO训练通常还需要一个data.yaml文件定义类别名、类别数量和路径path: ../datasets/your_dataset train: images/train val: images/val nc: 2 names: [cat, dog]YOLO格式最大的坑就在类别ID从0开始而COCO从1开始。所以COCO转YOLO时category_id要减1VOC转YOLO时classes列表的索引要严格对应训练配置里的names顺序。这个减1操作漏了训练时类别错位损失函数会乱套。还有一个很容易出错的地方txt标签文件里的坐标要求严格在0到1之间但很多原始标注框截断到图片边缘后计算出的归一化坐标依然可能因为浮点误差略大于1或者因为坐标是负的而小于0。所以转换时最好做一次clip操作。4.4 三种格式参数对照速查表对比项VOCCOCOYOLO文件组织XML文件 图片文件单个JSON文件每图一个txt文件框表示xmin, ymin, xmax, ymaxx, y, width, heightx_center, y_center, width, height坐标系像素坐标像素坐标归一化坐标取值0~1类别ID起始由程序决定10图片尺寸来源XML里size字段JSON里image字段依赖txt本身需从图片读取空标注表示无object节点或文件不存在对应image没有annotations空txt文件常用框架老牌检测框架、VOC评测脚本Detectron2、MMDetection、pycocotoolsUltralytics YOLO、Darknet这张表建议存下来写转换脚本的时候对照着看能避免一半以上的低级错误。5. 格式互转实战核心代码与验证方法工具和规范都定了主格式也选好了接下来进入最关键的实操环节。以下代码基于Python依赖标准库加一个opencv-python任何常规深度学习环境都能跑。5.1 VOC转YOLO标注归一化的完整脚本VOC转YOLO是最常见的需求。核心逻辑就三步读XML、取坐标、归一化写txt。import os import glob import xml.etree.ElementTree as ET CLASSES [cat, dog, bird] # 顺序必须和训练yaml里的names一致 def voc_to_yolo(xml_dir, labels_dir): os.makedirs(labels_dir, exist_okTrue) for xml_path in glob.glob(os.path.join(xml_dir, *.xml)): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) if img_w 0 or img_h 0: print(f[skip] {xml_path} 图像尺寸为0) continue filename os.path.splitext(os.path.basename(xml_path))[0] out_path os.path.join(labels_dir, filename .txt) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) xmin max(xmin, 0) ymin max(ymin, 0) xmax min(xmax, img_w) ymax min(ymax, img_h) if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) voc_to_yolo(path/to/Annotations, path/to/labels)这段代码有两个关键点需要说明。一是类别顺序CLASSES里列表的索引就是YOLO的class id必须和后续训练时data.yaml里的names顺序严格一致否则类别就对不上号了。二是clip操作原始标注可能有轻微越界比如坐标是-2或者1930直接归一化会产生负数或大于1的值后面YOLO训练会警告甚至报错所以先clip到图片范围内再计算。5.2 VOC转COCOJSON结构构建细节VOC转COCO脚本不复杂但JSON结构的每一个字段都不能省。下面是一个可以直接用的版本import json import glob import os import xml.etree.ElementTree as ET def voc_to_coco(xml_dir, json_path, classes): images [] annotations [] categories [ {id: i 1, name: name, supercategory: none} for i, name in enumerate(classes) ] ann_id 1 for img_id, xml_path in enumerate(glob.glob(os.path.join(xml_dir, *.xml)), start1): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) width int(size.find(width).text) height int(size.find(height).text) images.append({ id: img_id, file_name: filename, width: width, height: height, }) for obj in root.findall(object): name obj.find(name).text if name not in classes: continue category_id classes.index(name) 1 box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) w xmax - xmin h ymax - ymin if w 0 or h 0: continue annotations.append({ id: ann_id, image_id: img_id, category_id: category_id, bbox: [xmin, ymin, w, h], area: w * h, iscrowd: 0, }) ann_id 1 coco { info: {description: converted by voc_to_coco}, images: images, annotations: annotations, categories: categories, } with open(json_path, w) as f: json.dump(coco, f, indent2) voc_to_coco(path/to/Annotations, output.json, CLASSES)这里注意几个细节image_id我直接用了图片在列表里的序号而annotation_id是单独的全局计数器两者千万不要混用。COCO的file_name建议只保留文件名不要带JPEGImages/这类前缀后面训练时路径拼接更灵活。如果XML里存在difficult1/difficult的目标这个脚本没有特殊处理需要的话加个判断直接跳过。5.3 COCO转YOLO注意图片尺寸从哪里拿COCO转YOLO恰恰是最容易出错的逆向流程因为COCO的bbox是像素坐标YOLO要的是归一化中心点坐标而且COCO的尺寸信息在JSON的images字段里不在XML里。import json import os def coco_to_yolo(json_path, output_dir): with open(json_path) as f: coco json.load(f) os.makedirs(output_dir, exist_okTrue) cat_id_to_cls_id {cat[id]: idx for idx, cat in enumerate(coco[categories])} anns_by_image {} for ann in coco[annotations]: anns_by_image.setdefault(ann[image_id], []).append(ann) for img in coco[images]: img_id img[id] filename os.path.splitext(os.path.basename(img[file_name]))[0] width img[width] height img[height] anns anns_by_image.get(img_id, []) lines [] for ann in anns: x, y, w, h ann[bbox] cx x w / 2.0 cy y h / 2.0 cx / width cy / height w / width h / height cls_id cat_id_to_cls_id[ann[category_id]] lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(output_dir, filename .txt), w) as f: f.write(\n.join(lines)) coco_to_yolo(annotations.json, labels)这个脚本里最关键的是拿到width和height。有些偷懒的COCO转换脚本会从文件系统读图片尺寸但更好的做法是从JSON的images字段读因为那是标注时的基准尺寸。如果标注时的图片尺寸和实际图片文件尺寸不一致以JSON里的为准否则转换后框会漂移。还要注意cat_id_to_cls_id这个映射是必须的因为COCO的category_id从1开始而YOLO的class id从0开始。5.4 可视化校验一张图验证全部转换结果转换完最怕的就是“看起来转了实际上错了”。可视化校验是最直接有效的验证方式。我通常用它画YOLO格式的标签import cv2 import os def draw_yolo_labels(image_path, label_path, classes, output_path): img cv2.imread(image_path) if img is None: print(fcannot read image: {image_path}) return h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:5]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) color [(0, 255, 0), (0, 0, 255), (255, 0, 0), (255, 255, 0)][cls_id % 4] cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, classes[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) cv2.imwrite(output_path, img) classes [cat, dog, bird] draw_yolo_labels(images/000001.jpg, labels/000001.txt, classes, check_000001.jpg)把转换后的txt标签绘制到原图上和原始标注对比一眼就能看出有没有偏移。我检查时会特别注意三条框是否贴住目标边缘、类别文字是否正确、是否有明显的漏标。如果批量转换可以写个循环一次画几十张保存到check目录快速翻看。6. 常见问题排查与实操避坑做数据集制作和格式转换几乎每个人都会遇到下面这类问题。我把它们按现象归类方便快速定位。6.1 “转换后框全跑偏”类问题的排查路径框跑偏的原因说白了就是坐标变换时用错了基准。最常见的有三种一是VOC的xmax, ymax被当成宽高直接使用导致框的尺寸错误二是COCO转YOLO时忘记把x, y转换成中心点直接用左上角当中心点三是归一化时用错了图片尺寸把标注时的预览小图尺寸和原始大图尺寸混用了。排查时我一般先把坐标打印出来手算一遍比如从VOC转YOLO工具脚本里(xmin xmax) / 2 / img_w这一步输出是否落在0到1之间。如果出现负数或者大于1说明图片尺寸获取有问题或者XML里的size字段本身和图片不一致。一条条检查不要猜。还有一种隐蔽情况COCO转YOLO时COCO JSON里的bbox有时不是严格的[x, y, w, h]有些工具导出的数据可能把x, y记录成了中心点。转之前先随机抽几条打印看看确认数据规范再批量转换。6.2 类别ID、图片路径、文件名这些细节坑类别ID是重灾区。COCO的category_id从1开始YOLO的class_id从0开始中间差1。很多脚本转换后不检查直接拿去训练结果第一个类别的目标全被当成第二个类别。有一个笨但有效的方法转换后随机挑选几个类别目标打印class_id和类别名逐个人工核对。图片路径问题也常见。YOLO训练时data.yaml里的train和val路径如果用绝对路径换机器或者换项目目录就会失效。我习惯在训练脚本里通过项目根目录动态拼接路径或者把data.yaml写在数据集目录下用相对路径引用。文件名里的坑更加隐蔽如果图片是000001.jpg标签文件必须是000001.txt多一个空格、大小写不一致都不行。Windows下批量重命名产生的1这类后缀也要提前清理。养成一个习惯标注前先把所有图片统一重命名为纯数字或者纯英文小写。6.3 训练前的最后一轮自查清单格式转换完、开始训练前花五分钟跑一遍自查能省下后面调试训练的好几个小时图片目录、标签目录的文件名一一对应没有多余的孤儿文件。每个txt文件的行数大于0除非你有意保留负样本空文件格式是cls cx cy w h。label文件中所有坐标都在0到1之间没有负数、没有大于1。类别ID都小于data.yaml里的nc没有越界。随机画了至少10张图框的位置、类别名都正确。如果用了预训练模型权重确认权重对应的类别数量和你自己的数据集类别数量一致不一致时要么裁掉分类头要么从头训练。这些检查听起来琐碎但每一个坑我都真金白银踩过。尤其最后一条YOLO训练时如果nc不匹配预训练权重有时会报错有时不报错但loss在训练初期就剧烈波动最后模型输出类别错乱。与其花时间Debug训练过程不如把检查做在前面。7. 一点个人体会写了这么多最后分享一个我自己养成的工作习惯无论是个人小项目还是团队协作我都会建一个“数据集主目录”里面至少包含原始图片、主格式标注、训练格式标注、可视化检查四个子目录。所有转换脚本都从主格式标注目录读禁止任何人直接修改训练格式目录里的文件。这样即使中间转换出了问题也能随时回到主格式重新生成不会越改越乱。这种方式帮我省掉的返工时间比我做数据集标注的时间还多。标注和格式转换看起来是“脏活累活”但恰恰是检测项目里最值得花心思的部分。数据对了模型训练再怎么折腾都有兜底数据错了再先进的模型结构也只是在错误的前提上打转。希望这篇能帮你少踩一些我当年踩过的坑。