YOLOV5图像分类实战:小麦叶病害识别从数据整理到模型部署

YOLOV5图像分类实战:小麦叶病害识别从数据整理到模型部署 简介面向小麦叶片病害识别场景的YOLOV5分类实战资源包包含完整可运行代码、标注数据集与训练好的权重参数适合有一定深度学习基础、希望快速落地YOLOV5分类任务的学习者也可用于农业病害检测项目的前期验证。整个压缩包为7z格式整体265.15MB解压后共2000个文件核心是1866张jpg病害样本图像另有51个yaml配置、51个py训练/推理脚本、sh启动脚本及ipynb示例按datasets目录摆放即可开始训练。预训练模型仅10个epochtop1准确率已达0.93以上说明网络尚未收敛加大轮次可进一步提升性能配套的权重和脚本让用户无需从零调参即可复现并继续优化。目前已有198人学习下载素材组织清晰适合课程设计、毕业设计或实际农业场景的快速原型开发。1. 7种小麦叶病害YOLOV5分类模式为什么够用小麦叶片病害识别不少方案一上来就套检测框这份资源里走的是另一条路用 YOLOV5 自带的分类模式输入整张叶片图网络直接输出 7 个类别的概率取 top1 作为识别结果。实测只训练 10 个 epochtop1 准确率就过了 0.93而且这个数字是在学习率还没走完 OneCycle 升温段、网络远未收敛时拿到的可见 ImageNet 预训练主干对叶片纹理的迁移收益相当大。项目总大小 300MB代码、数据集、训练好的权重都齐了里面还附了 tutorial.ipynb 教程文件拿到手能直接跑。它适合大田巡检初筛、农业系统图片归档、检测任务前置分类这三类场景。与检测模式不同分类模式不需要一个一个画框数据组织就是「一个类别一个文件夹」上手成本低很多但真要把 0.93 的结果稳定复现出来数据集目录怎么摆、hyp 里动哪几个参数、评估时看哪些指标还是有不少细节值得抠。2. 从散图到 ImageFolderdatasets 目录的摆法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.pttest 只在全部训练结束后跑一次。表里列了常见的错法。目录作用常见的错trainImageFolder 扫描类别并训练类别文件夹与 valid 不一致valid每轮评估输出 top1/top5把 test 的图片混进 validtest训练结束后做最终评估用训练日志当最终指标这份资源的图片命名是这种风格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(parentsTrue, exist_okTrue) shutil.copy(img, out_dir / img.name)正则^([A-Za-z0-9])[-_]只取文件名最开头的连续字母数字段Hispa-112-...取到 Hispaflower_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(parentsTrue, exist_okTrue) 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.933.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.ptresults.csv 是逐 epoch 的指标。默认学习率 0.01配合 OneCycle 调度前几个 epoch 实际上还在预热阶段所以 top1 到第 810 个 epoch 还在涨是正常现象不代表代码有问题。3.2 超参数表这次真正值得动的几项classify/train.py 默认读data/hyps/hyp.scratch-low.yaml里面很多字段是检测头用的分类任务真正敏感的项不多整理成表参数默认值作用10 轮短训建议lr00.01初始学习率0.0050.01图少时降半lrf0.1终点学习率比例0.1 保持warmup_epochs3.0预热轮数1.010 轮里预热 3 轮太奢侈weight_decay0.0005权重衰减0.0005 保持hsv_h0.015色调扰动保持叶片颜色不是强特征fliplr / flipud0.5 / 0.0翻转概率保持病害方向敏感别开 flipud最容易踩的是 warmup_epochs。官方默认 3.0 是给 100300 轮长训练设计的10 轮短训里它占掉近三分之一真正高速学习只剩后面几轮。改成 1.0 后同样的 10 个 epochtop1 通常能再涨 12 个点。改超参的常规做法是复制一个自己的 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 大约占 68 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 明显跟不上的情况说明预训练先验还撑着但模型已经开始记忆训练集此时加数据增强比改结构更有效。用官方评估脚本跑一遍 validpython 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 出逐类 reportimport 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_namesclass_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 logitsimport 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 这类边缘设备上能省掉大量无效推理。本文还有配套的精品资源点击获取