YOLOv11-seg农业叶片分割与病虫害识别实战:从标注到边缘部署
简介这份PDF文档面向农业智能化、计算机视觉方向的开发者与研究人员系统讲解如何基于YOLOv11构建农作物叶片自动分割与病虫害识别系统。全文共44页支持目录章节跳转与阅读器左侧大纲快速定位内容完整、条理清晰图表与文字显示正常。文档从研究背景、YOLOv11技术基础讲起依次覆盖开发环境搭建、数据集收集标注与预处理、模型设计与训练、叶片分割算法实现、病虫害识别算法优化直至系统集成测试与部署应用并配有农场与科研机构的应用案例分析。资源包为单一PDF文件大小约2.27MB便于随时查阅与离线学习。目前已有61人学习适合希望将单阶段目标检测技术落地到农业场景的读者参考可帮助快速理清从数据准备到模型部署的完整开发链路。1. 从一张叶片照片到分割掩码YOLOv11 在农业病虫害识别里到底能做什么大棚里拍回来一批黄瓜叶片照片背景是滴灌管、地膜、铁丝和另一株的叶子你要的不是「这张图有没有病」而是「病斑长在哪、占多大面积、属于哪一类」。这就是农作物叶片自动分割与病虫害识别要解决的问题先做实例分割把单片叶子从杂乱背景里抠出来再在叶片区域内做病害分类或病斑分割。YOLOv11 的 seg 分支正好把这两步压进一个前向推理输出 mask 和类别省掉传统「先检测再抠图再分类」的三段式流水线。适合谁有几百到几千张标注图的植保团队、做农业物联网的嵌入式工程师、以及想用 ultralytics 生态快速搭一套可演示系统的开发者。它不解决「零样本识别新病害」也不解决「手机随手拍就能用」——这两点后面会讲清楚边界。2. YOLOv11-seg 的 mask 分支与农业场景的适配逻辑2.1 从检测头到原型掩码YOLOv11 分割头到底输出了什么YOLOv11 的 seg 模型在检测头之外多了一条 mask 分支。检测头输出的是每个实例的边界框和类别置信度mask 分支输出的是 k 个原型掩码prototype masks尺寸通常是 160×160 或 128×128再通过每个实例的 mask 系数做线性组合得到该实例的掩码。最终输出张量里masks的 shape 是(n, 32, 160, 160)这种形式boxes是(n, 4)cls是(n, num_classes)。理解这一点很关键你拿到的不是一张二值图而是一组浮点系数需要做阈值化和上采样才能变成可用的分割结果。农业场景的特殊性在于叶片之间重叠严重病斑和健康组织颜色差异小背景里有大量与叶片颜色相近的杂草。YOLOv11 的原型掩码机制在 COCO 上表现不错但直接迁移到叶片分割时mask 系数容易在重叠区域「糊」在一起。常见做法是先把输入分辨率提到 1024 或 1280再在训练时把overlap_mask设为 True让重叠区域的 mask 参与损失计算。这个参数在 ultralytics 的配置里默认是 True但很多人为了省显存关掉结果就是相邻叶片的分割边界像被水泡过一样。2.2 为什么不用纯检测分割掩码对病害面积统计的价值如果只做检测你得到的是「这片叶子有病」的框但植保上真正要的是「病斑面积占叶面积的比例」。这个比例决定了要不要打药、打多少。纯检测框会把背景和健康组织一起框进去算出来的比例偏差可能超过 30%。分割掩码能直接做像素级统计病斑 mask 的像素数除以叶片 mask 的像素数就是病斑率。这个数字在田间管理里比「置信度 0.87」有用得多。另一个价值是数据回流。分割结果可以自动生成伪标签把置信度高的 mask 反哺到下一轮训练集里减少人工标注量。我一般会设一个阈值比如 mask 置信度 0.85 且面积 500 像素的实例自动存成 YOLO 格式的标注文件人工只做抽检。这一步能把标注成本压掉一半以上。2.3 环境配置ultralytics 装完之后先跑通这条命令# 创建环境Python 3.10 是当前 ultralytics 兼容性最好的版本 conda create -n yolo11_agri python3.10 -y conda activate yolo11_agri # 安装 ultralytics不要手动装 torch让它自己解析依赖 pip install ultralytics # 验证安装这条命令会下载 yolo11n-seg.pt 并跑一张示例图 yolo segment predict modelyolo11n-seg.pt sourcehttps://ultralytics.com/images/bus.jpg imgsz640逻辑说明ultralytics 的 CLI 会自动处理权重下载、预处理、推理和后处理。imgsz640是推理分辨率农业图像建议改成 1024 或 1280但显存占用会翻倍。参数说明model指定权重文件source可以是单张图、文件夹或视频流imgsz必须是 32 的倍数。如果这条命令报 CUDA 相关错误先确认torch.cuda.is_available()返回 True再检查显卡驱动版本是否匹配。2.4 数据准备把 LabelMe 标注转成 YOLO-seg 格式的脚本农业数据标注常见用 LabelMe 画多边形但 YOLOv11 训练需要的是归一化的多边形坐标。下面这个脚本做转换import json import os from pathlib import Path def labelme_to_yolo_seg(json_path, output_dir, class_map): json_path: LabelMe 导出的 json 文件路径 output_dir: 输出 txt 的目录 class_map: {病斑: 0, 健康叶片: 1} 这种映射 with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue cls_id class_map[label] points shape[points] # 归一化并展平 coords [] for x, y in points: coords.append(str(round(x / img_w, 6))) coords.append(str(round(y / img_h, 6))) lines.append(f{cls_id} .join(coords)) out_path Path(output_dir) / (Path(json_path).stem .txt) with open(out_path, w) as f: f.write(\n.join(lines)) # 批量处理 class_map {病斑: 0, 健康叶片: 1, 虫害: 2} for jf in Path(labelme_jsons).glob(*.json): labelme_to_yolo_seg(str(jf), labels/train, class_map)逻辑说明LabelMe 的坐标是绝对像素值YOLO 需要归一化到 0-1 之间。多边形点顺序不影响训练但点数量要一致LabelMe 画的时候尽量用相同数量的点。参数说明class_map必须和后续data.yaml里的names顺序一致否则类别会错位。转换完记得检查有没有空 txt 文件空文件会导致训练时 dataloader 报错。3. 训练自己的叶片分割模型参数、显存与收敛判断3.1 data.yaml 的四个必填字段与路径陷阱# data.yaml path: /home/user/agri_dataset # 数据集根目录 train: images/train # 相对 path 的路径 val: images/val test: images/test # 可选 names: 0: 病斑 1: 健康叶片 2: 虫害path必须是绝对路径train和val是相对路径。很多人把path写成相对路径结果训练时找不到图。另一个坑是names的键必须是整数写成字符串0在某些版本会报错。验证集和训练集的类别必须完全一致少一个类都会导致评估指标异常。3.2 训练命令与 batch size 的显存换算yolo segment train \ datadata.yaml \ modelyolo11s-seg.pt \ epochs200 \ imgsz1024 \ batch8 \ device0 \ workers4 \ patience30 \ save_period20 \ projectruns/agri_seg \ nameexp1逻辑说明modelyolo11s-seg.pt是预训练权重农业数据量少时用预训练能快很多。imgsz1024是分割任务比较合适的输入尺寸再大显存吃不消。batch8在 1024 分辨率下大约占 10-12GB 显存如果 OOM 就降到 4 或 2。patience30表示 30 个 epoch 验证指标不提升就早停。save_period20每 20 轮存一次权重防止训练中断丢进度。参数说明workers是 dataloader 线程数Windows 下建议设 0 或 2Linux 下可以设 8。device0指定第一块 GPU多卡用device0,1。如果训练 loss 震荡厉害先把lr0从默认的 0.01 降到 0.001 试试。3.3 看 loss 曲线判断欠拟合还是过拟合训练日志里重点看三个 lossbox_loss、seg_loss、cls_loss。正常情况是三个都下降然后趋于平稳。如果seg_loss一直不降说明 mask 分支没学到东西检查标注多边形是不是太稀疏或者有自相交。如果box_loss降但seg_loss不降可能是overlap_mask关了或者原型掩码分辨率太低。验证集指标看mask mAP50和mask mAP50-95。农业场景下mask mAP50能到 0.85 以上就算可用0.9 以上算好。如果训练集 mAP 很高但验证集低就是过拟合加数据增强或者减模型容量。YOLOv11 自带的增强里mosaic对叶片分割有帮助但mixup容易把不同病害的叶片混在一起建议关掉。3.4 小目标病斑的优化imgsz 与 anchor 的取舍病斑在整张图里可能只占几十个像素属于小目标。YOLOv11 的 anchor 是自适应生成的但默认的imgsz640下小目标特征图只有 80×80病斑可能只剩几个像素。把imgsz提到 1280 能显著改善但显存和推理时间都翻倍。折中方案是用imgsz1024配合close_mosaic10最后 10 轮关掉 mosaic 增强让模型在真实尺度上收敛。另一个技巧是在data.yaml里加rect: True让 dataloader 按长边缩放并做最小填充减少无效像素。这个参数对长条形叶片特别有用能省 20% 左右的显存。4. 推理、后处理与面积统计从 mask 到可读报告4.1 保存推理结果的三种模式与 mask 提取from ultralytics import YOLO import cv2 import numpy as np model YOLO(runs/agri_seg/exp1/weights/best.pt) # 模式一直接保存可视化结果 results model.predict( sourcetest_images/, imgsz1024, conf0.4, iou0.5, saveTrue, save_txtTrue, save_confTrue, projectinference_out, namebatch1 ) # 模式二手动提取 mask 做面积统计 for r in results: if r.masks is None: continue masks r.masks.data.cpu().numpy() # (n, h, w) boxes r.boxes for i, mask in enumerate(masks): cls_id int(boxes.cls[i].item()) conf float(boxes.conf[i].item()) area_px int(mask.sum()) # 假设叶片 mask 是类别 1病斑是类别 0 if cls_id 0: disease_area area_px elif cls_id 1: leaf_area area_px if leaf_area 0: ratio disease_area / leaf_area print(f病斑率: {ratio:.2%})逻辑说明r.masks.data是已经上采样到原图尺寸的掩码值在 0-1 之间需要自己阈值化。save_txtTrue会保存 YOLO 格式的预测结果方便后续做 mAP 评估。参数说明conf0.4是置信度阈值农业场景建议设 0.3-0.5太低会引入杂草误检。iou0.5是 NMS 的 IoU 阈值叶片重叠多时可以降到 0.4。4.2 病斑面积占比的计算与阈值设定面积占比的计算公式很简单病斑像素数 / 叶片像素数。但有两个坑一是 mask 边缘有半透明像素直接sum()会把边缘算进去建议先做mask 0.5的二值化二是如果一张图里有多片叶子要按实例分别计算不能把所有叶片面积加在一起。阈值设定要看作物和病害类型。比如黄瓜霜霉病病斑率超过 5% 就建议用药番茄早疫病超过 3% 就要警惕。这些阈值不是模型给的是植保经验值需要和农技人员确认。我一般会在报告里同时给出病斑率和置信度让使用者自己判断。4.3 把结果写进 CSV 和可视化图的完整脚本import csv from pathlib import Path report_rows [] for r in results: img_name Path(r.path).name if r.masks is None: report_rows.append([img_name, 0, 0, 0, 0]) continue masks r.masks.data.cpu().numpy() boxes r.boxes leaf_area 0 disease_area 0 for i, mask in enumerate(masks): cls_id int(boxes.cls[i].item()) binary (mask 0.5).astype(np.uint8) area int(binary.sum()) if cls_id 1: leaf_area area elif cls_id 0: disease_area area ratio disease_area / leaf_area if leaf_area 0 else 0 report_rows.append([img_name, leaf_area, disease_area, round(ratio, 4), len(masks)]) with open(report.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([图片, 叶片面积, 病斑面积, 病斑率, 实例数]) writer.writerows(report_rows)逻辑说明encodingutf-8-sig是为了 Excel 打开不乱码。len(masks)是检测到的实例数可以用来判断图片质量。参数说明如果leaf_area为 0说明模型没检测到叶片这张图需要人工复核。CSV 里的病斑率可以直接导入到田间管理表格里做趋势分析。5. 避坑与排查农业分割任务里最容易翻车的五件事5.1 现象训练 loss 正常但 mask 全是糊的原因overlap_mask被关掉或者原型掩码数量npr设得太小。YOLOv11 默认npr32如果改成 16 以下mask 细节会明显丢失。解决检查训练配置里overlap_maskTruenpr保持 32。如果显存不够优先降 batch 而不是降npr。5.2 现象验证集 mAP 很高但实际推理一塌糊涂原因验证集和训练集来自同一批照片光照、角度、背景都一样。模型学到了「这张图里的叶片长这样」而不是「叶片本身长这样」。解决划分数据集时按拍摄批次分不要随机分。比如上午拍的全部做训练下午拍的全部做验证。另外加hsv_h0.015、hsv_s0.7、hsv_v0.4做颜色增强模拟不同光照。5.3 现象病斑 mask 把健康组织也包进去了原因标注时病斑边界画得太松或者类别不平衡导致模型倾向于扩大病斑区域。解决重新检查标注病斑边界要贴着坏死组织画。训练时加cls_pw1.0让类别权重均衡或者用 focal loss 替代默认的 BCE。如果病斑样本太少用copy_paste0.3做粘贴增强。5.4 现象推理速度慢到无法接受原因imgsz设太大或者用了yolo11x-seg.pt这种大模型。农业场景通常不需要 x 级别的容量。解决换yolo11n-seg.pt或yolo11s-seg.ptimgsz降到 640 或 768。如果精度掉得厉害用知识蒸馏把大模型的能力迁移到小模型。推理时开halfTrue用 FP16速度能快 30% 左右。5.5 现象保存的 mask 图片全是黑色原因r.masks.data的值在 0-1 之间直接乘 255 转 uint8 时如果没做归一化或者保存时用了错误的色彩空间。解决先做(mask * 255).astype(np.uint8)再用cv2.imwrite保存。如果还是黑的检查 mask 是不是全 0可能是conf设太高把实例都过滤掉了。6. 把模型塞进边缘设备ONNX 导出与 TensorRT 加速的实操细节训练完的.pt文件在服务器上跑没问题但田间设备往往是 Jetson 或者树莓派。这一步讲怎么把 YOLOv11-seg 导出成 ONNX 再转 TensorRT以及导出时最容易忽略的dynamic和simplify参数。# 导出 ONNXdynamic 让 batch 和 imgsz 可变 yolo export modelruns/agri_seg/exp1/weights/best.pt formatonnx imgsz1024 dynamicTrue simplifyTrue opset12 # 在 Jetson 上用 trtexec 转 TensorRT /usr/src/tensorrt/bin/trtexec \ --onnxbest.onnx \ --saveEnginebest.engine \ --fp16 \ --workspace4096 \ --minShapesimages:1x3x1024x1024 \ --optShapesimages:1x3x1024x1024 \ --maxShapesimages:4x3x1024x1024逻辑说明dynamicTrue导出的 ONNX 支持动态 batch 和动态尺寸但 TensorRT 转换时需要指定 min/opt/max shapes。simplifyTrue会调用 onnx-simplifier 做图优化能去掉一些冗余节点。opset12是兼容性比较好的版本Jetson 的 TensorRT 8.x 都支持。参数说明--fp16开启半精度Jetson Orin 上能快 1.5-2 倍精度损失通常在 1% 以内。--workspace4096是 4GB 显存工作区如果转换失败就降到 2048。--minShapes和--maxShapes的 batch 维度要一致否则推理时会报 shape 不匹配。导出后验证一下 ONNX 的输入输出import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider]) input_name sess.get_inputs()[0].name # 构造一个假输入 dummy np.random.randn(1, 3, 1024, 1024).astype(np.float32) outputs sess.run(None, {input_name: dummy}) for o in outputs: print(o.shape)正常输出应该包含output0和output1output0是检测结果output1是原型掩码。如果只有output0说明导出时 mask 分支没被正确追踪检查model.task是不是segment。最后说一个我踩过的坑Jetson 上 TensorRT 的版本和 PyTorch 导出的 ONNX opset 不匹配时转换会静默失败生成的 engine 文件大小正常但推理输出全 0。解决办法是先跑一遍trtexec --onnxbest.onnx --verbose看日志里有没有Unsupported operation的警告。如果有降 opset 到 11 重新导出。这个习惯帮我省了很多返工时间希望帮到你。本文还有配套的精品资源点击获取