☰
YOLOv11多光谱叶片分割与虫害计数实战:从训练到边缘部署
2026/9/30 10:00:18 网站建设 项目流程

简介:一份面向农业智能化与计算机视觉开发者的实战型PDF技术文档,系统讲解如何基于YOLOv11与多光谱图像处理完成农业病虫害检测系统的开发。YOLOv11凭借单阶段检测特性兼顾速度与精度,使系统在实时监测场景中具备明显优势。文档针对传统人工检测效率低、结果主观性强、难以实时监测等农业痛点,以叶片分割与虫害计数两大任务为主线,覆盖多光谱数据采集与预处理、YOLOv11基础理论、叶片分割算法实现、虫害计数方法设计、系统开发与集成、实验结果分析与典型应用场景等完整技术链路,并涵盖数据增强、模型融合与评估指标等细节,帮助读者建立从算法到工程落地的认知,适合算法工程师、农业科研人员及高校学生按目录快速查阅。资源为PDF格式,共1个文件,压缩包大小约2.02MB,文档共40页,文字、图表、目录显示完整,支持大纲跳转和章节定位,目前已有101人浏览学习,可作为农业病虫害检测项目入门参考。

1. 农业病虫害检测的硬骨头:为什么是YOLOv11+多光谱

田间拍回来的照片,要么背景全是土和杂草,要么叶片被阴影压成一片死黑。做病虫害检测的同行都有体会:用普通RGB图训练出来的分割模型,换个地块、换套光照就崩,虫体小到只有十几个像素,数都数不清。农业病虫害检测实战里,把多光谱信息和YOLOv11的实例分割能力结合起来,走“多光谱叶片分割+虫害计数”这条路,是这几年我们验证下来最稳的方向。多光谱能把健康叶片和病斑从光谱上分开,YOLOv11的分割头负责把叶片轮廓抠出来,计数交给掩膜分析而不是靠数框框。这套方法适合正在做植保信息化、智能虫情测报,或者想用视觉模型替代人工田间调查的团队。读完这篇,你能拿到一条从数据采集、模型训练、计数统计到边缘设备部署的完整落地路径。

2. 多光谱数据从哪来:波段选择、采集设备与标注规范

2.1 多光谱和RGB的区别:五个波段还是越宽越好

多光谱不是玄学,它的核心价值在于把“颜色”扩展成“光谱响应曲线”。普通RGB相机把400到700nm可见光压缩成三个通道,而多光谱传感器在同样的视场里多采了几个窄带波段。水稻稻瘟病、小麦条锈病这类病害,发病初期叶片在RGB图里只是微微发黄,人眼很难判断,但红边波段(700到750nm)和近红外波段(750到900nm)的反射率变化非常明显。这也是为什么植保方向的公开数据集大多带RedEdge和NIR通道。

选波段的时候一个常见误区是“波段越多越好”。我做过的方案里,用的是典型植保无人机多光谱相机的五通道配置:蓝(450nm中心波长)、绿(560nm)、红(650nm)、红边(730nm)、近红外(840nm)。为什么不直接上十几个波段的高光谱?数据量、标定成本和模型复杂度都会失控,而且YOLOv11的输入端按通道数接受图像,通道太多会把训练时间拖垮。五个波段里,蓝绿红合成RGB图喂给模型做外观特征,红边和近红外作为额外通道参与植被指数计算,这个组合在绝大多数田间场景下已经够用。

通道中心波长典型半高宽在叶片分割里的用途
Blue450nm35nm合成RGB基础通道,提供纹理细节
Green560nm40nm正常叶绿素反射峰,区分活体叶片
Red650nm40nm叶绿素吸收带,病斑区反射率偏高
RedEdge730nm40nm病害早期敏感波段,红边位移检测
NIR840nm50nm叶片含水量与细胞结构,区分枯叶与背景

多光谱数据的采集方式有两种:无人机搭载多光谱相机飞正射,或者地面手持多光谱相机贴近叶片拍。无人机适合田块级虫情普查,地面拍摄适合叶片级分割和虫害细粒度计数,后者和“叶片分割”这个目标更匹配。采集时有个硬性要求:多光谱相机的光照传感器必须直视天空,每次起飞前做一次白板校准,否则太阳高度角变化会让各波段反射率失去可比性。把这种校准视为拍照流程的一部分,不要指望软件后处理能完全救回来。

2.2 叶片分割的标注规范:掩膜格式与类目设计

多光谱数据拿到手之后,要先做预处理才能进入训练管线。常见做法是:把原始五通道TIFF拆成单波段,蓝绿红合成为RGB图,红边和近红外分别存为两个单通道灰度图,然后五通道直接拼成一个5通道Tensor送入YOLOv11。有人会把RGB、红边、近红外做成三图并列的多流输入,也有人把红边NIR合成伪彩色图,工程上最稳妥的还是直接拼通道,YOLOv11的输入层改一个参数就能接住。

标注规范是这套方案能不能复现的关键。叶片分割的标注用COCO的polygon格式,一个叶片一个实例,病斑区域单独标一个类,不要混在叶片轮廓里。类目设计上至少分四类:正常叶片、病斑叶片、虫害叶片、背景。“虫害叶片”和“病斑叶片”的差别在于,虫害叶片通常有咬噬边缘或虫粪附着,分割模型需要靠纹理细节区分。标注时要放大到单个叶片占画面至少30%再勾轮廓,否则小面积类的边缘会被标注员随手画成多边形,训练完掩膜边界全是锯齿。

{ "info": { "description": "multispectral leaf segmentation dataset", "channels": 5, "width": 1280, "height": 960 }, "categories": [ {"id": 0, "name": "normal_leaf", "supercategory": "leaf"}, {"id": 1, "name": "diseased_leaf", "supercategory": "leaf"}, {"id": 2, "name": "pest_leaf", "supercategory": "leaf"}, {"id": 3, "name": "background", "supercategory": "background"} ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "segmentation": [[310, 210, 315, 260, 355, 250, 360, 205]], "area": 2670.5 } ] }

上面这个JSON是COCO格式标注的最小骨架。segmentation字段是polygon坐标点,按先x后y排列,闭合点不重复。标注完成后要统计类别平衡度,田间数据非常容易出现正常叶片占80%、虫害叶片只有5%的长尾分布,这时不要急着训练,先用基于类别的加权裁剪增强把少数类的小图在每轮随机多采样几次。这一步不做好,模型会乖乖把所有叶片全预测成正常叶片。

2.3 从5通道TIFF到YOLOv11能吃的输入:预处理三步走

多光谱相机导出的一般是16位TIFF,YOLOv11默认接3通道8位图,直接硬喂会出问题。第一步做辐射定标,把DN值乘以相机出厂标定系数转成反射率;第二步做波段拉伸,把五个波段分别截取2%到98%的直方图分位点,线性映射到0到255。这里有个我踩过的坑:如果五个波段各自独立拉伸,会破坏波段间的相对亮度关系,红边和近红外的数值域会被拉跑。正确做法是先按NIR波段的统计量算出一个全局映射,再把这个映射套用到所有波段。

import cv2 import numpy as np from glob import glob # 5波段TIFF读取和全局拉伸 def load_and_stretch(tiff_path, percent=2): img = cv2.imread(tiff_path, cv2.IMREAD_UNCHANGED) # 16位 bands = np.split(img, 5, axis=-1) # 假设TIFF按波段存储 # 以NIR波段为基准计算全局分位数 nir = bands[4].astype(np.float32) low = np.percentile(nir, percent) high = np.percentile(nir, 100 - percent) stretched = [] for b in bands: b = np.clip((b.astype(np.float32) - low) / (high - low), 0, 1) stretched.append((b * 255).astype(np.uint8)) return np.concatenate(stretched, axis=-1) # H, W, 5 data = load_and_stretch("field_001.tiff") print(data.shape, data.dtype) # (960, 1280, 5) uint8

这段代码的要点在全局拉伸。percent取2意味着用2%到98%的分位数压制极端反光点,反光叶片上的镜面反射不会把动态范围全部吃掉。最终输出的5通道数组,前3通道是RGB,后2通道是RedEdge和NIR。训练增强上要注意:不要给多光谱通道做RandomBrightnessContrast,波段间的比例关系太敏感,我只用几何增强,旋转、翻转、随机缩放0.8到1.2倍。颜色类增强只对RGB部分做轻微HSV扰动,RedEdge和NIR不参与。

3. 跑通YOLOv11叶片分割:网络结构、环境配置和训练命令

3.1 为什么选YOLOv11的分割头:网络结构里藏着对田间数据的妥协

YOLOv11延续了Ultralytics系列anchor-free的设计,分割分支和检测分支共用主干和颈部,只在Head尾部多出一条输出掩膜的卷积路径。它用C3k2模块替代了早期版本的C3模块,在保持感受野的同时把计算量降下来;PSA注意力机制放在深层Stage,能提升分割任务对细小病斑的敏感度。这些结构上的改进带来的直接结果是:在同样精度下模型体积更小,在NVIDIA Jetson这类边缘设备上能把推理帧率提上去,这一点对田间部署很重要。

YOLOv11官方给出的分割模型从yolo11n-seg到yolo11x-seg,参数覆盖从几MB到上百MB。农业现场我一般选yolo11n-seg作为起点做快速验证,等确认数据没问题之后再用yolo11m-seg提精度。不要一上来就上最大的x版本,多光谱五通道输入本身就把参数量放大了,x版本在Jetson Nano上跑不动,最后还得回头压缩。

# 激活虚拟环境并安装ultralytics conda create -n agri-yolov11 python=3.10 -y conda activate agri-yolov11 pip install ultralytics torch torchvision --index-url https://download.pytorch.org/whl/cu118

安装时有两个注意点。一是Python版本别选3.12,某些多光谱图像处理库的C扩展还没适配;二是PyTorch和CUDA版本要看推理设备,如果在Jetson上跑,主机装CUDA 11.8版本,设备端用JetPack自带的PyTorch,两边版本不要强行统一。装完后用yolo predict model=yolo11n-seg.pt source=bush.jpg跑一次自带权重,确认推理链路通。

3.2 最小训练命令:改三个参数让模型接受5通道输入

要让YOLOv11接受多光谱输入,需要动两个地方:模型配置里把ch从3改成5,数据集配置里把nc改成4。常见的做法是直接改yaml文件,不推荐在训练脚本里动态改模型通道数,那样可复现性差。

# leaf_seg.yaml path: /data/multispectral_leaf train: images/train val: images/val nc: 4 names: 0: normal_leaf 1: diseased_leaf 2: pest_leaf 3: background
# yolo11n-seg-multispectral.yaml # 基于官方yolo11n-seg.yaml修改,只改ch nc: 4 ch: 5 scales: # 模型缩放系数 n: [0.50, 0.25, 1024]
# 开始训练 yolo train \ model=yolo11n-seg-multispectral.yaml \ data=leaf_seg.yaml \ epochs=200 \ imgsz=640 \ batch=8 \ workers=4 \ device=0 \ optimizer=SGD \ lr0=0.01 \ cache=ram

这段训练命令用SGD而不是默认的AdamW,原因是多光谱数据标注成本高、数据量普遍偏小,SGD配合warmup能收敛到更平滑的极小值,测试集mAP一般比AdamW高1到2个点。batch设为8是因为5通道输入显存占用比同样尺寸的RGB大不少,12GB显存卡上用8比较稳。cache=ram把整个数据集加载进内存,多光谱数据读取比RGB慢,不做这个训练时会频繁等磁盘IO。imgsz先用640把流程跑通,后续小目标优化再提1024。

训练时打开两个观察指标:mask_mAP@0.5和mask_mAP@0.5:0.95,前者看分割轮廓的整体准确性,后者看掩膜边界贴合度。田间叶片分割的验收底线一般是mAP@0.5达到0.8以上、mAP@0.5:0.95达到0.5以上,达不到就先检查标注质量,而不是盲目加训练轮数。

3.3 小目标优化:虫体只有十几个像素时模型为什么漏检

虫害计数场景里,蚜虫、蓟马这类小型害虫在640尺寸图像里只有十几个像素,YOLOv11的默认检测头在最深层输出特征图,下采样32倍后这些小目标几乎消失。社区里常用的解法有三个,按见效速度排序:切图推理、增加P2检测层、提高输入分辨率,三个可以叠加用。

切图推理是成本最低的优化。把1024尺寸的输入切成四张512的小图分别推理,再把结果映射回原图坐标,小目标相当于被放大了一倍。代价是推理时间翻四倍,但对课题组联排分析离线图片来说完全值得。增加P2层要改模型结构,在neck部分把第2层输出接入检测头,YOLOv11的yaml文件里加一层C2PSA和对应输出节点,这部分官方文档没有现成模板,改起来需要看模型参数量变化来调宽度系数。

# 提高分辨率重新训练,通常比改结构收益更直接 yolo train \ model=leaf_seg_last.pt \ data=leaf_seg.yaml \ epochs=50 \ imgsz=1024 \ batch=4 \ device=0 \ resume=False

imgsz从640提到1024的收益在虫害计数场景非常明显,标注里的密集小虫目标会被激活更多anchor位置。注意batch要相应减半,显存不足时先把workers降到2。实测经验是640到1024能让小目标的recall提升约10个百分点,代价是单张推理时间增加,但离线分析场景几乎不在乎。换到实时测报场景时再考虑用切图推理和TensorRT压缩拉回帧率。

4. 虫害计数不靠数框:从分割掩膜到密度统计的完整管线

4.1 为什么不用检测框数数:重叠虫体会把计数变成玄学

有段时间我直接用检测分支的box数量当虫口数,结果一塌糊涂。蚜虫密集聚集时,几十只虫挤在一片叶子背面,检测框互相重叠,NMS之后只剩几个框。换成分割掩膜之后,每个虫体是一个独立的连通域,即使虫体之间有轻微粘连,也能通过轮廓分析拆开。这就是“叶片分割+虫害计数”这个组合的底层逻辑:分割模型负责把虫体从叶片背景中分离出来,计数交给二值图像分析,不再依赖检测框。

import cv2 import numpy as np def count_pests(mask, min_area=8, max_area=800): # mask: 模型输出的二值掩膜,背景为0,虫体为255 mask_u8 = (mask.squeeze() * 255).astype(np.uint8) # 形态学开运算去掉单像素噪点 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) cleaned = cv2.morphologyEx(mask_u8, cv2.MORPH_OPEN, kernel) # 连通域分析 num, labels, stats, centroids = cv2.connectedComponentsWithStats(cleaned, connectivity=8) valid_count = 0 valid_areas = [] for i in range(1, num): area = stats[i, cv2.CC_STAT_AREA] if min_area < area < max_area: valid_count += 1 valid_areas.append(area) return valid_count, np.array(valid_areas), centroids[1:][valid_areas] # 假设pest_mask来自模型输出的分割掩膜 count, areas, cents = count_pests(pest_mask) print(f"检出的虫口数: {count}, 平均面积: {areas.mean():.1f} px")

这段计数代码有三个关键参数。min_area=8用于过滤分割噪声点,多光谱红边通道有时会把土壤颗粒误分为虫体,小于8像素的噪点直接丢掉;max_area=800用于排除粘连成片的虫团,如果叶片背面虫害密集成片,这个阈值要放宽后配合分水岭做区域拆分。连通域用8邻域而不用4邻域,因为4邻域容易把细长的虫腿断开,导致一只虫被数成两只。面积和重心的输出可以用来计算虫体大小分布,蚜虫和叶蝉的体积差异能辅助区分种群。

4.2 把计数结果写回推理结果:保存带统计的JSON和可视化

YOLOv11的推理结果默认只保存标注图,但田间调查需要的是能直接导入统计分析软件的数据。常见做法是把每个叶片的病斑面积、虫口数、虫体平均面积按叶片实例聚合,输出成JSON。用Ultralytics的Results对象直接取masks属性,做坐标偏移映射后再跑连通域统计。

from ultralytics import YOLO model = YOLO("best.pt") results = model.predict( source="test_field/", imgsz=1024, conf=0.35, save=True, save_txt=True, project="runs/pest_count", name="exp1" ) summary = [] for res in results: masks = res.masks.data.cpu().numpy() # 每个实例一个掩膜 class_ids = res.boxes.cls.cpu().numpy().astype(int) names = res.names for i, mask in enumerate(masks): cls_id = class_ids[i] if names[cls_id] != "pest_leaf": continue # 提取该实例在整图坐标系里的掩膜块 binary = mask > 0.5 pest_count, areas, cents = count_pests(binary.astype(np.uint8)) summary.append({ "image": res.path.split("/")[-1], "leaf_area_px": int(binary.sum()), "pest_count": int(pest_count), "pest_avg_area_px": float(areas.mean()) if len(areas) else 0, }) import json with open("runs/pest_count/summary.json", "w") as f: json.dump(summary, f, indent=2)

predict参数里conf=0.35要重点说明。分割模型的mask置信度偏低,默认0.25在密集虫害场景会混入大量背景噪声,0.5又容易把边缘模糊的小虫整块滤掉,我调试下来0.3到0.4之间最稳,具体数值要按验证集调。save=True保存可视化结果,save_txt=True会把每个实例的归一化坐标存为txt,这两个参数是排查模型输出时最常用的对照材料。

如果要把计数结果绘制到原图上,用Results对象的plot()方法就能拿到带掩膜的图像,再用cv2.putText把虫口数写到图片左上角。这个可视化版本不是给人看的,是给资方和植保专家看的,他们需要一眼确认“数出来的虫和图上标的位置对得上”。

4.3 验证计数精度:不要只看mAP,用人工计数对比

模型分割mAP再高,也不等于虫口数准确。我吃过这个亏:掩膜mAP到了0.85,人工复核时发现模型把叶片背面的虫粪颗粒当成了虫体,计数虚高。正确做法是专门建一个计数验证集,每张图拍完后人工数一遍,和模型输出做对比,用平均绝对百分比误差(MAPE)和决定系数(R²)两个指标评判。之前交付的虫情测报系统里,验证标准是MAPE低于15%才算验收通过,达不到就回去调min_area阈值,最差情况才考虑换更大的模型。

计数验证还需要划出“混淆区间”。人工数10只以下和人工数200只以上的图,模型误差形态完全不同:低密度主要错在漏检,高密度主要错在粘连。优化时不要只用全量MAPE,分密度段去看误差来源,低密度段提升分辨率,高密度段改善分割边界的收缩程度。这两个方向的改动是不同的,混在一起调参容易两头不讨好。

5. 常见问题与避坑记录:五通道数据训练与部署的五个坑

5.1 多光谱通道全堆进去,精度反而比纯RGB更差

现象:把五个通道直接拼起来训练了一个百轮模型,验证集mAP只有0.62,回到纯RGB三通道重新训练,同样数据mAP反而到了0.70。这看起来违反直觉,多光谱信息明明是增强,怎么成了负优化。

原因:训练数据量不足时,五个通道的参数量把模型自由度过分放大,红边和近红外通道的标注噪声也被模型完整吃进去。尤其是标注了背景类的场景,土壤在不同波段的反射分布差异巨大,模型学会了用波段组合模式去猜背景,而不是真正理解叶片边界。

解决:先用RGB部分加载COCO预训练权重,把多光谱新增的两个通道的输入层做权重重置,然后冻结主干前10层,只训练分割头和neck部分。等损失曲线平稳后解冻全部层,用小学习率0.0001微调。这个策略能把多光谱的增益真正发挥出来,通常比全量从头训练高出4到5个mAP点。

5.2 16位TIFF直接喂进模型,所有叶片预测成背景

现象:训练loss不下降,预测结果全图都是黑色,掩膜一片空白。检查输入图像发现大部分区域全黑或过曝。

原因:OpenCV读16位TIFF返回的dtype是uint16,模型内部默认按uint8的0到255范围做归一化,16位数据的数值范围是0到65535,模型把所有像素当成“大于1”的异常值,归一化后几乎全部压到1,信息全丢。

解决:手写预处理脚本,把五通道TIFF按2%到98%分位数拉伸成uint8,再拼接成5通道输入。这个坑在2.3节的处理流程里已经解决,但常有同学跳过预处理直接开始训练,强烈建议训练前用cv2.imwrite输出一张RGB合成图目视检查。

5.3 切图推理后小目标计数重复,一个虫体被数成三只

现象:对大图做滑窗切图推理,把小图结果拼回原图后,切图边缘的虫体出现重复计数,最多能重复三倍。

原因:切图窗口之间通常有20到50像素的重叠,用来避免目标在边缘被截断。但合并结果时没有做跨图去重,落在重叠区的虫体在两三张切图里各被识别一次。

解决:合并阶段按物体中心点坐标聚类,两中心点距离小于10像素的认为是同一个虫体。实现方式是在4.1节的连通域分析上叠加一个去重循环,把全部切图的检测结果先收集到同一坐标系,然后按中心点做空间哈希去重。另外一个更省事的方案是让切图重叠区保持0像素,把确实被截断的虫体交给后续的掩膜拼接逻辑处理,但这样就损失了切图增强召回率的意义。

5.4 换了个太阳角度,模型预测结果大面积漂移

现象:上午10点采集的数据训练出的模型,放到下午4点的田块上预测,病斑叶片被大量识别为背景,虫害叶片漏检一半。

原因:多光谱反射率对光照角度和太阳高度角极其敏感。上午采集时红边波段的值域整体偏高,模型学到了这个统计特征;下午太阳角度变化后红边值域偏移,模型认为所有样本分布异常,输出趋于保守。

解决:预测前不做任何图像增强,直接调整模型推理时的对比度归一化参数,把输入图像的分位数中心化到训练集统计值。根治办法是在采集阶段固定每天的采集时段,比如只做上午9点到11点的窗口,另外每次飞行前做白板校正。如果必须覆盖全天场景,训练数据必须包含不同时段样本,哪怕牺牲一部分单时段精度也要保全天鲁棒性。

5.5 Jetson Nano上部署推理,显存直接溢出

现象:在Jetson Nano 4GB版部署yolo11m-seg模型,TensorRT推理时CUDA out of memory,换成n分支勉强能跑但帧率只有3FPS。

原因:YOLOv11分割模型比检测模型多一条掩膜分支,显存占用高出一大截。直接用官方PyTorch权重做推理时,动态shape的显存分配没有优化,4GB内存根本不够。

解决:导出TensorRT engine时固定输入尺寸,禁止动态batch。batch_size固定为1,输入尺寸固定为640而不是1024,开启FP16量化。再极致一些可以把掩膜输出插值操作挪到CPU上做,GPU只保留网络推理部分。实测固定shape后显存占用可以从4.6GB压到1.8GB,帧率从3FPS提到15FPS,配合2.3节的切图推理能勉强满足实时监测。如果目标是Rockchip这类ARM Linux平台,常见做法是在yocto系统里交叉编译TensorRT或使用Rockchip的NPU工具链,但别指望直接复用NVIDIA的engine文件。

6. 把东西交出去:系统部署的验证方式和最后一步校准

模型在开发机上跑通只是完成了一半,田间系统要交付的是稳定输出。我的习惯是先在室内测试集上跑全量数据,确认没有类别崩溃和掩膜空洞问题后,再带着便携屏和模型去种植户的大棚里做三组现场验证:一组正常光照、一组遮阴、一组喷灌后叶片带水珠。带水珠的工况最容易暴露问题,水珠在近红外波段恰好是高反射体,会伪装成虫体边缘。

部署时导出TensorRT固定shape模型之后,我会额外写一个很小的校验脚本,把导出前后模型的输出结果做逐通道对比,允许误差控制在1e-2以内。这个步骤能抓出TensorRT对某些自定义算子的精度退化。把最后一段推理结果保存脚本里加上一张“置信度直方图”,每隔30分钟统计一次输出分数分布,如果分布和训练时差异明显,说明现场工况已经漂移出训练数据覆盖范围,系统会在后台提醒重新采集数据微调。

最后一步是把虫口数换算成发生程度。植保上一般按单叶虫口数和病斑面积占比分成轻、中、重三级,模型输出的绝对数值并不直接等于防治决策信号。我现在交付的系统里都会加一段归一化逻辑:把虫口数除以检测叶片的总面积得到虫口密度,再查分级表输出预警等级,避免叶片大小差异导致的计数误判。这个细节是多个项目甲方回头找我们改需求的共同原因,第一次做时很容易漏。

希望这篇开发指南能帮你在自己的多光谱数据集上少走弯路,从模型训练到最终交付都能稳一点。

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

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

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

立即咨询