
简介面向计算机视觉与深度学习研究者的烟叶病害检测技术讲解PPT围绕改进YOLOv5算法在烟草病害识别中的应用展开。内容系统覆盖烟叶病害的经济危害背景、传统诊断方法的时效性局限、嵌套式YOLOv5识别方法、环境搭建、数据集采集与标注、模型训练及系统整合等完整技术链路预览内容还涉及优化函数Adam/SGD、激活函数Leaky-ReLU/Sigmoid、损失函数GIOU/CIOU对比以及添加注意力模块等YOLO改进思路有助于快速梳理算法脉络与论文研究方向。包体为1个pptx演示文稿约10.34MB图文结构完整涵盖从问题提出到方案落地的全过程适合学术汇报、方案答辩或算法入门参考。目前已有3118人学习/下载是快速了解烟叶病害检测课题方案、获取汇报素材的实用资料。1. 烟叶病害检测为什么值得用改进YOLOv5来做烟草种植是很多产区农户的主要收入来源而烟叶病害一旦在团棵期到旺长期爆发往往几天内就蔓延到整片烟田。传统的人工巡检靠植保员一片片翻叶子经验差异大漏检率高等发现时已经过了最佳防治窗口。把目标检测模型接到田间拍照设备上让系统自动标出病斑位置和病害类别是这几年植保信息化里落地最快的一条路。而 YOLOv5 之所以被选作基底不是因为它的 mAP 最高而是因为它在边缘设备上的推理速度和工程成熟度最平衡——改进 YOLOv5 去做烟叶病害检测本质上是在「检测精度」和「可部署性」之间找一个现实可用的折中方案。这套系统适合谁适合有 Python 基础、想把手头烟叶图像数据变成检测模型的学生或一线技术人员也适合正在做智慧农业项目汇报、需要交付可演示原型的团队。2. 先把数据和环境铺平烟叶病害数据集的整理与训练环境搭建2.1 病害类别怎么定从植保标准到检测标签做检测系统第一步不是写模型而是把「病害」这个业务概念翻译成计算机能学习的标签体系。烟叶常见病害里最容易和 YOLOv5 检出能力匹配的是具有明显病斑的类别——赤星病、野火病、角斑病、气候斑点病加上病毒类的脉斑病。这里要注意不是所有病害都适合目标检测像花叶病这种整叶均匀褪绿的用分类网络做整叶判别更合适强行画框反而会增加标注噪音。我一般会先拉一份产区植保站的病害图谱用 Excel 把每类病害的典型症状、高发时期、病斑形状列出来再对着原始图片确认哪些类别视觉可分。这一步的产出是标签类别清单比如classes [juxing_bing, yehuo_bing, jiaoban_bing, qihou_bandian, maiban_bing]类别数量控制在 5 类以内时YOLOv5 的训练难度和漏检率都比较好控制。如果一张叶片上同时有赤星病和野火病标注时两个框都要画这也是后面评估时容易打架的地方。2.2 数据增强脚本用小样本撑起烟叶病害的多样性烟叶病害图像采集往往受季节限制一个产区一年能拍到的有效病害图可能只有几千张。直接拿这点数据训 YOLOv5 很容易过拟合尤其是病斑纹理变化大的野火病。这里需要在训练前对数据集做增强常见做法是先用脚本把原始图扩增 5 到 8 倍。下面这个脚本是增强阶段比较实用的一份参考生成 mosaic、旋转、亮度和噪声扰动样本输出为 YOLO 格式的 txt 标注import cv2 import numpy as np import os, random # 原始图片与标签目录 src_img_dir raw_images/ src_lbl_dir raw_labels/ out_img_dir aug_images/ out_lbl_dir aug_labels/ os.makedirs(out_img_dir, exist_okTrue) os.makedirs(out_lbl_dir, exist_okTrue) for img_name in os.listdir(src_img_dir): img_path os.path.join(src_img_dir, img_name) label_path os.path.join(src_lbl_dir, img_name.replace(.jpg, .txt)) img cv2.imread(img_path) h, w img.shape[:2] # 亮度与对比度扰动模拟不同光照 alpha random.uniform(0.8, 1.2) beta random.randint(-25, 25) img_aug cv2.convertScaleAbs(img, alphaalpha, betabeta) # 旋转 90 度的整数倍避免标注框变形 k random.choice([0, 1, 2, 3]) img_aug np.rot90(img_aug, k) # 高斯噪声模拟田间手机拍摄的噪点 noise np.random.normal(0, 8, img_aug.shape).astype(np.uint8) img_aug cv2.add(img_aug, noise) # 写增强图 aug_name img_name.replace(.jpg, f_aug{random.randint(100,999)}.jpg) cv2.imwrite(os.path.join(out_img_dir, aug_name), img_aug) # 旋转后原标注框坐标也跟着旋转这里要同步处理 boxes [] with open(label_path, r) as f: for line in f.readlines(): cls, xc, yc, bw, bh line.strip().split() boxes.append([int(cls), float(xc), float(yc), float(bw), float(bh)]) with open(os.path.join(out_lbl_dir, aug_name.replace(.jpg, .txt)), w) as f: for cls, xc, yc, bw, bh in boxes: # 90度旋转的坐标变换中心点 (xc,yc) - (1-yc, xc) xc_new, yc_new 1 - yc, xc bw_new, bh_new bh, bw f.write(f{cls} {xc_new:.6f} {yc_new:.6f} {bw_new:.6f} {bh_new:.6f}\n)这段脚本的关键在于旋转增强时坐标必须跟着转否则标签和图像对不上训练时模型会学着预测错误位置。YOLO 格式的坐标是归一化后的中心点加宽高旋转 90 度时中心点坐标互换算一次、宽高互换一次即可。亮度扰动系数 alpha 建议不要低于 0.7否则暗光样本会把病斑细节抹掉反而让模型学到错误纹理。2.3 环境配置Python 版本与 YOLOv5 依赖的匹配训练环境这块踩坑最多的是 Python 版本和依赖库版本打架。YOLOv5 官方仓库对 Python 3.8 到 3.11 都有较好支持但如果你用的是较新的 Python 3.12torch 和 torchvision 的版本匹配要格外小心。我一般建议用 conda 单独建一个环境Python 固定为 3.9torch 选 1.13 或 2.0 系列这样能避开很多兼容性报错。用 vscode 配置完 Python 环境后安装关键依赖的命令大致是conda create -n tobacco python3.9 -y conda activate tobacco pip install torch2.0.1 torchvision0.15.2 pip install -r requirements.txt其中 requirements.txt 里的 numpy 版本要注意YOLOv5 老版本代码对 numpy 1.24 以上某些接口不兼容常出现np.int报错。解决方式是锁定 numpy 版本为 1.23.5或者升级 YOLOv5 代码到较新版本。训练时显存不够的话batch size 调小比换显卡更实在YOLOv5 对小 batch 的 BN 层处理在 8 以下时衰减明显所以数据增强阶段尽量把每个类别的样本量补足缓解 batch 小带来的统计不稳定。3. 改网络从YOLOv5基线到烟叶病害检测的四个改进点3.1 小病斑漏检的根源下采样次数与特征图尺寸烟叶病斑的特点是面积占比小、边缘不规则赤星病初期的病斑直径可能只有几个像素到十几个像素。YOLOv5 的默认检测头在 80x80、40x40、20x20 三张特征图上做预测小目标主要靠 80x80 的 P3 层但经典结构里 P3 层的语义信息偏弱病斑纹理容易在多次卷积后被背景淹没。改进的常见思路是增加一次上采样融合让 P2 层160x160也参与检测或者调整 anchor 的尺寸分布。改 yaml 是最直接的做法。以 YOLOv5s 为基础把检测层从 3 层扩到 4 层# yolov5s_tobacco.yaml nc: 5 # 烟叶病害类别数 depth_multiple: 0.33 width_multiple: 0.50 anchors: - [5, 6, 8, 10, 12, 15] # P2 层小 anchor - [10, 13, 16, 30, 33, 23] # P3 层 - [30, 61, 62, 45, 59, 119] # P4 层 - [116, 90, 156, 198, 373, 326] # P5 层这里 anchor 的数值来自对烟叶病斑标注框的聚类统计不是照搬 COCO 的默认值。跑训练前用 kmeans 脚本对训练集 txt 里的宽高做一次聚类再把结果填进 yaml。有些同学不做这一步直接沿用默认 anchor小病斑的 recall 会明显偏低——这就是泥潭里最容易陷进去的细节。3.2 注意力模块插入让网络盯着病斑而不是叶脉网上的改进 YOLOv5 十有八九会加注意力机制但加的位置和方式差别很大。烟叶病害里赤星病和角斑病都需要模型区分「病斑」和「叶脉纹理」SE 或 CBAM 加在主干尾部效果不明显因为病斑尺度小通道注意力在深层已经丢了空间细节。更适合的做法是在 Neck 的 P3 层前插入一个轻量注意力块。下面这段代码是在 YOLOv5 的 common.py 里追加一个简化的坐标注意力模块CA并注册import torch import torch.nn as nn import torch.nn.functional as F class CoordAtt(nn.Module): def __init__(self, inp, oup, reduction8): super().__init__() # 先做 1x1 卷积压缩通道减少计算量 self.conv nn.Sequential( nn.Conv2d(inp, inp // reduction, 1, biasFalse), nn.BatchNorm2d(inp // reduction), nn.ReLU(inplaceTrue) ) # 水平方向和垂直方向各自生成注意力权重 self.conv_h nn.Conv2d(inp // reduction, oup, 1, biasFalse) self.conv_w nn.Conv2d(inp // reduction, oup, 1, biasFalse) def forward(self, x): b, c, h, w x.size() x_h x.mean(dim3, keepdimTrue) # 水平池化 x_w x.mean(dim2, keepdimTrue) # 垂直池化 x_cat torch.cat([x_h, x_w], dim2) x_cat self.conv(x_cat) x_h, x_w x_cat.split([h, w], dim2) x_h torch.sigmoid(self.conv_h(x_h)) x_w torch.sigmoid(self.conv_w(x_w)) return x * x_h * x_w这段代码里水平池化和垂直池化分别捕获病斑在行、列方向上的分布响应得到的两个权重图相乘后作用回原特征图。和 SE 模块只做通道加权相比它保留了空间位置信息对细长形病斑更友好。插入位置是在 Neck 的 Concat 之后、Detect 之前以 YOLOv5s 的 yaml 为例在 Head 部分对应层号的 Concat 后加一行CoordAtt即可。加了之后参数量涨得不多但推理时间会略微增加边缘设备上要考虑取舍。3.3 损失函数与正样本匹配病斑样本不平衡怎么办烟叶病害数据里背景占比极高一张 640x640 的图里病斑区域可能只占 2% 到 5%。YOLOv5 默认的 CIoU 损失对框的位置回归比较友好但分类损失在极端不平衡下容易让模型偏向预测为背景。常见的处理方式有两种一是调整 loss 里的类别权重让少数类别的梯度贡献更大二是替换成 VariFocal 或 Focal Loss 变体把难样本的权重提上来。YOLOv5 的 loss.py 中分类损失默认是 BCEWithLogitsLoss改成带 alpha 参数的 Focal Loss 是相对可控的改进class FocalLoss(nn.Module): def __init__(self, alpha0.25, gamma2.0): super().__init__() self.alpha alpha self.gamma gamma def forward(self, preds, targets): # preds: 预测 logits, targets: 0/1 标签 bce F.binary_cross_entropy_with_logits(preds, targets, reductionnone) p torch.sigmoid(preds) # 对难样本放大损失对易分样本压低损失 focal_weight torch.where(targets 1, (1 - p) ** self.gamma, p ** self.gamma) return (self.alpha * focal_weight * bce).mean()alpha 取 0.25 表示正样本的损失权重被压低避免背景和前景极端不平衡时模型只学背景。gamma 越大模型越关注难分样本但 gamma 超过 2.5 后训练容易震荡烟叶病害场景下 2.0 是稳妥起点。改完 loss 后要留意训练日志里的cls_loss如果下降曲线比 baseline 更陡说明正样本梯度确实被激活了如果反而更慢就是权重设大了。3.4 后处理 NMS 的边界病斑重叠框怎么压下来YOLOv5 默认的 NMS 在病斑密集区域有个典型问题相邻病斑的预测框互相抑制导致一个病斑被删掉。赤星病爆发期的叶片上病斑间距极小IOU 很容易超过默认的 0.45 阈值模型输出时就会发现「同一个位置只留一个框另一个病斑丢了」。改进思路是换用 Soft-NMS 或 DIoU-NMS把重叠框的分数衰减而不是直接置零。在 YOLOv5 的 general.py 里把 NMS 函数换成 DIoU-NMS 的核心差异是衰减公式def diou_nms(bboxes, scores, iou_thresh0.5): # bboxes: [N, 4] 的 xyxy 格式, scores: [N] # 这里的实现省略排序细节核心是衰减而非删除 keep [] order scores.argsort(descendingTrue) while order.numel() 0: i order[0] keep.append(i) # 计算其余框和当前框的 DIoU ious compute_diou(bboxes[order[1:]], bboxes[i]) # 把 IoU 超过阈值的框分数乘以衰减系数而不是直接丢掉 scores[order[1:]][ious iou_thresh] * 0.6 order order[1:][scores[order[1:]] 0.1] return keep改成衰减而不是置零之后重叠病斑的两个框都有机会保留代价是假阳性可能略微上涨。如果实际测试时发现误检变多可以把衰减系数从 0.6 调到 0.4或者把阈值从 0.5 提到 0.55。这一步属于工程调参的范畴没有绝对最优值要在产区验证集上跑了之后才能定。4. 训练跑通与超参数调优让改进YOLOv5在烟叶病害上收敛4.1 训练数据组织train/val 划分与数据 yaml 的写法YOLOv5 的数据组织方式比较固定图片和标注要分目录放好图片用 jpg 或 png标注 txt 和图片同名。train 和 val 的比例我习惯按 8:2 切如果某类病害样本特别少先把该类样本全部放进 train在 val 里留少量代表即可否则 val 的 mAP 波动会非常大。数据 yaml 这样写# tobacco_data.yaml train: /data/tobacco/train/ val: /data/tobacco/val/ nc: 5 names: 0: juxing_bing 1: yehuo_bing 2: jiaoban_bing 3: qihou_bandian 4: maiban_bing训练前用一行命令检查标注是否可读python train.py --data tobacco_data.yaml --weights --cfg yolov5s_tobacco.yaml --img 640 --epochs 100如果标签文件缺失或格式错误训练会直接跳过该图日志里skipped数量过多时就要停下来检查标注。常见翻车点是用 LabelImg 导出时类别名和 yaml 里的 names 对不上训练集里某一类的标注被全部跳过模型训练完对这一类完全无感。4.2 关键超参数的含义与建议区间YOLOv5 的超参数在 hyp.scratch-low.yaml 里定义烟叶病害检测需要重点调的是以下几个参数默认值烟叶场景建议说明lr00.010.005~0.01初始学习率小数据集偏高容易震荡lrf0.20.1~0.2最终学习率系数影响后期收敛精度momentum0.9370.9~0.937动量微调阶段可以降到 0.9weight_decay0.00050.0005~0.001正则化样本少时适当加大warmup_epochs3.03~5预热轮数让 BN 统计先稳定iou_t0.200.20~0.25正样本匹配阈值小目标建议 0.2训练命令里通过--hyp指定修改后的超参数文件。跑第一个 epoch 时要观察 loss 曲线是否下降如果前 3 个 epoch loss 不降反升大概率是学习率太大或数据标注有大量错框。还有一种常见情况是背景占比太高模型前 20 轮一直在学习「不检出」这时可以考虑把 mosaic 增强概率调低让模型更早看到真实病斑形态。4.3 迁移学习用 COCO 预训练权重还是从零训烟叶病害数据集再大也很难和 COCO 的千万级图像规模相比。从零训练 YOLOv5s 在 5000 张图的数据集上mAP 往往不如用 COCO 预训练权重微调后的效果。训练时加载官方预训练权重的命令是python train.py --data tobacco_data.yaml --cfg yolov5s_tobacco.yaml --weights yolov5s.pt --epochs 150 --batch-size 16 --img 640 --device 0加载预训练权重后前几轮骨干网络的梯度会被冻结权重主导收敛速度显著加快。这里有个实践细节烟叶图像和 COCO 图像风格差异大冻结前 10 层骨干往往不如只冻结 BN 层效果好。做法是在 train.py 里把 backbone 参数的requires_grad全部设为 True但把 BN 层的track_running_stats保持默认让网络快速适应病斑纹理的同时不破坏已学到的通用特征。epoch 150 对 5000 张级别数据来说基本够用超过 200 轮容易过拟合验证集的 mAP 曲线如果出现明显回退就要早停。4.4 训练过程中看什么日志指标与曲线判读训练时终端会输出每个 epoch 的 loss 组成包括 box_loss、obj_loss、cls_loss 和最终的 mAP。这里有一个判断模型是否学到位的小技巧obj_loss 如果降不下去说明模型对「哪里是目标」的定位还不准问题往往出在 anchor 设置或数据标注的框边界太松cls_loss 降不下去则说明类别区分不够注意力模块或损失函数权重需要调整。训练完成后会生成runs/train/exp目录里面包含 results.png 的曲线图和混淆矩阵。PR 曲线上如果 P 高 R 低说明模型检出的基本都是对的但漏了很多病斑这时需要向下调 NMS 阈值或增大 anchor 数量R 高 P 低则相反误检太多需要提高置信度阈值或加强背景样本的抑制。这些判断直接影响后面的部署策略一定要在训练阶段就看清楚而不是等部署到田间的设备上再后悔。5. 避坑与排查烟叶病害检测训练里最常翻车的5个问题5.1 标注框越界导致训练 loss 变成 nan现象训练到中途box_loss 突然变成 nan之后 mAP 一直停在 0。原因YOLO 格式标注的中心点坐标和宽高归一化后超过 0 到 1 的范围尤其是旋转增强脚本里坐标算错时边界框一部分在图像外模型回归时梯度爆炸。解决训练前对 train 和 val 的每个 txt 做一次合法性检查把超出边界的框裁剪回图像范围内或者直接丢弃该样本。可以写个脚本遍历所有标注文件检测xc bw / 2 1或xc - bw / 2 0的情况。5.2 叶子互相遮挡导致病斑框标注不一致现象验证集上模型对某一类病斑的 mAP 特别低但训练集 loss 已经很低明显过拟合。原因叶片重叠区域里标注员对同一张图的理解不同有的人把遮挡部分画在框内有的人只画可见区域。模型学习到的边界语义是混乱的。解决重新看一遍该类别的全部标注图统一规则——可见部分超过病斑总面积一半就完整画框否则不标。把标注规则写进项目文档多人标注时还要每张图抽查保证前后一致。这是数据质量里最耗时但最值得投入的部分。5.3 增强脚本把病斑纹理磨没了模型学不到特征现象加了旋转和亮度增强后训练 loss 下降很快但验证集效果比不增强还差。原因亮度扰动范围太大低亮度下病斑和健康叶肉的对比度消失高斯噪声太强把病斑边缘的细小锯齿纹理抹平了。解决把亮度扰动系数限制在 0.85 到 1.15高斯噪声标准差从 8 降到 4随机裁剪时保证裁剪区域里至少包含一个完整病斑。增强的目的不是把图变花哨而是模拟不同光照和拍摄角度下的真实变化超过真实范围的扰动只会教坏模型。5.4 部署到树莓派或 Jetson 上 fps 太低达不到实时现象训练好的模型在 PC 上跑 30ms 一帧换到树莓派 5 上变成 300ms 一帧演示时卡顿明显。原因float32 的 YOLOv5s 模型在 ARM 设备上计算量大而且没有用硬件加速库做算子优化。解决先用torch.onnx.export导出 ONNX再转成 TensorRT 的 fp16 或 int8 模型Jetson 平台或者 rknn 模型瑞芯微平台。树莓派上没有专用 NPU 的话只能把输入尺寸从 640 降到 416同时把 NMS 的置信度阈值从 0.25 提到 0.4减少后处理的计算量。实测 fps 能提升 3 到 5 倍代价是 mAP 下降 1 到 3 个点在病害检测这种场景里可接受。5.5 病毒类病害和缺素症状分不清误检率飙升现象把花叶病样本加进训练集后健康但缺氮的叶片也被检测成花叶病。原因花叶病和缺素症状在视觉上都表现为叶片颜色不均匀而且没有明显的病斑边界检测框的语义本身就不清晰。解决这类病害不要硬塞进目标检测系统在业务上做分流——有清晰病斑的用 YOLOv5 检测整叶变色的用分类网络做第二级判断。检测系统输出的低置信度框交给分类模型复核可以避免误检。这个方案比强行调模型更符合植保业务的真实判断逻辑。6. 验证模型并收进PPT从混淆矩阵到演示脚本的一站做法训练完成后评估环节要跑全部验证集而不是只看训练时的末轮指标。YOLOv5 自带的 val.py 可以输出各类别的 precision、recall 和 mAP50但更好的做法是直接看保存的 confusion_matrix.png——它比 mAP 数字更能反映哪两类病害互相混淆。烟叶病害里最常见的是野火病和角斑病互相认错因为两者病斑都偏褐色且形状不规则。如果混淆矩阵里这两类的互相误判率超过 10%回看标注时重点观察这两类的边界图例往往能找到标注判据不统一的证据。演示脚本方面给 PPT 答辩准备一段推理演示比贴训练曲线更有说服力。写一个简单的推理脚本从视频或图片序列里逐帧检测实时标注出病斑框和类别import cv2 import torch model torch.hub.load(./yolov5, custom, pathbest.pt, sourcelocal) model.conf 0.3 model.iou 0.45 cap cv2.VideoCapture(field_video.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame) # 把检测结果画回原帧 annotated results.render()[0] cv2.imshow(tobacco_detect, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里的model.conf 0.3在演示时要根据现场画面动态调光线好的时候提到 0.4 减少误检光线差的时候降到 0.25 保证召回。视频演示容易暴露模型的真实弱点建议提前用产区实拍视频试跑一遍把容易误检的帧挑出来思考怎么在 PPT 里解释——可以在现场展示时主动说「这种缺素叶片被模型标记为可疑病害置信度只有 0.3可以通过第二级分类进一步确认」这比掩饰翻车更能体现工程思维。这几年做烟叶检测项目我最大的教训就是数据和标注规则决定上限模型改进只是把上限兑现出来希望帮到你。本文还有配套的精品资源点击获取