YOLOv8大豆叶病检测实战:环境搭建到模型部署完整链路
简介面向计算机视觉初学者与农业智能化研究者这份压缩包以YOLOv8实现大豆叶病目标检测完整展示从数据集构建、模型设计到训练推理的YOLO框架搭建流程。资源共7个文件包含6个Python脚本和1个Markdown说明脚本分别覆盖网络结构、数据加载、损失计算、训练与推理等核心模块说明文档则可辅助快速上手。压缩包仅9KB代码精简适合逐行研读YOLO整体构建思路。目前已有47人学习浏览。对于希望脱离纯理论、通过实际项目掌握PyTorch目标检测流程的读者既能作为大豆叶病识别的参考实现也可当作搭建自定义YOLO检测器的起点借助轻量代码快速迁移到其他检测场景加深对YOLO框架各环节的理解。1. 大豆叶病检测为什么绕不开YOLOv8从预训练权重翻车说起大豆叶病的病斑通常只占叶片面积的百分之几叶片本身形态各异光照和土壤背景又混杂直接拿现成的YOLOv8预训练权重跑要么漏检要么把叶脉当成病斑。真正的问题在于多数人用YOLO只学会了跑predict没学会从环境、数据到训练、评估的完整链路。这份基于YOLOv8的大豆叶病目标检测项目拆的正是这条链路环境搭建、Labelme标注转换、训练调参、避坑闭环、部署验证。适合两类人——课题或实习需要做植物病虫害检测的从业者以及想借一个具体任务把YOLO框架整体摸透的学习者。2. YOLOv8的工程结构与环境搭建先把Anaconda和PyTorch版本对齐2.1 读懂ultralytics包结构训练流水线其实只有三个入口拿到这份资源先别急着跑demo先看一下ultralytics包的目录结构。YOLOv8在2023年之后把代码统一收进ultralytics这一个包里入口是CLI命令yolo和Python API网上大量旧版YOLOv5的detect.py、train.py那种用法在v8上走不通。我一般会花二十分钟把ultralytics目录过一遍重点看cfg/models/v8下的yolov8n.yaml、yolov8s.yaml以及cfg/datasets里自带的data.yaml模板。真正训练和推理的逻辑全在ultralytics/engine/trainer.py和predictor.py里入口脚本只是一层薄壳。这种全局观到后面调参时特别有用。训练命令里传的data、epochs、batch这些参数进了trainer.py之后会经过settings和checker校验很多报错信息在v8里被规范化成一句英文提示。看不懂英文提示时直接翻trainer.py里对应的raise语句一眼就能定位到问题是出在数据路径还是参数类型。靠这个办法排查比在论坛里搜同样的报错快得多也更不容易被各种过时答案带偏。另外一个值得注意的点是YOLOv8网络结构本身。v8在C2f模块里把v5的CSP结构做了升级颈部PAN-FPN保留Head换成了Decoupled Head分类和回归分支分开输出。理解这一点不是浪费时间——后面遇到loss曲线不收敛、框回归不准时你会知道问题大概率出在回归分支的损失权重上而不是整体结构。2.2 Ubuntu 20.04下CPU/GPU环境PyTorch版本和驱动对齐是第一步环境配置这一步新手最容易翻车的地方是PyTorch版本和CUDA版本没有对齐。先创建一个独立的conda环境别把YOLOv8装进base环境里后面换项目时依赖冲突会非常难受conda create -n yolo python3.9 conda activate yolopython3.9是稳妥选择。YOLOv8官方要求Python大于等于3.83.10和3.11也能跑但有些标注转换脚本依赖的库还没跟上回到3.9能少踩很多坑。这一步本身没有技术难度难的是养成每个项目一个独立环境的习惯。接下来装PyTorch。这里CPU版本和GPU版本的安装命令完全不同最容易犯的错是拿GPU安装命令丢给一台没有NVIDIA显卡的机器然后收到No CUDA GPUs are available。先确认机器上有没有卡nvidia-smi有输出说明驱动在记下右上角CUDA Version那一行。比如显示CUDA Version 12.0不代表你要装cu120的torch而是告诉你驱动支持的上限。接着看显卡算力GTX 1660 Ti这类图灵架构和RTX 30系安培架构都能用cu118的预编译包。确认完硬件之后再选安装命令# CPU版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # GPU版本CUDA 11.8覆盖多数GTX 10系之后显卡 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118细节在这里装CPU版和GPU版都指定了--index-url而不是直接pip install torch。直接装默认拉最新版torch可能跟你机器上的CUDA驱动不匹配而--index-url能精确控制要装的CUDA版本后缀。装完别急着下一步先验证python -c import torch; print(torch.__version__, torch.cuda.is_available())GPU机器上看到类似2.1.0cu118 True才算通过。CPU机器上返回False不影响后续流程只是训练慢。这里顺便说一句GPU显存6G以下的机器比如GTX 1660 Ti训练时batch设8、imgsz设640是能跑的但如果同时开着浏览器很容易在某个epoch中间突然OOM训练前把占用显存的应用全关掉。2.3 装ultralytics与首跑验证用yolov8n.pt验环境而不是验模型PyTorch就位后装主包pip install ultralyticsultralytics会自动带上opencv-python、numpy、pandas这些依赖。我第一次装的时候卡在opencv的版本冲突上——项目里老代码装的是opencv-contrib-python-headlessultralytics又要opencv-python两个包会互相覆盖。解决方法是先卸载再装pip uninstall opencv-contrib-python-headless -y pip install opencv-python然后下载预训练权重yolov8n.pt。这个权重是v8全系列里最轻量的一版参数量约320万几十MB大小拿来做环境验证最合适。执行后权重文件会自动下载到当前目录后续运行会优先读本地文件。# verify_env.py from ultralytics import YOLO model YOLO(yolov8n.pt) results model.predict(sourcehttps://ultralytics.com/images/bus.jpg, saveTrue) print(results[0].boxes.data.shape)这段代码的逻辑是加载一个预训练模型对示例图片做推理saveTrue把结果图写到runs/detect/predict目录最后打印检测框的张量形状。输出类似(3, 6)就说明一张图里框出了3个目标每条记录是[x1, y1, x2, y2, conf, cls]。首跑验证的目的是验证环境而不是验证模型。如果卡在权重下载通常是网络问题这个文件可以从镜像站手动下载放到当前目录。看到输出shape正常说明从CUDA到opencv到权重加载整条链路都通环境就算立住了。这里也兼顾了纯CPU环境——CPU机器跑同样代码只要不报错就算过只是推理耗时明显长属正常现象。3. 大豆叶病数据集构建从Labelme标注到YOLO格式的四步流水线3.1 类目设计与图像采集先定小目标再定标签大豆叶病的检测难点在于病斑小、和健康叶片颜色反差小而且不同病害在普通RGB图像上差异不如想象中明显。做数据集之前先和植保背景的人确认类目边界。常见做法是把霜霉病、灰斑病、细菌性斑疹病、大豆锈病先分开背景和健康叶片单独作为背景类处理或者干脆不标。我建议初期类目宁少勿多四五个类目先跑通流程再逐步加类。图像采集阶段要注意多样性。用手机或无人机在不同光照、不同角度、不同生育期拍摄不要全是同一块地同一角度。如果训练图全部来自同一个下午的同一块田模型极易过拟合到那个时段的光照和土壤颜色上后续换场景实测基本废掉。图片数量上每个类目至少200张起步单类图像少于100张时训练出来的模型基本靠预训练权重的底子硬撑病斑特征学不到多少。类目不平衡的问题也一样某个病种只有几十张图训练时会被其他类目淹没后面可以用class_weights选项补偿。3.2 Labelme标注要点多边形工具和矩形框的取舍标注工具我固定用Labelme因为它是开源工具里对不规则轮廓支持最好、跨平台体验最一致的一个pip install labelme labelme --labels labels.txtlabels.txt里按行写类目名换行就是分隔符soybean_rust frog_eye_leaf_spot brown_spot bacterial_blight有个跟YOLO默认标注相关的细节Labelme默认让你画多边形优势是能把病斑不规则的轮廓贴合得更准但YOLOv8训练时用的是矩形框多边形标注的信息在转换时会退化成外接矩形。所以标注时直接使用Create Rectangle工具按对角线拉框省掉后面多边形的坐标整理。Labelme生成的JSON本质上是一样的结构shapes字段里每个元素有label、points、shape_type矩形也存成四个点的多边形转换脚本要兼容这个数据结构。每张图标完会生成同名JSON文件建议图片和JSON放在同一个文件夹。标注工作量如果超过几百张按先标50张→训练一版→挑漏检再补标的节奏推进一次性标完三百张再训练返工成本极高——你会在训练完才发现标注标准不对比如有的框把病斑和健康叶脉框在一起然后回头改两三百个文件非常消磨耐心。3.3 标注统一转YOLO格式JSON与XML转换脚本与坐标坑Labelme导出的是JSON老项目常用labelImg导出VOC风格的XML。这份资源里给了转换脚本把两种格式统一成YOLO训练需要的txt文件。YOLO格式每行是class x_center y_center width height全部除以图像宽高做归一化。核心代码逻辑如下import json import os from PIL import Image def labelme_to_yolo(json_path, img_dir, out_dir, class_names): with open(json_path, encodingutf-8) as f: data json.load(f) img_path os.path.join(img_dir, data[imagePath]) img Image.open(img_path) w, h img.size base os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(out_dir, base .txt), w) as out: for shape in data[shapes]: label shape[label] if label not in class_names: continue cls_id class_names.index(label) points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) x_center ((x_min x_max) / 2) / w y_center ((y_min y_max) / 2) / h box_w (x_max - x_min) / w box_h (y_max - y_min) / h out.write(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n)代码逻辑不复杂读出每个shape的标签和顶点坐标取所有顶点的最小最大值构成框再算中心点和宽高最后全部除以图像原始宽高归一化。这里循环里的cls_id class_names.index(label)是低效写法类目多了以后建议改成字典映射但这个量级的数据影响不大。这个脚本有两个关键注意点。第一data[imagePath]跨平台时经常带着反斜杠或绝对路径不能直接拼路径用最好统一用自己传入的img_dir。第二分母用的是原始图像宽高如果图像后面做了resize转换完的txt必须整体重新算不能拿640×640的尺寸去归一化1920×1080的原始坐标。这类错误训练时不报错但框全部偏掉排查起来很费劲。XML转YOLO的思路相同只是把json.load换成解析VOC的xml树取bndbox节点里的xmin、ymin、xmax、ymax。VOC的坐标天然是矩形框不需要处理多边形退化的问题反而更省事。我见过不少项目在两种格式之间来回倒腾最后标准不统一导致训练集里混了两种坐标系所以转换完最好抽几张图可视化验证。3.4 数据集划分8:1:1切分与固定随机种子划分脚本我固定用8:1:1import os import random import shutil imgs images src labels_txt train_imgs, val_imgs, test_imgs [], [], [] all_files [f for f in os.listdir(imgs) if f.endswith(.jpg)] random.seed(42) random.shuffle(all_files) n len(all_files) train_files all_files[: int(n * 0.8)] val_files all_files[int(n * 0.8) : int(n * 0.9)] test_files all_files[int(n * 0.9):] for split, files in [ (train, train_files), (val, val_files), (test, test_files), ]: os.makedirs(fdataset/{split}/images, exist_okTrue) os.makedirs(fdataset/{split}/labels, exist_okTrue) for f in files: shutil.copy(os.path.join(imgs, f), fdataset/{split}/images/{f}) label os.path.splitext(f)[0] .txt shutil.copy(os.path.join(src, label), fdataset/{split}/labels/{label})random.seed(42)这行很关键。不固定种子的话每次运行划分结果都不同训练集和验证集的图片集合一直在漂移两次实验之间没法对比。固定种子后任何人在任何机器上跑同一个脚本得到完全一样的划分这是复现实验结果的地基。并行地train、val、test三个目录下必须同时存在images和labels两个子目录。YOLOv8的data.yaml配置的就是这两个路径labels目录缺失时训练直接报No labels found in ...。另外注意脚本里只处理了jpg后缀如果数据集里有png或其他格式的图片要把后缀判断改成白名单。到这里数据集已经是标准YOLO格式。有些人会纠结要不要生成train.txt、val.txt这种列表文件YOLOv8的ultralytics包不依赖这类文件它只认目录结构。按目录摆放就是最不容易出错的方案。4. 训练一个真正认识大豆叶病的模型配置与调参实录4.1 data.yaml的写法路径和类目不能各写各的训练前先写data.yaml这份配置是模型和数据之间的桥梁写错一个字段训练直接起不来或者悄悄用错类目映射path: /home/user/soybean/dataset train: train/images val: val/images test: test/images names: 0: soybean_rust 1: frog_eye_leaf_spot 2: brown_spot 3: bacterial_blightpath写绝对路径train和val写相对path的目录。如果path是相对路径ultralytics会基于当前工作目录去拼一旦在别的机器或者别的目录下启动训练路径就会失效。names的列表顺序必须与第3章转换脚本里class_names的顺序完全一致——YOLO的txt标签里存的是类ID不是类名ID和类名错位时训练不会报错但推理结果命名全是错的。这里有一个看似离谱但很常见的坑class_names列表写的是soybean_rust, frog_eye_leaf_spot, brown_spot, bacterial_blightdata.yaml里手一抖改成soybean_rust, brown_spot, bacterial_blight, frog_eye_leaf_spot前50轮loss正常下降等到看结果时才发现模型把灰斑病全部识别成了细菌性斑疹。这类错误定位很花时间所以每次调整类目后第一件事是打开data.yaml核对names顺序最好同时打开一个txt标签文件抽查类ID和图像内容是否对应。4.2 训练命令与超参数含义CPU机器和高显存机器各怎么取舍数据就绪后启动训练yolo train modelyolov8n.pt datasoybean.yaml epochs100 batch8 imgsz640 device0各参数含义拆开说epochs100是完整遍历数据集100轮大豆叶病这类小数据集50到100轮足够再多会过拟合batch8是每轮迭代喂给模型的图像张数对损失计算的稳定性影响很大imgsz640是输入分辨率YOLOv8默认值就是640数据原始分辨率比这个高时会在训练时自动缩放device0指定0号显卡CPU环境改成devicecpu或直接不写。预先训练权重档位的选择可以看这个表权重参数量推理速度适合场景yolov8n.pt约320万最快CPU部署、快速验证流程yolov8s.pt约1110万快显存够用时的默认选择yolov8m.pt约2590万中等精度优先、GPU推理大豆叶病属于小目标检测更大模型理论精度更高但训练时间和部署成本都上去了。我一般训练阶段用yolov8n或yolov8s起步验证能收敛后再考虑加模型容量。如果手头只有一台没有独立显卡的机器比如现在很多教程里提到的Ubuntu 20.04搭建YOLOv8环境CPU版本场景参数选择完全不同yolo train modelyolov8n.pt datasoybean.yaml epochs50 batch2 imgsz416 devicecpu workers0batch2是因为CPU训练时矩阵运算在内存上跑batch太大容易把内存打满16G内存的机器batch4已经是压力线imgsz416直接把输入分辨率降下来特征图变小、计算量减半代价是病斑这种小目标更难点到workers0关闭数据加载多进程避免CPU环境下频繁出现worker崩溃的旧毛病。这套配置在入门级CPU机器上一个epoch大约几分钟到十几分钟整个训练跑完大概需要过夜不是不能接受但要有预期管理。4.3 迁移学习的正确姿势冻结层数与训练轮次训练从预训练权重继续不是从零开始。yolov8n.pt在COCO数据集上见过的类目跟大豆病害毫不相关但底层的边缘、纹理、颜色特征对植物病害一样有效这部分先验信息能显著缩短收敛时间。数据量少的时候直接全参训练容易把预训练权重里那点通用特征也带偏所以有了freeze参数yolo train modelyolov8n.pt datasoybean.yaml epochs100 freeze10freeze10表示冻结模型前10层的权重反向传播时这些层不更新只微调后面的检测头。数据量只有两三百张时冻结防过拟合数据量大于一千张时冻结反而限制了新特征的学习。我自己的经验是300张以下冻结前10到12层300到1000张冻结前5层或不冻结超过1000张直接全参训练。另外还有个参数和epochs搭配使用——patience早停。ultralytics默认patience100意思是验证集指标连续100个epoch不提升就自动停。在小数据集上这个值偏大我一般设到30训练到40轮左右指标不再动脚本自动跳到下一个阶段省下大量等待时间。4.4 看损失曲线判断是不是在真学box_loss、cls_loss与dfl_loss训练过程中ultralytics会在runs/detect/train/下生成weights目录和results.png。results.png里画着三条损失曲线——box_loss、cls_loss、dfl_loss和三条指标曲线——precision、recall、mAP。很多初学者只看mAP忽略损失曲线其实损失曲线更能暴露问题。YOLOv8的损失由三部分组成box_loss用CIoU衡量边界框定位误差cls_loss用BCEWithLogits衡量分类误差dfl_loss让边界框的分布更贴合目标形状三者的权重在ultralytics配置里可调。正确读曲线的方式train的loss曲线持续下降且不振荡说明模型在收敛val的loss曲线在某个epoch后开始回升说明过拟合开始这时把epochs往回调。如果train和val的loss从头到尾几乎不下降先怀疑数据而不是模型——检查标注框是不是全写成了0.5000 0.5000这种中心点默认值或者类别ID是不是全部写成了0。训练结束后使用weights/best.pt它是验证集指标最好的权重不是最后一轮的。每次都有人拿last.pt去部署然后发现效果比训练时差一大截这就是典型的使用错误。5. 避坑指南从标注到训练最常见的五个翻车现场5.1 模型全不检或框全偏中心点坐标算错了现象训练完成后推理框的位置明显偏向左上或右下或者一张图只检到零星几个目标。原因JSON转YOLO时把YOLO格式的x_center, y_center理解成了左上角坐标。YOLO格式存的是归一化后的中心点VOC和COCO格式存的是左上右下顶点转换时坐标系对不上框自然会整体偏移。解决先在自己转换脚本里打印一条记录用一张已知目标位置的图人工验证x_center0.5的框是不是落在图片正中。我每次转完都会挑三张图把txt坐标反向解析还原成像素坐标画框肉眼核对后才会进训练。这一步多花五分钟能省掉后面几小时的无效训练。5.2 训练中途loss变成nan现象epoch跑到第10轮左右训练日志里loss突然变成nan后面所有指标跟着变nan。原因学习率过大。尤其batch很小或数据集很小时梯度更新步长超出数值范围参数直接发散。另一个常见原因是标签里出现了0宽0高的空框训练时模型对空框求损失分母为零。解决先降学习率。YOLOv8默认从0.01开始遇到nan我一般直接改到0.001再试。同时检查labels目录下是否有空txt文件空文件对应的是标注后被删除的图片直接用脚本清掉。改了这两处nan基本不会再出现。5.3 验证集精度高但实拍图几乎不检现象训练和验证的mAP看着有0.8拿到实地拍的照片却几乎无输出或者检出结果全是同一个类目。原因训练图大多来自同一个拍摄环境背景和光照单一模型记住了那个环境的浅层特征而不是病斑本身的特征。另一个常见原因是推理时置信度门槛推得太高把真正的小病斑过滤掉了。解决推理时先把置信度降低试试yolo predict modelbest.pt sourcexxx.jpg conf0.1。如果调到0.1能检出来说明模型本身没问题是阈值的取舍问题。如果还是检不出来就要回去补数据——把实地拍的照片里漏检的目标补标加进训练集再训一版。5.4 CPU推理慢到没法用现象普通笔记本CPU上跑yolov8n推理640×640的图像单张耗时两秒以上完全没法做实时检测。原因模型虽然是轻量级但网络结构是深度神经网络CPU上矩阵运算天然慢几个量级。而且imgsz640时特征图大前向传播的计算量翻倍。解决模型保持yolov8n不比把imgsz降到416conf阈值从默认的0.25提到0.5。这两个参数一动单张推理时间能降到原来的三分之一左右。不过在大豆叶病场景里漏检比误检更严重conf提到0.5要谨慎。如果是科研用途需要看全量输出保持0.25就行如果是为快速筛选拍照样本0.5再配合高recall权重效率更高。5.5 images目录与labels目录数量对不上现象训练开始时报错Found 120 images but only 118 labels或者反过来labels比images多。原因有图片没有标注或转换脚本漏写了txt或空的标签文件被自动跳过。这类问题在小数据集里更隐蔽因为数量差异不大很多人直接忽略但漏标的那部分图片在训练时会被当成无目标的负样本参与损失计算干扰分类器。解决用一个脚本把两边文件名做差集找出缺哪几张图补标或删图确保数量一一对应。切分完之后还要抽查验证集里有没有空标签空标签文件会让这一张图始终被当成背景验证指标虚高。这个过程每次换数据集都要走一遍没有捷径。6. 模型验证与轻量部署混淆矩阵、置信度门限和RKNN导出的实操6.1 用val和混淆矩阵做最终验收训练结束后第一步不是急着导出模型而是跑一个完整的验证流程yolo val modelruns/detect/train/weights/best.pt datasoybean.yaml这条命令会重新在验证集上推理输出每个类目的precision、recall和mAP50、mAP50-95。注意mAP50和mAP50-95的区别——前者是IoU阈值0.5下的均值后者是0.5到0.95每隔0.05取一次再平均。大豆叶病这种小目标场景mAP50看着高但mAP50-95偏低说明框的定位精度不稳边界没有贴合病斑。这时候优先检查box_loss和dfl_loss的训练曲线而不是急着加数据。val输出里有一张confusion_matrix.pngYOLOv8的混淆矩阵总和常常不等于样本总数这是正常现象不需要死磕加和。因为每个预测框既可能命中真实框也可能被归类为背景并且一张图里的多个目标会各自独立贡献计数口径本身不是闭合的。我一般只关注对角线对应类目的数值占比以及背景被误检成病斑那一行的量级后者高说明背景干扰严重。6.2 从PyTorch到ONNX到RKNN的导出链路部署到边缘设备通常不是直接用PyTorch权重而是先转成中间格式。先做ONNX导出yolo export modelruns/detect/train/weights/best.pt formatonnx导出后可以用onnxruntime在CPU上跑一遍验证导出的模型和PyTorch原版输出是否一致。跑完验证再走下一步——如果目标设备是RK3588这类瑞芯微平台还需要经过RKNN-Toolkit转成rknn格式。这条路最常卡住的地方是YOLOv8的DFLDistribution Focal Loss结构在RKNN上解码输出不对框的位置全是偏移的需要手写DFL解码逻辑替换默认的后处理。这一步没有太多官方教程可以参考属于典型的看着简单、实际要调一两天的活。从此以后我每次训练完都强制自己走一遍固定流程先看val指标和混淆矩阵再抠出验证集里的三四张难例送进推理脚本肉眼检查最后确认没问题才考虑导出部署。这个习惯帮我砍掉了大半次导出后才发现模型不行的返工。完整的数据处理脚本、训练配置模板和转换工具链都收在这份项目资源里照着走一遍比抱着文档啃十天有用得多。希望帮到你。本文还有配套的精品资源点击获取