简介:基于YOLOv8的水果叶片病害图像分割设计,面向毕业设计、课程设计与机器学习实践者,聚焦作物叶片斑点、黄斑、褐斑等病害的自动识别与分割。压缩包共25个文件,包含19张标注示例图像、4个Python脚本、1个Markdown说明和1个Word文档,总大小3.02MB,结构简洁便于查阅。已有27人学习浏览。脚本覆盖训练、验证、预测及界面交互等环节,配套图像样例与说明文档,可支撑从数据预览、模型训练到病害分割结果输出的完整流程。该资源既适合快速理解YOLOv8目标检测与分割任务的代码组织方式,也可作为农业病害诊断项目的起点,便于进一步优化模型精度并适配自定义数据集,对深度学习应用开发有直接参考价值。
1. 一个 .zip 里装着整套 YOLOv8 叶片病害分割方案?先搞清楚它到底要解决什么
水果叶片病害识别这类项目,检测模型只能给你一个矩形框,告诉你“这片叶子有病”,但病斑到底占多大面积、扩散到什么程度,框是答不上来的。基于 YOLOv8 的叶片病害图像分割,本质是把病斑区域逐像素地“抠”出来,每一片病斑对应一个 mask,面积、形状、位置一起拿到。这个能力直接服务于病情分级和精准施药,比单纯目标检测更贴近农业现场的需求。这个压缩包标题之所以常见于毕业设计和工程验证,是因为它把“图像分割算法选型→数据集标注→训练调参→指标评估→导出部署”整条链路都压进了一个课题里,适合正在做相关方向的学生,也适合想快速验证分割方案可行性的工程师照着落地。
2. 把叶片病害数据变成 YOLOv8 能吃的格式:标注、转换与目录组织
2.1 标注工具怎么选:LabelMe 是默认答案
做图像分割训练,第一道坎是标注。YOLOv8-seg 官方数据格式要求每个目标由一组归一化多边形顶点表示,而 LabelMe 导出的 JSON 正好是“多边形 + 类别名”的结构,两者配合非常顺。备选工具还有 X-AnyLabeling 和 CVAT,但 LabelMe 的优势在于单机离线可用、安装简单、标注结果直观,项目里常见做法就是它。
标注时把病斑边缘用多边形逐点描出来,类别按病害类型命名,比如 apple_scab、apple_rust、leaf_blight 这类英文标识。这里要特别注意:同一张图上重叠的病斑也要逐一分 mask 标注,漏标会导致训练时模型把重叠区域当成背景,分割结果会莫名缺角。标注完的 JSON 文件与图像同名存放在一起,等下转换脚本要按这个约定读取。
标注质量直接决定分割上限,模型只是把标注的规律学出来。叶片边缘模糊、病斑边界不清晰的图,宁可多花时间放大描边,也不要草草框个大概。用 LabelMe 自带的分割预览功能,每标完一张就从“标签列表”里核对一遍类别和形状,能省下后期不少返工时间。
2.2 labelme 转 YOLOv8 分割格式:转换脚本与归一化细节
LabelMe 的 JSON 里,shapes数组的每个元素是“label + points”,而 YOLO 分割格式的每一行是“类别ID + 归一化后的顶点坐标序列”。转换脚本的核心就两件事:把类别名映射成数字ID,把像素坐标除以图像宽高归一化到 0~1。
import json import os from pathlib import Path def labelme_to_yolo_seg(json_path, out_dir, class_dict): """ json_path: LabelMe 导出的 JSON 文件路径 out_dir: 输出的 YOLO 分割标签目录 class_dict: 类别名 -> ID 的映射,例如 {"apple_scab": 0, "apple_rust": 1} """ with open(json_path, "r", encoding="utf-8") as f: label = json.load(f) img_w = label["imageWidth"] img_h = label["imageHeight"] txt_name = Path(json_path).stem + ".txt" lines = [] for shape in label["shapes"]: cls_name = shape["label"] if cls_name not in class_dict: # 未在类别表里的标注直接跳过,避免脏数据进训练 print(f"跳过未知类别: {cls_name} in {json_path}") continue cls_id = class_dict[cls_name] norm_points = [] for x, y in shape["points"]: # 坐标归一化:x 除以图宽,y 除以图高 norm_points.append(f"{x / img_w:.6f}") norm_points.append(f"{y / img_h:.6f}") lines.append(f"{cls_id} " + " ".join(norm_points)) if lines: out_path = Path(out_dir) / txt_name with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) else: # 标注文件为空时也生成空 txt,与 images 目录一一对应 Path(out_dir / txt_name).touch()这段脚本有几个点容易翻车。归一化时x / img_w和y / img_h必须分别用宽和高,不要图省事统一除以宽,否则叶片会被横向拉伸,mask 形状完全变形。imageWidth和imageHeight必须与 PGN/JPG 实际尺寸一致,批量处理时可以用 PIL 二次确认。逐点坐标保留 6 位小数足够,太多小数反而让标签文件膨胀。此外,每个 JSON 转换前要确认图像没有在标注后被裁剪过,否则坐标会和图像内容错位。
2.3 数据集目录组织与类别平衡检查
YOLOv8-seg 训练时默认按固定目录结构读取数据:
fruits_leaf_seg/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml标签文件与图片同名(后缀 .txt),放在对应的 labels 目录下。划分比例常见做法是 8:1:1 或 9:1,而且要按“整张图像”划分,不能把同一张图切出的 patch 散到多个集合里,否则验证集会泄漏训练信息,mAP 虚高。
data.yaml 是训练入口,内容如下:
# data.yaml path: ./fruits_leaf_seg # 数据集根目录,相对或绝对路径都行 train: images/train val: images/val test: images/test nc: 2 # 类别数 names: ["apple_scab", "apple_rust"] # 类别名必须与 class_dict 一致常见错误是nc与names长度不一致,训练直接报错;另一个是训练数据里某个类别只有十几张,分割 mask 学不充分,表现为验证集上该类 AP 很低。建议训练前统计每个类别的实例数量,最少的类别不要低于总量的 5%,不够就用数据增强或者补标数据。统计脚本很简单,遍历 labels 目录统计每个类别 ID 出现次数即可,这里不展开。
3. 训练配置:从最小跑通到损失曲线解读
3.1 环境搭建:CPU 也能跑,但要认清时长预期
热词里高频出现的“ubuntu20.04 搭建 yolov8 环境 cpu 版本”,说明很大一部分人只有 CPU 环境。YOLOv8-seg 用 CPU 训练小型数据集是可行的,只是要有耐心。GTX 1660 Ti 这类 6GB 显存显卡能跑yolov8s-seg,纯 CPU 则建议用yolov8n-seg起步。
# 创建虚拟环境并安装 ultralytics python3 -m venv yolov8_env source yolov8_env/bin/activate pip install ultralytics只要这一步装完不报错,环境就算通了。GPU 用户再加一句pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118(按你本机 CUDA 版本选择,NVIDIA 驱动版本老的话就装 cu118)。CPU 用户装默认的 CPU 版 torch 即可,不用额外指定。
验证环境是否正常,一条命令跑通推理:
yolo predict model=yolov8n-seg.pt source=https://ultralytics.com/images/bus.jpg第一次运行会下载预训练权重,能看到分割 mask 叠加在原图上就算成功。如果这一步卡在“下载超时”,手动从官方仓库下载yolov8n-seg.pt放到当前目录再运行,常见做法是把权重文件放到项目根目录的weights文件夹里,命令改成model=./weights/yolov8n-seg.pt。
3.2 训练参数:epochs、batch、imgsz、patience 的推荐设定
训练自己的数据集用 yolov8n-seg 起步,命令如下:
yolo segment train \ model=yolov8n-seg.pt \ data=fruits_leaf_seg/data.yaml \ epochs=100 \ imgsz=640 \ batch=8 \ patience=30 \ device=0 \ workers=4 \ seed=42model=yolov8n-seg.pt是官方预训练权重,它会自动加载 COCO 上学到的特征,再在叶片数据集上微调,收敛速度和精度都远好于从零训练。imgsz=640是默认值,如果叶片图像里病斑很小,比如只有几十个像素,建议升到 1024,但显存占用会翻倍还多,CPU 训练时间也会显著拉长。batch在 GPU 上建议从 8 起步,显存不够就降到 4;CPU 上 batch=8 往往已经能让内存吃紧。
patience=30是早停参数,连续 30 轮验证集 mAP 不涨就停止训练,省时间。对新手来说,最实用的做法是把epochs=100配合patience=30,一般到 60 轮左右就能看到效果。seed=42固定随机种子,保证复现时结果一致,这个参数经常被忽略,但写论文或做对比实验时必须固定。
类别不均衡时可在命令里加class_weights=True,YOLOv8 会对样本少的类别加大 loss 权重。另一种做法是调fraction=0.8快速验证数据没问题,再全量训练。
3.3 训练日志与损失曲线:看什么才算正常
训练时终端会滚动输出每一轮的box_loss、seg_loss、cls_loss、dfl_loss和验证集指标。热词里的“yolov8 画损失函数曲线图”其实就是从runs/segment/train/目录下的results.png直接看的,训练结束后 Ultralytics 会自动画出 loss 和 mAP 曲线。
正常收敛的表现是:前 20 轮 loss 快速下降,之后进入平缓期;验证集mAP50稳步上升,到后期趋于平稳。如果seg_loss降不下去,先检查 mask 标注是否粗糙;如果cls_loss高,大概率是类别 ID 错位;如果训练集 loss 一直降但验证集 mAP 不涨,是过拟合,调大patience提前止损,或者增加数据增强。
训练结束后runs/segment/train/weights/下会有best.pt和last.pt,以后验证推理都用best.pt。这一步是整条链路的分水岭,很多人训练完不看曲线直接跑下一个实验,结果反复在同一个问题上翻车,损失曲线图是最直接的排查工具。
4. 验证评估与模型导出:mAP、mask 可视化与 ONNX 部署
4.1 验证集指标怎么读:mAP50、mAP50-95 与 mask 精度
训练完先对验证集跑一次评估:
yolo segment val \ model=runs/segment/train/weights/best.pt \ data=fruits_leaf_seg/data.yaml输出里最重要的两个指标是mAP50和mAP50-95。mAP50是 IoU 阈值 0.5 时的平均精度,要求低,新模型一般能到 0.8 以上;mAP50-95是 0.5 到 0.95 每隔 0.05 取平均,要求高得多,叶片病害这类小目标通常 0.5~0.7 就算不错。如果mAP50-95明显偏低,不一定是模型问题,可能是标注边界不平滑、框太小,也不排除类别不均衡。
除了数字,还要看runs/segment/val/下保存的可视化图片。它会把预测的 mask 画在原图上,绿框代表正确、红框代表漏检或误检。这里你很快会发现问题:某些病斑被分割成两半、mask 边缘锯齿严重、或者把叶脉误判为病斑。这些现象在指标上很难看出来,但肉眼看可视化一眼就能定位。
4.2 导出 ONNX:跨平台推理的关键一步
训练完的.pt只能在 PyTorch 环境跑,实际部署时需要导出成 ONNX 或 TensorRT。RK3588、Hi3516CV610 这类边缘设备上的部署,热词里讨论很多,导出命令如下:
yolo export \ model=runs/segment/train/weights/best.pt \ format=onnx \ imgsz=640 \ opset=12导出后得到best.onnx,可以用 ONNX Runtime 加载。这里常踩的坑是导出时imgsz必须与训练时一致,否则换尺寸后 mask 输出分辨率会变化,推理精度下降。另外 ONNX 的输出是四个张量:检测框、类别、置信度、mask 系数,mask 系数要配合原型 mask 做后处理,这跟直接在 PyTorch 里拿结果不一样。用 ONNX Runtime 做简单推理:
import onnxruntime as ort import numpy as np from PIL import Image # 加载 ONNX 模型 sess = ort.InferenceSession("best.onnx") input_name = sess.get_inputs()[0].name # 预处理:resize + 归一化,注意要与训练时的 imgsz 保持一致 img = Image.open("leaf.jpg").resize((640, 640)) img_array = np.array(img).astype(np.float32) / 255.0 img_array = img_array[:, :, ::-1] # RGB -> BGR img_array = np.transpose(img_array, (2, 0, 1))[None, ...] # HWC -> NCHW outputs = sess.run(None, {input_name: img_array})ort.InferenceSession加载模型后,输入是归一化到 0~1 的 BGR 图像,NCHW排布。不同版本 Ultralytics 导出的输出张量顺序略有差异,建议用sess.get_outputs()打印每个输出的 shape,再对照官方文档确定哪一个是 mask 系数。这一步属于“黑匣子”区域,很多人卡在输出张量解读上,最好的办法是先用 Python 完整跑一遍yolo segment predict与 onnx 输出的数值对比,逐项对齐。
4.3 部署到边缘设备前的权重压缩
CPU 机器上跑分割推理,n 模型本身就比较快,但边缘设备内存有限时仍建议做 INT8 量化。ONNX Runtime 提供quantize接口,常见做法是准备一小批校准图片,量化后用同样指标再验证一次,掉点超过 2% 就换回 FP16 或者原模型。量化不是这篇文章能穷尽的,但记住一点:INT8 对 mask 边界的影响比检测框更明显,量化后务必看一眼可视化输出。
5. 常见问题避坑:从标注错位到训练中断的实战排查
5.1 现象:训练 loss 下降很快,但验证集上 mask 全部偏到叶片边缘
第一次跑就碰到这问题,loss 还挺好看,但 mask 全部画在叶片边缘、病斑区域一片空白。
原因:标注 JSON 里的坐标没有归一化,或归一化时用错了参考尺寸。LabelMe 的 JSON 里imageWidth是原始图宽,但很多人用预处理后的裁剪尺寸除以坐标,导致 mask 整体偏移。
解决:检查转换脚本中img_w与img_h来源,必须与原始图像尺寸一致;转换后随机抽三张图,把 txt 坐标反向画到图上人工比对,这一步能拦下 90% 的错位问题。
5.2 现象:训练时报 “Assertionncdoes not match” 直接中断
训练开始前检查data.yaml时报错,提示类别数不匹配。
原因:data.yaml的nc写成了 3,但标签里实际只有 2 类;或者names列表比nc少一个。
解决:先统计数据集里所有出现过的标签 ID,再倒推nc。用python -c遍历 labels 目录统计即可,或者直接让代码在转换阶段打印class_dict全集。还有一个常见变体是标注里出现空文件(无目标),YOLOv8 会跳过它,但要确保空 txt 存在,不要缺失文件列表里的对应项。
5.3 现象:CPU 训练时内存暴涨,跑到第 20 轮被 OOM 杀掉
CPU 上跑 yolov8m-seg 或以上规格,batch 设 16,训练到中途内存耗尽直接被杀。
原因:CPU 训练时每个进程都持有数据加载缓存,workers与batch双高导致内存占用爆炸;用 GPU 训练时显存限制掩盖了这个问题。
解决:CPU 上把workers降到 2,batch降到 4;如果还涨,加一行cache=False关掉数据缓存,宁可在数据读取上慢一点,也不让训练中途翻车。另外 CPU 训练的预期是yolov8n-seg每轮 3~5 分钟起步,别拿 m 模型硬撑。
5.4 现象:训练中断后再启动,直接从第 0 轮开始
训练跑到一半停电或手动 Ctrl+C,重新执行同样的命令,发现又从 0 开始。
原因:Ultralytics 的resume机制需要显式开启,默认不自动加载断点。
解决:中断前训练目录里的last.pt就是后悔药,重新运行:
yolo segment train resume model=runs/segment/train/weights/last.pt它会自动读取上次训练的状态和参数,从断点继续。关键点是中断后不要修改data.yaml路径,否则恢复流程会找不到数据集。
5.5 现象:小病斑总是漏检,大叶片上多个小点只检出一个
分割结果里大块病斑都能圈出来,但小病斑漏检很多,可视化图上全是红框。
原因:imgsz=640时,图像里小于 10×10 像素的病斑在特征图上只剩一两个像素,分割头学不到足够的特征。
解决:把imgsz升到 1024,同时给训练命令加augment=True让 Ultralytics 自动做随机裁剪增强;如果还是不够,按 512 的滑窗把大图切块训练,推理时再用滑窗拼接。另一个偏方是换用yolov8m-seg或yolov8l-seg,但小目标问题更多由输入尺寸决定,不是简单的模型越大越好。
6. 让分割结果真正可用:mask 面积统计与置信度筛选的落地技巧
训练完成的模型只是第一步,给植保用户看时,他们真正关心的是“病斑面积占叶片面积多少”。YOLOv8 的 mask 输出是二值矩阵,计算面积占比可以用几行 Python 完成:
import cv2 import numpy as np # results 是 yolo predict 的输出 for r in results: for mask in r.masks.data.cpu().numpy(): # mask 是 0/1 的二值矩阵,sum 即像素面积 disease_area = (mask > 0).sum() leaf_area = mask.shape[0] * mask.shape[1] ratio_percent = disease_area / leaf_area * 100 print(f"病斑面积占比: {ratio_percent:.2f}%")实际使用中要注意:多张病斑 mask 叠在同一片叶子上时,这个比值会大于 100%,需要先做一次并集合并再计算。置信度阈值conf=0.25是默认值,生产环境中建议调高到 0.4,宁可漏掉边界模糊的病斑,也不要让误检干扰病情统计。
在这类项目上,我自己的教训是:拿到训练结果后永远先看三张可视化图再谈指标,指标是模型在平均意义上的表现,可视化才能暴露病斑边界、小目标漏检这类具体问题。模型导出 ONNX 之前,用验证集先确认 mask 输出,比部署后才发现问题少了整轮返工。这个方向技术上已经成熟,YOLOv8-seg 的分割能力配合合理的标注质量,完全能支撑农业病害识别场景的落地试验,希望这条链路能帮你在自己的数据集上少走几步弯路。
本文还有配套的精品资源,点击获取