芒果叶片病斑分割系统:基于YOLOv8-seg与注意力机制改进的实战解析 做这个系统的起因其实是一次挺狼狈的果园调研。当时想统计芒果叶片上病斑覆盖的面积比例传统做法还是靠人蹲在树下一片片翻叶子拿个透明方格板去数又慢又容易漏。市场上能直接用的果实检测模型不少但针对叶片病斑做像素级分割的开源方案却很少。后来我决定用 YOLOv8-seg 自己搭一套芒果叶片损伤分类图像分割系统结合两类改进分支——yolov8-seg-CSwinTransformer 和 yolov8-seg-C2f-FocusedLinearAttention并整理出可以复现的源码、数据集和部署流程。这篇文章把整个过程中的关键取舍、训练配置和踩坑记录都写清楚适合想入门农业视觉分割、或者打算在 YOLOv8-seg 上做注意力机制改进的开发者参考。1. 为什么要用分割模型而不是继续用普通目标检测1.1 检测框给不了“病斑到底占多大面积”的答案刚开始我也想过直接用现成的 YOLOv8 检测模型能不能完成任务。检测模型确实能输出“哪一片区域有炭疽病”“哪一块是机械损伤”但框是矩形的芒果叶片上的病斑大多是不规则形状有些沿着叶脉蔓延有些在叶缘形成半圆形凹陷。一个矩形框会把大量健康组织框进来面积估算就会严重偏大。更关键的是农业生产上评估病情严重度参考的是病斑面积占整个叶片面积的比例而不是检测框的面积。只有分割模型能输出像素级 mask让每个病斑像素都有明确的归属后续才能准确计算病斑总面积占比、病斑数量、最大斑块直径这些指标。这也是我最终放弃纯目标检测方案的根本原因。1.2 分割输出给后续分析留下了很大的空间拿到 mask 之后可以做的事情比单纯画框多很多。比如按类别统计每一片叶子的病斑面积占比用于严重度分级也可以把 mask 叠加到原图上做病斑位置的分布热力图反推病虫害是从叶缘还是叶脉附近开始的。这些后续分析是农户和植保专家真正需要的而不仅仅是三张效果图。另外一个实用价值在溯源场景。芒果收购时如果叶片损伤超标供应商需要能说清楚损伤类型和程度。分割模型给出的结构化数据可以直接导出成 CSV 或者 JSON 记录形成可追溯的检查单这在田间巡检、冷库入货抽检里都很实用。1.3 两条技术路线的定位差异标题里提到的两个改进分支yolov8-seg-CSwinTransformer 和 yolov8-seg-C2f-FocusedLinearAttention代表了两种不同的思路。前者偏重全局语义建模CSwinTransformer 的跨窗口注意力机制能够把叶片上分散的、颜色相近的病斑区域关联起来对大面积变色、褪绿、枯萎这类损伤更敏感。后者偏重轻量化和局部细节建模FocusedLinearAttention 在注意力计算时会更聚焦于关键像素抑制背景叶片纹理的干扰这对那些只有几个像素大小的早期病斑特别重要。我在实际项目里是把两个分支分别训练、分别部署测试的而不是互相替换代码块具体差异后面有一整节来分析。2. 芒果叶片数据集的采集、清洗和标注一线经验2.1 采集阶段最容易忽略的“像素密度”问题数据集的拍摄规范直接决定模型能否真正落地。第一批数据我让团队用手机随手拍结果很多照片分辨率不够叶片占比太小病斑区域只有十几个像素连人工标注都很勉强。后来我定了一个基础要求单张照片不低于 1200×900叶片主体占画面 60% 以上拍摄时尽量让手机和叶片平面平行减少透视畸变。如果叶片本身卷曲或者有严重凹凸倾斜拍摄会导致病斑形状失真模型学到的 mask 形状是扭曲的部署到真实环境时表现会很差。对于卷曲叶片我一般会选择压平后再拍或者记录多角度照片但训练集里只保留正视角度的样本否则同一种病斑在不同角度下看起来像两个类别。2.2 同一棵树的照片会“数据泄露”必须按叶片个体划分这是我踩过比较重的一个坑。如果训练集和验证集里包含同一片叶子的不同角度照片模型会通过叶片纹理和背景“记住”这张图而不是真正理解病斑特征指标会虚高。验证集上的 mAP50 看起来很好一到新果园就掉得厉害。正确做法是按叶片个体进行分组划分。每拍完一片叶子我给文件名加上叶片编号比如leaf_023_rot.jpg、leaf_023_healthy.jpg。划分数据集时用编号做分组确保同一个叶子的所有照片只出现在训练集、验证集或测试集里的一个子集内。2.3 标注细节mask 边缘决定整个系统的上限标注工具我最终选了支持多边形分割的开源标注软件但重点不在工具而在标注规范。第一病斑边界要放大到 300% 以上再取点。很多小病斑边缘模糊肉眼看是一个模糊过渡带必须统一规则以肉眼可见的变色边界为准不把黄晕算进病斑否则 mask 边缘会普遍膨胀一圈面积计算产生系统性偏差。第二重叠病斑不能一笔带过。叶片上经常出现两个病斑互相挤在一起的情况我规定如果有明显边界就分开标如果边界连成一片就标成一个整体对象。标注阶段不一致模型后续输出就会不稳定同一个场景今天输出两个 mask明天输出一个 mask。第三每类至少安排两次交叉复核。我遇到过标注员把日灼损伤误标成炭疽病的情况这种类别歧义在训练时会极大干扰特征提取而且很难靠数据增强弥补。复核机制非常关键不能省。2.4 样本不均衡和小目标问题的增强策略芒果叶片损伤类别里炭疽病这类常见病种样本可能超过 2000 张而细菌性角斑这种早期小损伤可能只有 200 张。直接训练会造成模型对少数类别几乎不学习mask 输出质量极差。针对小目标少样本我用了一个非常有效的增强方式copy-paste。把单独标注好的少数类病斑 mask 抠出来随机贴到健康叶片上同时保留 mask 对应的位置信息。这里要注意贴图时避免让病斑边缘被叶片边界截断否则模型会学到残缺边缘的形态。另外不建议对病斑做 HSV 随机扰动过强因为病斑颜色是类别区分的关键特征颜色偏移过大会导致类别混淆。3. yolov8-seg-CSwinTransformer 分支提升病斑整体形态感知3.1 我把 CSwinTransformer 换到了哪个位置YOLOv8-seg 的基础结构是 Backbone Neck Head原始 Backbone 主要依赖 C2f 模块提取特征。C2f 本身是局部卷积堆叠感受野有限适合捕捉边缘纹理但病斑的整体分布和跨区域关联需要更大的感受野。CSwinTransformer 的跨窗口注意力是在不同窗口之间建立信息交互对芒果叶片这种“表面纹理复杂、但病斑分布往往跨区域”的场景很有效。我采用的是将 Backbone 最后两阶段的局部模块替换为 CSwinTransformer 模块让深层特征图能获得全局上下文信息。如果你在自己的源码里复现注意替换后特征图的通道数和下采样倍数必须与 YOLOv8-seg 的 Backbone 保持一致这是最容易编译报错的地方。3.2 训练这类模型时的超参数和收敛情况CSwinTransformer 分支的参数量通常比原始 YOLOv8-seg 大不少训练时要相应调低批次我用的配置是单卡 8 张图输入尺寸 640初始学习率 0.001优化器 AdamW训练 300 轮并打开早停机制设置 patience 为 40。在实际验证集表现上这个分支对整片褪绿、大面积炭疽斑块的 mask 分割效果提升明显mAP50 比基线高出 3 到 5 个百分点尤其对大目标的 mask 边缘平滑度改善显著。但它也有明显的弱点小病斑召回率偏低那些只有几个像素的早期病斑经常被直接漏掉。原因也容易理解全局注意力会倾向于把注意力分配到占面积更大的健康叶脉和背景纹理上小目标反而得不到足够关注。3.3 对这个分支的训练日志观察训练过程中我注意到一个典型现象分类损失和 box 损失在前 50 轮快速下降但 mask 损失下降非常慢而且 mAP50-95 指标会在中间出现长时间的平台期。这个阶段不要急着调低学习率我建议先检查数据标注中是否普遍存在 mask 边缘过大的问题因为 model 学到的是标注的“平均边缘风格”如果标注本身偏粗糙无论训练多久都无法达到精细分割。4. yolov8-seg-C2f-FocusedLinearAttention 分支轻量化的同时兼顾小病斑4.1 FocusedLinearAttention 解决的是“注意力被背景带偏”的问题C2f-FocusedLinearAttention 的改进重点不是简单换一个注意力头而是引入聚焦线性注意力的思想。传统线性注意力计算效率高但对背景像素的响应不敏感导致病斑和健康叶肉的区分度下降。FocusedLinearAttention 在计算注意力权重时会对每个像素的查询向量进行聚焦分组增加小病斑区域和病斑边缘像素的注意力占比降低连续叶脉纹理的干扰。体感上的效果是相同的数据和训练配置下模型对芒果叶片上非常小的角斑点、早期炭疽小斑点召回率显著提升。训练速度也比 CSwinTransformer 版更快显存占用更友好用同一张显卡batch size 可以提高到 12。4.2 两个分支的横向对比与选型建议我在同一份数据集、同样的训练轮次和图像尺寸下做过对比整体情况如下表。需要说明的是不同设备上的绝对数值差异取决于显卡型号和推理框架优化程度这里更值得关注的是相对差异和适用场景。对比项yolov8-seg-CSwinTransformeryolov8-seg-C2f-FocusedLinearAttention参数量偏高相对轻量训练收敛速度较慢较快大块病斑分割精细度优秀良好密集小病斑召回率一般优秀边缘设备推理速度较慢快适合场景高精度田间调查、复杂背景实时巡检、边缘端部署我的建议是如果目标是做实验室级别的精度分析优先试 CSwinTransformer 分支如果要做果园巡检机器人或手机端实时检测C2f-FocusedLinearAttention 分支是更稳妥的起点。两套模型不要只比 mAP还要看推理延迟和内存占用很多边缘设备上参数量差异会直接决定能不能跑起来。4.3 轻量化分支在部署时的额外收益C2f-FocusedLinearAttention 分支的另一大优势是导出其他格式后变化较小。同样导成 ONNX模型文件大小通常会小 30% 左右这对带宽有限的边缘部署场景很重要。加上它对小目标召回率更高实拍场景里那些还没扩散的早期病斑往往才是防治的关键窗口漏掉一个都可能意味着后期大面积扩散。5. 完整实验记录训练命令、参数说明与典型错误分析5.1 端到端训练指令与关键参数解释整理这套源码时我把训练流程固定成了一条可复现的生产命令。下面以 C2f-FocusedLinearAttention 分支为例# 训练芒果叶片损伤实例分割模型 yolo segment train \ data./datasets/mango_leaf.yaml \ modelmodels/yolov8-seg-C2f-FocusedLinearAttention.yaml \ epochs300 \ imgsz640 \ batch8 \ workers4 \ optimizerAdamW \ lr00.001 \ patience40 \ close_mosaic10 \ device0参数看起来简单但每一项背后的选择都有原因。imgsz640是精度和速度的折中芒果叶片病斑细节多小于 640 很容易丢失小目标大于 640 则训练时间成倍增加close_mosaic10表示在最后 10 轮关闭 mosaic 增强这一步非常关键避免模型在训练末期还面对经过拼接的、不自然的图像分布导致最终模型在真实单图场景上表现反而下降。patience40用于早停当连续 40 轮验证指标不再提升时自动停止训练。在芒果叶片数据集上个人建议保留这个机制因为最后一两个类别的过拟合风险很高多训练并不一定更好。我使用的数据配置文件mango_leaf.yaml结构如下path: ./datasets/mango_leaf train: images/train val: images/val test: images/test names: 0: anthracnose 1: bacterial_spot 2: powdery_mildew 3: leafhopper_damage 4: sunburn 5: mechanical_damage这里类别可以按你实际标注的损伤类型调整。关键点是每一类都必须有足够的独立叶片样本不要为了凑数量把同一个叶片多次复制到不同文件夹。VOC 和 COCO 格式的转换脚本我也整理进了源码包直接用脚本即可完成批量转换。5.2 验证集上的典型错误与调整思路第一类典型错误是类别混淆。比如炭疽病早期和机械损伤在视觉上都是不规则褐色斑块模型很容易分错。我在调整时先把这两类的训练样本配比拉平然后单独给炭疽病早期样本增加局部放大增强让模型更关注内部的轮纹纹理而不是外观颜色混淆率明显下降。第二类是 mask 边缘的锯齿状。即使训练指标不错部署时 mask 边缘仍会出现很多细微凹凸这会直接导致面积计算抖动。我在后处理管线中加入一个小步骤对输出的 mask 做形态学闭运算再用cv2.approxPolyDP做多边形简化面积计算的稳定性显著提升。第三类是漏检严重重叠的病斑。芒果叶片上病斑密集时模型倾向于输出一个大 mask 而不是多个独立 mask。这里调整的关键是 NMS 阈值我把 NMS 从默认 0.45 降到 0.35 再试同一片叶片上的多个独立病斑更多情况下会被分开识别代价是会多产生少量低置信度框但整体分割效果更好收益比风险高。5.3 分割质量的评估不能只看 mAP50-95在这个项目里我发现纯检测指标的参考意义有限因为 mask 边缘质量才是面积计算的直接决定因素而 mAP 对边缘偏差并不敏感。我额外加了一个指标叫 boundary IoU也就是只在 mask 边缘带状区域内计算 IoU。这个指标对标注质量和模型精细度更敏感。如果在 boundary IoU 上始终上不去通常不是模型问题而是标注边缘质量不行。需要回到标注阶段重新规范取点密度别指望模型能从粗糙标注里学到精细边缘。6. 部署到真实环境的几个要点6.1 ONNX 导出与推理管线的完整流程训练好的模型要进入实际部署我建议先统一导出为 ONNX再根据目标设备决定是否继续转为加速格式。下面是我常用的导出指令# 导出 ONNX 格式 yolo export model/path/to/best.pt formatonnx opset12 simplifyTrue dynamicTruedynamicTrue允许动态输入尺寸适合部署时需要不同分辨率输入的场景simplifyTrue会清理一些冗余节点减少推理耗时。导出后建议用onnxruntime跑一遍样例图片确认输出 shape 是否是你预期的那三个维度[batch, num_detections, 4num_classesmask_dim]这样后续写后处理脚本时才不会踩维度坑。如果目标设备是带 TensorRT 加速的环境再用导出的 ONNX 构建 TensorRT 引擎精度损失通常可以接受。需要注意构建引擎时用固定尺寸会获得更高的帧率但也会限制输入分辨率灵活调整我倾向于现场部署时根据相机固定分辨率设置引擎尺寸。6.2 边缘设备上的精度与速度平衡以及一个面积计算后处理实例实际部署到低算力边缘设备后CSwinTransformer 分支虽然精度更好但推理延迟高在需要实时轮询多相机的场景里很难顶住。C2f-FocusedLinearAttention 分支在 mAP 损失可以接受的情况下推理速度明显占优所以我最后在移动端设备上用的是轻量分支。以下是推理后计算病斑面积占比的一段后处理代码import cv2 import numpy as np def compute_disease_ratio(result_mask, leaf_mask): h, w leaf_mask.shape leaf_pixels int(np.sum(leaf_mask 0)) if leaf_pixels 0: return 0.0 disease_pixels 0 # result_mask 的形状: [num_detections, h, w]每个检测输出一个二值 mask for i in range(result_mask.shape[0]): mask result_mask[i] binary (mask 0.5).astype(np.uint8) # 只统计落在叶片范围内的病斑像素 binary cv2.bitwise_and(binary, leaf_mask) # 形态学闭运算去掉琐碎噪点 binary cv2.morphologyEx(binary, cv2.MORPH_CLOSE, np.ones((3, 3), np.uint8)) disease_pixels int(np.sum(binary 0)) return round(disease_pixels / leaf_pixels, 4)这个函数背后的逻辑很简单但有价值只计算落在叶片范围内的病斑像素避免背景杂物被误判为病斑闭运算可以消除 mask 上的孤立噪点让面积统计更稳定。输出结果可以直接写入数据库形成随时间变化的病情曲线。6.3 部署后仍要留一个“人工复核”兜底我实际部署后得到的个人体会是任何分割模型都不可能 100% 准确尤其在不同品种的芒果叶上表现差异很大。比如成熟期的翠绿叶片和嫩梢黄绿色叶片同一模型输出可能会产生系统性偏差。在正式生产环境里我建议大家保留一个简易复核界面让操作员快速确认模型输出的 mask 是否合理而不是完全黑箱自动流转。这个兜底看起来朴素但在实际项目里能省掉大量后期返工。最后分享两个小技巧按我的经验新手最容易忽略的是训练集与验证集的叶片个体划分问题宁可少一些图片也要保证不泄漏。另一个实用技巧是训练过程中偶尔把验证集里最难的那几十张图片打印出来一张张贴在笔记本旁边肉眼盯着看 mask 边缘质量比任何指标都直观。如果你后续想把这套系统扩展到其他作物比如水稻叶片、柑橘叶片建议保留同样的数据目录结构和训练命令只需要替换数据集和类别名称。很多适配工作其实都花在数据采集和标注规范上模型结构改动反而很少。这也是我这次整理源码时最想传递的一个观点真正的难点不在模型而在数据端到端的工程闭环。