ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLO数据集制作全攻略:COCO/VOC/LabelMe转YOLO格式与训练前检查

YOLO数据集制作全攻略:COCO/VOC/LabelMe转YOLO格式与训练前检查 1. 先想明白一个问题YOLO到底要的是什么样的数据做目标检测训练很多人一上来就找模型、调参数结果数据没整明白训练一个晚上出来的mAP惨不忍睹然后开始怀疑网络结构、怀疑学习率、怀疑显卡。实际情况是一大半问题都出在数据集上。yolo的代码仓库能跑通不代表你的数据能训练出好东西。训练前花半天整理数据比训练后花三天找bug划算得多。先说一个最容易被忽略的底层事实YOLO自己并不直接消费图片和“画好的框”它默认读取的是图片文件 同名的txt标签文件。txt里面每一行代表一个目标内容是class_id x_center y_center width height这里的四个坐标值全部是归一化到0~1之间的小数不是像素值。x_center和y_center是目标中心点相对图片宽高的比例width和height是目标框宽高相对图片宽高的比例。为什么YOLO要用这种格式因为它把检测当成回归问题网络输出就是一组归一化坐标。训练时读入图片后模型不关心你的原图是1920x1080还是640x640它只需要知道“这个目标中心在图片的哪个相对位置、占了多大比例”。归一化之后不同分辨率图片才能在一个batch里同时喂进去mosaic增强、随机缩放这些操作才不会把坐标搞乱套。每一个txt文件必须和一张图片同名例如images/train/000001.jpg labels/train/000001.txt训练时图片在images目录标签在labels目录目录结构一般是dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── 000002.jpg │ └── val/ │ ├── 000100.jpg │ └── 000101.jpg └── labels/ ├── train/ │ ├── 000001.txt │ └── 000002.txt └── val/ ├── 000100.txt └── 000101.txtdata.yaml里写清楚train、val路径和类别列表就完事了。整个流程听起来很简单但现实世界里的标注数据不会天然长这样。你手里的标注可能是COCO的json可能是VOC的xml可能是LabelMe画出来的json甚至可能是VisDrone这种基于VOC风格的无人机数据集。这些格式各有各的组织方式转成YOLO的txt之前必须先理解它们各自的“脾气”。这一篇就把转换、检查、划分这三个环节一次讲透。2. COCO、VOC、LabelMe转YOLO的实操细节2.1 COCO格式先理解json的“骨架”COCO格式的核心是一个大json文件。训练集一个json验证集一个json里面主要包含两个数组images和annotations。images数组里每一行描述一张图关键字段是id、file_name、width、height。annotations数组里每一行描述一个标注目标关键字段是image_id、category_id、bbox、segmentation。COCO的bbox长这样[x_top_left, y_top_left, width, height]注意COCO的bbox给的不是中心点而是左上角坐标加上宽高。转成YOLO格式时需要做一次换算x_center (x_top_left width / 2) / image_width y_center (y_top_left height / 2) / image_height box_width width / image_width box_height height / image_height这段代码在任何COCO转YOLO脚本里都是核心。转换之前要先读取categories数组把每个category的id映射成连续的从0开始的类别索引。很多新手会直接拿COCO的category_id当YOLO的class_id用这绝对是坑。COCO原始类别id从1开始而且有缺失比如某些数据集里id1id3id4没有id2。YOLO的class_id必须是从0开始的连续整数否则训练时类别数量对不上损失函数直接乱套。如果做实例分割任务想训练YOLOv8-seg这类模型那还需要把segmentation里的多边形坐标转成YOLO分割格式。YOLO分割标签同样是一行一个目标格式是class_id x1 y1 x2 y2 x3 y3 ...坐标依然是归一化的点的顺序任意但建议顺时针排列。COCO的segmentation字段通常是一个多边形的点列表转的时候直接把每个坐标除以对应的图片宽高就行。如果一个目标有多个多边形比如一个目标被分成了几块需要先合并成一个多边形再转换不然训练时mask解析会出问题。多边形合并可以用shapely的union操作这是最省事的方法。2.2 VOC格式xml里藏着“左上右下”VOC数据集的标注是每个图片对应一个xml文件。xml结构比较直观annotation filename000001.jpg/filename size width1920/width height1080/height /size object namecar/name bndbox xmin100/xmin ymin150/ymin xmax800/xmax ymax600/ymax /bndbox /object /annotationbndbox里是左上角和右下角的像素坐标直接可以用x_center ((xmin xmax) / 2) / image_width y_center ((ymin ymax) / 2) / image_height box_width (xmax - xmin) / image_width box_height (ymax - ymin) / image_height这里有个容易踩的坑VOC的xml里有些标注框的xmax和ymin会比图片尺寸还大或者xmin/ymin出现负值。这通常是因为标注工具或人工操作导致的越界。UItraLytics YOLO在训练时会自动把越界坐标裁剪到边界内但如果你用的是比较严格的加载器坐标越界可能导致计算出的宽高为负最终训练报错。所以转换脚本里最好加个clip操作把所有坐标约束在图片范围内同时打印出越界信息方便回头修数据。VisDrone2019这类无人机数据集也常被拿来训YOLO。它的原生标签就是VOC风格xml但有两个明显特点第一目标普遍比较小密密麻麻的汽车和行人很多框只有十几个像素第二类别里有ignored区域需要单独过滤掉。转YOLO时建议把“ignored”这类不参与训练的框直接不要输出只保留你实际需要的类别。小目标数据训练时YOLO默认的输入尺寸可能需要调大比如从640改成960或者1280不然小目标在下采样之后直接在feature map上消失了。2.3 LabelMe格式多边形标注怎么转成矩形框LabelMe是目前最常用的手动标注工具之一它导出的每个图片对应一个同名json。json里存的是多边形点坐标核心结构是shapes数组每个元素包含label和pointspoints就是一串[x, y]像素坐标。热词里关于LabelMe的搜索很多比如“labelme多边形json转txt”、“labelme 5.3.1”、“labelme安装使用教程”说明大量新人卡在安装和转换这两步。安装LabelMe没太多玄学Python环境里直接pip install labelme就行。启动命令是labelme打开界面后左侧是图片列表中间是标注区。如果标注矩形框直接点create rectangle标注多边形就点create polygons。保存后就是json文件。LabelMe的json转YOLO分两种情况。如果你只做目标检测不关心精细轮廓那就从多边形的points里算出外接矩形取所有点x坐标的最小值和最大值作为xmin和xmax取所有点y坐标的最小值和最大值作为ymin和ymax然后按照和VOC相同的公式转成YOLO的归一化中心点加宽高。如果你做实例分割那更简单直接把points里的每一个坐标除以图片宽高得到归一化坐标拼成class_id加上一串点的格式就行。实际操作中我发现一个细节LabelMe标注时如果你的目标是不规则物体比如积水区域、破损部件用多边形画的轮廓非常精细但从多边形求外接矩形会引入很多背景噪声导致目标框过大检测精度反而受影响。这种情况下有两种处理思路一是接受外接矩形适用于目标整体形状接近矩形、或者你只关心存在性不关心轮廓的检测任务二是直接用YOLO分割模型保留精细轮廓做实例分割。两种方案对应完全不同的标签转换路径动手前先想清楚你要检测还是分割。3. 训练前必做的5项数据检查3.1 完整性检查图片和标签必须一一对应看到这里很多文件已经通过脚本转换好了但直接拿去训练之前还有一堆检查要过。第一个检查是文件对应关系。把images目录和labels目录下的文件名分别读出来取交集找出哪些图片没有对应的txt、哪些txt没有对应的图片。这种情况比你想象中常见得多。我见过一个团队标了三天数据最后发现图片是从手机直接导出的复制到电脑时有些文件名后面多了一个“-1”和标注工具里自动生成的json文件名对不上。还有一次用的是中文文件名系统之间迁移后编码变了labelme打开时能找到图片但脚本一读就乱码。写检查脚本时建议用路径的stem做匹配也就是去掉扩展名之后的纯文件名。图片格式可能是.jpg、.jpeg、.png、.bmp标签永远是.txt不能直接拿整个文件名对比。如果有缺文件的情况可以用一个简单的脚本把缺失清单打出来再考虑是重新标注还是放弃那些图。一般来说训练集里缺标签的图片应该直接隔离出去不要抱着侥幸心理留在目录里不然某个batch随机采样到这张图加载器找不到标签轻则警告重则中断训练。3.2 合法性检查坐标、类别、编码都要验第二个检查是标签内容本身的合法性。我建议把转换完的所有txt文件读一遍逐行检查class_id是否在0到类别总数减1之间出现越界说明类别映射表写错了每个坐标值是否都在0到1之间这个我见过很多次像素值忘了归一化直接拿原图尺寸写进了txt训练出来loss怎么都降不下去width和height是否大于0如果出现负值多半是原始标注里左上和右下坐标顺序颠倒了txt文件编码是否为UTF-8无BOMWindows下导出的文件有时候是GBK读进来第一行会带不可见字符导致yaml里类别数量和txt中实际类别对不上。这里有个实用技巧把所有txt文件当成纯文本批量读一遍用正则表达式匹配每一行的格式是否满足“一个整数加四个浮点数”的结构不满足的行打印出文件名和行号。这一步能在几分钟内扫完几千个文件比训练到一半报错快多了。3.3 类别平衡检查少样本类别会成为训练盲区第三个检查是类别分布。把整个数据集里每个类别的目标数量统计出来画一个柱状图你会发现绝大多数数据集都不平衡。比如无人机视角下的行人检测汽车类别可能有几万个实例摩托车就只有几十个实例。直接在YOLO上训练这类数据几十个实例的类别大概率学不好因为模型看到它的机会太少了。处理方法有几个层次。最基础的是不做任何处理靠YOLO自带的mosaic增强和随机翻转去硬学适合类别差距在10倍以内的情况。差距更大就要考虑过采样少数类别图片、复制增强、或者使用带有类别权重或焦点损失的改进版YOLO比如YOLOv8本身支持cls_loss_weight参数调整分类损失权重YOLOv9和YOLO11也保留了类似机制。这个阶段的目标不是消除不平衡而是让少数类别至少能出现在训练集中足够多次模型才有机会学到它的特征。3.4 内容抽样检查用肉眼看一遍标注质量第四个检查可能最不起眼但最救命随机抽样部分图片把标签框可视化出来用肉眼看一遍。这个步骤很多人嫌麻烦但像积水标注这种场景水面反光、边界模糊标注员很容易把阴影当成积水框进去或者只框了积水的一部分。模型学到的是“阴影区域”还是“积水区域”完全取决于你的标注边界怎么画的。实际操作时可以用OpenCV直接把txt标签画回图片上脚本也不复杂读图片读同名txt把归一化的坐标换算回像素坐标用rectangle画框用putText标上类别名最后输出到另一个目录。然后随机挑个几十张图翻一遍重点关注三类图片小目标多的图、目标密集的图、以及你场景中最难判断的目标。发现标注问题后及时和标注人员确认标准统一标注协议而不是自己偷偷改几张图。标注不一致对训练的影响是系统性的改单张图救不回来。我建议在正式标注之前就定一份简单的标注规范里面明确几条规则遮挡超过多少比例算一个实例、目标紧贴着图片边缘时怎么处理边界、几个目标连在一起时按一个框还是多个框。这些规则看起来琐碎但它们决定了训练出来的模型对边界模糊场景的鲁棒性。像分不清“旁边那辆车”和“这辆车”这种问题训练时模型很难靠loss自己纠回来。3.5 针对性检查结合你的场景“开小灶”最后一类检查是场景相关的专项检查。如果你是用VisDrone转的无人机数据检查重点应该是小目标框是否窄到只有几个像素这种框在resize到640之后基本就消失了需要在训练时调高输入分辨率或者使用SAHI这类切片推理工具。如果你是把DOTA数据集从旋转框转成水平框用于mmrotate或YOLO那要检查旋转框转水平框后框之间是否大量重叠重叠严重会影响NMS后处理效果。如果你做的是垂直领域的积水或路面病害检测重点关注不同光照条件和不同拍摄角度下的样本是否都有覆盖样本分布太单一的话模型换个监控角度就失灵了。4. 数据划分训练/验证/测试集怎么分才靠谱4.1 随机划分的最大隐藏风险数据整理好、检查完终于到了划分环节。这里有个隐藏很深的坑不能简单粗暴地随机打乱图片再按比例切分。如果你的数据是从视频里抽帧出来的或者同一场景在很短时间内连续拍摄了多张照片那么相邻帧之间内容高度相似。随机划分会让极度相似的图片同时出现在训练集和验证集里训练时模型相当于已经“见过”验证集的图了验证分数会虚高。真正部署到新场景时性能可能掉一大截。正确的做法是先从数据来源层面分组。如果是视频抽帧按视频片段分组同一个视频的帧只能出现在同一种集合里。如果是不同地点采集的图片按地点分组。如果一组图片是同一时间从同一个摄像头连续拍到的也要把它们绑定到一起。先把图片按照视频id或场景id分成若干组再在这些组上做随机划分。4.2 比例和划分脚本划分比例没有一个绝对标准。样本量大、类别丰富时98%训练、1%验证、1%测试都够用。样本量不大时每类目标至少要保证验证集里有足够的实例建议验证集和测试集加起来不低于总样本的20%。对于几千张的小数据集我习惯按85%训练、10%验证、5%测试来切测试集哪怕只有几十张图也一定要留出来作为最终评估的真实参考。我这里分享一个简单可靠的划分脚本思路。先把所有图片文件名读出来按照分组信息打乱然后按比例切三段。把train.txt、val.txt、test.txt三个文件写到data目录下每一行是一个图片的绝对路径或相对路径。YOLO训练时data.yaml里可以不用images/train这种目录结构而是直接指定train.txt的路径读取时会逐行加载图片。那种把图片分别复制到三个目录的做法也不是不行但一是浪费磁盘空间二是容易复制漏文件三是后续做数据增强时不好追踪来源。我更推荐“一张图片只有一个副本用txt列表控制哪个图片进哪组”的做法干净又灵活。划分完之后要做最后一道确认把train.txt、val.txt里的类别统计单独跑一遍确保训练集和验证集的类别覆盖大致一致。比如某个类别只在验证集里出现、训练集里完全没有那模型根本没见过这个类别验证自然一塌糊涂。出现这种情况就要回到分组阶段重新划分或者考虑用分层抽样的思路确保每个类别在每个集合里都有一定比例。5. 常见问题与排查技巧实录5.1 一张速查表解决80%的报错训练开始前总会遇到各种报错这些报错大多数有固定的套路。我从实际经历里整理了一张表按出现频率排序算是踩过坑之后的速查手册。典型现象根本原因处理办法训练时提示“assertion failed: labels shape”txt文件中某一行坐标值不是0到1之间检查转换脚本的归一化查看损坏文件某个类别的AP一直为0该类别样本太少或标签里类别id映射错误统计类别分布修正categories映射表loss值变成NaN标签里有宽高为0的框或者某个归一化坐标出现负数脚本扫描所有txt剔除非法框验证时报无法读取图片图片路径错误或文件名含中文统一使用英文路径检查train.txt路径是否相对路径下有效训练很快但指标明显偏低数据集划分信息泄漏相似图片跨集合按视频/场景分组后重新划分mAP曲线一直乱抖验证集太小几十张图每次评估波动太大扩大验证集到至少图片总数的5%以上表格里的每一个问题我基本都亲手遇到过其中训练时坐标越界这个坑在从LabelMe转出来的数据里尤其常见。因为LabelMe画多边形时很多人习惯在目标上为了“切得干净”多双击出去一个点导致最后一个自动闭合的点落到图片外面。如果转换成YOLO时没有把多边形clip回图片边界多个点坐标会出现大于1的情况训练时目标框直接画到画面外面去。5.2 我建议你写一个“一键自检”脚本每次新接手一份数据集不要急着训练。我建议花半小时写一个通用的自检脚本功能包括遍历所有txt文件检查格式和坐标范围、检查图片与标签数量并列出缺失文件、统计类别分布、随机可视化一部分标注框。这个脚本不需要多复杂完全可以用Python加OpenCV在几十行内搞定关键是它可以复用到以后每一个数据集上。以后你换到第二份、第三份数据时训练前跑一遍自检脚本已经成为固定动作很多潜在问题根本来不及出现在训练日志里就被拦下了。再补充一个容易被忽略的点在转换和划分的过程中所有中间结果尽量只增不改。原标注文件备份一份转换后生成的txt备份一份划分完的train/val列表再备份一份。训练失败了随时可以追查到是哪一步出了问题。我见过有人图省事直接在原LabelMe目录里生成YOLO txt结果标注返工后txt和json对不上了又得全部重新转浪费了一天时间。5.3 关于YOLO分割任务的额外提醒如果你不是做纯检测而是训练YOLOv8-seg这类实例分割模型标签检查和转换还有一个特殊注意点一个目标的轮廓点在txt里顺序写反没关系但一定不能出现自相交的多边形不然模型计算mask损失时面积是错的。LabelMe里手动画多边形时如果不小心让线条交叉了一下一定要在导出检查阶段识别出来。简化的检测脚本可以判断多边形是否自相交如果发现就直接报错。这种错误在检测模式下完全无感因为在转bbox时已经把这个信息丢了但在分割模式下会成为模型训练的隐形炸弹。另外还要说一句YOLOpose和YOLO-OBB这类更细分的任务标签格式又不一样。姿态估计的keypoint坐标、旋转目标检测的旋转框参数都是独立的一套规格跟检测的txt完全不同。做这类任务时别拿检测标签去硬套先去翻对应官方文档里labels的说明再写转换代码也不迟。这一步能帮你省下很多试错时间。说到底数据整理这件事不性感但它是整个训练流程里杠杆效应最大的一环。数据质量不过关换更强的网络、调更久的超参都像是在漏水的桶里加水。把转换、检查、划分这三件事形成一套固定流程每次开展新项目先跑一遍自检再进训练阶段训练过程会顺很多。我自己每次拿到一份新数据前半天基本都在干这些“脏活累活”但恰恰是这些不起眼的步骤决定了训练出来的模型是能实际部署用还是只能留在训练曲线里自我感动。
返回列表