尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

苹果树叶病害检测实战:916张数据集与YOLOv8训练全流程

发布时间:2026/9/26 19:15:58

资讯中心
01
ARTICLE

苹果树叶病害检测实战:916张数据集与YOLOv8训练全流程

苹果树叶病害检测实战:916张数据集与YOLOv8训练全流程
简介面向智慧农业与病害智能识别场景该数据集包含916张苹果树叶实拍图像覆盖花叶病、斑点落叶病和叶枯病三类常见叶片病害。图像背景多样目标大小不一角度丰富且分布均匀适合课程设计、算法比赛及实际检测系统开发。数据集采用纯手工标注目标框精准并同时提供VOCxml、YOLOtxt和json三种标签可直接用于主流目标检测框架。压缩包共有3665个文件包括917个txt、916个xml、916个json及916张jpg整体约84.45MB结构清晰。目前已吸引1208人学习下载便于中高级开发者直接开展模型训练与部署支撑智慧农业APP叶部病害识别功能。1. 苹果树叶病害数据集916张花叶病和斑点落叶病识别先过格式这一关做智慧农业叶片病害检测的同行手上大概率存过这类苹果树叶病害数据集916张原图涵盖花叶病、斑点落叶病在内的三种叶部病害压缩包里同时躺着VOC的XML、YOLO的TXT和一份JSON。它的定位很明确——这是一套目标检测数据集不是分类数据集每个病害实例都被边界框圈出来了。适合刚拿到数据、准备跑YOLOv8的开发者也适合已经训练过一轮、却被标注格式和坐标转换反复卡住的工程师。这篇笔记按“格式理顺→接入训练→避坑→验证”往下走目标是让你今晚就能跑出第一版mAP而不是把周末耗在格式返工上。2. 三种标注格式的分工VOC、YOLO、JSON怎么读、怎么转一个数据集同时给你三种标签格式听起来很贴心实际上是把选择困难丢给了你。同一份真值用三种载体表达VOC的XML、JSON都是给“人”读的YOLO的TXT是给训练循环读的。先把它们的差异说清楚再给转换脚本后面训练才不玄学。2.1 先认清目录里有什么images、Annotations与labels的分工解压ZIP之后典型的目录结构长这样apple_leaf_dataset/ ├── images/ # 916张苹果叶片原图jpg或png ├── Annotations/ # VOC风格XML标注一个XML对应一张图 ├── labels/ # YOLO风格TXT标注一个TXT对应一张图 └── annotations.json # 一份COCO风格的JSON标注不同打包者的目录命名会略有差异但逻辑一致。images放原图Annotations放VOC的XMLlabels放YOLO的TXTannotations.json把全部标注塞进一个文件里。拿到手第一件事不是看图而是抽查几张同名文件的坐标是否对得上——我见过不止一次ZIP里三种格式不是同一版本VOC和JSON相差一个框。检查顺序建议是先看images里的图分辨率是否统一再看Annotations里任意一个XML的size字段最后比对annotations.json里对应的width和height。三者对不上后面所有转换都会产生坐标偏移而且极难排查。2.2 VOC的XML怎么读一棵XML树里藏着一个病斑框VOC格式在农业数据集里很常见因为很多标注工具LabelImg默认导出它。它的粒度是“一个XML文件描述一张图”每个object节点就是一个病害实例import xml.etree.ElementTree as ET tree ET.parse(Annotations/apple_leaf_001.xml) root tree.getroot() filename root.find(filename).text width int(root.find(size/width).text) height int(root.find(size/height).text) print(f图片: {filename} 尺寸: {width}x{height}) for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) print(f病害: {name} 框: ({xmin},{ymin})-({xmax},{ymax}))bndbox里存的是绝对像素坐标约定是左上角(xmin, ymin)和右下角(xmax, ymax)开区间。有一个细节容易翻车早期VOC标注工具的某些版本从1开始计数而YOLO内部从0开始转换时统一减一或统一不减都行但必须全数据集一致否则框会整体偏移一个像素对花叶病早期的小病斑影响不小。2.3 JSON标注其实也是COCO风格字段解读与快速校验annotations.json通常是MS COCO的字段约定结构分三层images存每张图的文件名和尺寸categories存类别名到ID的映射annotations存每个框的类别ID、bbox和所属图片ID。读取并校验的脚本如下import json with open(annotations.json, r, encodingutf-8) as f: coco json.load(f) img_map {img[id]: img for img in coco[images]} cat_map {cat[id]: cat[name] for cat in coco[categories]} print(类别表:, cat_map) print(图片数:, len(img_map)) print(标注数:, len(coco[annotations])) # 抽查第一张图的所有框 first_ann coco[annotations][0] img img_map[first_ann[image_id]] print(示例图:, img[file_name], f{img[width]}x{img[height]}) print(bbox:, first_ann[bbox]) # [x, y, width, height] print(类别:, cat_map[first_ann[category_id]])COCO的bbox是[x, y, width, height]不是VOC的[xmin, ymin, xmax, ymax]这是两类格式转换时最容易算错的地方。校验方法是随便抽一张图拿bbox算一遍中心点和宽高再到图上画框肉眼核对。这一步不值得省后面YOLO训练的很多怪毛病根子都在JSON转出来的坐标不正确。2.4 转成YOLO的TXT两段脚本解决三格式互转YOLO训练只认TXT每行一个目标格式是类别ID x_center y_center width height全部归一化到0到1之间。所以无论数据集给的是VOC还是JSON最终都要落到这个格式。VOC转YOLO的脚本import xml.etree.ElementTree as ET import os class_to_id {mosaic: 0, alternaria_leaf: 1, other_leaf_disease: 2} xml_dir, out_dir Annotations, labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue root ET.parse(os.path.join(xml_dir, xml_file)).getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_path os.path.join(out_dir, xml_file.replace(.xml, .txt)) with open(txt_path, w) as f: for obj in root.findall(object): name obj.find(name).text if name not in class_to_id: continue box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h f.write(f{class_to_id[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n)JSON转YOLO的脚本同理核心差异只在坐标换算import json with open(annotations.json, r, encodingutf-8) as f: coco json.load(f) img_map {img[id]: img for img in coco[images]} cat_to_id {mosaic: 0, alternaria_leaf: 1, other_leaf_disease: 2} cat_id_map {cat[id]: cat[name] for cat in coco[categories]} out_dir labels os.makedirs(out_dir, exist_okTrue) for ann in coco[annotations]: img img_map[ann[image_id]] stem img[file_name].rsplit(., 1)[0] txt_path os.path.join(out_dir, stem .txt) x, y, w, h ann[bbox] x_center (x w / 2) / img[width] y_center (y h / 2) / img[height] norm_w, norm_h w / img[width], h / img[height] class_name cat_id_map[ann[category_id]] class_id cat_to_id[class_name] with open(txt_path, a) as f: f.write(f{class_id} {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}\n)两个脚本的共同点是保留六位小数归一化坐标用浮点数不要截断成整数否则小病斑的框会直接消失。转换之后抽三张图把TXT里的坐标画回图上比对——这是唯一可信的验证方式比任何格式规范都可靠。三种格式的对比整理成一张表方便速查格式存储单位坐标体系适合场景VOC XML一图一个XML绝对像素左上右下人工标注、历史工具链JSON全部标注一个文件绝对像素xywh程序化处理、COCO生态YOLO TXT一图一个TXT归一化中心xywhYOLO训练模型输入3. 接进YOLOv8训练目录、data.yaml、命令行参数一次设对格式转完后下一步是让YOLOv8把数据集吃进去。这里最容易犯的错是目录结构不对、data.yaml的类别顺序和TXT里的ID对不上。先讲目录硬规则再讲配置最后给一条能直接跑的训练命令。3.1 目录摆放是硬规则images与labels必须同名YOLOv8要求的目录结构训练集和验证集分开图片和标签必须同名同前缀mkdir -p datasets/apple_leaf/{images/{train,val},labels/{train,val}} python - EOF import os, random, shutil images [f for f in os.listdir(images) if f.endswith(.jpg)] random.seed(42) random.shuffle(images) split int(len(images) * 0.8) for i, img in enumerate(images): sub train if i split else val stem img.rsplit(., 1)[0] shutil.copy(fimages/{img}, fdatasets/apple_leaf/images/{sub}/{img}) shutil.copy(flabels/{stem}.txt, fdatasets/apple_leaf/labels/{sub}/{stem}.txt) EOF916张图按8:2划分训练集700多张验证集180多张对检测任务来说刚刚够用。注意两点一是random.seed(42)固定下来方便复现二是用copy而不是move万一划分出了问题还能退回原始目录重来。3.2 data.yaml里的三个陷阱路径、names顺序与换行data.yaml是训练时的唯一数据入口写错一个字段训练不报错但精度会莫名低# apple_leaf.yaml path: /absolute/path/to/apple_leaf train: images/train val: images/val names: 0: mosaic # 花叶病 1: alternaria_leaf # 斑点落叶病 2: other_leaf_disease # 第三种叶部病害第一个陷阱是path写相对路径有时会解析失败直接写绝对路径最稳。第二个陷阱是names的ID必须和TXT里的类别ID完全一致转换脚本里class_to_id怎么映射这里就必须怎么写。第三个坑是YAML对names字段要求严格ID从0连续递增中间跳一个数字训练时就少一个类。3.3 一条训练命令与六个必调参数环境用Anaconda建干净环境最省心python 3.10配ultralytics是当前最稳的组合conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics yolo train \ dataapple_leaf.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ workers4六个参数里modelyolov8n.pt是Nano版预训练权重显存占用最小农业数据集规模不大时足够算力富余再换yolov8s.pt。batch16是16G显存的稳妥值显存不够就降到8但别低于4。workers4是数据加载线程数Windows下过高容易卡死4是个安全值。imgsz640是YOLOv8的默认输入尺寸不要一上来就1024对916张的数据集来说640和1024的收益差距远小于显存压力。3.4 训练日志判读三个损失分量和两个精度指标训练启动后终端会实时刷新loss跑完在runs/detect/train/目录下会生成results.csv。YOLOv8的损失由三部分组成box_loss是边框回归损失cls_loss是分类损失dfl_loss是分布焦点损失关注它们的下降趋势而不是具体数值。真正要盯的是metrics/precision和metrics/recall一个管“检出来的对不对”一个管“漏没漏”。病害检测任务里开始阶段精度高、召回低是正常的随着训练推进两条线会逐渐靠拢。如果跑了50轮以后精度和召回还在大幅抖动回头查数据而不是加轮数。另外提醒一句patience20的意思是20轮mAP没有提升就早停农业数据集通常50到80轮就能收敛不用真的跑满100轮。4. 苹果病害训练避坑五条翻车记录和它们的现场解法这五条坑不是从文档里摘的是我拿类似规模农业病害数据集反复训练时真实遇到过的。每一条都按“现象、根因、解法”写供你排查时对照。4.1 类别不平衡斑点落叶病的AP悄悄缩水现象训练完整体mAP50在0.85左右但翻各类别AP斑点落叶病只有0.62花叶病却有0.9。差距不是模型能力问题而是样本量问题。根因916张图里花叶病占了近一半斑点落叶病样本少而且它早期病斑小、框也小小目标本身学习就慢。YOLO的默认损失函数里大框天然贡献更多损失小框容易被淹没。解决先用脚本统计每个类别的TXT行数和框面积分布确认不平衡程度。然后做两件事一是把augmentTrue下的hsv_h、hsv_s、hsv_v适当调大让病斑颜色变化更丰富二是对斑点落叶病这类少数类别做过采样在train目录里复制它的图片和TXT若干份注意文件名不能重复。复制虽然粗暴但对小数据集的提升往往比换模型结构更直接。4.2 标注框拖满整片叶背景误检的根因现象模型在验证集上把不少健康叶片边缘误检成了花叶病precision看着不错但一看误检图片框全压在叶子边缘的阴影上。根因原始标注里部分框是“懒框”整个叶子甚至叶子加背景一起框进去了模型学到的是“叶缘阴影绿色背景”等于病斑。YOLO的框回归本身不区分框内哪些像素是目标标注得糙它就学得糙。解决如果原始XML里病斑其实是局部但框拖满了整片叶子靠训练参数无法纠正。我的做法是把这类框重新标注一遍裁到病斑边缘。手工复查优先看误检最高的那几张验证图通常改十来个框就能显著改善。不想改标注的话还有一个补救办法——训练后把置信度阈值从默认0.25提到0.4牺牲一点召回换掉背景误检。4.3 花叶病早期病斑与背景对比度低召回率上不去现象mAP50挺高但conf0.25时召回率怎么也过不了0.8漏检的都是病斑颜色浅、和叶脉纹理混在一起的花叶病早期叶片。根因花叶病是系统性病害早期病斑不是规则圆斑而是叶片颜色不均的斑驳区域边界天然模糊。YOLO这类anchor-free检测器对低对比度小目标并不敏感加上输入分辨率不够病斑细节在降采样后直接丢了。解决先确认imgsz没被降到480以下640是起步。然后调数据增强把augment配置里的hsv_v调高让颜色亮度扰动更大相当于逼模型去学纹理而不是颜色均值。另外mosaic1.0在有病斑小目标时效果不错它会把四张图拼成一张小目标出现的频率相对提高。4.4 JSON转YOLO后坐标越界训练不报错但指标诡异现象训练日志正常loss在降但val的混淆矩阵输出明显偏少检查TXT发现有的行里x_center是1.03w是0.98明显出界了。根因原始JSON里部分bbox的width或height是0或负数还有个别框直接画到了图像边缘外除以图像宽高后就超过了1。YOLO训练时遇到越界坐标不会报错它会按照边缘截断处理但这部分框等于白标注了。解决转换脚本里加一道clip操作把归一化后的坐标强制限制在0到1之间并且丢弃宽高小于阈值的框。顺手打印出被修正的框的图片名反向检查原图是不是标注质量有问题。这一步不丢人坐标越界的bug靠肉眼几乎不可能发现全靠脚本兜底。4.5 batch太小触发BN崩溃loss首轮就变NaN现象训练刚开始几十步loss直接变NaN终止迭代。换batch重跑还是一样看起来毫无规律。根因显存限制把batch降到了4YOLOv8默认的BatchNorm在batch太小时统计不稳定方差估计失真loss一步就爆。还有个隐蔽原因是输入图里混进了一张全黑或全白图片归一化后特征全部一样BN直接算崩。解决先把batch提到16或更大如果显存不够就把imgsz从1280降到640释放显存。NaN之前先确认数据里有没有坏图——写个脚本读一遍所有images下的文件用PIL打开失败的直接删掉并同步删对应TXT。这两个动作做完绝大多数BN崩溃都能解决。5. 验证闭环调整置信度门限、看懂混淆矩阵、导出ONNX训练出best.pt只算完成了一半另一半是验证它到底能不能用。这一章讲三件事用predict跑推理并调置信度、用混淆矩阵判断错在哪一类、导出ONNX到推理框架做最终确认。5.1 用conf调整检测门限predict命令里的两个关键参数验证的第一步不是看mAP而是拿没见过的图片跑一遍推理yolo predict modelbest.pt sourcetest_images/ conf0.30 iou0.5conf是置信度门限默认0.25农业病害场景建议从0.3开始试。iou是NMS的IoU阈值默认0.5同一片叶子上多个病斑距离很近时iou调低到0.4可以避免相邻框被吞掉。跑完后在runs/detect/predict/里逐张看结果图重点看两类错误漏检多就把conf往下调误检多就往上调。这个调参过程没有公式全靠肉眼但值得做——mAP再高门限不对落地效果就是两个样。5.2 混淆矩阵先看对角再看斜线yolo val modelbest.pt dataapple_leaf.yaml跑完在runs/detect/val/下生成confusion_matrix.png。第一次看这张图容易误判——矩阵里的数值是归一化比例而不是绝对数量不同类别的行和不一定等于1所以叫“混淆矩阵总合不唯一”。我习惯先看主对角线每个类别的召回一目了然再看背景那一列如果“花叶病预测成背景”的比例高于10%说明框偏小或对比度不够回到第4章的4.3去调。三次病害之间互相混淆的格子如果特别亮说明两类病斑形态确实像这时候靠模型硬分意义不大考虑合并类别或加更多同类样本。5.3 导出ONNX并验证从PyTorch到onnxruntime训练完成后若要做Web服务或边缘部署PyTorch权重不方便直接上线导出ONNX是常规一步yolo export modelbest.pt formatonnx imgsz640 opset12导出后立刻用onnxruntime验证一遍输出尺寸import onnxruntime as ort import numpy as np from PIL import Image sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name img Image.open(test_images/apple_001.jpg).resize((640, 640)) x np.array(img).astype(np.float32) / 255.0 x x.transpose(2, 0, 1)[None] out sess.run(None, {input_name: x}) print(out[0].shape) # (1, 84, 8400)输出的84是4个坐标加80个COCO类别数的老结构你的模型训练时只有3类所以输出通道应该是4 3 7即(1, 7, 8400)。如果这里输出了84说明导出时套用了COCO预训练的分类头回去检查data.yaml的names有没有传对。ONNX推理的预处理要和你训练时保持一致——同样的resize方式、同样的归一化分母255差一点精度就会掉。这套流程走完你的苹果树叶病害检测模型才算真正落了地。我自己的习惯是每完成一轮训练就把best.pt、data.yaml和转换脚本一起归档到项目目录里标注清楚当时用的样本划分和增强配置方便隔月回看时不至于变成黑匣子。农业病害检测的迭代往往要跨生长季数据会不断补充留好这套“格式转换训练验证”的模板下次来新数据时跑一轮新模型就是复制粘贴的功夫。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。