30类果蔬叶片病害YOLO数据集:从标注规范到训练避坑全指南
简介面向目标检测与植物病理识别学习者这是一份可直接用于YOLO训练的30类别水果、植物叶片病害缺陷数据集。内容涵盖苹果、玉米、番茄、马铃薯、大豆、樱桃等作物叶片图像为600-2000分辨率RGB大图边界框完整且每张含多个目标标注采用YOLO相对坐标class,x_center,y_center,w,h无需额外预处理即可投入训练。包体共2000个文件主要由1999个TXT标签文件与1个可视化Python脚本构成压缩包约422MB。数据已按YOLO目录结构划分训练集2240张图片及对应标签、验证集311张图片及标签并附带30类别class文本文件可直接替换或扩展自有数据集。配套show.py脚本支持随机选取图片绘制边界框并保存到当前目录无需修改参数便于快速核查标注质量。已有346人学习/下载适合开展病害检测模型训练、数据增强实验也适合入门YOLO格式标注与数据校验流程。1. 30类果蔬与叶片病害检测这套 YOLO 数据集把最耗时的环节提前做完了做过目标检测项目的人都清楚真正耗时间的不是调模型而是把图片变成 YOLO 能吃的格式逐张标注、按类别归档、划分训练集和验证集、写 class 文件。这套 30 种水果与植物叶片病害缺陷检测数据集把上面这些环节提前做完并打包好了——划分好的 train/val/test、类别 class 文件、数据可视化脚本一起给出拿到手可以直接接进 YOLOv8 训练。它适合两类人一是刚入门农业视觉检测、想跑通一套完整训练流程的开发者二是要做果蔬病害检测基线方案、但没时间从零整理数据的工程师。有一个点需要提前提醒30 类里病害类别的样本数量往往远少于正常果实类直接训练大概率翻车动手之前先看数据分布再决定训练策略。2. 从 class 文件到 images/labels先看懂这套 YOLO 数据集的目录与标注规范2.1 YOLO 格式标注文件里每一行在说什么拿到数据集先别急着训练打开 labels 目录下任意一个 txt 文件看一眼。几乎每个 YOLO 数据集的标注文件都是同一个模板每行代表图里的一个目标包含 5 个字段空格分隔。下面是一个真实存在的标注文件内容示例3 0.500000 0.500000 0.250000 0.250000 7 0.812500 0.310417 0.085000 0.127083 0 0.220000 0.560000 0.130000 0.180000第一行的含义是类别 id 为 3 的目标其边界框中心点落在图片水平方向 50%、垂直方向 50% 的位置框的宽度占整张图片宽度的 25%高度占整张图片高度的 25%。后面四个数字全部是相对图片宽高的归一化坐标取值在 0 到 1 之间。之所以用归一化坐标是因为这样同一份标注可以适配任意输入分辨率训练时把图缩放到 640x640 或 1280x1280标注不需要做任何转换模型读取后自行按比例映射回特征图。标注文件的第一列是类别 id它是整数这个 id 对应到 classes.txt 文件里的行号。classes.txt 第一行对应 id 0第二行对应 id 1以此类推整个文件的行序就是类别 id 的字典。这套数据集的 classes.txt 应该恰好是 30 行和标题里的 30 种水果、叶片病害一一对应。改错一行、增删一行都会导致整类目标的名字对错位置训练出来的模型 mAP 直接归零。我拿到任何数据集的第一件事就是数一遍 class 文件行数再和训练配置里的类别名逐行对齐。这里有一个非常容易踩的坑某些公开数据集虽然标注文件也叫 txt但内容是 PASCAL VOC 格式的像素坐标即左上角 x、左上角 y、右下角 x、右下角 y完全没有归一化。如果这种文件没有经过转换直接放进 YOLO 训练loss 会一直不降mAP 为 0而且很难排查。检查方法很简单打开一个标注文件确认所有坐标列的值都在 0 到 1 之间。只要出现大于 1 的数说明格式根本不对先别训练。提示用awk {for(i2;i5;i) if($i1 || $i0) print FILENAME: $0} dataset_root/train/labels/*.txt扫一遍所有标注文件能快速定位非法坐标。2.2 划分好的 train/val/test目录组织与文件对应规则这套数据集已经在目录层面上完成了训练集、验证集、测试集的划分常见的目录结构如下dataset_root/ ├── data.yaml ├── classes.txt ├── train/ │ ├── images/ │ │ ├── apple_001.jpg │ │ ├── apple_002.jpg │ │ └── ... │ └── labels/ │ ├── apple_001.txt │ ├── apple_002.txt │ └── ... ├── val/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/图片和标注文件通过同名前缀关联apple_001.jpg 对应 apple_001.txt。Ultralytics YOLO 训练时读取的是 data.yaml里面写清楚 train、val、test 三个集合的 images 目录路径训练过程中程序会自动把 images 路径替换成 labels 路径去加载标注。所以目录组织上有一个硬性要求images 和 labels 必须同级并列名字一一对应不能出现某张图片没有同名 txt 的情况。划分好的数据集并不代表可以无脑训练。一个需要自己验证的事情是检查 train 和 val 之间有没有同名文件或近似重复样本。如果划分脚本写得粗糙同一张图既出现在训练集又出现在验证集验证指标会虚高等到部署到现场照片上才发现模型实际效果远不如 val mAP那种感觉比训练翻车还难受。我一般会写一个几行的脚本分别扫出三套集合里的文件名列表求交集import os def collect(root): imgs set() labels set() for f in os.listdir(os.path.join(root, images)): imgs.add(os.path.splitext(f)[0]) for f in os.listdir(os.path.join(root, labels)): labels.add(os.path.splitext(f)[0]) return imgs, labels train_imgs, train_labels collect(dataset_root/train) val_imgs, val_labels collect(dataset_root/val) print(train 无标注的图片:, len(train_imgs - train_labels)) print(train 无图片的标注:, len(train_labels - train_imgs)) print(train/val 重叠文件:, len(train_imgs val_imgs))这个脚本输出三个数字第一个大于 0 说明有图片没标注训练时会读取不到标签而报错第二个大于 0 说明有孤儿标注文件通常不影响训练但说明数据集整理粗糙第三个大于 0 说明数据泄露验证集指标不具备参考意义。这套数据集如果划分得规范三个数字应该全为 0。验证完这几点才算真正把手里的数据接进训练流程。2.3 data.yaml 与 classes.txt两处类别名单必须保持同一顺序Ultralytics YOLO 训练时只认 data.yaml不直接读 classes.txt。data.yaml 里有一份类别名列表训练过程的日志、验证集的混淆矩阵、最后的模型输出全部以这份列表为准。典型内容如下path: /home/user/dataset_root train: train/images val: val/images test: test/images nc: 30 names: 0: apple_healthy 1: apple_scab 2: apple_rot 3: banana_healthy 4: banana_freckle 5: grape_healthy 6: grape_black_rot 7: grape_leaf_blight # ... 其余 22 个类别names 列表的书写顺序必须和 classes.txt 的行序完全一致。val 和 test 两个集合的标注 id 也是按这份顺序生成的一旦 data.yaml 里的 names 顺序和标注文件里的 class_id 错位训练过程不会报任何错但模型会把苹果病斑学成香蕉黑斑这种错误在混淆矩阵上能看得很明显。路径写法也要留个心眼。data.yaml 里的 train 字段如果写相对路径是相对于你执行训练命令时所在的工作目录而不是相对于 yaml 文件所在目录。很多人把 yaml 放在 dataset_root 下train 写成 train/images然后从别处执行 yolo 命令结果报错找不到图片。最省事的做法是把 path 写成绝对路径train、val 写相对名Ultralytics 会拼成 path train 的完整路径。另外划分好的 test 集在训练阶段不会用到它是留给最终评估的推荐训练完成后单独在 test 上跑一次预测用这个指标作为模型能否交付的依据。3. 处理数据集并跑进 YOLOv8Anaconda 环境、训练命令与损失函数诊断3.1 Anaconda 环境配置和预训练模型下载很多人在环境配置这一步就耗掉半天。YOLOv8 的依赖不算复杂用 Anaconda 建一个干净环境最稳妥避免和已有项目的包版本冲突。我常用的创建命令如下conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics创建环境后建议先按 PyTorch 官网给出的命令安装对应 CUDA 版本的 PyTorch再执行 pip install ultralytics。如果直接执行上面第三条命令pip 会把 ultralytics 的依赖 torch 也拉下来得到的多半是 CPU 版本训练速度慢几十倍这是很多人没注意到的坑。安装完成后用python -c import torch; print(torch.cuda.is_available())验证一下输出 True 说明 GPU 可用。预训练模型的下载同样是在命令里自动完成的。第一次执行训练命令时Ultralytics 会检测本地是否有 yolov8s.pt没有就自动下载。如果下载速度不理想可以手动把权重文件放到当前工作目录训练命令发现本地存在同名文件后就不会重新下载。对于这套 30 类果蔬病害数据我一般推荐从 yolov8s 起步而不是更小的 yolov8n。原因是数据里存在大量小目标——叶片上的病斑可能只有几十个像素n 模型的感受野和特征表达能力在小目标上吃亏s 模型在速度和精度之间更平衡。直接上 m 或 l 也可以但显存占用翻倍训练时间也明显拉长对第一次跑通流程的人来说不划算。3.2 训练命令关键参数与 bn 崩溃的处理环境配好后把数据集路径写进 data.yaml执行下面这条命令就能开始训练yolo detect train \ data/home/user/dataset_root/data.yaml \ modelyolov8s.pt \ imgsz640 \ epochs150 \ batch16 \ patience30 \ workers4 \ cacheTrue \ device0 \ lr00.01几个关键参数的作用按训练逻辑排列如下。imgsz 是输入分辨率640 是默认值如果确认数据里的病斑目标很小可以提到 960代价是显存占用和训练时间增加。epochs 设 150 是因为 30 类数据通常需要更多轮次收敛配合 patience 早停机制——验证集指标连续 30 轮不提升就自动停止不会白等。batch 受显存限制16 在 12GB 显存上搭配 640 分辨率是安全的起点显存不够就降到 8。workers 是数据加载线程数Windows 上建议设为 0 避免多进程报错Linux 上设 4 到 8 均可。cacheTrue 会把图片提前缓存到内存里显著加速数据读取代价是占用几十 GB 内存。训练过程中最常见的异常是 loss 突然变成 nan同时终端打印出类似RuntimeError: running_mean should contain 1 element的报错GitHub issue 里常把这个现象叫做 bn 崩溃。原因通常是 batch size 太小导致 BN 层的统计量不稳定或者学习率设置过大导致梯度爆炸。处理办法有两个方向一是把 batch 从 8 往上调到 16 或 32让每个 batch 里的样本足够多二是把 lr0 从默认 0.01 降到 0.001 甚至更低。yolo 默认开启 3 个 epoch 的 warmup前几轮学习率是慢慢爬升的如果 loss 在前 3 轮内就炸掉基本可以判断是学习率问题。这个现象和数据本身没有直接关系换了任何数据集都可能遇到。3.3 看训练产物损失曲线、混淆矩阵和置信度门限训练结束后所有产物输出到 runs/detect/train 目录下里面包含训练日志、权重文件、损失曲线、混淆矩阵、PR 曲线等文件。目录内容大致如下runs/detect/train/ ├── weights/ │ ├── best.pt │ └── last.pt ├── results.csv ├── confusion_matrix.png ├── PR_curve.png ├── labels.jpg ├── train_batch0.jpg └── args.yaml重点看三样东西。第一是损失函数曲线Ultralytics 的训练过程会输出 box_loss、cls_loss、dfl_loss 三条曲线分别衡量边界框回归误差、分类误差和分布聚焦损失。三条曲线都应该在前 30 轮快速下降然后趋于平缓如果某条曲线反复震荡或先降后升说明数据里有错标样本或者学习率不合适。第二是混淆矩阵很多刚上手的人会纠结矩阵里所有格子的数值加起来不等于 1觉得是 bug。这是因为 YOLO 的混淆矩阵默认把 background 和漏检算进了分母且最终数值受到置信度门限影响不同门限下矩阵数值不同总和自然不唯一。看矩阵只需要关注对角线对角线越亮越好对角线之外出现明显亮块说明两个类别互相混淆需要回到数据里查这两类的标注。第三是 PR 曲线曲线下方的面积就是 mAP这套数据集的病人通常关心 mAP50 和 mAP50-95 两个指标前者在 0.6 以上算基线可用后者超过 0.4 说明模型对小目标病斑的定位比较稳定。results.csv 里存了每一轮的完整指标可以自己画更细的曲线。下面的脚本读取 csv 并绘制类别损失的收敛过程import csv import matplotlib.pyplot as plt rows [] with open(runs/detect/train/results.csv) as f: for row in csv.DictReader(f): rows.append(row) epoch [int(r[epoch]) for r in rows] cls_loss [float(r[train/cls_loss]) for r in rows] plt.plot(epoch, cls_loss, labelcls_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(cls_loss.png, dpi150)这个脚本把分类损失单独拎出来画判断收敛情况比看 Ultralytics 自带的小图更清楚。如果 100 轮后分类损失还在缓慢下降说明模型仍在对 30 个类别的边界做精细调整可以延长 epochs 继续训练如果损失出现反弹立即停止并检查学习率和数据标注质量。4. 数据可视化脚本训练前必跑的类别分布、标注抽检与推理图输出4.1 按 class 统计框数30 类的长尾一眼看清数据可视化脚本在训练前的主要价值是帮你建立对数据分布的直觉。30 个类别的样本量几乎不可能均匀正常果实类可能每类上千个标注框病害类可能只有几十个。直接拿这种长尾数据训练模型会把多数类学得很好少数病害类几乎学不到特征。先跑一个统计脚本把每个类别在训练集中的标注框数量列出来import os from collections import Counter label_dir dataset_root/train/labels counter Counter() for name in os.listdir(label_dir): if not name.endswith(.txt): continue with open(os.path.join(label_dir, name)) as f: for line in f: parts line.split() if len(parts) 5: # 跳过空行和损坏行 counter[int(parts[0])] 1 with open(dataset_root/classes.txt) as f: class_names [x.strip() for x in f] total sum(counter.values()) for idx, name in enumerate(class_names): cnt counter.get(idx, 0) ratio cnt / total * 100 print(f{idx:2d} {name:20s} {cnt:5d} {ratio:5.2f}%)脚本逻辑很简单遍历 train/labels 下所有 txt每读一行就把该行第一列的 class_id 计数加一。这样统计的是标注框数量而不是图片数量因为一张图上可能同时有多个同类目标。输出结果里框数低于 100 的类别就是长尾类需要在训练阶段单独处理。常见的处理方式是对这些类别做过采样——把它们所在图片在训练时多喂几遍或者针对这些类别做更强的数据增强。这一步不做训练 150 轮的结果通常是多数类 mAP 很高长尾类 mAP 接近 0。4.2 画框回原图抽查 50 张就能发现三类低级错误统计分布只能说明数量说明不了质量。标注质量检查最直接的办法是把标注框画回原图肉眼抽查。下面这段脚本读取验证集的图片和标注用 OpenCV 把每个目标框画出来import cv2, os img_dir dataset_root/val/images label_dir dataset_root/val/labels out_dir check_vis os.makedirs(out_dir, exist_okTrue) for name in os.listdir(img_dir)[:50]: stem, ext os.path.splitext(name) img cv2.imread(os.path.join(img_dir, name)) h, w img.shape[:2] txt os.path.join(label_dir, stem .txt) if not os.path.exists(txt): continue with open(txt) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls, cx, cy, bw, bh ( int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) ) # 归一化坐标换算回像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(os.path.join(out_dir, name), img)抽查时重点看三类问题边界框没有完整包住目标比如病斑延伸到框外一个目标被画了两个重叠框框的位置正确但类别明显标错。第一类和第二类问题会在训练时给模型传递矛盾信号表现为 loss 曲线长期抖动。第三类问题最隐蔽因为模型会把这个错误当正确学进去最后混淆矩阵上出现莫名其妙的跨类误检。我抽查过不少公开数据集的标注几乎每套都会发现这些问题区别只是比例多少。标注质量差的数据集模型精度天花板很低再怎么调参也没用。4.3 调整置信度门限可视化推理结果定位模型盲区训练完成后可视化脚本还有一个用途把模型在验证集上的预测结果画出来对比真实标注直观定位模型盲区。执行推理的命令如下yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcedataset_root/val/images/ \ conf0.25 \ saveTrueconf 参数控制置信度门限取值范围 0 到 1默认 0.25。门限设得越低模型输出的框越多漏检越少但假阳性越多门限设高则相反。对于病害检测这类需要尽量不漏的落地场景门限往往压在 0.15 到 0.25对于只需要判定有没有病的场景0.5 以上的门限可以把误报压得很低。调整门限时不能只看数字把预测结果可视化出来看是最直接的。把同一个 batch 的预测图和对应的真实标注图并排放在一起一眼就能看出模型在哪一类上漏检严重、在哪一类上频繁误检。这两类信息直接决定后续是补数据还是调门限。5. 常见问题避坑30 类果蔬数据集最容易翻车的五个细节5.1 类别索引错位训练正常mAP 却是 0现象训练命令执行流畅loss 正常下降每个 epoch 结束后日志里的 mAP 一直是 0或者类别名称和图片里的目标明显对不上。原因data.yaml 的 names 列表顺序和标注文件里的 class_id 不一致。常见于手工编辑过 data.yaml或者在转换过程中给类别重新排了序但标注文件是按旧顺序生成的。解决打开 classes.txt逐行记录类别名顺序再打开 data.yaml 对比 names 列表。最好的核对方式是把两个文件按行拆开 diff确认每个索引位上的名字完全一致。这个坑在划分好的数据集上依然存在因为划分脚本只会搬运文件不会校验标注内容是否合法。5.2 统计脚本输出某类框数为 0可视化脚本和直觉对不上现象跑完 4.1 的统计脚本发现某个类别计数为 0但打开 images 目录肉眼能看到该类别目标确实存在。原因可能有三层。一是 names 列表顺序和 class_id 错位统计脚本按 classes.txt 顺序读名字对应关系错了自然显示为 0。二是标注文件里这一类的 id 写成超出 0-29 范围的数值比如 30 或 31Counter 统计了但输出循环没覆盖到。三是有一个 txt 文件内容为空整张图没有任何标注。解决先检查标注文件里所有 class_id 的最大值是否小于类别总数用一条命令即可awk {if($129) print FILENAME: $0} dataset_root/train/labels/*.txt。如果输出为空再用 5.2 的核对方式排查 names 和 classes.txt 的顺序。最后用 2.2 的核对脚本检查无标注图片数量。5.3 val mAP 很高部署到真实照片上召回骤降现象验证集 mAP 达到 0.7 以上模型表现不错但拿到果园现场拍摄的照片上一跑大量漏检尤其是病害类。原因最常见的是数据泄露train 和 val 之间混入了同源图片或者划分时把同一场景的连续帧拆到了两个集合。另外是场景单一训练数据基本都是实验室环境、单一背景、固定光照现场照片光照、角度、遮挡情况完全不同。解决先用 2.2 的脚本查 train/val 文件交集确认没有同名文件再确认划分时是否按场景进行分组同一棵树的照片不能跨集合。如果校验没问题说明是数据本身覆盖度不够需要补充现场数据做 fine-tune。划分好的数据集不等于数据充分val 指标从来只是参考现场部署才是最终验收。5.4 loss 突然变成 nan是 bn 崩溃还是学习率问题现象训练进行到第 10 轮左右终端输出 loss 从个位数突变到 nan继续训练也不会恢复只能中断。原因这个现象在 YOLOv8 的训练里经常被归因于 bn 崩溃本质是 BN 层统计量在某个 batch 上计算异常通常由学习率过大或 batch size 过小引起。显存不足时强制用 batch4 训练最容易触发。解决先看这一轮之前的学习率变化如果前几轮正常、中间突变优先把 lr0 从 0.01 降到 0.001 重新训练如果一开始就 nan把 batch 调到 16 以上。还有一个后悔药Ultralytics 会自动保存 last.pt如果 nan 出现在后期可以加载 last.pt 用更低学习率继续训练不需要完全从头开始。5.5 matplotlib 中文乱码可视化脚本画出来的标签全是方块现象4.1 的统计图或者其他可视化脚本里类别名是中文比如苹果健康苹果疮痂病画出来全是方块或者乱码图没法看。原因matplotlib 默认字体不包含中文字符集Linux 服务器上尤其常见。这不是数据集的问题而是可视化脚本的通用坑。解决在绘图代码前加两行指定一个支持中文的字体比如 Windows 下的 SimHei 或 Linux 下安装的 Noto Sans CJKimport matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False如果服务器上没装任何中文字体先执行apt-get install fonts-noto-cjkDebian/Ubuntu 系安装再重启 Python 进程。这个坑不处理后面所有含中文 label 的图都会有问题。6. 进阶用类别分布反哺数据清洗把长尾病害类精度再抬一档第 4 章的统计脚本不只是用来看一眼它的输出可以直接指导后续的数据清洗和增强策略。下面这组规则是我处理这类果蔬病害数据时常用的参考标准类别标注框数量建议动作说明少于 100 框人工复核全部标注剔除错框病害类样本少错标占比高需要先保证质量100 到 500 框保留现有样本训练时开启强增强数据量够用但多样性不足靠增强补500 到 1500 框正常训练不做特殊处理分布合理超过 1500 框检查是否大量重叠框考虑随机下采样防止多数类压制长尾类对于少于 100 框的类别最有效的清洗动作是把这类样本的全部图片抽出来人工过一遍。病斑标注最容易出现的问题是把背景纹理当病斑和只标了病斑中心没标全边缘这两种错标在训练时会直接教坏模型。抽出来看一遍把明显的错框删除或修正哪怕最后只剩 60 个框训练效果也比保留 80 个含错的框要好。增强策略上mosaic 增强对这类小目标病害类有稳定收益可以让模型学会在复杂背景下识别病斑如果还是不够对长尾类单独做 copy-paste 增强把病斑贴到正常果实上增加背景多样性。这个思路的本质是用可视化脚本把数据集变成可决策的先统计分布再按分布决定动作而不是拿到数据就直接训练。现在我拿到任何数据集第一件事始终是跑统计脚本先看分布再决定要不要训练这已经成了改不掉的习惯。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →