苹果叶病害数据集实战:VOC/YOLO/JSON格式解析与YOLOv8训练避坑指南
简介这份苹果树叶病害数据集面向智慧农业、病害智能识别App开发及课程作业、竞赛与科研项目聚焦花叶病、斑点落叶病、叶枯病三类叶片病害的检测与识别任务。数据共916张实拍图像背景丰富、目标大小与角度多样且分布均匀整体多样性充足适合训练与验证多种目标检测算法。压缩包共3665个文件约84.45MB包含916张jpg原图以及一一对应的voc格式xml、yolo格式txt和json三种标注文件另有少量说明类txt可直接接入主流检测框架省去格式转换成本。所有标注均为纯手工完成目标框精准算法拟合效果良好数据质量可靠。目前已有1209人学习下载无论用于课程设计、比赛打榜还是实际项目落地都能获得一份开箱即用、标注规范的苹果叶病害检测数据基础。1. 苹果叶病害数据集到底解决什么问题从 916 张图说起去年帮一个做智慧农业的团队看模型他们信心满满地拿公开的 PlantVillage 数据集训了个分类网络准确率刷到 98%结果拉到陕西一个果园实测斑点落叶病和花叶病几乎全认错。问题不在网络在数据——PlantVillage 是实验室单叶、纯色背景、均匀光照而果园里是逆光、遮挡、多叶重叠、病斑还处在早期。这就是为什么一个靠谱的苹果叶病害数据集比换十个模型都重要。手上这个「苹果树叶三种病害数据集 916 张含 VOCYOLOJSON 三种格式标签」本质上是把目标检测落地到果园场景的第一块砖它同时给了 PASCAL VOC 的 XML、YOLO 的 txt、以及 COCO 风格的 JSON意味着你不管走 YOLO 系还是走 Detectron2、MMDetection 系都能直接开训不用再自己写格式转换脚本。它适合三类人想入门智慧农业目标检测的学生、要快速验证果园病害识别可行性的算法工程师、以及需要给采摘机器人或巡检无人机做视觉模块的集成方。916 张不算大但三种格式齐全、类别聚焦在花叶病和斑点落叶病这两类高发叶部病害上做迁移学习和基线验证足够用。2. 三种标签格式怎么选VOC、YOLO、JSON 的取舍与转换拿到一个数据集第一件事不是急着训而是搞清楚三种标签格式各自代表什么、你的训练框架吃哪一种。很多人栽在这一步下载完发现 YOLO 的 txt 里全是归一化坐标自己按 VOC 的绝对像素去读框全飞到图外白跑一晚上。2.1 VOC XML、YOLO txt、COCO JSON 的本质差异PASCAL VOC 格式用 XML 存每张图的标注一个object对应一个框bndbox里是xmin/ymin/xmax/ymax的绝对像素坐标还带name类别名。它的好处是可读性强、工具链老牌LabelImg 默认就吐这个。缺点是文件多、解析慢一张图一个 XML916 张就是 916 个文件。YOLO 格式是每张图一个同名 txt每行class_id x_center y_center width height全部是相对图像宽高的归一化值范围 0~1。它没有类别名类别靠一个classes.txt或data.yaml里的names列表按索引对应。这个格式训练时读取最快Ultralytics 系的 YOLOv5/v8/v11 都直接吃。COCO JSON 是单个大文件里面images、annotations、categories三个数组互相用 id 关联框是[x, y, width, height]绝对像素。Detectron2、MMDetection、以及大部分做实例分割和多任务的框架都认它。它的坑在于 id 必须严格对应category_id从 1 开始还是从 0 开始不同框架要求不一样。格式坐标类型类别信息典型框架单文件还是多文件VOC XML绝对像素内嵌 nameLabelImg、老版 TF每图一个YOLO txt归一化 0~1靠索引namesUltralytics YOLO 系每图一个COCO JSON绝对像素categories 数组Detectron2、MMDetection单个大文件2.2 用脚本在三种格式间互转并校验实际项目里你经常需要从一种转到另一种。下面这个脚本把 VOC XML 转成 YOLO txt同时做边界校验防止出现坐标越界或宽高为负的脏标注。import os import xml.etree.ElementTree as ET # 类别映射顺序决定 YOLO 的 class_id务必和 data.yaml 的 names 一致 CLASS_MAP {huayebing: 0, bandianluoyebing: 1} def voc_to_yolo(xml_dir, out_dir, img_w, img_h): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() # 优先从 XML 里读真实尺寸读不到才用传入的默认值 size root.find(size) w int(size.find(width).text) if size is not None else img_w h int(size.find(height).text) if size is not None else img_h lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue # 遇到未登记类别直接跳过避免索引错乱 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) # 裁剪到图像范围内防止标注越界 xmin, xmax max(0, xmin), min(w, xmax) ymin, ymax max(0, ymin), min(h, ymax) if xmax xmin or ymax ymin: continue # 宽高非法丢弃这个框 xc (xmin xmax) / 2.0 / w yc (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{CLASS_MAP[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) voc_to_yolo(./Annotations, ./labels, 640, 640)逻辑上分四步解析 XML、按类别映射取 id、把绝对坐标转成归一化中心点加宽高、写同名 txt。参数上CLASS_MAP是最容易翻车的地方它的顺序必须和训练时data.yaml里names的顺序严格一致否则模型学出来的类别会整体错位。img_w/img_h只是兜底真正靠谱的做法是从 XML 的size里读因为数据集里图片尺寸可能不统一。校验部分做了两件事坐标裁剪到图像边界、丢弃宽高非法的框这两条能挡掉大部分脏标注导致的 loss 爆炸。2.3 转换后必须做的三项一致性检查转完不是就完事了我一般会跑三个检查。第一数量对齐labels目录下的 txt 数量应该等于images目录下的图片数量少一个都说明有图没标注或文件名不匹配。第二类别分布统计把所有 txt 的 class_id 拉出来计数如果某一类为 0说明映射写错了。第三可视化抽检随机抽 20 张把归一化框还原画回图上肉眼看框有没有偏移。这三步加起来不到十分钟能省掉你训到一半发现 mAP 死活上不去的痛苦。# 数量对齐检查 ls images | wc -l ls labels | wc -l # 类别分布统计统计每行第一个字段 cat labels/*.txt | awk {print $1} | sort | uniq -cawk {print $1}取每行第一列即 class_iduniq -c统计频次。如果输出里只有 0 没有 1或者某一类数量明显异常少回去查CLASS_MAP和原始标注。3. 用 YOLOv8 在 916 张图上跑通第一个基线格式理清之后最实际的目标是先跑出一个能看的基线。916 张图属于小数据集直接从头训容易过拟合正确姿势是用预训练权重做迁移学习。这一章按 Ultralytics YOLOv8 的流程走因为它的数据配置最简单适合快速验证。3.1 目录结构与 data.yaml 的正确写法YOLO 系对目录结构有约定最稳妥的是这样组织apple_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml内容如下path: ./apple_dataset train: images/train val: images/val nc: 2 names: 0: huayebing 1: bandianluoyebingpath是数据集根目录train/val是相对path的路径别写成绝对路径换机器就废。nc是类别数这里 2 类。names的索引必须和前面转换脚本里CLASS_MAP完全对应这是血泪经验——顺序错了模型不会报错只会安静地学错。3.2 训练命令与关键超参设置yolo detect train \ dataapple_dataset/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ projectruns/apple \ namebaseline逐项说参数。modelyolov8n.pt用 nano 版预训练权重916 张图用大模型纯属浪费还算得慢n 版足够跑基线。epochs150对小数据集偏多但配合patience30早停实际可能 80 轮就停了。imgsz640是 YOLO 的默认输入尺寸如果你的原图分辨率远大于 640病斑又小可以提到 960 试试但显存和速度会涨。batch16在 8G 显存上跑 640 尺寸基本安全爆显存就降到 8。lr00.01是初始学习率迁移学习场景下这个值偏大如果 loss 前几轮就震荡降到 0.001。patience30表示 30 轮验证指标不涨就停省时间。3.3 训练过程看什么指标、怎么判断过拟合训练时终端会打印每轮的 box_loss、cls_loss、mAP50、mAP50-95。重点看两个cls_loss是否稳定下降mAP50是否在涨。如果box_loss一直降但mAP50不涨多半是分类头没学好检查类别映射。如果训练集 loss 降得很低但验证集 mAP 早早到顶然后回落就是过拟合这时候加数据增强mosaic、hsv_h、hsv_s或者减模型容量。# 训练完在验证集上单独评估 yolo detect val modelruns/apple/baseline/weights/best.pt dataapple_dataset/data.yamlval命令会输出每类的 P、R、mAP50。如果花叶病和斑点落叶病两类指标差距很大说明其中一类样本太少或标注质量差回去补数据比调参有用。4. 小数据集训练避坑从标注脏到过拟合的五个真实翻车916 张图做检测坑基本集中在数据和训练配置上模型结构反而没什么可调的。下面五条是我和身边人真实踩过的。4.1 现象mAP 卡在 0.3 上不去loss 也不降原因标注框大面积偏移或类别索引错位。常见于自己用脚本转格式时CLASS_MAP顺序和data.yaml的names不一致或者 VOC 转 YOLO 时忘了归一化。解决先跑 2.3 里的可视化抽检把框画回图上肉眼确认。再核对CLASS_MAP和names的索引顺序。这两步能解决八成「训不动」的问题。4.2 现象训练正常但验证时提示找不到标签原因YOLO 要求images/train/xxx.jpg对应labels/train/xxx.txt路径是替换images为labels并改后缀。如果你把图片和标签放在不同层级或者文件名有大小写差异就会找不到。解决保证 images 和 labels 目录结构完全镜像文件名不含后缀严格一致。用diff (ls images/train | sed s/\..*//) (ls labels/train | sed s/\.txt//)对比两边文件名列表。4.3 现象某一类几乎检测不出来原因类别不平衡。花叶病和斑点落叶病在 916 张里数量可能差好几倍少的那类模型学不到。解决统计两类框数量差距超过 3 倍就考虑对少样本类做过采样或者用copy_paste增强。也可以在 loss 里给少样本类加权但 YOLOv8 默认不直接暴露这个参数改起来麻烦优先补数据。4.4 现象训练 loss 很低实测新图全错原因过拟合加域偏移。916 张如果都来自同一批次、同一光照条件模型记住的是这批图的纹理而不是病斑特征。解决加强数据增强特别是hsv_h0.015、hsv_s0.7、hsv_v0.4这类颜色扰动模拟不同光照。mosaic1.0默认开着别关。如果条件允许混入少量其他来源的苹果叶图做验证。4.5 现象显存溢出batch 降到 1 还是爆原因imgsz设太大或者workers开太多导致内存泄漏。解决先把imgsz降到 640 甚至 512再把workers设为 4 或 2。如果还爆检查是不是cacheTrue把整个数据集缓存进内存了小显存机器关掉缓存。5. 从基线到可用提升小样本病害检测的几个具体技巧基线跑通只是开始真正要落地到果园还得在有限数据上榨出更多性能。这一章讲几个我实际用过、对 916 张这种量级有效的技巧。5.1 用预训练权重的冻结策略省数据YOLOv8 支持冻结 backbone 前若干层让模型先学检测头再解冻微调。数据少的时候这招很管用。# 先冻结 backbone 训 50 轮让检测头适应新类别 yolo detect train dataapple_dataset/data.yaml modelyolov8n.pt \ epochs50 freeze10 lr00.01 namefrozen # 再解冻全部用更小学习率微调 yolo detect train dataapple_dataset/data.yaml \ modelruns/apple/frozen/weights/best.pt \ epochs100 lr00.001 namefinetunefreeze10表示冻结前 10 层具体层数看模型结构n 版可以试 10。第一阶段学习率可以大一点因为只训检测头第二阶段解冻后学习率必须降下来否则预训练特征会被破坏。这个两段式训练在小数据集上通常比直接训涨 2~5 个点 mAP。5.2 针对病斑尺寸调整 anchor 和输入分辨率苹果叶病斑在整张图里占比可能很小尤其是早期斑点。默认 anchor 是为 COCO 那种中大目标设计的小目标召回差。两个办法一是把imgsz提到 960让病斑在特征图上占更多像素二是用 YOLOv8 的自适应 anchor它训练时会自动聚类但前提是你的标注框尺寸分布合理。# 提高分辨率重训注意 batch 要相应减小 yolo detect train dataapple_dataset/data.yaml modelyolov8n.pt \ imgsz960 batch8 epochs150 namehighresimgsz960相比 640小目标召回通常有提升代价是显存翻倍、速度减半。如果部署端是边缘设备得权衡。5.3 用验证集上的混淆矩阵定位类别混淆训练完 YOLO 会在 runs 目录生成confusion_matrix.png。如果花叶病和斑点落叶病互相误判严重说明两类视觉特征在模型眼里太像可能是标注时边界没划清或者某些图里两类同时出现但只标了一类。# 生成混淆矩阵和 PR 曲线 yolo detect val modelruns/apple/finetune/weights/best.pt \ dataapple_dataset/data.yaml plotsTrueplotsTrue会输出 PR 曲线、混淆矩阵、F1 曲线。重点看混淆矩阵对角线非对角线上的大数值就是主要错误来源。如果两类混淆严重回去复查标注规范明确花叶病叶片黄绿相间斑驳和斑点落叶病褐色圆形斑点的判定边界重新过一遍有争议的图。5.4 导出与部署前的最后验证训完的模型要部署先导出成通用格式。边缘设备常用 ONNX 或 TensorRT。# 导出 ONNX yolo export modelruns/apple/finetune/weights/best.pt formatonnx imgsz640 # 导出 TensorRT需要 GPU 环境 yolo export modelruns/apple/finetune/weights/best.pt formatengine imgsz640 halfTruehalfTrue用 FP16 推理速度大约翻倍精度损失通常可接受。导出后务必用几张真实果园图跑一遍推理确认框的位置和类别和 PyTorch 版一致格式转换偶尔会有坐标偏移的玄学问题。我自己的习惯是任何数据集拿到手先花半天把格式、类别分布、可视化抽检做完再动训练命令。这半天看着慢实际是给自己买后悔药——数据层面的问题训到一半才发现浪费的是整晚的算力和第二天的调试时间。916 张不算多但把上面这些步骤走扎实跑出一个能在果园里认出花叶病和斑点落叶病的基线模型是完全可行的。希望帮到你。本文还有配套的精品资源点击获取