ARTICLE DETAIL

建站实战干货

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

YOLO白萝卜检测实战:数据集格式解析与训练配置指南

2026/9/14 1:57:42 拓冰建站 浏览量
YOLO白萝卜检测实战:数据集格式解析与训练配置指南 简介面向YOLO系列算法的白萝卜检测标注数据集已完成数据划分并配套data.yaml配置适用于农业视觉检测、算法课程实践与毕业设计等场景可帮助使用者跳过数据标注与格式整理的繁琐步骤。压缩包共2000个文件包含1000份VOC格式xml标签、999份YOLO格式txt标签及1份训练配置文件整体约32.8MBtxt标签采用归一化中心点与宽高坐标能被YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10及YOLO11直接读取。同时保留xml标签与txt标签两种标注形式便于在不同框架或标注规范之间切换减少格式转换成本。数据集已按训练与验证需求划分完毕下载后可直接开展模型训练与指标验证目前已有59人学习使用适合希望快速上手目标检测实验的开发者。1. 白萝卜检测YOLO 数据集的选型与标注边界做农业视觉项目的人应该都有同感真正的瓶颈往往不是模型结构而是数据本身。白萝卜这类目标看起来简单实际拍出来的场景却非常“脏”——萝卜一半埋在土里、叶片遮挡、不同光照下表皮颜色从白到绿黄都有还有采收线上萝卜一个挨一个的密集堆叠。通用 COCO 预训练权重在这种场景下经常把萝卜柄误检成目标或者漏掉被土覆盖的果实。这次拿到的是一个针对性很强的 YOLO 系列目标检测数据集1000 张白萝卜图像带标签官方说法是“已经划分好训练集和验证集”并且内置 data.yaml。它的价值在于标签同时给出 yolo 格式 txt 和 voc 格式 xml既能直接喂给 YOLOv5/v8/v9/v10/yolo11 这类 anchor-free 或 anchor-based 模型也能用 XML 路径手动校验样本质量。对于要做落地部署的人来说这个数据集刚好覆盖了“数据集应该怎么划分、格式怎么统一、训练时哪些参数最容易被忽略”这几个问题。2. 目录结构与 yolo/voc 双标签格式的解析2.1 压缩包内的文件组织方式先看压缩包根目录下的文件列表中出现的一系列形如img_06_13.txt、img_0639_15.txt、img_06_146.txt的文件。这些.txt不是标签文件而是数据集清单文件里面往往写的是对应图像名称列表。这类清单通常由自动标注工具导出用于记录哪些图像分到了训练集、哪些分到了验证集。实际目录结构一般是如下形式radish_detection/ ├── data.yaml ├── train/ │ ├── images/ │ │ ├── img_001.jpg │ │ └── ... │ └── labels/ │ ├── img_001.txt │ └── ... ├── valid/ │ ├── images/ │ └── labels/ ├── annotations_voc/ │ ├── img_001.xml │ └── ... └── train.txt / valid.txtdata.yaml是 YOLO 系列训练的统一入口。以 YOLOv8 为例它要求train和val指向图像目录nc为类别数names为类别名称列表。这个数据集只有单个类别通常是names: [radish]nc: 1。我做项目时习惯把annotations_voc单独放一个目录而不是和 yolo 格式混在一起。因为多数训练框架会递归读取labels目录如果同时存在 xml 文件且被解析器误读为 txt会在数据加载阶段直接报警。2.2 yolo 格式 txt 每一列的含义白萝卜数据集的 yolo 格式标签遵循标准规则每行对应一个目标框五列分别是class x_center y_center width height。所有坐标值都是相对图像宽度和高度的归一化比例范围 0 到 1。下面是一个真实标注行的示例0 0.5146 0.3879 0.2341 0.4238这行数据的含义是类别索引为 0边界框中心点位于图像 51.46% 宽度、38.79% 高度位置框宽度占图像宽 23.41%高度占图像高 42.38%。这里的常见误读是认为后两列是像素绝对值。实际上如果写成了绝对像素训练时 YOLO 的损失计算会异常放大表现为 loss 一开始就上亿或者检测框全部集中在图像边缘。我一般会先写一个快速统计脚本检查标签范围import os label_dir train/labels for name in os.listdir(label_dir)[:200]: with open(os.path.join(label_dir, name)) as f: for line in f: parts line.strip().split() vals list(map(float, parts[1:])) if not all(0 v 1 for v in vals): print(f异常文件: {name}, 行: {line.strip()}) break如果没有任何输出说明归一化坐标基本符合要求。如果出现大于 1 的值那就要回看标注工具的导出设置或者用后面的 XML 转换脚本重新生成。2.3 voc 格式 xml 的结构与转换价值voc 格式的 xml 和 yolo 格式互补。它的组织结构是annotation下的object节点每个目标含name、bndbox等子节点。区别于 yolo 的归一化比例voc 存的是像素绝对坐标annotation filenameimg_001.jpg/filename size width640/width height640/height depth3/depth /size object nameradish/name bndbox xmin132/xmin ymin96/ymin xmax442/xmax ymax368/ymax /bndbox /object /annotation很多做过目标检测的人手里没有 voc 版本这个数据集同时提供两种格式意味着你不需要额外装 labelImg 去核对标注。我会直接用xml.etree.ElementTree把 voc 转回 yolo 格式做交叉验证因为两种格式如果出自同一次标注理论上每个框的面积中心应当完全一致。3. data.yaml 训练配置与 YOLOv5/v8 的复现步骤3.1 data.yaml 的关键项与路径写法拿到数据集后第一件事不是改模型结构而是确认data.yaml里的路径。YOLOv5 和 YOLOv8 对路径的处理方式有细微差别YOLOv5 允许相对路径但必须相对于运行目录YOLOv8 则更推荐绝对路径或datasets/下的相对路径。这个数据集的 data.yaml 大致如下train: ./train/images val: ./valid/images nc: 1 names: [radish]train指向训练图像目录val指向验证图像目录nc要和names长度一致。如果之后想在该数据上跑毫米波或边缘设备量化最好再补一行测试目录test但这不是必须的。我遇到不少人在 colab 或服务器上直接复制压缩包路径结果训练时报AssertionError: train: ... does not exist。建议先用yaml.safe_load读取然后打印绝对路径确认它指向正确位置。更稳妥的做法是在 data.yaml 里写成绝对路径path: /data/radish_detection train: train/images val: valid/images nc: 1 names: [radish]注意path字段是 YOLOv5 7.0 之后和 YOLOv8 都支持的写法所有子路径都以它为基准。这样在服务器换目录时只需改一处。3.2 从 YOLOv8 开始训练的命令与参数选择在数据准备好了之后训练命令很直接。以 YOLOv8 为例yolo train modelyolov8n.pt data/data/radish_detection/data.yaml epochs100 imgsz640 batch16 device0 projectradish_result nameexp1命令参数中modelyolov8n.pt是 n 版本nano适合快速确认数据标注是否有大问题imgsz640是输入尺寸batch16视显卡显存而定8GB 显卡跑 n 模型没问题。第一轮训练我建议只跑 30 epochs而不是 100因为如果标签有错误loss 曲线在前 30 个 epoch 就会表现得非常反常。对 YOLOv5同样配置可以用下面命令python train.py --data /data/radish_detection/data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --project radish_result --name exp1这两个框架对这个数据集的兼容性都不错。由于是单类目标我更倾向直接用 YOLOv8n 起步确认 mAP50 能达到 0.9 以上后再迁移到 YOLOv8s 或 v9 做精度对比。白萝卜形状细长如果原始图像不是正方形imgsz保持 640 即可不要用矩形训练因为这种数据集里萝卜长宽比变化大矩形训练反而容易漏检。训练过程中需要重点观察的指标如下表指标正常表现异常表现box_loss前 10 个 epoch 快速下降震荡不降或冲高cls_loss趋向 0 附近不下降可能是类别索引错mAP50收敛后高于 0.9低于 0.7检查标签范围mAP50-95高于 0.75偏低说明框边界不贴合上表里cls_loss最容易被忽视。如果文件名末尾是“部分类别名称”比如这个数据集里的img_06_13.txt可能对应图片中包含多个萝卜但类别索引错写成了 1 或更高就会导致 cls_loss 居高不下。3.3 兼容 YOLOv9、v10、yolo11 的格式差异YOLOv9、YOLOv10 和 yolo11 的数据加载逻辑与 YOLOv8 基本一致都读取 data.yaml 指向的图像目录和同名的 txt 标签。区别主要在于YOLOv9 的--hyp默认超参数与 v5 相近训练时需要手动指定--hyp hyp.scratch-high.yaml才能达到较好精度。YOLOv10 去掉 NMS 后推理速度更快但对边界框阈值的敏感度更高训练时建议保持默认conf0.25验证而不是像 v8 那样调低到 0.1。yolo11 是较新的结构对 dense 场景召回更好但显存占用略高于 v8n。这些模型对标签格式的要求完全一致文本文件名为图像名加.txt每行五列。直接把同一个 data.yaml 放进 v9 或 yolo11 的工程目录就可以训练。在切换到不同框架时我习惯把data.yaml备份一份到每个项目目录避免因为相对路径问题在不同框架间来回排查。因为 YOLOv5 默认把数据集放到../datasets而 YOLOv8 直接使用path字段容易产生混淆。4. 归一化坐标回算、XML 校验与类别不平衡处理4.1 把 yolo 格式转回像素坐标进行可视化验证训练前最有效的检查是把 txt 标签画到原图上用 OpenCV 直接显示出来。这个步骤能发现两个问题一是标注框是否明显偏离萝卜轮廓二是归一化坐标是否因为宽高比不同而错位。下面是一个将 yolo 归一化坐标转换为像素坐标并绘制边框的脚本import cv2 def draw_yolo_box(image_path, label_path, class_names): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path) as f: for line in f: cls, xc, yc, bw, bh map(float, line.strip().split()) 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.putText(img, class_names[int(cls)], (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return img img draw_yolo_box(valid/images/img_001.jpg, valid/labels/img_001.txt, [radish]) cv2.imwrite(check_visual.jpg, img)脚本中(xc - bw / 2) * w是框左上角 x 坐标(xc bw / 2) * w是右下角 x 坐标y 方向同理。如果画出来的框整体偏向图片某一侧或是框明显小于萝卜实际轮廓那就要怀疑标签生成时的归一化基准尺寸和当前图像尺寸不一致。4.2 用 voc 格式做交叉验证与误标修复由于数据集同时提供了 xml 文件我们可以从 xml 解析出目标框再与 txt 中的对应目标做数量对比。村庄数据很多时候里面混杂了背景图或标记错误的图像通过比对目标数量能快速找到遗漏或重复。import xml.etree.ElementTree as ET def count_xml_objects(xml_path): tree ET.parse(xml_path) root tree.getroot() objs root.findall(object) return len(objs) def count_yolo_objs(label_path): with open(label_path) as f: return sum(1 for line in f if line.strip())如果两张表的数量对不上优先检查该图像是不是旋转过的版本。部分标注工具在导出 voc 时会保留原始旋转图信息而 yolo 格式导出时忘记应用旋转矩阵导致坐标错位。遇到这种情况直接用 xml 重算 yolo 格式是更好的选择def voc_to_yolo(xml_path, label_path, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text 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) xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{class_map[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(label_path, w) as f: f.write(\n.join(lines))转换脚本里除以w和h时要注意size节点中的宽高必须和图像实际尺寸一致。这是最常见的坑xml 里的尺寸有时是标注软件拖动后的缓存尺寸切换过多窗口后就会出错。4.3 类别不平衡与密集场景的参数调整白萝卜在田里看起来密集但单一类别的情况下不存在传统意义的类别不平衡。真正需要处理的是“小目标”不平衡远处的萝卜只占几十个像素。YOLO 默认的 mosaic 增强在 dense 场景下能提升小目标召回但也会让萝卜重叠严重时标签之间相互遮挡。这时我会在超参数里降低mosaic频率mosaic: 0.5 mixup: 0.1 copy_paste: 0.3mosaic降到 0.5 意味着只有一半的训练数据使用四图拼接避免模型过早拟合拼接后的纹理。copy_paste在实例分割里有奇效对检测也有帮助它能把一张图里的萝卜“贴”到另一张图变相增加密集样本。不过这个参数只在 YOLOv8 以上支持YOLOv5 需要用--hyp传入对应字段。如果发现验证集小目标面积小于图像 1% 的框的召回率偏低不要急着改模型。先把imgsz从 640 升到 768 试一轮白萝卜的细长结构在更高分辨率下边界更清晰通常 mAP50-95 能有 2 到 3 个点的提升。5. 验证集评估指标与推理阶段的有效性检查5.1 用 val.py 或 yolo val 得到混淆矩阵训练结束后的第一件事不是立刻部署而是确认验证集上的指标可信。用 YOLOv8 自带的评估命令yolo val modelradish_result/exp1/weights/best.pt data/data/radish_detection/data.yaml imgsz640输出结果里重点关注per-class mAP50。如果只有一个类all那一行就是最终值。同时生成confusion_matrix.png在这个数据集里主要看背景被误检成萝卜的比例。如果背景误检高通常是因为标注时把萝卜旁边的土块或杂草也框进去了。5.2 推理时按场景调节置信度阈值白萝卜检测和工业零件检测有个区别漏检的代价比误检更大。采收机上如果漏掉一根萝卜后续分拣工位要人工处理误检一个土块反而影响不大。因此推理阶段我会把默认置信度从 0.25 降到 0.1然后统计单帧平均检测数。yolo predict modelradish_result/exp1/weights/best.pt sourcetest_samples/ conf0.1 iou0.45 save_txtTrueconf0.1会让模型输出更多低分框再结合 NMS 的iou0.45抑制重叠。如果地面真值框很小还可以加augmentTrue开启测试时增强相当于多尺度预测对小目标召回更友好。这个参数对速度有影响但白萝卜采收线通常运行在 GPU 上影响可以接受。5.3 输出坐标从归一化回原图的像素换算save_txtTrue保存的标签是 yolo 归一化格式部署到上位机时需要通过x1 (xc - bw/2) * frame_width换算到像素坐标。建议在推理脚本里封装成一个函数同时限制框的最小宽高避免输出面积为 1 像素的噪声框def denormalize_boxes(boxes, img_w, img_h, min_wh10): out [] for xc, yc, bw, bh in boxes: x1 (xc - bw / 2) * img_w y1 (yc - bh / 2) * img_h x2 (xc bw / 2) * img_w y2 (yc bh / 2) * img_h if (x2 - x1) min_wh and (y2 - y1) min_wh: out.append((int(x1), int(y1), int(x2), int(y2))) return out这一步骤要和标注阶段的归一化逻辑完全对称不能一边用letterbox后的尺寸计算另一边用原图尺寸回算。YOLOv8 推理模块自带还原逻辑但如果你自己写了预处理letterbox必须保存ratio和pad否则框会整体偏移。白萝卜这类目标的长宽比跨度大建议在部署端对框做一次宽高比过滤去掉明显不符合萝卜形态的输出例如长宽比小于 0.2 或大于 5 的框。这样在低置信度阈值下依然能保持较低的误检率。本文还有配套的精品资源点击获取