☰
基于YOLOv5的烟叶病害识别:从数据标注到模型部署全流程实践
2026/9/26 13:20:28 网站建设 项目流程

简介:这套基于YOLOv5的烟叶病害识别项目,面向深度学习初学者及高校学生,可支撑课程设计、期末大作业与毕业设计等教学场景。内容覆盖数据预处理、模型训练、病害预测与结果后处理完整流程,同时包含可供训练的多类烟叶病害图像及对应标注,并提供演示视频与安装教程,便于快速复现和二次开发。压缩包共378个文件,以jpg烟叶病害样本、txt标注文件、py源码与yaml配置为主,辅以mp4演示、pt权重、Dockerfile及部署脚本,整体约782.55MB,目录结构清晰,适配多种实验环境。已有147人浏览学习。附带的环境部署文档与操作视频,能帮助学生避开配置弯路,直观理解数据增强、特征提取、损失函数与梯度下降等核心概念,从数据构建到模型部署全流程动手实践,实现理论与实操的紧密结合。

1. 基于 YOLOv5 的烟叶病害识别源码:从数据集到演示视频一次跑通

烟草病害搞不定,整片烟田的收成就悬了。毕设选题想往深度学习上靠,很多学生最后都会落在目标检测上,而基于 YOLOv5 的烟叶病害识别源码,恰好把最耗时间的几件事打包好了:标注后的数据集、可训练可推理的完整代码、演示视频和安装教程一次给齐。它能解决的核心问题就一个——你不用再东拼西凑病害图片,也不用从零调通训练流程,照着教程走完,就能得到一份能跑、能出检测框、能答辩演示的成品。适合三类人:赶毕业设计的学生、想快速入门 YOLOv5 的开发者、以及需要给烟叶病害做识别验证的农业相关从业者。下面我按自己的复现顺序,把环境、数据集、训练和排错拆开讲。

2. 环境配置与源码结构:先把 YOLOv5 跑起来再谈识别

这套源码拿到手的第一感觉是“文件比想象的多”。因为是毕设项目,一般不会直接躺在官方 YOLOv5 仓库上,而是多出数据目录、训练好的权重、演示视频和一份安装教程。别急着训练,先把环境锁死,再顺着源码结构搞清楚 train.py 和 detect.py 在哪,跑通一次推理再碰训练,后面会省很多事。

2.1 环境准备:先用 conda 把 Python 和 PyTorch 锁死

环境这块是整条链路里最容易翻车的:torch 版本和 CUDA 对不上,训练一开始就报奇怪的符号错误,或者干脆检测不到显卡。我一般会新建一个独立的 conda 环境,避免把实验室或自己电脑上的 Python 环境搞乱。

conda create -n smoke python=3.8 -y conda activate smoke pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 --extra-index-url https://download.pytorch.org/whl/cu117 pip install -r requirements.txt

这段命令里,python=3.8 是 YOLOv5 比较稳的版本,3.10 以上有时会遇到某些依赖包编译报错。torch 和 torchvision 后面的 +cu117 表示对应 CUDA 11.7,装之前先用 nvidia-smi 看一眼自己显卡驱动支持的 CUDA 版本,如果驱动支持到 11.3,就把 cu117 改成 cu113。没有 NVIDIA 显卡的机器,装 CPU 版就行,训练会慢几倍,但做验证和演示完全能跑。requirements.txt 里主要是 numpy、opencv-python、PyYAML、matplotlib、tqdm 这类常规库,一条命令装完。

环境装完先做一个最小检查:python -c "import torch; print(torch.cuda.is_available())"。打印 True 说明 CUDA 版本没配错;打印 False 也不一定就是坏的,可能只是没装 GPU 版,后续训练命令会自动落到 CPU 上。我一般还会顺手跑一次python detect.py --help,如果能正常打印参数说明,依赖就算齐了。

注意:conda 环境名和数据集路径都不要用中文,YOLOv5 对中文路径的处理很脆弱,后面避坑章会展开讲。

2.2 源码目录:train.py 和 detect.py 是两根主线

解压源码后先不急着跑,在根目录列一下结构。常见结构大致是这样:

smoke-yolov5/ ├── train.py ├── detect.py ├── requirements.txt ├── data/ │ ├── smoke.yaml │ └── images/ ├── models/ │ ├── yolov5s.yaml │ └── common.py ├── utils/ │ ├── datasets.py │ ├── loss.py │ └── torch_utils.py ├── weights/ │ └── yolov5s.pt └── runs/ ├── train/ └── detect/

train.py 是训练入口,所有训练参数都从这里传进去;detect.py 是推理入口,训练完用它跑图片、视频或者摄像头。data/ 里放数据集配置和图片样本,smoke.yaml 里写着类别数量和类别名,这是整个项目的说明书。models/ 里的 yolov5s.yaml 定义网络结构,不是训练超参数,训练超参数在 data/hyps/hyp.scratch-low.yaml。utils/ 是数据集加载、损失函数、模型工具这些底层逻辑,一般不用动。weights/ 放预训练权重,runs/ 是训练和推理结果的默认输出目录。

这套毕设源码和官方仓库最大的差别在 data/ 和顶层多出来的几个文件夹。比如 dataset/ 可能直接放标注好的烟叶图片和 txt 标签,教程文档里会写清数据集是什么病害、标签格式怎么定义。如果你拿到的包里还有 demo.mp4,那就是给演示用的视频样本,训练完成之后可以直接用 detect.py 跑它出结果。

结构搞清楚之后,我的习惯是先在 data/smoke.yaml 里确认两件事:第一,train 和 val 的路径是否跟自己数据集实际位置一致;第二,nc 和 names 的数量是否跟自己标签里的类别编号对得上。这两处不一致,后面会有一堆莫名其妙的报错。

2.3 首次推理:用预训练权重当 hello world

环境弄完,先别碰自己的数据集,拿官方预训练权重跑一次推理。这步的目的是验证安装没问题,也顺便把 detect.py 的输入输出方式熟悉一下。

python detect.py --weights weights/yolov5s.pt --source data/images/leaf.jpg --conf-thres 0.35

如果包里没有 leaf.jpg,直接用官方仓库自带的 data/images/bus.jpg 也行。跑完后终端会打印每条检测结果,比如 class、confidence、box 坐标,并在 runs/detect/exp 下生成一张画了框的图。看到框画出来,环境就算验完了。

--weights 指定权重文件,--source 可以是图片、视频文件、文件夹或者摄像头设备的编号,比如 0 表示笔记本前置摄像头。--conf-thres 是置信度阈值,0.35 意思是模型只有把握超过 35% 才画框;演示视频里我一般设 0.25~0.4,太高会漏掉病害小斑块。

第一次跑容易踩的坑是库版本冲突:如果之前装过其他版本的 torch 或 opencv,可能报AttributeError: module 'numpy' has no attribute 'bool'。这是 numpy 版本太新把 np.bool 删了,把 numpy 降到 1.24 以下就行。另一个坑是 detect.py 会把结果写进 runs/detect/exp、exp2、exp3 这样递增的目录里,要看最新结果,找目录名数字最大的那个。

3. 烟叶病害数据集:标签格式、划分脚本与类别不均衡

跑通预训练推理后,接下来是把烟叶病害数据集喂给模型。很多毕设在这步翻车,不是模型的问题,而是标签文件的问题。YOLOv5 的数据集不是给整张图打一个标,而是给图里每个病斑画框并写成一个 txt,理解了这个格式,后面 90% 的报错都能自己解决。

3.1 标签格式:一行五个数字,坐标全是相对值

数据集的 labels 目录里,每一张图片对应一个同名的 txt 文件。里面每一行表示一个目标,格式是五个数字:类别编号、归一化中心点 x、归一化中心点 y、归一化宽度、归一化高度。归一化的意思是这些数都除以了图片本身的宽和高,值在 0 到 1 之间。

烟叶病害在数据里通常会分好几类,比如烟草花叶病、黑胫病、赤星病、野火病,具体类别以包里的 data.yaml 为准。类别编号从 0 开始,不是从 1 开始,这一点最容易弄错。我习惯写个小脚本把标签坐标还原成像素坐标,看看框是否落在病斑上:

img_w, img_h = 640, 640 # 换成图片实际尺寸 with open('data/smoke/labels/train/000001.txt') as f: for line in f: cls, cx, cy, w, h = map(float, line.split()) x1 = (cx - w / 2) * img_w y1 = (cy - h / 2) * img_h x2 = (cx + w / 2) * img_w y2 = (cy + h / 2) * img_h print(f'class {int(cls)}: ({x1:.1f}, {y1:.1f}) -> ({x2:.1f}, {y2:.1f})')

这段脚本的逻辑是把 YOLO 格式的中心点和宽高先转换成左上角、右下角,再乘回图片宽高,得到像素坐标。检查时随便找几张图和对应标签,用画框工具确认框是否贴合病斑。如果出现 cx、cy 大于 1,或者 w、h 为 0 甚至负数,这张图的标签就是坏的,训练时会直接报错或被跳过。注意 txt 文件名必须和图片名完全一致:图片叫 000001.jpg,标签就必须叫 000001.txt,大小写也别差。

3.2 data.yaml 与数据集划分:别让验证集里有训练图

数据准备好了,还要让 YOLOv5 知道去哪读。data/smoke.yaml 是训练用的数据配置,一个标准的烟叶病害配置长这样:

train: data/smoke/train/images val: data/smoke/val/images nc: 4 names: ['tobacco_mosaic', 'black_shank', 'brown_spot', 'wildfire']

train 和 val 指向的路径,相对源码根目录写也行,绝对路径也行。nc 写类别数量,names 写类别名,顺序必须和标签里的编号一致:编号 0 对应 names[0],编号 1 对应 names[1],以此类推。如果 names 顺序和标注时不一致,训练不会报错,但 mAP 会错乱,画出来的框类别名也是错的。

划分数据集时,图片和标签要一起搬,而且训练集和验证集不能有同一张图。常见问题是有同学把整个目录塞进 train,val 里也复制了一份,结果模型在验证集上成绩虚高。给一个简单可靠的划分脚本:

import os, random, shutil base = 'data/smoke' images = os.listdir(os.path.join(base, 'images')) labels = os.listdir(os.path.join(base, 'labels')) random.seed(42) idx = list(range(len(images))) random.shuffle(idx) split = int(len(images) * 0.85) for i, pos in enumerate(idx): img, lab = images[pos], labels[pos] subset = 'train' if i < split else 'val' shutil.copy(os.path.join(base, 'images', img), os.path.join(base, subset, 'images', img)) shutil.copy(os.path.join(base, 'labels', lab), os.path.join(base, subset, 'labels', lab))

这段脚本按 85% 和 15% 划分,随机种子固定为 42,保证每次运行划分结果一致。脚本假设 images 和 labels 里的文件名一一对应,如果某张图片少了标签,Copy 会直接报错,恰好暴露数据缺失问题。训练集比例一般取 0.8 到 0.9,样本多就 0.9,样本少就 0.8;验证集留太少,mAP 曲线会抖动得很厉害。

3.3 标签体检:统计类别数量,找出漏标图

训练之前我建议先给数据集做个体检,统计每个类别有多少个框、有多少张图完全没标签,这个动作能提前发现大多数噩梦:

import os from collections import Counter labels_dir = 'data/smoke/labels' counter = Counter() empty = [] for root, _, files in os.walk(labels_dir): for f in files: if not f.endswith('.txt'): continue path = os.path.join(root, f) lines = [l for l in open(path).read().strip().splitlines() if l] if not lines: empty.append(f) for line in lines: counter[int(line.split()[0])] += 1 print('类别分布:', dict(counter)) print('空标签文件:', len(empty), empty[:5])

统计结果如果发现某个类别只有十几二十个框,这个类别的 mAP 大概率会很低。解决办法不是删掉它,而是先做数据增强:对这类病害图片做水平翻转、小角度旋转、亮度抖动,再重新标注。如果只差几张,直接手工补标比换模型更快。空标签文件也要注意,YOLOv5 遇到空 txt 不会报错,但会当负样本处理,空文件太多会让模型倾向不输出任何框。

提示:报错里常见的Label class 5 exceeds nc=4就是标签体检漏掉的问题——某个标签出现了类别 5,但 yaml 只定义了 4 类。

4. 训练烟叶病害模型:命令、超参数与怎么看 mAP

数据集就位后,训练命令本身不复杂,真正影响毕业设计成绩的是三个选择:用哪个模型骨架、超参数怎么设置、训练完怎么判断好坏。下面按这个顺序说。

4.1 模型选型:yolov5s 起步,yolov5m 兜底

YOLOv5 按深度和宽度分成 s、m、l、x 几档。烟叶病害目标不算太小,病斑一般有几十到几百像素,yolov5s 的 640 输入完全够用。做毕设我推荐用 s 起步,显存占用小、训练速度快,先跑通流程再考虑升级。

模型参数量640 输入显存占用(参考)适合场景
yolov5s约 7.2M8~12G 可训练毕设起步、快速出结果
yolov5m约 21.2M12~16G 可训练想要更高 mAP、显卡不紧张
yolov5l约 46.5M16~24G 可训练追求精度、对推理速度不敏感

表中的显存占用是 batch-size 16 时的粗略估计,实际跟图片分辨率、mosaic 是否开启有关。实验室单卡 8G 的机器,用 s 配 batch-size 8 比较稳;16G 显存,s 可以开到 16,m 也能跑。l 和 x 对烟叶病害这种目标形态比较固定的场景有点浪费,训练时间翻倍,mAP 提升却有限。

4.2 训练命令与超参数:改 yaml 比改代码安全

模型结构文件 models/yolov5s.yaml 一般不用动,训练参数通过命令行传。最基础的训练命令:

python train.py \ --data data/smoke.yaml \ --cfg models/yolov5s.yaml \ --weights weights/yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --device 0

--cfg 指定网络结构,--weights 指定预训练权重。用官方预训练权重做迁移学习,收敛速度和最终精度都比随机初始化好得多;如果 --weights 传空字符串,就是从头训练,对烟叶这种中等规模数据集一般不推荐。--epochs 100 是毕设常见设置,60 个 epoch 能有明显效果,100 个足够收敛。--batch-size 受显存限制,报 OOM 就降。--imgsz 必须是 32 的倍数,640 是默认值,想加速可以试 416,但小病斑会被缩小,精度会掉。

超参数文件在 data/hyps/hyp.scratch-low.yaml,训练时用 --hyp 指定。里面有几个值得手动改的:lr0 是初始学习率,默认 0.01;mosaic 是马赛克增强开关,数据量小的时候建议开着;label_smoothing 是标签平滑,默认 0.0,烟叶病害类间差异大,开到 0.05~0.1 能缓解个别误标样本的影响。改超参数改 yaml 文件,不要去改 train.py 源码,因为很多默认值会被命令行覆盖,改 yaml 更容易记录每次实验做了什么。

4.3 训练日志与结果:loss 曲线、混淆矩阵、best.pt

训练结束后,runs/train/exp 目录里最重要的三样东西:results.png、confusion_matrix.png 和 weights 下的 best.pt。results.png 里有 box_loss、obj_loss、cls_loss 三条 loss 曲线和 mAP 曲线。正常情况三条 loss 整体下行,尾部允许振荡;如果 mAP@0.5 一直趴在地上,先回数据集查标签,不建议盲目加 epoch。

confusion_matrix.png 是混淆矩阵,横纵轴都是类别,对角线越亮越好。烟叶病害里像赤星病和野火病初期都是褐色小点,如果混淆矩阵里它们互相串得厉害,说明标注边界不够清晰,需要回标签里修框,而不是换模型。weights/best.pt 是验证集上 mAP 最高的权重,最后部署跑视频用它,不用 last.pt。

训练过程中想实时看曲线,可以开 tensorboard:python -m tensorboard.main --logdir runs/train,浏览器打开对应端口就能看到。如果嫌小项目开 tensorboard 麻烦,直接等训练完看 results.png 也完全够用。

5. 避坑与排查:烟叶数据训练翻车的五个常见现场

这套资源我前前后后复现了两回,第一回几乎把该踩的坑都踩遍了。下面这五个问题是毕设群里问得最多的场景,每一条按现象、原因、解决三步讲清楚。

5.1 中文路径报错:标签读不出来不是玄学

现象:训练启动时报错,常见的有AssertionError: Label class 5 exceeds nc=4 in data/smoke.yaml,或者图片加载时找不到文件,路径里明明有对应文件。更糟的是在自己电脑上能跑,拷到实验室电脑上一跑就崩,因为两个人的用户名不一样。

原因:YOLOv5 的数据加载模块对中文路径支持不好,Windows 下中文路径加上文件名的编码问题,会导致标签文件读不到或图片路径对不上。另一层是标注时的类别编号和 yaml 里定义的对不上,比如标签里出现了类别 5,但 nc 只定义了 4 类。

解决:把整个项目放到纯英文路径下,数据集也一起,比如 D:\yolov5-smoke。用户名是中文就放 D 盘根目录,不要放进“C:\用户\张三”下面。图片名和标签名用英文或数字,不要用“病斑1.jpg”这种中文名。然后打开标签文件,检查里面最大的类别编号是否小于 nc。

5.2 CUDA out of memory:先降 batch-size 再考虑换卡

现象:训练命令一敲下去,屏幕直接刷红,最后一行写着RuntimeError: CUDA out of memory.。

原因:显存被 batch-size、imgsz 和 mosaic 数据增强一起吃光。YOLOv5 的 mosaic 增强会把 4 张图拼成一张训练,显存峰值比正常单图训练高不少。

解决:把 batch-size 从 16 降到 8 或 4,再不行把 --imgsz 从 640 降到 416。对烟叶病害检测,416 输入在 8G 显卡上能比较舒服地训练。如果还 OOM,检查是不是有其他程序占着显存,Windows 下关掉浏览器再试。千万别为了省显存去改代码砍 mosaic,那是把数据增强玩残,得不偿失。

5.3 mAP 低但 loss 正常:先查类名编号和标签坐标

现象:训练 100 个 epoch,loss 曲线下降很漂亮,最后 mAP@0.5 只有 20%~30%,怎么看都不正常。

原因:这类情况十有八九是标签编号和 yaml 对不上。比如标注工具用的编号是 1、2、3、4,但 yaml 里 nc=4、names 从 0 开始,于是第一个类别在训练时被整个忽略。另一种是坐标范围越界,归一化坐标算出了 1.2 这种值,模型学了个寂寞。

解决:用 3.3 节的统计脚本,把每个标签文件里的编号分布和坐标范围打出来。编号如果从 1 开始,写个脚本全部减 1,再检查坐标是不是都在 0~1 之间。调整完重新划分数据集,再训练一次。这个坑最坑的地方在于它不报错,只能靠标签体检发现。

5.4 全图一个大框:通常是锚框或标签文件出了问题

现象:推理时每张图都被画了一个铺满整图的大框,或者一个框反复出现在画面中心,置信度还不低。

原因:两个来源。第一,标签文件被误改成了 cx=0.5、cy=0.5、w=1、h=1 这种整图标注,模型学到的就是“整张图都是目标”。第二,训练时关闭了自动锚框计算,默认 COCO 数据的锚框不适合烟叶病斑这种更小更密集的目标。

解决:先排除标签问题,随机打开几个 txt 确认坐标分布。确认标签没问题后,训练时不要加 --noautoanchor 参数,让 YOLOv5 在训练前自动从数据集里算锚框;如果已经训练完,删除 runs 里的旧结果,重新训练一次,并观察训练日志里有没有打印 AutoAnchor 相关的输出。

5.5 断电后续训:--resume 才是后悔药

现象:训练到第 70 个 epoch 时断电或者手滑关了终端,再启动时直接用了python train.py --weights runs/train/exp/weights/last.pt ...,结果 loss 从头涨,前面 70 个 epoch 白费了。

原因:这样传 --weights 只是把 last.pt 当作预训练权重重新初始化训练,优化器、学习率计划、epoch 计数全部重来,等于重新训。

解决:用 YOLOv5 自带的断点续训命令:

python train.py --resume runs/train/exp/weights/last.pt

train.py 会从 last.pt 里恢复 optimizer 状态和 epoch 计数,继续原来的 exp 目录往下训。从那以后我每次训长任务前都会确认一下 exp 目录还在不在,中断后第一时间用 --resume,而不是 --weights。

6. 用训练好的模型跑演示视频:推理参数与部署技巧

6.1 视频推理命令与参数:conf-thres 别设太高

训练完拿到 best.pt,最后一步是让它在家演示视频上出结果。命令不复杂:

python detect.py \ --weights runs/train/exp/weights/best.pt \ --source demo.mp4 \ --conf-thres 0.35 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect

--source 支持单张图片、视频文件、整个文件夹,甚至摄像头编号。--conf-thres 0.35 表示置信度超过 35% 才画框;烟叶病斑小、和背景对比不明显,阈值设到 0.5 以上容易漏检,0.25~0.4 是稳妥区间。--iou-thres 是 NMS 的 IoU 阈值,默认 0.45 够用,两个病斑靠得近时可以调低到 0.4,减少框被合并。--save-txt 会在输出目录里留下与检测框对应的文本结果,格式和训练标签一致,方便写数据分析报告。

演示视频跑完,输出目录下是逐帧标注的图片序列。如果想合成一个视频用于答辩演示,用 ffmpeg 就行:

ffmpeg -framerate 25 -i runs/detect/exp/exp_frame_%05d.jpg -c:v libx264 -pix_fmt yuv420p result.mp4

-framerate 25 要和原视频帧率一致,否则画面会快进或放慢。这里演示的是通用做法:先出帧序列,再合成视频,比直接在 detect.py 里配视频编码省心。

6.2 导出手检模型:ONNX 与后续优化方向

如果后续想部署到手机上做实时检测,可以把 best.pt 转成 ONNX,再用 ONNX Runtime 推理。导出命令:

python export.py --weights runs/train/exp/weights/best.pt --include onnx

导出后可以用 onnxruntime 在 CPU 上跑,速度比 PyTorch 部署快不少。不过对绝大多数毕设场景,到 best.pt 跑通演示视频这步已经完成了闭环,ONNX 只是加分项。

这套流程复现下来最大的体会是:烟叶病害识别在 YOLOv5 这条链路上,决定结果上限的是数据集质量,不是网络结构。最初拿到这份源码时,我也急着省事直接跑预训练,结果在中文路径和类别编号上各自栽了一次跟头。从那以后,只要拿到新数据集,我都会先强制走一遍标签体检和类别编号核对,再扔进 train.py。这套习惯应该能帮你省下不少排查时间,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询