简介:面向小麦叶片病害识别场景的YOLOV5分类实战资源包,包含完整可运行代码、标注数据集与训练好的权重参数,适合有一定深度学习基础、希望快速落地YOLOV5分类任务的学习者,也可用于农业病害检测项目的前期验证。整个压缩包为7z格式,整体265.15MB,解压后共2000个文件,核心是1866张jpg病害样本图像,另有51个yaml配置、51个py训练/推理脚本、sh启动脚本及ipynb示例,按datasets目录摆放即可开始训练。预训练模型仅10个epoch,top1准确率已达0.93以上,说明网络尚未收敛,加大轮次可进一步提升性能;配套的权重和脚本让用户无需从零调参即可复现并继续优化。目前已有198人学习下载,素材组织清晰,适合课程设计、毕业设计或实际农业场景的快速原型开发。
1. 7种小麦叶病害,YOLOV5分类模式为什么够用
小麦叶片病害识别,不少方案一上来就套检测框,这份资源里走的是另一条路:用 YOLOV5 自带的分类模式,输入整张叶片图,网络直接输出 7 个类别的概率,取 top1 作为识别结果。实测只训练 10 个 epoch,top1 准确率就过了 0.93,而且这个数字是在学习率还没走完 OneCycle 升温段、网络远未收敛时拿到的,可见 ImageNet 预训练主干对叶片纹理的迁移收益相当大。项目总大小 300MB,代码、数据集、训练好的权重都齐了,里面还附了 tutorial.ipynb 教程文件,拿到手能直接跑。它适合大田巡检初筛、农业系统图片归档、检测任务前置分类这三类场景。与检测模式不同,分类模式不需要一个一个画框,数据组织就是「一个类别一个文件夹」,上手成本低很多,但真要把 0.93 的结果稳定复现出来,数据集目录怎么摆、hyp 里动哪几个参数、评估时看哪些指标,还是有不少细节值得抠。
2. 从散图到 ImageFolder:datasets 目录的摆法
2.1 分类模式只认目录,不认标注文件
YOLOV5 的分类训练入口是 classify/train.py,它和检测模式最大的差异在于:不使用 YOLO 格式的 .txt 标注文件。数据加载走的是 ImageFolder 约定,根目录下按 train、valid、test 三个子目录组织,每个子目录内再按类别建文件夹,图片就放在类别文件夹下。对这份资源来说,拿到手的是 Roboflow 导出的一批散图,文件名是下面的风格:
datasets/wheat_leaf/ ├── train/ │ ├── hispa/ # 文件名第一段作为类别 │ ├── shape/ │ └── flower/ ├── valid/ └── test/三个目录在分类任务里的分工比检测更明确:train 负责更新权重,valid 参与每个 epoch 的评估并决定 best.pt,test 只在全部训练结束后跑一次。表里列了常见的错法。
| 目录 | 作用 | 常见的错 |
|---|---|---|
| train | ImageFolder 扫描类别并训练 | 类别文件夹与 valid 不一致 |
| valid | 每轮评估,输出 top1/top5 | 把 test 的图片混进 valid |
| test | 训练结束后做最终评估 | 用训练日志当最终指标 |
这份资源的图片命名是这种风格:Hispa-112-_jpg.rf.9262afd3...jpg、shape-433-_jpg.rf.56390ea7...jpg、flower_0022_jpg.rf...jpg。看到_jpg.rf.基本可以断定是 Roboflow 导出时生成的,类别信息就藏在最前面的第一段。写一个一次性整理脚本,先把散图按类别复制到photos/类别名/下:
import re, shutil from pathlib import Path src = Path('raw_images') # 原始散图目录,按你的解压路径改 dst = Path('datasets/wheat_leaf/photos') def extract_label(name: str) -> str: # 开头连续字母数字,遇到 - 或 _ 即截断 m = re.match(r'^([A-Za-z0-9]+)[-_]', name) return m.group(1) if m else 'unknown' for img in src.glob('*.jpg'): label = extract_label(img.name).lower() # 统一小写,避免 Linux 下分两类 out_dir = dst / label out_dir.mkdir(parents=True, exist_ok=True) shutil.copy(img, out_dir / img.name)正则^([A-Za-z0-9]+)[-_]只取文件名最开头的连续字母数字段,Hispa-112-...取到 Hispa,flower_0022...取到 flower。这里做了lower()是因为 Linux 文件系统区分大小写,Hispa 和 hispa 会被识别成两个类,小麦病害所有类名统一小写最省事。脚本用 copy 而不是 move,先确认统计数量对得上,再删原始文件,避免整理出错后没法恢复。如果从 Roboflow 直接导出,可以选 Folder 格式让它自动分好 train/valid/test 和类别文件夹,这个脚本只在拿到散图时用。
2.2 按 8:1:1 划分并保持每个类别的分布
照片归好类别之后,要拆成 train、valid、test 三份。拆分时最容易犯的错是全局 shuffle 之后再切:如果某个类别只有 30 张图,全局乱序后 valid 里可能只剩 2 张,评估结果方差会很大。正确做法是逐类 shuffle,再按比例切分:
import random, shutil from pathlib import Path random.seed(42) root = Path('datasets/wheat_leaf/photos') out = Path('datasets/wheat_leaf') ratios = (0.8, 0.1, 0.1) for cls_dir in root.iterdir(): if not cls_dir.is_dir(): continue images = list(cls_dir.glob('*.jpg')) random.shuffle(images) n1 = int(len(images) * ratios[0]) n2 = int(len(images) * (ratios[0] + ratios[1])) for split, chunk in zip( ('train', 'valid', 'test'), (images[:n1], images[n1:n2], images[n2:])): target = out / split / cls_dir.name target.mkdir(parents=True, exist_ok=True) for f in chunk: shutil.copy(f, target / f.name)random.seed(42)是为了复现同一份划分,后面调参时如果发现某个类别一直表现差,先确认训练集里它的样本量,而不是怀疑网络结构。检查各类数量一条命令就行:
find datasets/wheat_leaf/train -mindepth 2 -type f | awk -F/ '{print $(NF-1)}' | sort | uniq -c看到某个类别只有个位数时,优先做类别加权采样,或者给这个类单独补离线增强,比如水平翻转、随机裁剪。分类模式下 YOLOV5 对每类样本量的要求不算高,但 10 个 epoch 的短训练尤其怕冷门类样本太少,预训练先验容易把这一类带偏。
3. 训练命令与超参数:10 个 epoch 怎么稳定复现 0.93+
3.1 最小可复现命令
数据集摆好后,用 YOLOV5 训练自己的数据集,核心动作就一条命令。环境上按官方 requirements.txt 装好 PyTorch 和 torchvision 就够,classify 入口不需要额外依赖:
python classify/train.py \ --model yolov5s-cls.pt \ --data datasets/wheat_leaf \ --epochs 10 \ --img-size 224 \ --batch-size 32 \ --name wheat_leaf_exp1参数里最关键的是--model yolov5s-cls.pt,这是官方在 ImageNet 上预训练过的分类权重,第一次运行会自动下载。没有它,或者下载失败时换成yolov5s-cls.yaml从零开始,但 10 个 epoch 远拿不到 0.93,因为叶片病斑的纹理先验全丢了。--data指向上一章搭的 datasets/wheat_leaf 根目录,脚本会自动找 train、valid 子目录。--img-size用 224,这是分类模式默认分辨率,原图很大时提高 288 会有收益,但 10 个 epoch 的短训里,把精力花在分辨率上不如多跑几个 epoch。
训练产物在runs/train-cls/wheat_leaf_exp1/,weights 目录里存 best.pt 和 last.pt,results.csv 是逐 epoch 的指标。默认学习率 0.01,配合 OneCycle 调度,前几个 epoch 实际上还在预热阶段,所以 top1 到第 8~10 个 epoch 还在涨是正常现象,不代表代码有问题。
3.2 超参数表:这次真正值得动的几项
classify/train.py 默认读data/hyps/hyp.scratch-low.yaml,里面很多字段是检测头用的,分类任务真正敏感的项不多,整理成表:
| 参数 | 默认值 | 作用 | 10 轮短训建议 |
|---|---|---|---|
| lr0 | 0.01 | 初始学习率 | 0.005~0.01,图少时降半 |
| lrf | 0.1 | 终点学习率比例 | 0.1 保持 |
| warmup_epochs | 3.0 | 预热轮数 | 1.0,10 轮里预热 3 轮太奢侈 |
| weight_decay | 0.0005 | 权重衰减 | 0.0005 保持 |
| hsv_h | 0.015 | 色调扰动 | 保持,叶片颜色不是强特征 |
| fliplr / flipud | 0.5 / 0.0 | 翻转概率 | 保持,病害方向敏感别开 flipud |
最容易踩的是 warmup_epochs。官方默认 3.0 是给 100~300 轮长训练设计的,10 轮短训里它占掉近三分之一,真正高速学习只剩后面几轮。改成 1.0 后,同样的 10 个 epoch,top1 通常能再涨 1~2 个点。改超参的常规做法是复制一个自己的 hyp 文件,只动这几项,再通过--hyp传进去:
python classify/train.py \ --model yolov5s-cls.pt \ --data datasets/wheat_leaf \ --epochs 10 --batch-size 32 \ --hyp hyp.wheat_leaf.yaml \ --label-smoothing 0.1 \ --name wheat_leaf_exp2分类损失函数默认走带 label_smoothing 的交叉熵,0.1 的平滑系数在 7 类小数据集上建议保留,它把 one-hot 目标换成软标签,能明显抑制对训练集的过度自信。注意 YOLOV5 的 classify 入口和检测入口参数不完全一样,与其在命令行反复试覆盖项,不如直接改 hyp 文件,保证每次实验命令一致。
提示:10 轮短训性价比最高的一步是把 warmup_epochs 从 3.0 降到 1.0,其余超参先保持默认。
3.3 显存边界和收敛边界
224 分辨率下 batch-size 32 大约占 6~8 GB 显存。显存小于 6 GB 就把 batch-size 降到 16,同时把数据读取线程--workers调到 4 以内,否则 CPU 读图会成为瓶颈。另一个边界是类别数:classify 要求至少两个类别文件夹,如果--data指向了 datasets 根目录而不是 wheat_leaf 子目录,报错信息里会出现 train 目录不存在,顺着路径检查就能定位。
4. Top-1 0.93 背后的评估逻辑:先看混淆矩阵再看收敛
4.1 训练产物里先看哪几个文件
训练结束后第一件事不是打开权重,而是看runs/train-cls/wheat_leaf_exp1/results.csv。每一行是一个 epoch,包含 train_loss、test_loss、metrics/accuracy_top1、metrics/accuracy_top5 几列。注意这里的 test_loss 实际是 valid 集合上的结果。如果出现 train 的 top1 很高、valid 明显跟不上的情况,说明预训练先验还撑着,但模型已经开始记忆训练集,此时加数据增强比改结构更有效。
用官方评估脚本跑一遍 valid:
python classify/val.py \ --weights runs/train-cls/wheat_leaf_exp1/weights/best.pt \ --data datasets/wheat_leaf \ --img-size 224 \ --name val_exp1输出会打印每个类别的 top1 和总体 top1,并把混淆矩阵图写到runs/val-cls/val_exp1/confusion_matrix.png。只看总体 top1 容易被多数类掩盖问题,比如某个冷门类只有十几张图,per-class top1 掉到 0.6,但对总体 0.93 几乎没影响。
4.2 混淆矩阵怎么看:先找对角线的缺口
打开 confusion_matrix.png 先看对角线:哪个类别的值明显低于其他类别,哪个就是当前模型的短板。再看这个类别的行方向,概率落到了谁头上,两类大概率视觉上很像。叶部病害里最常见的是同一叶片上早期和晚期病斑并存,模型倾向于把晚期样本归到早期类,这种混淆靠调学习率解决不了,要么补数据,要么考虑合并类别。可以按下面的顺序检查:
| 检查项 | 看什么 | 典型结论 |
|---|---|---|
| 对角线值 | 各类别正确率 | 某类明显低,先补该类数据 |
| 行方向分布 | 错误集中到哪类 | 两类互混,考虑合并或补差异样本 |
| 最后几个 epoch 曲线 | top1 是否仍在上涨 | 在涨就加 epoch,不要动结构 |
如果觉得内置矩阵图不够细,classify/val.py 还会把标签和预测的 tensor 写到输出目录,直接用 sklearn 出逐类 report:
import torch from sklearn.metrics import classification_report labels = torch.load('runs/val-cls/val_exp1/labels.pt') preds = torch.load('runs/val-cls/val_exp1/preds.pt') class_names = [...] # 与 train 目录类名字母序一致 print(classification_report(labels.numpy(), preds.numpy(), target_names=class_names))这里唯一的坑是类别编号:ImageFolder 按类名字母序编号,跟数据目录里的展示顺序不一定一致,class_names 要以训练日志或数据集的 classes 属性为准。report 里单个类别的 precision、recall、f1 比 top1 更能说明问题:recall 低是漏检,precision 低是误报,业务上两个的代价完全不同。
4.3 top1 0.93 够不够用取决于错误长在哪
素材里说「10 个 epoch 网络还没收敛」,对应的现象就是 results.csv 最后几行 top1 还在往上走,test_loss 也没有进入平台期。这时候继续加 epoch 就好,不用调结构。但 0.93 的 top1 意味着大约每 15 张错 1 张,拿去做施药决策显然不够,做图片归档和初筛则可以接受。关键是把那 7% 的错误集中在哪几个类别上搞清楚,而不是盯着平均值自我安抚。
5. 批量推理与导出的排错顺序
5.1 官方 predict 与类别映射
分类权重跑批量预测,一条命令:
python classify/predict.py \ --weights runs/train-cls/wheat_leaf_exp1/weights/best.pt \ --source data/leaf_images \ --img-size 224 \ --name batch_infer结果写到runs/predict-cls/batch_infer,每张图带预测标签输出。source 传目录会递归子目录,传单图只处理一张。批量跑之前先挑 20 张肉眼核对,同时把类别 id 和名称映射存成 JSON 放在权重旁边:
{"0": "hisa", "1": "shape", "2": "flower"}业务系统直接读这份映射,id 顺序与训练时类名字母序一致,导出前后都不要改目录结构。
5.2 导出 ONNX 并用 onnxruntime 推理
分类权重脱离 PyTorch 部署时先导出:
python export.py \ --weights runs/train-cls/wheat_leaf_exp1/weights/best.pt \ --include onnx \ --img-size 224onnx 输入是 1×3×224×224、归一化到 [0,1] 的 RGB 张量,输出 1×7 logits:
import cv2, numpy as np, onnxruntime as ort names = ["hisa", "shape", "flower"] # 从 json 读,别手写 img = cv2.imread("leaf.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (224, 224)).astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1))[None] sess = ort.InferenceSession("best.onnx") out = sess.run(None, {sess.get_inputs()[0].name: img})[0] idx = int(out[0].argmax()) print(names[idx], float(out[0][idx]))出问题按三个顺序查:先查 RGB/BGR 顺序,反了会导致结果集中到少数类别;再查归一化,YOLOV5 分类导出默认 0-1,不是 ImageNet 的 mean/std;最后查类别顺序,onnx 输出下标与训练时类名字母序对齐。上线后可以把这套权重当检测前置:先筛掉健康叶片,疑似病叶再送检测模型,在 Jetson 这类边缘设备上能省掉大量无效推理。
本文还有配套的精品资源,点击获取