简介:这份资源面向深度学习入门者与计算机视觉方向的学习者,以大豆叶病检测为实战场景,帮助读者理解YOLOv8目标检测框架的整体构建流程。内容围绕数据采集与预处理、网络架构设计、基于PyTorch的训练与推理、模型评估指标监控以及部署时的模型压缩优化等环节展开,适合希望从零梳理YOLO工程结构、积累农业视觉应用经验的中级学习者。资源包共7个文件,以6个Python脚本和1个Markdown说明文档为主,涵盖模型定义、数据集加载、损失计算、训练与推理等核心模块,压缩包约9KB,结构精简便于快速阅读源码。目前已有47人学习下载。通过这份资料,读者可以对照完整代码理解YOLOv8各模块的职责划分与调用关系,掌握从数据准备到模型训练、再到推理输出的实现思路,并借鉴其中的排错与优化方向,为后续迁移到其他检测任务打下基础。
1. 大豆叶病检测为什么适合拿来吃透 YOLOv8 框架
大豆叶片上的病斑,是那种典型的“看起来简单、做起来全是细节”的目标检测任务。褐斑、霜霉、灰斑、花叶病毒,早期症状都是叶面上零散的小斑点,尺寸小、对比度低、同类病斑形态差异大,还经常几种病混在同一片叶子上。你拿它练手,等于一次性把 YOLOv8 里最容易被忽略的几个环节全踩一遍:小目标、类别不均衡、标注粒度、数据增强的边界。而它又不像遥感目标检测或三维目标检测那样一上来就要处理超大分辨率、点云配准这些劝退门槛,一张手机拍的叶子图就够跑通全流程。
所以这篇不是讲“大豆叶病有多重要”,而是把“基于 YOLOv8 对大豆叶病做目标检测”当成一条完整的框架构建路径来拆。适合两类人:一类是刚接触 yolo 入门、想找一个真实数据集把 yolov8 环境配置、yolov8训练自己的数据集、yolov8画损失函数曲线图这条链路走完的新手;另一类是已经会调model.train(),但说不清 YOLOv8 网络结构图里每个模块在干什么、yolo损失函数到底怎么算、导出 onnx 模型后为什么掉点的熟手。读完你应该能自己搭出一套可复现的大豆叶病检测流程,并且知道每一步的参数为什么这么设、翻车了去哪看。
2. 把大豆叶病数据集整理成 YOLOv8 能直接吃的格式
2.1 先搞清楚你的标注该画到多细
大豆叶病的标注粒度,直接决定后面模型能不能收敛。常见做法有三种:整片叶子打一个类别(分类思路,不是检测)、每个病斑单独画框、按病斑区域画多边形(实例分割思路)。标题说的是目标检测,那就走第二种,每个独立病斑一个矩形框。但这里有个血泪经验:如果病斑密集到连成片,硬拆成几十个小框,标注一致性会崩,模型学到的边界全是噪声。我一般会设一个最小框面积阈值,比如原图 640×640 下小于 8×8 像素的斑点直接合并到相邻大框或丢弃,并在标注规范里写死。
类别命名也要提前定死。建议按“作物_病害_部位”这种可检索的格式,比如soybean_frogeye_leaf、soybean_downy_mildew_leaf,别用中文或空格,后面转 YOLO 格式、写 data.yaml 时能省一堆编码坑。类别数控制在 4 到 8 类之间比较稳,太少区分度不够,太多每类样本撑不起来。
2.2 从原始标注到 YOLO txt:转换脚本与四个边界坑
YOLOv8 要的标签是每行class_id x_center y_center width height,全部归一化到 0~1。如果你手上是 LabelImg 存的 VOC xml 或者 Labelme 的 json,得转一道。下面这个脚本处理 VOC xml 转 YOLO txt,是我用了很多次的版本:
import xml.etree.ElementTree as ET import os # 类别映射,顺序必须和 data.yaml 里的 names 完全一致 CLASS_MAP = { "frogeye": 0, "downy_mildew": 1, "gray_spot": 2, "mosaic": 3, } def voc_to_yolo(xml_path, out_txt_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLASS_MAP: continue # 未登记类别直接跳过,避免 class_id 错位 cls_id = CLASS_MAP[name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 边界裁剪,防止标注越界导致归一化后出现负值或大于1 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) if xmax <= xmin or ymax <= ymin: continue # 宽高为0的脏框丢弃 x_c = (xmin + xmax) / 2.0 / img_w y_c = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}") with open(out_txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))逻辑说明:先按类别映射表把名字转成整数 id,这一步顺序错了后面全乱;再做边界裁剪,因为手工标注经常有框超出图像边缘的情况,不裁的话归一化会出现负数或大于 1,训练时虽然不一定报错,但会悄悄拉偏损失。参数上img_w、img_h必须传原图真实尺寸,不能传网络输入尺寸,YOLO 内部会自己缩放。四个边界坑分别是:类别名大小写不一致、框越界、宽高为 0、xml 里有多余的difficult节点没过滤。转换完随手抽 5 张用可视化脚本画框核对一遍,比训练跑完再回头查便宜得多。
2.3 data.yaml 与目录结构:一次写对省得反复改
YOLOv8 对目录结构有约定,常见做法是:
datasets/soybean/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml内容:
path: /home/user/datasets/soybean train: images/train val: images/val nc: 4 names: 0: frogeye 1: downy_mildew 2: gray_spot 3: mosaic注意names的索引必须和转换脚本里的CLASS_MAP完全对齐,这是最常见的“训练不报错但 mAP 一直 0.0x”的元凶。path用绝对路径最稳,相对路径在不同工作目录下启动训练时容易找不到。train/val 划分建议 8:2,如果某类样本特别少,用分层抽样保证 val 里每类都有,否则验证集指标会失真。
3. YOLOv8 网络结构与损失函数:训练前必须搞懂的两件事
3.1 从 yolov8网络结构图看骨干、颈部、头部各自在干嘛
YOLOv8 的结构可以粗暴分成三块:Backbone(CSPDarknet 改进版,负责提特征)、Neck(PAN-FPN,负责多尺度融合)、Head(解耦头,分类和回归分开)。大豆叶病检测里,病斑普遍偏小,真正起作用的是 P3 这一层的高分辨率特征图。如果你只记一句话:小目标靠浅层,大目标靠深层,Neck 就是把两者缝起来。看 yolov8网络结构图的时候重点盯三个地方——SPPF 模块的位置(决定感受野)、上采样和 C2f 的拼接顺序(决定融合方向)、检测头输出的三个尺度(80×80、40×40、20×20,对应 640 输入)。
很多新手直接yaml一加载就开训,结果小病斑全漏。原因往往不是模型不行,而是数据里小目标占比太高,而默认的 anchor 匹配和正样本分配对小目标不够友好。YOLOv8 用的是 TaskAlignedAssigner,不需要手工设 anchor,但对小目标的标签分配仍然依赖特征图分辨率。所以如果你的病斑在原图里普遍小于 16×16 像素,要么提高输入尺寸到 960 或 1280,要么在数据增强里少用随机缩放缩小。
3.2 yolo损失函数到底在算什么
YOLOv8 的损失由两部分组成:分类损失用 BCE(二值交叉熵),回归损失用 CIoU 加 DFL(Distribution Focal Loss)。分类部分好理解,每个正样本对每个类别算一次 BCE。回归部分才是容易懵的地方:CIoU 管框的位置和形状,DFL 把框的四个边当成离散分布来学,让边界回归更稳。你不需要手推公式,但要知道两个调参含义——box和cls的权重在默认配置里是 7.5 和 0.5,意思是回归占大头。如果大豆叶病类别之间长得像(比如灰斑和褐斑早期),可以适当调高cls权重;如果框定位总是不准,优先检查标注质量而不是加box权重。
训练时想看损失曲线,YOLOv8 默认会在runs/detect/train/下生成results.csv,里面每轮都有train/box_loss、train/cls_loss、val/box_loss等列。用下面这段画 yolov8画损失函数曲线图:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") df.columns = df.columns.str.strip() # 列名常带空格,不去掉会 KeyError plt.figure(figsize=(10, 5)) plt.plot(df["epoch"], df["train/box_loss"], label="train box") plt.plot(df["epoch"], df["val/box_loss"], label="val box") plt.plot(df["epoch"], df["train/cls_loss"], label="train cls") plt.plot(df["epoch"], df["val/cls_loss"], label="val cls") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.savefig("loss_curve.png", dpi=150)逻辑说明:results.csv的列名前后可能有空格,先 strip 再取列,这是踩过坑的写法。看曲线时重点不是绝对值,而是 train 和 val 的分离趋势——val 先降后升就是过拟合,两条都平在高位就是欠拟合或学习率不对。参数上dpi只影响出图清晰度,不影响数据。
3.3 训练命令与关键参数怎么设
最小可跑命令:
yolo detect train \ model=yolov8n.pt \ data=datasets/soybean/data.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=30 \ project=runs/soybean \ name=exp1参数说明:model从预训练权重起步,大豆叶病样本通常几千张以内,用yolov8n或yolov8s就够,别一上来上x,显存和过拟合都扛不住。imgsz是输入尺寸,小病斑多就上 960。batch按显存调,GTX1660Ti 这种 6G 卡跑 640 大概能到 16。lr0初始学习率 0.01 是默认值,数据量小于 2000 张时可以降到 0.005。patience是早停轮数,设 30 意味着 30 轮验证指标不升就停,省时间。project和name决定输出目录,建议按实验编号命名,不然跑多了自己都分不清哪个是哪个。
4. 训练过程避坑与排查:大豆叶病检测最容易翻车的五个地方
4.1 现象:mAP 从第一轮就卡在 0.001 不动
原因:九成是标签路径或类别索引对不上。YOLOv8 找不到对应 label 文件时不会报错,直接当背景处理,模型学到的全是负样本。解决:训练前用yolo detect train加--verbose看它实际读了多少张图、多少个标签;再抽查labels/train下 txt 文件名是否和images/train一一对应(只有扩展名不同)。类别索引错位则表现为 loss 能降但 mAP 极低,回去核对data.yaml的names顺序和转换脚本的CLASS_MAP。
4.2 现象:训练 loss 正常下降,但验证集框位置整体偏移
原因:标注时用了缩放后的图,但转换脚本传的是原图尺寸,导致归一化坐标系统性偏差。解决:确认转换时img_w、img_h来自原图,而不是你预览时缩放的尺寸。另一个可能是数据增强里的mosaic和mixup在小目标上把病斑拼没了,可以先把mosaic=0跑一轮对比,确认是增强的锅再调mosaic概率。
4.3 现象:显存爆了,报 CUDA out of memory
原因:batch或imgsz设太大,或者workers开太多导致内存泄漏。解决:先把batch减半,再考虑降imgsz。workers在 Windows 上建议设 0 或 2,Linux 上可以到 8。如果用的是 6G 显存的卡,640 输入下yolov8s的batch别超过 16。实在不够就开amp=True(默认开),混合精度能省不少显存。
4.4 现象:验证集指标很好,实际拍的新叶子一张都检不出
原因:数据集的叶子都是实验室白底摆拍,实际场景有土壤、阴影、多叶重叠,域差异太大。解决:训练时加hsv_h、hsv_s、hsv_v做颜色扰动,加degrees、translate、scale做几何扰动,尽量模拟真实拍摄条件。如果已经有真实场景图,哪怕只有几十张,也混进训练集,比纯合成增强管用。
4.5 现象:导出 onnx 后推理结果和 pytorch 不一致
原因:导出时imgsz和训练时不一致,或者opset版本和推理引擎不匹配。解决:导出命令里显式指定imgsz=640,和训练保持一致;opset用 12 或 17,别用太新的。导出后用onnxruntime跑一张图,和 pytorch 输出对比,差超过 1e-3 就要查预处理(归一化方式、通道顺序)是否一致。
5. 从训练到部署:验证模型是否真的学到了病斑
5.1 用混淆矩阵和热力图定位“假学会”
训练完别只看 mAP50,先看runs/soybean/exp1/confusion_matrix.png。如果某类病斑大量被分到背景,说明召回不够,可能是该类样本太少或标注框太小。再看val_batch0_pred.jpg,重点看模型画的框是不是集中在叶脉或高光区域——如果是,说明它学的是纹理而不是病斑,这时候要回去检查标注是否把非病斑区域也框进去了。想看模型关注哪里,可以用 yolov8可视化热力图,常见做法是拿model.model的某一层特征图做 Grad-CAM,或者直接用ultralytics的predict加save=True看预测框分布,后者更省事。
5.2 导出与推理:onnx 和 tensorrt 怎么选
如果只是本地验证,导出 onnx 就够:
yolo export model=runs/soybean/exp1/weights/best.pt format=onnx imgsz=640 opset=12如果要上边缘设备,比如 rk3588部署yolov8,那得走 rknn 工具链,先把 onnx 转 rknn,再量化。这里有个参数坑:rknn 量化时如果校准集用的图和你实际场景差太多,量化后小病斑直接消失。校准集一定要从验证集里抽,别随便找几张图凑数。至于 t4 1080p25帧每秒用tensorrt yolo 640分辨率检测能支持多少路,这个没有固定答案,取决于 batch 和精度,但大豆叶病这种场景通常不需要实时多路,单路 30fps 足够。
5.3 一个具体技巧:用 TTA 把漏检的小病斑捞回来
如果训练完发现小病斑漏检还是多,又不想重训,可以试测试时增强(TTA)。YOLOv8 的predict支持augment=True:
yolo detect predict \ model=runs/soybean/exp1/weights/best.pt \ source=test_images/ \ imgsz=960 \ augment=True \ conf=0.15 \ save=True逻辑说明:augment=True会对每张图做多尺度、翻转等变换后融合结果,对小目标召回有提升,代价是推理变慢。imgsz从 640 提到 960 能让小病斑在特征图上占更多像素。conf降到 0.15 是配合 TTA 用的,因为融合后置信度会被平均拉低,阈值不降反而漏更多。这个组合我在几个小目标数据集上试过,mAP50 能涨 2 到 4 个点,但推理时间大概翻倍,自己权衡。
我自己的习惯是:每次训完先不急着调参,把val_batch0_pred.jpg和confusion_matrix.png各看五分钟,比盲目跑十组实验有用。大豆叶病这个任务教会我的就是,标注质量决定上限,增强和 TTA 只是把已经学到的东西榨干。希望帮到你。
本文还有配套的精品资源,点击获取