☰
芒果叶片损伤分割系统:YOLOv8-Seg改进与部署实战
2026/10/19 9:41:51 网站建设 项目流程

做农业视觉项目的朋友应该都有体会:看起来是“给图片分个类”,实际落地时要命的是分割。芒果叶片上的病斑、虫眼、日灼痕迹,形态各异、边缘模糊、背景全是枝叶和泥土,光靠整图分类根本满足不了生产需求。我最近在整理一套芒果叶片损伤分类图像分割系统,包含完整源码、自采集数据集和部署教程,重点做了两条模型改进路线:yolov8-seg-CSwinTransformer 和 yolov8-seg-C2f-FocusedLinearAttention。这篇文章把整个项目的设计思路、模型改造、数据标注、训练调参、部署推理和踩坑记录完整梳理一遍,既是项目存档,也方便想做类似农业病害检测的朋友直接参考复现。

1. 项目到底在做什么:芒果叶片损伤检测不只是“分类”

1.1 从真实农业巡检场景说起

芒果种植园里,巡检人员最常做的事是翻叶片。炭疽病的圆形褐色斑、叶蝉和螨类造成的密集小白点、日灼导致的枯黄卷曲、机械采收时的撕裂伤,每一种损伤的分布位置、扩散形态和严重程度都不一样。传统做法是人工目测打分,效率低不说,不同人打分标准还不一致。

我在实际做这个需求时发现,果园想要的不是“这张叶片有病”这种粗粒度判断,而是要具体知道:哪里有损伤?损伤属于哪一类?占整片叶面积的百分比是多少?只有拿到了这些信息,才能对病害扩散趋势做定量分析,指导后续打药或修剪决策。这正好对应了图像分割的能力范畴,最终选了实例分割方案而不是单纯的目标检测或图像分类。

1.2 为什么选择“分类 + 分割”二合一架构

YOLOv8-Seg 在官方实现里属于单阶段实例分割模型,输入一张图,同时输出每个目标的边界框、类别标签和像素级掩码。对芒果叶片场景来说,这个设计有两个天然优势:

  • 边界框可以快速定位叶片和损伤区域,方便后续自动化设备去定位拍照或喷洒;
  • 掩码能精确统计损伤像素面积,用于计算真实损伤率,这是框框做不到的。

如果只用分类网络,输出只有一个标签,完全不知道损伤在哪里、面积多大;如果只用检测网络,也只有框,面积估算误差极大。YOLOv8-Seg 这套“分类 + 分割”组合,刚好覆盖了核心需求。

1.3 源码包里到底有什么

这份分享内容整理成了三块:

  • 模型部分:YOLOv8-Seg 官方基线、加入 CSWinTransformer 骨干的改进版、加入 C2f-FocusedLinearAttention 模块的改进版;
  • 数据部分:自采集的芒果叶片图像数据集,包含原始图片、标注 JSON、转换后的 YOLO 格式分割标签,以及类别说明文件;
  • 部署部分:从 PyTorch 权重导出 ONNX、推理后处理脚本、简单 Web 演示接口和边缘设备部署的参考配置。

拿到之后不是要看一堆零散文件,而是按照下面这套流程从训练到部署走通,这也是我写这篇文章的核心目的。

2. 模型改造思路:从 YOLOv8-Seg 基线到注意力加强版

2.1 基线选型:为什么用 YOLOv8-Seg 而不是其他分割模型

对比过几个主流方案。经典的 Mask R-CNN 两阶段方案精度不差,但推理速度慢,部署到边缘设备比较吃力;SegFormer、DeepLabV3 这类语义分割模型拿不到个体实例的区分,叶片与叶片重叠时没法分别输出每个叶片的掩码。YOLOv8-Seg 是 Anchor-Free 结构,分割头和解耦检测头共享骨干网络特征,在精度和速度之间相对平衡,生态也成熟,Ultralytics 官方库一直在维护。

尤其是训练流程,自带数据增强、损失函数、EMA、AMP 混合精度这些工程化实现,不用自己搭流水线,对于农业项目这种需要频繁迭代数据的场景非常友好。所以基线就锁定 YOLOv8-Seg。

2.2 CSWinTransformer 到底在改进什么

芒果叶片图像有个特点:叶片本身是大尺寸目标,但损伤斑点是分散的小目标。局部的病斑纹理要用小感受野去看,而叶片整体轮廓和边缘需要大范围上下文来判断,普通 CNN 靠堆叠卷积层来扩大感受野,效率不高,还容易丢边缘细节。

CSWinTransformer 核心是 Cross-Shape Window(跨形状窗口)注意力机制。和 Swin Transformer 用方形窗口不同,CSWin 把注意力计算拆成水平和竖直两个方向的条带窗口。这样做的好处是:既能捕捉长距离的空间依赖,又不至于像全局自注意力那样计算复杂度爆炸。用在叶片分割上,细长的叶脉、横向蔓延的病斑区域,都能被条带窗口有效感知到。

我是在 YOLOv8-Seg 的 backbone 部分做替换,把原本 CSPDarknet 的部分 stage 换成 CSWinTransformer 的 stage,而不是整个骨干全换,这样计算量不会失控。改进后的模型名就是 yolov8-seg-CSwinTransformer。

2.3 C2f-FocusedLinearAttention 的定位和作用

另一条改进路线是调整 C2f 模块。YOLOv8-Seg 的 C2f 结构通过 split 和 concatenation 操作增加梯度流,瓶颈层通常还是普通卷积。我参考 Focused Linear Attention 的思路,把瓶颈中的标准卷积注意力计算替换为聚焦线性注意力模块,也就是 yolov8-seg-C2f-FocusedLinearAttention。

这个模块属于线性注意力的一种变体,通过聚焦函数(通常是一个可学习的映射)突出重要 token,以线性复杂度近似 softmax 注意力的效果。相比普通自注意力,显存占用更低、FLOPs 更少;相比纯卷积,又能捕捉长距离依赖。在实际实验里,这个改动对小目标的损伤区域召回率有提升,原因是聚焦函数能让模型更关注病斑边缘这种高判别性位置。

2.4 改造时最容易踩的兼容性坑

改模型结构不是改完 yaml 文件就完事,有几个兼容性关键点:

  • 预训练权重加载:直接把官方 Ultralytics 的预训练权重加载到改进后模型上,经常会报 shape 不匹配。我的处理是分段加载:没改的层用 COCO 预训练权重初始化,新加的 CSWin stage 和 FocusedLinearAttention 模块用默认初始化,然后再训练。
  • 通道数对齐:CSWinTransformer 输出的通道数必须和后续 Neck 网络输入的通道数一致,不然到 FPN/PAN 阶段直接维度报错。我建议先用单张图跑一次 forward,确认输出 feature map 的尺寸。
  • 计算开销:CSWin 的窗口注意力和线性注意力都需要额外算子,如果底层的 CUDA 算子没实现,会自动走 PyTorch 的 fallback。训练速度会打折,但至少不会崩。

提示:如果环境里是纯 PyTorch 没有额外编译扩展,优先选择代码里自带 Pure PyTorch 实现的版本,否则部署时会遇到算子缺失的问题。

3. 数据集是项目的命门:采集、标注与增强

3.1 图像采集和初筛流程

这次用的数据是在多个芒果种植地块收集的,时间跨度覆盖了不同季节,目的是让模型看到尽量多的光照条件和叶片状态。采集设备就是普通手机和一台便携微单,拍照时保持叶片在画面中占比 60% 以上,分辨率统一缩放到合适尺寸。

初筛阶段做了两件事:

  • 剔除严重过曝、失焦、遮挡面积超过 50% 的图像,这类图标注质量没保证;
  • 同一棵树的重复角度照片只保留最清晰的一张,避免训练集和验证集之间出现“近似重复”导致指标虚高。

最终整理出 1500 多张有效图像,其中健康叶片约 30%,其余是各种损伤叶片,保证模型见过足够多的负样本和多样损伤形态。这部分数据是这套系统能复现效果的基础。

3.2 标注规范和格式转换细节

标注用的是多边形标注工具,不支持的话我用 LabelMe 或 X-AnyLabeling 也都可以。每个实例的标注要点是:

  • 对于叶片本身,标注整个可见叶片轮廓,即使部分边缘被遮挡也尽可能贴合可见边界;
  • 对于损伤区域,以视觉可辨的边界为准,病斑边缘模糊时取像素过渡带的中点;
  • 同一个叶片上有多块不相连的损伤,分别标成同一个类别的不同实例,而不是合并成一个多边形。

标完之后导出的 JSON 文件需要转换成 YOLO 分割格式。YOLO 分割标签的每一行是:

class_id x1 y1 x2 y2 x3 y3 ...

所有坐标都要做归一化,除以图像的宽和高,点数没有固定限制,但实测采样 30 到 50 个点之间效果比较平衡。点太少拟合不了弧形边缘,点太多容易把标注误差一起学进去。转换完成后我都会做一次可视化检查,把标签画到原图上逐张看一眼,这一步能过滤掉绝大部分标签错位问题。

3.3 类别设计与样本不均衡处理

芒果叶片损伤的分类粒度不能太粗也不能太细。我最终确定了四个损伤类别加一个健康类:

类别编号名称典型特征
0健康叶片无明显病斑虫孔,表面基本完整
1炭疽病斑圆形或不规则褐色斑,边缘有黄色晕圈
2虫害损伤咬食缺口、密集刺吸点、虫粪附着
3日灼损伤叶片局部枯黄或褐色焦枯,边界相对清晰
4机械损伤撕裂、断裂、压痕,边缘不规则但无扩散

健康叶片数量偏多,而机械损伤样本最少。为了不让模型被健康类带偏,训练时对少样本类别做了两种处理:一种是在增强时提高重采样概率,另一种是针对机械损伤类做额外的旋转和随机形变增强。实验中把类别权重调成按样本数的倒数比例,mAP 提升比较明显。

3.4 数据增强配置和验证集划分

训练时启用了 Mosaic、随机透视、HSV 扰动、翻转、缩放和部分 Cutout。Mosaic 对大叶片和小病斑的组合很有效,能让模型学会在不同尺度下识别损伤。但是要小心:Mosaic 合成后的图像里叶片边界可能被切断,所以我在最后 30 个 epoch 关掉了 Mosaic,让模型适应真实分布。

验证集划分建议按“地块”而不是按“图片”分。如果同一果园同一棵树的照片同时出现在训练集和验证集,模型相当于见过部分答案,测试结果会虚高。我按不同采集时间和地块来切分,训练集 1200 张,验证集 300 张,尽量保证独立分布。

4. 完整训练流程与关键参数配置

4.1 环境搭建和依赖版本

训练环境用的是单张 24G 显存显卡,系统层面装好 CUDA 和 cuDNN 后,建虚拟环境安装依赖。核心依赖版本参考如下:

python>=3.10 torch>=2.0.0 torchvision>=0.15.0 ultralytics>=8.0.0 onnxruntime>=1.15.0 opencv-python>=4.6.0

注意:如果你要跑带 CSWinTransformer 的版本,最好使用我提供的 requirements 文件锁定版本。Ultralytics 升级到较新版本后某些模块接口变了,需要同步调整代码。

4.2 训练超参数到底怎么调

训练命令本质上还是 Ultralytics 的入口,只是换成自定义 yaml 文件。关键参数我记录一下:

yolo seg train \ model=cfg/yolov8-seg-cswin.yaml \ data=config/mango_leaf.yaml \ imgsz=640 \ batch=16 \ epochs=300 \ optimizer='SGD' \ lr0=0.01 \ weight_decay=0.0005 \ warmup_epochs=3.0 \ cos_lr=True \ amp=True \ patience=50
  • imgsz=640:对大多数叶片损伤区域已经够用。如果部署设备能接受更高延迟,可以试 960,小病斑检测精度还会涨一点;
  • batch=16:配合 24G 显存和 AMP 混合精度刚好。显存不足时把 batch 降到 8 并用梯度累积,效果等价;
  • lr0=0.01 配合 SGD:比较稳。如果换成 AdamW,lr0 建议降到 0.001,否则头几个 epoch 的 loss 容易震荡;
  • cos_lr=True:余弦退火在高 epoch 数下比固定学习率稳定,尤其对分割这种细粒度任务有效。

4.3 损失函数配置和权重解释

Ultralytics 里分割任务的损失由几个部分组成:边界框回归损失、分类损失、DFL 损失和分割掩码损失。默认权重基本能用,但我调整了分割损失的权重,调大了一些,因为本项目的核心是掩码质量:

box: 0.05 cls: 0.5 dfl: 1.0 seg: 0.1

分割损失默认使用 BCE,目标是每个 mask 的像素级二分类。如果想进一步优化边缘质量,可以在源码里把分割损失切换成 Dice 或 BCE+Dice 的组合。我自己试过纯 Dice,训练收敛略慢,但掩码边缘更干净,最后是 BCE 和 Dice 按 1:1 混合。

4.4 训练日志、EMA 和早停判断

训练过程中主要盯这几个指标:

  • box_loss、cls_loss、seg_loss 整体趋势,有没有在 100 个 epoch 附近出现严重震荡;
  • mAP50 和 mAP50-95 是否在持续增长,如果 30 个 epoch 不涨就考虑降学习率或恢复 checkpoint;
  • precision 和 recall 的平衡关系,损伤类别多且目标小的模型 recall 往往偏低。

EMA(指数移动平均)在训练中默认开启,对最终权重平滑效果很好,除非显存实在不够,一般不要关。早停 patience 我设了 50,实际训练里大约 220 个 epoch 前后达到平台期。保存的最佳权重是 val 集上 mAP50-95 最高的那版,而不一定是最新 epoch 的权重。

5. 推理部署与落地细节

5.1 ONNX 导出和算子检查

训练完的 PyTorch 权重部署前先导出 ONNX。Ultralytics 提供了现成命令:

yolo seg export model=weights/best.pt format=onnx imgsz=640 opset=12 simplify=True

opset=12 是为了兼容更多推理框架,simplify 可以去掉一些冗余结构。导出之后务必用 onnxruntime 先跑一遍测试图,确认输出 shape 符合预期。

这里有一个易错点:Seg 模型导出的输出不止检测头,还包括 mask 系数和 prototype mask。后处理时要把检测头筛选出的实例对应的 mask 系数,和 prototype mask 做矩阵乘法,再通过 sigmoid 得到完整掩码。不要把输出原样当成成品掩码使用。

5.2 掩码后处理和损伤率计算

得到原始像素级 mask 之后,二值化阈值一般取 0.5。如果掩码碎边严重,可以做一个开运算去掉零散噪点,再做一次闭运算填补小空洞。

计算损伤率的方法很简单但容易犯错:

  • 先统计整片叶片的掩码总面积 A_leaf;
  • 再统计每个损伤类别的掩码像素数 A_damage;
  • 损伤率 = A_damage / A_leaf。

需要注意的是,不同损伤类别可能叠加,比如炭疽病斑和日灼区域在视觉上交叠,计算占比时可以先合并为“所有损伤总面积”,再分别输出各类占比。我后来在代码里增加了按叶片实例输出的功能,每一片叶子一行 JSON 数据,包括损伤类别、置信度以及面积占比,方便果园直接导入管理后台。

5.3 边缘设备部署的参考方案

芒果园现场不太可能一直放着带大显卡的服务器,实际部署用边缘设备更现实。我把流程整理成了三条路:

设备类型部署方式备注
服务器或工作站ONNX Runtime / TensorRT优先,吞吐量高
边缘盒子TensorRT FP16延迟可做到 50ms 以内每图
移动端NCNN / TFLite需要进一步量化,精度略有损失

如果使用 TensorRT,建议把输入分辨率固定为 640x640,FP16 精度损失很小,但推理速度提升明显。不要在 INT8 上强上,对分割掩码影响还是比较大的,除非做了足够的校准数据。

6. 常见问题与排查技巧实录

6.1 训练后 mAP 一直不涨

排查顺序先看数据再改模型。我先做的检查是验证集标签可视化,结果发现少量标签的点序反了,导致归一化后多边形变形。这是手工标注转换时常见问题。其次再检查类别是否均衡,如果少样本类别只占总量 5% 以下,光靠重采样不够,还要针对性补充数据。最后才考虑骨干网络是否加载了合适的预训练权重。

6.2 小尺寸病斑漏检率高

叶片上的虫眼有时候只有几个像素,640 输入下很容易被下采样丢掉。我在不改模型结构的前提下先提高 imgsz 到 960,mAP50 涨了约 3 个百分点。如果部署端接受不了高分辨率,再考虑在检测头增加浅层特征融合,或者用切片推理把图像切块后分别推理再拼接结果。

6.3 掩码边缘粗糙、锯齿明显

这个问题一般出在 prototype mask 分辨率不足和分割损失匹配上。尝试把输出 mask 的分辨率从 160x160 提高到 256x256(在 yaml 里调 mask resolution),锯齿会明显改善。另外在损失里加 Dice 分量也能让边缘更平滑。实在不行部署后处理阶段还可以加一轮轻量的条件随机场,但不是必须。

6.4 显存不足和训练速度慢

如果 batch=16 直接在常规显卡上跑会 OOM,我建议梯度累积模拟 batch。Ultralytics 里设置 batch=4,累积 4 次更新,等效 batch=16。AMP 必须开启,能节省接近一半显存。训练速度方面,关闭不必要的可视化输出和调低日志频率可以获得一定提升,但别把 EMA 关了。

6.5 数据集划分不当导致指标虚高

这个问题非常隐蔽。如果同一棵芒果树的照片在训练集和验证集各出现几张,模型学习到的可能是照片光照环境和枝条背景特征,而不是叶片损伤特征。重新按地块和叶片生命周期切分数据后,验证集 mAP50 反而下降了 5 个点左右,但这才是真实水平,上线后也没有出现明显漂移。

7. 经验收尾和一些值得继续做的事

这套芒果叶片损伤分类图像分割系统在项目里实际跑通之后,我最深的体会是:农业视觉项目真正的瓶颈通常不在模型结构,而在数据质量、标注规范和部署细节这三件“笨功夫”上。改模型只占总工作量的一部分,但把数据管好再训练,每一步都能感受到收益。比如把类别重采样和验证集按地块切分这两件事做完,mAP 的提升比替换一整个骨干网络还明显,这是很多朋友容易忽略的。

还有一个小技巧分享给大家:训练完不要只看 best.pt,把最后验证集上预测错误的样本集中导出来,按类别和错误类型做个月度回顾。这些错误样本往往反映了数据分布的新变化,比如新出现的虫害形态、不同光照下的成像差异,及时把它们补进数据集,模型长期跑在田里才会越来越稳。

如果你接下来想继续深挖,我建议朝两个方向走:一是把分割掩码和生长模型结合,在芒果产量预测或病害预警里做更前置的应用;二是优化端侧推理,通过模型蒸馏把两条改进路线的能力再压缩到移动端可用的规模。这套源码和数据集已经把这层地基打好了,后面能长出的东西会很多。

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

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

立即咨询