ARTICLE DETAIL

资讯详情

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

VOC转YOLO格式实战:用217张熊猫数据集训练目标检测模型

VOC转YOLO格式实战:用217张熊猫数据集训练目标检测模型 简介面向目标检测入门与算法验证提供一份熊猫图像标注数据集。数据集包含约217张jpg原始图片并配套VOC格式xml与YOLO格式txt标注文件类别统一为panda适合用于目标检测模型训练、迁移学习实践或标注工具操作练习。压缩包共652个文件其中jpg图片217张、xml标注217个、txt标注218个整体大小约90.97MB解压后分为图片、xml、txt三个独立文件夹文件一一对应可快速接入常用检测框架进行训练与评估。该数据集使用labelImg标注标注过程中强调目标边界准确、完整覆盖全部目标并执行一致性检查可作为学习标注规范及数据构建流程的参考模板。已有99人浏览学习资源无需解压密码下载后即可直接使用可大幅节省自行采集、筛选和标注的时间帮助读者将精力集中在模型优化与方案验证上。1. 一张只标了217张的熊猫数据集为什么值得你花一小时折腾做过目标检测的人都知道数据集的标注量永远是项目启动时第一个坎。开源数据集里 VOC 格式的 ImageNet 也好COCO 也好要么是类别太杂要么是场景跟你的业务差太远。真正做熊猫识别这类单一物种检测时最靠谱的路子反而是自己动手攒一个小而精的数据集。这份 217 张左右的熊猫数据集把 VOC 和 YOLO 两种格式一起给了其实就是帮你把「收集图片 → 转成模型能吃的格式 → 跑通训练」这条链路的最麻烦一段给铺平了。适合谁适合刚接触目标检测、手头没有现成数据、又想在本地显卡上跑通一次完整训练流程的人。做这种小数据集项目最大的错觉是数据量太小肯定训不出东西。实际跑过就知道217 张图配合预训练权重和 Mosaic 增强训练出一个能用的熊猫检测器完全可行。真正让你翻车的往往不是训练本身而是数据格式不一致这种低级错误。VOC 的 XML 转 YOLO 的 TXT本身就藏着一堆坑坐标系换算、类别 ID 映射、空标注文件处理哪个没处理好训练时直接报错或者干脆 Loss 不收敛。这篇文章就把这套流程掰开讲清楚从格式转换到参数调优再到排查坑一步不落。2. 先把数据格式的门道看透VOC 和 yolo 标注到底差在哪2.1 两种格式的核心差异坐标系、归一化、还有那个 class_idVOCPascal VOC格式是用 XML 文件存放图片的尺寸、目标类别和 bounding box 的绝对像素坐标人眼直接能看懂。这是最早的基准数据集格式之一算是对初学者最友好的格式标注软件导出的默认格式也大多是它。annotation folderpanda/folder filenameIMG_0001.jpg/filename size width640/width height480/height depth3/depth /size object namepanda/name bndbox xmin100/xmin ymin80/ymin xmax500/xmax ymax420/ymax /bndbox /object /annotationYOLO 格式则完全不同它用纯文本 TXT 文件每一行对应一个目标五个字段分别是 class_id、x_center、y_center、width、height而且所有值都是相对图片宽度和高度的归一化结果范围在 0 到 1 之间。同上面那个目标转成 YOLO 格式就是下面这行。0 0.46875 0.520833 0.625 0.708333这两者最大的差别在于坐标系。VOC 里 xmin100 就是图片上第 100 个像素YOLO 里 x_center0.46875 则是一个比例值。同一个目标在两种格式之间转换必须用图片的实际宽高做一次归一化和反归一化。很多新手直接把 XML 里的绝对坐标当成 YOLO 的归一化坐标丢进训练脚本训练出来的检测框就全跑到图片边角上这就是最常见的「格式没转换干净」翻车现场。2.2 class_id 映射和你必须提前规划好的类别清单VOC 格式里类别名称是字符串 pandaYOLO 格式里类别变成了整数 0。这里就有一个规划问题你的项目如果只识别熊猫那只有一个类别class_id 永远是 0很简单但如果以后想加类别比如区分成年熊猫和幼崽就必须在转换脚本里维护一个映射表。# 类别映射表VOC 的 name 字符串 - YOLO 的 class_id 整数 class_mapping { panda: 0, # panda_cub: 1, # red_panda: 2, }提前把这张表写好后续加类别只需要往字典里添一行转换脚本重新跑一遍就行。我见过有人项目做到一半才想起来要加类别结果所有已经标注完的 XML 都得改还得去标注软件里重新导出一遍非常浪费时间。这就是「格式规划」比「标注动作」更值钱的例子。3. 用脚本把 VOC 转成 yolo 格式转换逻辑、代码实现与四个边界坑3.1 一份可直接改的转换脚本从 XML 读入、归一化、写出 TXT拿到一份 VOC 格式的熊猫数据集第一件事就是写一个转换脚本批量把所有 XML 转成 YOLO 训练要用的 TXT。这个脚本的核心逻辑不复杂但边界情况很多。下面这份脚本是我平时在本地项目里常用的版本做 217 张图批量转换稳定不出错你可以直接抄。import os import glob import xml.etree.ElementTree as ET # 路径配置按自己的数据集目录改 voc_dir panda_voc # VOC 格式的标注目录 yolo_dir panda_yolo # 转换后的 YOLO 标注目录 os.makedirs(yolo_dir, exist_okTrue) # 类别映射表务必和训练用的 data.yaml 保持一致 class_mapping {panda: 0} def voc_to_yolo(xml_path, output_dir): tree ET.parse(xml_path) root tree.getroot() # 1. 读取图片尺寸这是坐标系归一化的关键 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[跳过] 图片尺寸非法: {xml_path}) return # 2. 遍历所有 object转换坐标并写入行 lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in class_mapping: print(f[警告] 未知类别 {name}跳过了 {xml_path}) continue class_id class_mapping[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) # 3. 计算宽高并做归一化分母必须是图片实际宽高不能写死 640 w xmax - xmin h ymax - ymin x_center xmin w / 2.0 y_center ymin h / 2.0 x_center_norm x_center / img_w y_center_norm y_center / img_h w_norm w / img_w h_norm h / img_h lines.append(f{class_id} {x_center_norm:.6f} {y_center_norm:.6f} {w_norm:.6f} {h_norm:.6f}) # 4. 写出的 TXT 和图片同名只是后缀不同 yolo_path os.path.join(output_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(yolo_path, w) as f: f.write(\n.join(lines)) # 批处理整个目录的 XML xml_files glob.glob(os.path.join(voc_dir, *.xml)) for xml_file in xml_files: voc_to_yolo(xml_file, yolo_dir) # 统计转换结果 txt_files glob.glob(os.path.join(yolo_dir, *.txt)) print(f转换完成: {len(xml_files)} 个 XML - {len(txt_files)} 个 TXT)这段脚本的核心逻辑分三步。第一步读尺寸这是最容易出错的地方有些标注工具会把宽和高写反导致归一化后的宽高比拧着。第二步遍历目标读 box 坐标这里出现了第一个边界坑如果有 difficult 或者被截断的目标要不要跳过需要你自己定。第三步归一化写入这里有个细节有人直接用 xmin/640 而不是 xmin/img_w这种写死尺寸的做法在图片尺寸不统一时隐患很大。3.2 边界坑一越界坐标和宽度为 0 的框转换脚本跑完不等于数据没问题。拿到这套 217 张的 VOC 标注时我统计了一遍锚框宽高分布结果发现有几张图的目标框 xmax 超出了图片宽度这类数据是标注时手滑产生的。YOLO 训练时图像会被强制缩放为 640x640越界的坐标在缩放过程中会变得非常夸张直接干扰 Loss 计算。解决方式在转换脚本里加一个 clamp 操作把 xmin、ymin、xmax、ymax 都限制在 [0, img_w] 和 [0, img_h] 之内。注意 clamp 要加在计算 center 和 width 之前不然框的中心点坐标会跑偏。xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) # 再做一个有效框判断宽度或高度太接近 0 直接跳过 if xmax - xmin 1 or ymax - ymin 1: print(f[跳过] 无效框: {xml_path}) continue3.3 边界坑二空标注文件到底该不该写转换脚本最后一步是写文件这里有个很容易忽略的问题如果某个 XML 里没有任何有效目标脚本按原逻辑会生成一个 0 字节的空 TXT 文件。不少开源代码会在训练时直接报错说某个图片没有 box中断整个训练流程。我一般的处理方式是在写文件之前判断 lines 是否为空为空就不写这个 TXT并维护一个 skip_list 记录文件方便接下来排查是图片漏标了还是转换逻辑有问题。当然有些训练框架会允许负样本图片参与训练但在 217 张这么小的数据集里负样本越多正样本的有效信息越稀疏不如直接把空白样本踢出训练集。if not lines: print(f[提示] {xml_path} 没有有效标注不生成 TXT) # 不要直接 pass要把这个文件记录下来方便二次检查 with open(empty_list.txt, a) as f: f.write(os.path.basename(xml_path) \n) return3.4 边界坑三图片文件名和标签文件名必须严格同名YOLO 的标注文件命名规则是跟图片同名、后缀从 .jpg 变成 .txt比如IMG_0001.jpg对应IMG_0001.txt。但如果图片后缀是 .JPEG、.png或者文件名差一个下划线训练时虽然能正常启动但表现为这个图片被当成了背景图因为没有匹配到对应的标注文件。检查方式很简单跑一个小脚本把 images 目录和 labels 目录的文件名做差集打印出没有对应标注的图片名。ls panda_images | sed s/\.[^.]*$// img_names.txt ls panda_yolo | sed s/\.[^.]*$// label_names.txt comm -23 img_names.txt label_names.txt如果 comm 输出有内容说明存在漏标或命名不一致的图片优先处理。这一步在 217 张这种小数据集上尤其重要因为每一张有效正样本都关系到模型能不能学到熊猫的纹理和形状特征。4. 用 YOLOv5 在本地把熊猫检测器训起来最小配置、训练脚本和参数怎么调4.1 按 YOLOv5 的目录规范整理数据写一个 data.yaml转换完标签之后把图片和 TXT 按 YOLOv5 的规范整理到 train/val 两个子集。常见做法是 8:2 的比例随机划分也就是 170 张训练、47 张验证左右。划分的时候注意把同一场景的连续帧尽量放到同一边避免验证集里出现跟训练集非常相似的帧导致验证指标虚高。panda_dataset/ ├── images/ │ ├── train/ # 约 170 张 jpg │ └── val/ # 约 47 张 jpg ├── labels/ │ ├── train/ # 对应的 txt │ └── val/ # 对应的 txt └── panda.yamlpanda.yaml 是训练入口写清楚类别数和类别名。注意 nc 必须和转换脚本里的 class_mapping 一致names 的类别名顺序也必须和 class_id 一一对应。# panda.yaml train: D:/panda_dataset/images/train val: D:/panda_dataset/images/val nc: 1 names: [panda]4.2 最小训练命令迁移学习、batch size 和 epoch 的平衡YOLOv5 是很多本地项目默认选的版本它把配置和训练链路封装得相对完善对小白友好。你可以用git clone拉 YOLOv5 仓库也可以直接pip install ultralytics用 YOLOv8两者命令略有差异但训练逻辑相通。下面以 YOLOv5 为例给最常用的最小命令。python train.py --data panda.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --project panda_runs --name exp1参数含义按顺序说--data指定数据配置文件--weights yolov5s.pt用官方预训练权重做迁移学习这个操作让模型在 COCO 上学到的通用特征迁移到熊猫识别上在 217 张图的小数据集上比从零初始化收敛快得多也稳得多--img 640是输入分辨率很多数据集的原始分辨率就在 640 附近不用额外放大--batch 16取决于显存大小6G 显存跑 16 没压力如果是 12G 显存可以加到 32--epochs 100对小数据集来说是偏保守的值实际训练到 60 epoch 左右 mAP 就会开始平稳。跑完训练后在panda_runs/exp1/下面会有weights/best.pt和last.pt以及训练过程中每个 epoch 的指标曲线。后续做检测best.pt 就是你要用的权重。检验训练结果是否正常第一眼先看results.png里的 train/val loss 曲线如果两条线都在缓慢下降且没有明显发散说明整个数据集准备没问题。4.3 参数调优顺序小数据集先动增强、再动锚框、最后动网络结构很多人一上来就改 YOLOv8 的 backbone 或 head这种思路在小数据集上是本末倒置。217 张图的样本量限制模型能力的关键不在网络结构而在数据增强、锚框设置和超参数。真正有效率的调优顺序是先把数据管好再看训练策略。首先锚框要自适应。YOLOv5 会在训练开始时自动用 k-means 对训练集的框做聚类重新计算锚框尺寸。如果发现自动算出来的锚框与默认值差别大熊猫的框相对整张图来说通常比较居中且偏大说明数据尺度分布和 COCO 差异很大。这个自动适配是框架自带的不需要你手动干预你只需要关注训练日志里的 anchor 聚类输出。其次增强参数要看场景。YOLOv5 的hyp.scratch-low.yaml里hsv_h: 0.015表示色调变化幅度熊猫是黑白色色彩偏移影响不大但flipud: 0.0表示不要做上下翻转因为正常的熊猫照片不会出现头朝下的情况上下翻转后模型会学到一个现实中不存在的特征水平翻转fliplr: 0.5则保留因为左右转的熊猫仍然合理。这个细节在小数据集上能直观地提升最终精度。至于网络结构的高阶优化比如更换 efficient head 或者调整 C3 模块的宽度一般留到模型在训练集上过拟合、验证集 mAP 上不去的时候再考虑。在这份 217 张的数据上yolov5s 默认结构已经足够用。5. 训练中的三大常见问题排查Loss 异常、标注错位和过拟合现象5.1 问题一Loss 明明是下降的但预测的框全部偏移到角落现象训练时 Loss 数值正常下降但在验证集上出框时框的位置总是偏在图片某一角跟真实目标完全对不上。原因训练数据里标签的坐标和图片内容不一致。最常见的是转换脚本里把 x_center 计算错写成了相减或者在读取 VOC XML 时xmin 和 ymin 顺序搞反。如果图片的 EXIF 自带旋转信息某些数据集还会出现「图片被库自动旋转校正但文本标注还是按旋转前坐标存的」这种错位。这个问题在小数据集上更隐蔽因为数据量小肉眼检查一遍标注和实际框就能发现冲突。解决不要只盯着 Loss 曲线。训练前抽几张图片把 TXT 里的归一化坐标反算成绝对像素画在图片上检查。YOLOv5 自带可视化工具或者直接用 OpenCV 画一个快速验证框。改完坐标逻辑后重新跑转换脚本再做一次可视化确认框都在熊猫身体上才进入训练。import cv2 img cv2.imread(panda_images/IMG_0001.jpg) h, w img.shape[:2] with open(panda_yolo/IMG_0001.txt) as f: line f.readline().strip().split() xc, yc, bw, bh map(float, line[1:]) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check.jpg, img)这段代码的作用就是把归一化坐标还原成像素坐标并画框。如果画出来的框位置跟肉眼看到的熊猫实际位置不一致问题就出在转换脚本需要排查的是归一化分母、中心点计算、以及 class_id 这一行是否正确而不是训练参数。5.2 问题二验证集 mAP 很高但一推理新照片就漏检现象在 47 张验证集上 mAP50 能到 0.95 以上看起来结果很好但拿手机随手拍一张新照片放进模型推理偶尔漏检尤其是熊猫离得远、在画面里占比小的时候。原因217 张样本的数据集规模太小验证集与训练集之间的重叠度很高。划分数据时直接随机切分导致同一只熊猫的不同角度照片可能训练集和验证集各占一半模型其实已经把这些样本背下来了而不是学到了泛化规则。真正的泛化能力需要在分布偏移更大的场景下检验比如背景是森林而不是人工圈舍或者光线偏暗。解决划分数据时按场景分组而不是随机划分。如果有同一个视频序列抽取的连续帧尽量把整个场景都放到训练集或验证集不要混着切。另外一个做法是保留一部分完全没经过训练的野外测试集单独跑验证脚本观察 mAP 与真实漏检的落差。对这份数据集来说合理的做法是接受「验证集偏高」的事实把模型部署到真实场景后通过失败案例再补一轮标注数据做微调。5.3 问题三训练速度正常但 Loss 在初期就剧烈震荡现象epoch 1 到 10 阶段train loss 和 val loss 都有明显上下跳动像锯齿一样甚至有时候 loss 直接飙到几十再弹回来。原因小数据集如果没有关闭强增强模型在每一轮看到的都是经过随机剪裁、拼接的图片相当于每个 epoch 都在换新的数据分布。加上 batch size 小梯度估计的噪声大Loss 曲线自然抖动得厉害。这在小数据集上很常见不能说完全没影响但确实会影响最终收敛精度的稳定性。解决先确认增强方式。YOLOv5 在最后一个 epoch 会自动关闭 Mosaic 增强作为微调所以大部分时候能自动稳住。如果训练曲线始终无法收敛考虑把mosaic: 1.0降到0.5或直接关掉代价是过拟合风险变大需要更早地做早停。还有一个参数值得留意warmup_epochs它在初始阶段用较低的学习率让模型先稳定小数据集上如果把 warmup 从 3 调到 5可以削弱前期的震荡幅度。注意小数据集调试时优先检查数据和标注再怀疑训练参数。数据错了调参是白调——这是我在本地项目里翻车最多的一条血泪经验。6. 训完以后怎么用写一次推理脚本把结果做成可展示的样例图6.1 用 best.pt 对验证集跑一个批量推理训练终点是拿到一个能用的权重文件而不是一堆训练曲线。接下来要把 best.pt 落到一张张真实图片上看看效果。用 detect.py 做批量推理是最直接的输出目录里会保存带框的图片。python detect.py --weights panda_runs/exp1/weights/best.pt --source panda_dataset/images/val --img 640 --conf-thres 0.25 --save-txt --project panda_detect重点是--conf-thres 0.25这个置信度阈值决定了哪些框会保留。阈值设低了会出现好几个重叠的框模糊不清设高了漏检率加大。对单类别小数据集我一般从 0.25 起手再根据输出结果调整到 0.4 左右。如果不想整目录推理也可以直接用 Python 接口加载模型对单张图推理。import cv2 from ultralytics import YOLO model YOLO(panda_runs/exp1/weights/best.pt) img cv2.imread(test_panda.jpg) results model(img, conf0.25, verboseFalse) boxes results[0].boxes for box in boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) conf float(box.conf[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, fpanda {conf:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(result.jpg, img)这里有个容易被忽略的点默认情况下 YOLO 模型输入是 RGB 顺序OpenCV 读出来是 BGR。简单推理时如果不做颜色顺序转换最终画框位置没问题但 result.jpg 输出会偏色。做展示样例图时可以接受做部署前测试最好加上cv2.cvtColor(img, cv2.COLOR_BGR2RGB)再送进模型。6.2 快速验证模型泛化能力的三个动作第一个动作拿训练集里没出现过的不同背景图片去测试比如白色背景的熊猫玩偶、黑白素描图或者远处树丛里的熊猫。这些虽然不算标准测试基准但很容易暴露模型到底是依赖熊猫本身的纹理特征还是把背景当了特征。第二个动作把输入尺寸从 640 调到 320 再推理同一张图对比框的位置和置信度。如果 320 分辨率下框变得不稳定说明模型的尺度鲁棒性不够部署时得保证输入尺寸稳定。第三个动作检查漏检框和误检框的比例如果误检集中出现在深色区域说明模型把深色圆形物体当成了熊猫这时需要补充负样本来压制假阳性。这些验证做完这套 217 张数据集和模型的整个链路才算闭环。下一步工作应该是围绕真实场景收集更多难例让模型在更贴近实际使用的数据上迭代。拿小数据集的项目去验证完整流程本来就是低成本学习最佳方式。数据集也好模型也好拿到手能跑通才是关键。希望这篇笔记帮到你也祝你第一次用 VOC 转 YOLO 时别踩到我当年踩过的坑。本文还有配套的精品资源点击获取
返回列表