☰
用大豆叶病数据集入门YOLOv8:从环境搭建到模型训练避坑指南
2026/9/26 20:11:50 网站建设 项目流程

简介:基于YOLOv8的大豆叶病目标检测项目,面向目标检测初学者与农业领域开发者,帮助读者通过大豆叶病识别这一具体场景,系统掌握YOLO框架的构建思路与训练流程。项目基于PyTorch实现,压缩包共7个文件,包含6个Python脚本和1个说明文档,总大小仅9KB,结构非常紧凑。6个Python脚本分别承担数据集加载、模型搭建、损失函数定义、训练与推理等核心功能,README则提供基本使用说明,便于按模块解读代码。已有47人学习,适合想了解YOLOv8代码组织方式、快速入门目标检测的开发者。通过研读这些文件,可以直观看到从数据预处理到模型输出的完整链路,对理解YOLO整体框架构建和深度学习模型训练优化都有参考价值。

1. 为什么我推荐用大豆叶病来入门yolov8目标检测框架

很多人学 YOLOv8 目标检测,第一步就是拿开源的猫狗 demo 跑一遍,跑通之后仍然说不清数据集、标注、训练、评估、部署之间到底怎么串。我的建议是换一个“小而真”的场景:大豆叶病。它的类别不多,通常只关心灰斑、锈病、霜霉等几种;叶片病害区域有大有小,有密集也有稀疏,能逼你把目标检测里最难啃的边界框回归和阈值选择都走一遍。这篇内容按“环境搭建 → 数据集制作 → 模型训练 → 避坑 → 模型验证”的顺序展开,目标只有一个:你在完成大豆叶病检测模型的同时,把 yolov8 如何从数据到权重、再到推理结果的完整框架记在脑子里,而不只是会敲两行命令。

2. Ubuntu 20.04搭建yolov8环境cpu版本:miniconda到ultralytics

服务器和开发机上 Ubuntu 20.04 的存量很大,用 CPU 版本先跑通环境是最稳妥的第一步。CPU 环境不是拿来训大模型的,而是用来验证代码路径、数据加载、前后向能否正常走通;真正大量训练时再切 GPU,成本更低。这里带上 conda 做环境隔离,避免把系统的 Python 搅乱,这也是深度学习项目最常见的翻车点。

2.1 用Miniconda隔离Python环境:为什么不用系统Python

系统自带的 Python 3.8 通常被 apt 和系统脚本依赖,直接 pip 安装 PyTorch 和 ultralytics,很容易出现包冲突,严重时系统命令都被破坏。用 Miniconda 创建独立虚拟环境,相当于给 yolov8 单独开一间屋子,里面装什么都不会影响整个系统的 Python。

# 下载 Miniconda 安装脚本并静默安装到用户目录 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda # 把 conda 加入当前 shell 的 PATH export PATH="$HOME/miniconda/bin:$PATH" # 创建 Python 3.10 虚拟环境 conda create -n yolo python=3.10 -y # 激活虚拟环境 conda activate yolo

代码里的-b是静默安装,-p指定安装目录到$HOME/miniconda,这样不用 sudo,也不会污染/usr。Python 3.10 是 YOLOv8 生态下比较稳妥的版本,3.8 也能跑,但部分新依赖在 3.10 上更少出现奇怪的 wheel 编译报错。

每次打开新终端都要重新执行export PATH="$HOME/miniconda/bin:$PATH",如果嫌麻烦,可以把这一行追加到~/.bashrc结尾。之后不要再用python3直接跑,先在终端确认提示符前面出现了(yolo)。

2.2 安装PyTorch CPU版和ultralytics:最小命令

CPU 版 PyTorch 的安装命令和 GPU 版不同,一定要加--index-url指定 cpu 源,否则默认装的是 CUDA 版本,虽然没有 GPU 也能用,但包体积大不少,而且在没有 Nvidia 驱动的机器上可能报 CUDA 初始化错误。

# 激活虚拟环境后执行 conda activate yolo # 安装 CPU 版 PyTorch pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics,会自动拉取依赖 pip install ultralytics

PyTorch CPU 版安装完成后,用python -c "import torch; print(torch.__version__)"检查。这里不要省略 CPU 源,很多初学者在这一步直接pip install torch,结果装了一个好几个 GB 的 CUDA 包,跑起来还不停弹 CUDA 警告。

ultralytics这个包就是 YOLOv8 的开源实现主包,它把训练、验证、导出、推理都封装成了yolo命令行工具。安装它之后,不需要再单独装 opencv-python、pandas、matplotlib,依赖会自动带上来。

2.3 用预训练模型验证环境:yolov8n.pt的第一次前向推理

环境装没装对,最快的方法是加载一个预训练模型并做一次假图推理。这里不使用网络图片,避免因为请求超时造成误判,直接生成一个零数组图片,让模型走一遍前向。

import numpy as np from ultralytics import YOLO # 第一次运行会自动下载 yolov8n.pt 预训练权重 model = YOLO("yolov8n.pt") # 打印模型结构信息,确认依赖加载正常 model.info() # 生成一张纯黑图,仅用于验证前向传播能否跑通 fake_img = np.zeros((640, 640, 3), dtype=np.uint8) # predict 会把 numpy 数组转成模型输入 results = model.predict(fake_img, verbose=False) print("检测结果数量:", len(results))

这段代码里,YOLO("yolov8n.pt")如果不写路径,包会从官方权重地址下载,下载位置在当前目录。model.info()能看到模型的层数和参数量,如果 CPU 也能顺利打印出来,说明 torch 和 ultralytics 的版本基本匹配。model.predict是推理接口,verbose=False关掉大量日志,否则第一次跑会刷屏。

在选择模型尺度时,很多人纠结 n、s、m、l、x,这里有一个大致关系可以参考:

模型尺度相对参数量推理速度适用场景
YOLOv8n最小最快CPU 可训练,适合学习框架和快速验证
YOLOv8s小快小数据集微调,显存 4G 可尝试
YOLOv8m中较慢需要更高精度且 GPU 显存 6G 以上
YOLOv8l / x大慢接近上线场景,训练成本明显高

第一次学 YOLO 框架构建,建议固定用yolov8n.pt。不是因为精度最好,而是它参数最少,训练一轮时间短,方便反复试错。等你把整个流程跑通,再换大模型也不迟。

3. 制作大豆叶病数据集并转成yolo标注格式:从拍摄到data.yaml

yolov8 训练自己的数据集,核心不是算法,而是数据。很多人在这一步翻车:标注工具用了,图片也拍了不少,最后训练时报 “All labels empty” 或 mAP 一直为 0。这一章把数据集的目录结构、labelme 转 YOLO 格式脚本、数据集切分讲清楚。

3.1 大豆叶病类别设计和数据集目录结构

先定义类别。大豆叶病常见的有大豆锈病、灰斑病、霜霉病、细菌性斑点病等,实际做项目时不需要一开始就追求十几个类别,选 2 到 4 类最典型的病害即可。每一类至少准备 200 到 500 个实例,如果图片数量不够,宁可减少类别数,也不要做成极端不平衡的多分类。

推荐的数据集目录结构如下:

datasets/ images/ train/ val/ labels/ train/ val/ data.yaml

images/train下放训练图片,images/val下放验证图片;两个目录里的图片数量按 8:2 左右划分。labels/train和images/train下的文件名必须一一对应,比如soybean_leaf_001.jpg对应soybean_leaf_001.txt。

YOLO 的标签文件格式是每一行一个目标:

class_id x_center y_center width height

注意这里不是左上角坐标加宽高,而是中心点坐标,且全部除以图片宽高做了归一化。也就是说,坐标值都在 0 到 1 之间。写标签文件时,坐标小数位数建议保留 6 位,过少的位数会让小目标框出现抖动。

3.2 用labelme标注并转换成YOLO txt:转换脚本与五个边界条件

labelme 是常见标注工具,导出的是 json 文件,里面记录了图片尺寸和多边形点坐标。YOLO 不认 json,需要转换成 txt。下面是我常用的转换脚本,基于 labelme 的矩形标注和多边形标注都能处理。

import json import os # 类别列表,顺序就是 YOLO 类别 ID class_names = ["soybean_rust", "soybean_frog_eye"] def convert_labelme_to_yolo(json_path, output_dir): # 读取 labelme 生成的 json with open(json_path, encoding="utf-8") as f: data = json.load(f) img_w = data.get("imageWidth") img_h = data.get("imageHeight") if not img_w or not img_h: print(f"[跳过] {json_path} 缺少 imageWidth/imageHeight") return False basename = os.path.splitext(os.path.basename(json_path))[0] lines = [] for shape in data["shapes"]: label = shape.get("label") if label not in class_names: print(f"[跳过] {basename} 中出现未定义类别:{label}") continue class_id = class_names.index(label) points = shape["points"] # 兼容矩形和多边形,取外接矩形 xs = [point[0] for point in points] ys = [point[1] for point in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 边界检查:坐标不能超出图片范围 x_min = max(0, x_min) x_max = min(img_w, x_max) y_min = max(0, y_min) y_max = min(img_h, y_max) # 转为 YOLO 归一化坐标 x_center = ((x_min + x_max) / 2) / img_w y_center = ((y_min + y_max) / 2) / img_h box_w = (x_max - x_min) / img_w box_h = (y_max - y_min) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") if len(lines) == 0: print(f"[跳过] {basename} 没有有效标注") return False output_path = os.path.join(output_dir, f"{basename}.txt") with open(output_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) return True

这个脚本里最重要的逻辑是:labelme 标注的多边形,可能因为手抖不是水平矩形,YOLO 要求目标框只能是与图片轴对齐的矩形,所以必须取所有点的最小外接矩形,也就是代码里的min/max。这在小目标较多的叶片病害上特别关键,不要用旋转框。

转换时有几个边界条件需要留意。第一,json 里的imageWidth/imageHeight必须是图片原始分辨率,不能是 labelme 界面里缩放后的尺寸。第二,类别顺序一旦定下来,后续 data.yaml 里的 names 顺序必须和脚本里的 class_names 完全一致,否则训练时的标签会错位。第三,坐标值除以的是原图宽高,不是训练时的输入尺寸,这一点搞混会让检测框全部偏移。第四,如果一张图只有一个很小的病斑,归一化后的 width 和 height 可能只有 0.01 左右,这种目标本身很难学,建议缩略图时先裁剪出病害区域再标注。第五,txt 文件最后一行不需要强制加换行,但每一行必须是完整的六段内容,少一个空格都会让数据加载报错。

3.3 分割train/val和编写data.yaml:类别顺序的一票否决

转换完成之后,把原始图片和 txt 标签按照比例复制到训练集和验证集。为了避免同一种叶片的不同照片被分到两边,建议按叶片编号分桶,而不是直接对单个文件随机切分。

import os import random import shutil from glob import glob random.seed(42) # 假设 raw/images 下是原图,raw/labels 下是转换后的 txt image_paths = glob("raw/images/*.jpg") + glob("raw/images/*.png") random.shuffle(image_paths) train_ratio = 0.8 split_index = int(len(image_paths) * train_ratio) train_paths = image_paths[:split_index] val_paths = image_paths[split_index:] for split_name, paths in [("train", train_paths), ("val", val_paths)]: os.makedirs(f"datasets/images/{split_name}", exist_ok=True) os.makedirs(f"datasets/labels/{split_name}", exist_ok=True) for image_path in paths: base = os.path.splitext(os.path.basename(image_path))[0] label_path = f"raw/labels/{base}.txt" if not os.path.exists(label_path): print(f"[警告] {image_path} 缺少对应标签,跳过") continue shutil.copy(image_path, f"datasets/images/{split_name}/{os.path.basename(image_path)}") shutil.copy(label_path, f"datasets/labels/{split_name}/{base}.txt")

random.seed(42)是为了保证每次运行划分结果一致。这里shutil.copy是复制而不是移动,方便原始标注文件保留下来,后续如果发现标签错误可以直接回到 raw 目录修改。

划分完成后,在 datasets 目录下创建data.yaml:

path: datasets train: images/train val: images/val names: 0: soybean_rust 1: soybean_frog_eye

path是相对 data.yaml 文件的根目录。YOLOv8 会自动根据images/train推导出标签目录为labels/train,所以不需要在 yaml 里写 labels 路径。names的索引从 0 开始,实际文件里第一列如果是 1,就表示soybean_frog_eye。新手最常犯的错误是把 names 写成了 list,但把类别 ID 从 1 开始标,这样训练出来的模型和推理时的类别名会对不上。

4. 训练自己的大豆叶病检测模型:最小命令与参数调优

数据集就绪后,进入正式训练。这一章讲怎么用yolo train启动训练,以及 yolov8 的模型训练参数含义。很多人把训练当成黑匣子,只知道敲命令,遇到 loss 不降就慌。其实损失函数、学习率、batch 这些变量都有明确作用,理解了它们,框架就会变得透明。

4.1 用yolo train启动训练:最小命令到完整命令

训练最核心的命令只有一行:

# 在 conda yolo 环境中执行 yolo train data=datasets/data.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=16

这行命令会从yolov8n.pt加载预训练权重,然后在大豆叶病数据集上继续训练。model=yolov8n.pt的意思是加载预训练模型,而不是从零初始化。对学习者来说,这是一个非常好的习惯:用 ImageNet 和 COCO 上学到的特征做热启动,能在小数据集上得到明显更好的结果。

如果要在服务器后台跑,通常我会把输出目录和训练细节写全:

yolo train \ data=datasets/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=20 \ project=runs/detect \ name=soybean_leaf \ device=0 \ workers=4

这里project和name控制训练日志和权重保存位置,最终权重会在runs/detect/soybean_leaf/weights/best.pt中。device=0表示用第一张 GPU,如果没有 GPU,可以改成device=cpu,但训练速度会慢很多,建议先用下一小节的参数减负方案。

4.2 yolov8模型训练参数含义:epochs、batch、imgsz、patience

训练参数里影响最大的是epochs、batch、imgsz、patience和lr0。

epochs是完整遍历数据集的次数。大豆叶病这样的小数据集,200 张图片训练 50 到 100 轮已经能看到明显结果,再增大容易过拟合。batch是每次送入 GPU 的图片张数,它由显存决定,如果显存报 OOM,优先把 batch 从 16 降到 8 或 4。imgsz是输入图片的边长,YOLOv8 会等比缩放图片到 640x640,对叶片上的细小病斑,imgsz=640是底线,降到 416 会丢失太多细节,精度下降明显。patience是早停耐心值,验证集 mAP 连续多少轮不提升就停止训练,设 20 能省时间。lr0是初始学习率,默认 0.01,如果 loss 剧烈震荡,可以降到 0.001,但不要一开始就调优学习率,先把前面参数跑通再说。

训练过程中会打印三类损失:box_loss、cls_loss、dfl_loss。这就是 yolov8 的损失函数组成,box_loss 负责回归框的位置、宽度和高度;cls_loss 负责分类是否正确;dfl_loss 是 distribution focal loss,用于优化边框质量的分布。看到这三个 loss 都在缓慢下降,说明训练在正常收敛。

训练结束之后,建议把 results.csv 画成曲线,直观判断是否过拟合或欠拟合:

import pandas as pd import matplotlib.pyplot as plt # 路径根据实际训练结果修改 df = pd.read_csv("runs/detect/soybean_leaf/results.csv") fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 画 box_loss 曲线,train 和 val 对比 axes[0].plot(df["epoch"], df["train/box_loss"], label="train box_loss") axes[0].plot(df["epoch"], df["val/box_loss"], label="val box_loss") axes[0].set_xlabel("epoch") axes[0].set_ylabel("box_loss") axes[0].legend() # 画 precision 和 recall 曲线 axes[1].plot(df["epoch"], df["metrics/precision(B)"], label="precision") axes[1].plot(df["epoch"], df["metrics/recall(B)"], label="recall") axes[1].set_xlabel("epoch") axes[1].set_ylabel("metrics") axes[1].legend() plt.savefig("soybean_training_curve.png", dpi=150)

这段代码的核心是将results.csv里的历史指标读出来。如果train/box_loss持续下降而val/box_loss在第 30 轮开始反弹,就说明过拟合了,应该减少 epochs 或增加数据增强。如果两条 loss 都在高位不动,先检查数据集标签是否为空、学习率是否太大。

4.3 显存不够时的处理:gtx1660ti跑yolov8的可行方案

很多人在 6GB 显存的老显卡,比如 GTX 1660 Ti 上跑 YOLOv8。1650 之前我还见过用 4GB 显存硬跑 640x640 的,结果自然是 OOM。显存不足时按下面顺序调整:

# 方案一:减小 batch yolo train data=datasets/data.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=4 # 方案二:减小输入尺寸,但精度会下降 yolo train data=datasets/data.yaml model=yolov8n.pt epochs=50 imgsz=480 batch=8 # 方案三:显式开启混合精度,减少显存占用 yolo train data=datasets/data.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=8 amp=True

amp=True是混合精度训练,默认就是开启的,但对个别数据集可能会出现 loss 变为 nan,这时可以关闭。GTX 1660 Ti 的 Turing 架构对混合精度支持一般,实际收益没有 Ampere 显卡明显,但至少能多塞一点 batch。

要注意,batch太小会让 BatchNorm 统计不稳定,一般不要低于 4。如果 batch=2 还 OOM,说明模型尺度选大了,回到yolov8n.pt,这是 cpu 和低显存显卡训练 yolo 框架最稳的组合。

5. 避坑排查:大豆叶病yolov8训练中最常见的5个问题

训练过程不会一次顺顺利利,尤其是在自建数据集的时候。这一章记录几个我实际遇到过的坑,每条按“现象 → 原因 → 解决”来写,看完至少能省出两三天排错时间。

5.1 标签文件全空或数据加载报错

现象:训练刚开始,终端刷出一行All labels empty,或者警告某个图片找不到对应的 txt 文件。原因:我最早把 labelme 的 json 转成了 txt,但没把 txt 放到labels/train目录,而是直接放在raw/labels下,yaml 里的train指向了images/train,YOLOv8 找不到对应标签。解决:先手动检查datasets/labels/train下是否有和图片同名的文件,再用yolo train前打印目录树。最稳妥的方法是让datasets根目录下的images和labels保持绝对对应,不要用软链接。

5.2 训练时loss一直是nan

现象:训练第一个 epoch 就出现box_loss nan,之后所有指标全是 nan。原因:最常见是学习率过大,或者输入图片里有全黑、全白的坏图,导致 BN 层统计出现问题。解决:先把lr0从 0.01 降到 0.001,如果还 nan,检查数据集中是否混入了损坏的 jpg,用PIL.Image.open逐张打开验证。

5.3 mAP很低但训练集精度很高:过拟合与数据泄漏

现象:训练集 precision 到 0.95,验证集 mAP50 只有 0.3。原因:大豆叶病图片经常是同一株叶片连拍,如果把同一叶片的多个角度同时分到 train 和 val,模型在验证集上等于开卷考试,这属于典型的数据泄漏;另一种原因是总 epoch 太长,把训练集记死了。解决:按叶片个体划分数据集,确保同一株叶片的图片只出现在一个集合里;同时设置patience=20,早停避免过拟合。如果验证集还是低,检查 labelme 标注是否把病斑区域框得过大,导致 ground truth 与模型预测重叠率无法上升。

5.4 显存溢出OOM:batch与imgsz怎么看

现象:训练中途报CUDA out of memory,显卡明明是 6G 显存,还是崩了。原因:很多时候不只是 batch 的问题,而是workers和imgsz一起把显存打满。解决:先用batch=4 imgsz=640 workers=2跑,稳定后再逐步加大。如果还 OOM,检查后台是否有其他进程占用显存,用nvidia-smi看,不要一上来就怪代码。CPU 环境训练时内存占用被误认为显存,ulimit -v限制也不要随便调。

5.5 推理置信度过低:不要盲目调阈值

现象:训练完后用模型预测叶片,检测框有,但置信度只有 0.2,设置conf=0.5后什么都检测不到。原因:模型不是没学到特征,而是训练时标签框里包含了过多背景,或者病斑与叶片颜色接近,模型输出难以达到高置信度。解决:不要直接降低 conf 到 0.1,这是自欺欺人。先回到标注,把标注框收紧到只框病斑区域,并重新训练;推理阶段可以先设为conf=0.25观察,再按实际业务容忍度调。如果每一张图都低置信度,多半是 train 和 val 的数据分布不一致,比如训练图全是实验室平板拍摄,推理图是大田复杂背景。

6. 跑验证集、导出ONNX与调置信度:把学到的框架串起来

模型训练完,最后一步是验证和落地。先对验证集做一次完整评估,确认 mAP 达到你的预期,再导出成 ONNX,为后续部署留后路;最后调整置信度,让模型能真正用在叶片病害筛查流程里,而不是只在 metrics 上好看。

from ultralytics import YOLO # 加载训练得到的最优权重 model = YOLO("runs/detect/soybean_leaf/weights/best.pt") # 对验证集做推理并保存结果 results = model.predict( source="datasets/images/val", conf=0.25, iou=0.45, save=True, save_txt=True ) # 打印每张图的检测框数量,用于快速检查 for res in results: print(res.path, "boxes:", len(res.boxes))

conf=0.25和iou=0.45是常规起点。seaborn不必要,重点看save=True会把标注框画到图片上,save_txt=True会输出与 YOLO 标签同格式的推理结果,这两步能快速暴露模型在验证集上的漏检和误检。

验证 mAP 的官方命令是:

yolo val model=runs/detect/soybean_leaf/weights/best.pt data=datasets/data.yaml

它会输出 mAP50、mAP50-95、precision、recall 四个关键指标。对大豆叶病场景,mAP50 达到 0.8 以上,通常就能用于实际预筛。mAP50-95 如果比 mAP50 低很多,说明检测框和标签框贴合度不够,优先检查标注框精度。

如果之后要部署到嵌入式设备,可以导出 ONNX:

yolo export model=runs/detect/soybean_leaf/weights/best.pt format=onnx imgsz=640

导出时如果遇到算子不兼容,把opset固定为 12,并确认imgsz与实际输入一致。ONNX 文件可以直接用 onnxruntime 在 CPU 上跑推理,这也是从 yolov8 框架学习到工程落地之间最短的一条路。

最后说一个我自己的教训:学 YOLOv8 框架,不要一上来就囤十几个模型尺度,也不要一上来就调损失函数权重。先用yolov8n.pt和最小 data.yaml 把整个链路跑通,确认数据没病,再谈优化。我早期训大豆叶病模型时,把大部分精力花在改网络 head 上,结果回头发现只是标签类别顺序写错了,白白浪费了三天。先保证每个环节可见可控,再动手堆参数,这个习惯会让你的 yolov8 目标检测学习走得更快,也更扎实。希望帮到你。

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

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

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

立即咨询