跳甲危害严重的时候,一片菜叶上可以密密麻麻全是虫孔,农民喊它"百孔千疮",但真让植保员去田间逐片数虫孔、定虫情等级,一亩地下来就要耗掉半天时间,而且人眼在强光、阴影、湿叶环境下的一致性很差,上午数的和下午数的经常对不上。我这边一直在做蔬菜虫害的视觉识别,这次把整套检测逻辑跑通之后,决定把基于YOLO11-C3k2-FFCM的跳甲虫害叶片智能检测识别系统的完整思路写出来,从痛点分析、网络结构改动、数据准备、训练调参到实际部署翻车记录,尽量一次讲透。如果你也在做农业病虫害检测,或者正准备拿YOLO11做改进做小目标识别,这篇应该能帮你少走不少弯路。
1. 田间叶片识别,难在哪里
1.1 跳甲虫害的视觉特征决定了这是个小目标问题
跳甲的危害分两块:成虫啃食叶片形成不规则的孔洞和缺刻,幼虫则在地下啃根,但地面上最先能看到的就是成虫和叶片上的孔洞。从视觉检测的角度看,成虫本身只有两到三毫米,在一张640分辨率的田间照片里,单只成虫往往只占几十个像素;叶片上最典型的"虫眼"也很小,而且边缘不光滑、形状不规则,和炭疽病斑、机械损伤、药害灼伤具有高度相似性。这意味着你要检测的并不是"一个大物体在画面中央"这种场景,而是高密度的、互相交叠的、尺度变化剧烈的小目标集合。
这也是为什么很多人直接拿预训练的通用检测模型去跑农田数据时,效果看起来还行、一上场就露馅的原因。通用模型见过大量城市街景里面的行人、车辆,彤彤的语义类别和小目标分布完全不是一回事。跳甲场景的分布通常是:一张叶片上有几十个孔洞,孔洞尺寸从5像素到200像素不等,且部分孔洞被叶脉、水珠、阴影遮挡;成虫还会趴在叶片边缘,身体颜色和泥土、枯叶高度接近。这样的数据分布,对网络的纹理细节提取能力要求很高。
1.2 为什么不能只做分类,必须做检测定位
植保人员在实际使用中需要的不只是"这张叶片有没有跳甲",还要知道虫害分布在叶片的哪个区域、密度有多高、是点状发生还是全田扩散。分类网络只能给你一个整体的概率,定位能力为零;而如果只做语义分割,标注成本和推理开销又会显著上升。
检测方案的现实意义在于:锚框天然给了目标的空间位置和尺寸统计。只要你把检测结果落回原图,就能按像素坐标统计虫孔密度、估算受害面积占比,进而划分虫害等级。这是农业精准施药最需要的信息——不是"有虫没虫",而是"哪块地重、哪块地轻、该重点打哪片"。所以最终系统的目标定为目标检测,而不是分类或分割,这个前提要先想清楚。
1.3 现状方案的主要瓶颈
我也先后试过几种现成路线:用通用YOLOv8直接训练、用SSD、用Faster R-CNN做对比。真实体感是:这些模型在常规目标上确实成熟,但在跳甲叶片场景里普遍存在三个问题。第一,小目标漏检严重,尤其是10像素以下的孔洞,召回率掉得厉害;第二,叶脉的纹理干扰导致误检,模型经常把叶脉交叉处的阴影当成虫孔;第三,密集重叠目标的分辨能力差,多个虫孔挨在一起时只会输出一个大框,导致计数严重低估。
带着这三个问题再去重新审视网络结构,就明确了改进方向:增强模型对高频纹理异常的感知、提升小目标特征的表达能力,同时在不大幅增加推理成本的前提下完成改造。这就是后面整套YOLO11-C3k2-FFCM设计的出发点。
2. YOLO11-C3k2-FFCM的架构拆解:每一处改动都不是拍脑袋
2.1 为什么选YOLO11作为基线
近两三年一个明显的趋势是,YOLO系列已经不是简单的"你追我赶",而是每一代都在对骨干、颈部和检测头做深度重构。YOLO11相比前代的最大变化,是进一步去掉了冗余的C3模块,全面转向C3k2结构,同时把检测头做得更干净。它对我来说有两个核心吸引力:一是anchor-free机制,检测头直接用中心点偏移和宽高回归来预测目标,对小目标的定位精度提升明显;二是多尺度特征融合能力稳定,配合自适应锚框机制,在自定义数据集上的起步性能比旧版更稳。
更实际的一点是,YOLO11的训练和部署生态比较完整,数据格式统一用txt格式,导出ONNX或者做量化部署都方便。在农业项目里,时间成本往往比模型创新更重要,选一个工程生态成熟的基线,才能把精力集中在结构改造和数据处理上,而不是纠结配置文件怎么写。
2.2 C3k2到底改了哪里
C3k2这个名字看起来复杂,拆开就明了:C3是跨阶段局部结构,k代表内核尺寸的可选配置,后面的2指的是这个模块内部使用了两种尺度的卷积分支做特征提取。相比老式C3模块,C3k2最大的变化是减少了大残差旁路的堆叠,改用更加轻量的split卷积分组策略。
具体来说,输入特征进入C3k2之后会被分成两个分支:一个分支直接通过1×1卷积保持信息流,另一个分支则先做通道拆分,一部分特征经过不同内核尺寸的卷积提取局部细节,另一部分保留原始语义特征,最后在瓶颈部合并。这么设计的好处有两个方面。
一是计算效率。我记得在对比时,同参数量下C3k2的FLOPs明显低于同期的其他CSP变体,这对后续要跑在田间终端设备上的场景很关键。
二是感受野的多样化。跳甲叶片上的孔洞边缘既有精细的高频轮廓,也有因为叶片卷曲造成的模糊边界,双分支设计可以让不同尺寸的卷积核各自响应不同尺度的特征。
在整套系统里,骨干网络的主干部分采用了C3k2结构,但真正让我觉得提升明显的,是在骨干输出之后追加了FFCM模块,这个下面细说。
2.3 FFCM:把频域信息变成可学习的注意力
FFCM是我在这次项目中命名的模块全称是Frequency-domain Feature Contrast Module,频域特征对比模块。核心动机来自一个反直觉的观察:人眼判断叶片有没有被跳甲咬过,很大程度上不是靠"看到了什么形状",而是靠"感觉这片叶子的纹理不自然"。这说明有效的提示信息往往藏在高频变化中,但这恰恰是一般卷积网络比较钝的地方。
普通卷积天然是低频偏好型。一张图片经过多层卷积之后,高频边缘信息会在逐层下采样中不断丢失,而虫孔边缘、成虫腿部的纹理细节恰恰属于高频信号。这就导致小目标即使被网络注意到了,也可能因为边缘响应不足而被抑制。更麻烦的是,叶片本身的叶脉也是高频,所以在频域里直接增强高频,会把叶脉和阴影也一起放大,误检率不降反升。
FFCM的思路是"对比"而非"增强"。模块首先对骨干输出的特征图做一次离散余弦变换(DCT),把空间特征映射到频域,然后设计两个路径:一条路径取全局频域统计特征,另一条路径保留局部空间纹理特征。两条路径的结果在通道维度上做差分比较,得到一张"异常响应图"——凡是和全局统计显著不同的局部纹理,就会被放大;反之,如果只是常规叶脉纹理,则和全局统计接近,对比结果趋近于零,不会造成误检。最后用这个异常响应图作为空间注意力权重,对原始特征做残差加权。
放到网络里,FFCM的位置在骨干网络的最后一层和颈部网络之间。此时特征图分辨率还保留在较大的尺度,计算成本可控,而且恰好是高低层信息交汇点,频域对比生成的空间注意力能直接传递给后续的多尺度融合层。
2.4 完整前向链路
整套模型的前向过程可以概括为:输入一张640×640的田间叶片图像,经过YOLO11骨干网络提取四层不同尺度的特征图,骨干末端的特征图先经过一个FFCM模块完成频域对比注意力加权,再送入颈部网络做多尺度特征融合,最后由解耦检测头输出目标框和类别置信度。
我在改造时遵循了一个原则:尽量复用YOLO11的现成主干,只做加法不做减法。C3k2是本来就有的基础模块,我保留了它的完整结构;FFCM是额外插入的一个轻量模块,参数规模控制在骨干网络的百分之五以内。这样既保证了改进的有效性,也方便后续复用官方预训练权重做迁移学习。
# FFCM核心过程的简化描述(以PyTorch风格示意) # 输入: 骨干输出的特征图 x, shape=[B,C,H,W] dct_feat = dct_transform(x) # 频域映射,形状保持不变 global_stat = adaptive_avgpool(dct_feat) # 全局频域统计 local_stat = conv1x1(dct_feat) # 局部空间频域响应 contrast = local_stat - global_stat # 特征对比,异常区域被放大 attn = sigmoid(conv1x1(contrast)) # 归一化为注意力权重 out = x + x * attn # 残差加权输出3. 数据准备:影响模型上限的从来不是网络
3.1 采集环节的光照与角度
很多人拿到数据集就急着训练,忽略了一个现实:模型在测试集上的表现天花板,从拍照那一刻就决定了。跳甲叶片检测系统要面对的不是实验室里的标准图片,而是田间不同天气、不同时段、不同角度下的真实画面,数据采集如果过于单一,后续一切调参都是徒劳。
我这边整理下来,采集光线至少要覆盖三类场景:顺光、侧逆光和散射光。顺光下孔洞边缘清晰,模型容易学到正常的虫孔特征;侧逆光下孔洞边缘会出现强烈的明暗对比,虽然肉眼看明显,但容易训练出依赖阴影的坏习惯;散射光(比如阴天)下目标对比度低,是模型最难适应的情况,必须在训练集里占一定比例。
角度方面建议以俯拍为主,因为跳甲是地上害虫,植保人员日常巡田也是俯视观察;但可以额外加两成左右30到45度倾斜视角的图片,用于适应手持拍摄时的真实姿态。量化标准上,目标最小像素不建议低于15×15,但也不要全部都是大目标,否则小目标会严重过拟合不足。
3.2 标注的决策逻辑
标注是整条链路里最容易被低估的一环。我遇到过不少同学直接套用公开数据集的标注习惯,见到一个洞就标一个框,结果训练出来的模型又碎又乱。对跳甲叶片来说,标注策略要结合业务目标来定,我的选择是分两类目标:一类是"可见跳甲成虫",另一类是"受害虫孔/受害斑块"。
成虫的标注重点在于不要框得太大。跳甲成虫体长只有两三毫米,实际像素很小,如果标注框把触角、弹跳腿的模糊区域也包进去,会把背景噪声当成特征,所以成虫类目标采用紧致框策略,宁可略小于真实轮廓,也不包含周围叶面。虫孔的标注则要面对密集区域怎么办的问题。彼此紧挨但边界尚可分的小孔,我会分开标;完全连成一片已经看不出单个边界的,标成一个大的受害斑块。这么做的好处是,模型在地面部署时给出的计数逻辑更符合农艺习惯——农户关心的是"危害面积大概多少"而非"精确到第几个虫孔"。
3.3 增强策略:把小目标和多场景"喂饱"
YOLO11自带的Mosaic、MixUp、HSV扰动这些增强默认配置可以直接用,但针对这个场景我额外加了三个有效增强。
第一个是Copy-Paste增强。做法是把图片里标注好的小虫孔区域复制几份,随机贴到同图其他叶片上,同时自动生成对应的标注框。这样做直接缓解了小目标样本不足的问题,而且对密集场景的模拟非常自然。代价是贴图位置不能太离谱,否则模型会学到"漂浮的虫孔",我后来限制了贴图区域必须在标注的叶片掩膜内部。
第二个是频域增强。既然FFCM用的是频域对比,训练数据里也需要频域分布丰富的样本。我在训练前对部分图片做了随机高斯模糊和锐化扰动,让模型在频域上不至于只对某一种纹理响应。
第三个是光照扰动幅度控制。田间阴天和晴天的差异很大,所以HSV里的亮度通道扰动范围我设到了±30%,但色调扰动只开了±5%,因为叶片本身颜色变化不大,太大反而会把绿色叶的领域特征扭曲掉。
还有一个很多人会踩的坑:别把运动模糊样本直接删掉。我一开始把所有模糊图都筛除了,后来发现实际巡田时手机拍摄经常会糊,导致部署阶段遇到模糊目标直接漏检。正确的做法是保留一部分轻微模糊图,并且单独加一个数据类别标签不区分,让模型学会在模糊条件下依然做鲁棒检测。
4. 训练过程:我记录的指标曲线和超参选择
4.1 硬件与超参配置
整套训练我用的是一张24G显存的中高端消费级显卡,为了控制变量,对比实验全部在同一张卡上完成。数据集方面准备了两千多张田间实拍图片和一万多个标注实例,按照8:1:1划分训练、验证和测试集。
超参配置上,这个场景和其他目标检测任务略有不同,因为小目标占比高、样本量中等,尤其要注意过拟合和收敛速度的平衡,我最终的配置如下表。
| 配置项 | 数值 | 说明 |
|---|---|---|
| 输入尺寸 | 640×640 | 训练中开启随机多尺度640-1024 |
| Batch Size | 16 | 显存允许范围内尽量大 |
| 优化器 | AdamW | 比SGD收敛快,适合迁移学习 |
| 初始学习率 | 0.001 | 配合warmup 3轮启动 |
| 学习率调度 | Cosine Annealing | 总轮数300轮 |
| 权重衰减 | 0.0005 | 控制模型复杂度 |
| Mosaic | 前250轮开启,之后关闭 | 避免末期分布漂移 |
| MixUp | 0.15 | 比例不宜过大 |
| 类别数 | 2 | 跳甲成虫、受害叶片斑块 |
4.2 Loss是正常收敛吗
训练到大约第80轮时,盒损失降到较平稳的平台,但分类损失在雌雄之间仍然有一些小的周期性抖动。我当时一度以为是模型不收敛,后来查了训练日志才发现,这和Mosaic增强在批量样本中引入了同类目标的聚集有关,不完全是坏事。真正要警惕的是验证集上的mAP是否还在跟着训练loss一起上升。
到第200轮左右,验证集mAP@0.5基本稳定在93%上下,不再有大幅度波动,这时候我把Mosaic关掉,额外跑了50轮做精细微调。最后约300轮时停止训练,保留验证集上mAP@0.5:0.95最高的一版权重。
曲线观察上的一个建议:如果训练loss降了但验证mAP不涨甚至掉,先查是不是增强太强、样本分布漂移,而不是急着调学习率。我在早期就是因为Mosaic开得太猛,导致验证mAP在220轮出现回退,后来把MixUp比例从0.3降到0.15才缓解。
4.3 对比实验结果
为了确认每一处改动到底有没有用,我做了严格的消融实验:同一份数据、同样的训练配置,分别训练了YOLO11基础版、只把骨干替换为C3k2改进版、以及完整加入FFCM的最终方案。测试集完全独立,结果差距比我预想的要明显。
| 模型配置 | mAP@0.5 | mAP@0.5:0.95 | 参数量(M) | 单帧推理耗时(ms) |
|---|---|---|---|---|
| YOLO11基线 | 91.2 | 68.4 | 9.4 | 6.2 |
| + 骨干C3k2调整 | 92.0 | 70.1 | 9.7 | 6.6 |
| + FFCM频域对比 | 94.3 | 78.6 | 10.1 | 7.1 |
从数据上看,C3k2调整带来的提升属于锦上添花,真正拉升高指标的是FFCM,尤其mAP@0.5:0.95从70.1直接跳到了78.6,说明模型对小目标的定位精度和对密集目标的判别能力确实获得了实质提升。推理耗时只增加了约0.5毫秒,在田间可接受范围之内。
4.4 调参过程中值得记录的细节
一个容易忽略的参数是anchor的匹配机制。虽然YOLO11是anchor-free思路,但训练时依然需要预设网格匹配尺度。跳甲成虫极小,我把最小的匹配尺度额外往下调了一个档位,这直接抬升了成虫类别的召回率。实验里这一项大约贡献了2个点的mAP提升,成本却只是改了配置。
另一个值得记录的经验是迁移学习的取舍。第一批实验我用的是从零训练,loss收敛很慢,后来改用官方在通用目标数据上的预训练权重做初始化,前100轮的收敛速度明显加快,最终精度也有约1.5个点的提升。农业数据虽然和通用数据领域不同,但底层特征(边缘、纹理、基础物体形状)的预训练知识仍然是可迁移的。
5. 真实场景部署:效果、翻车与补救
5.1 导出与部署
训练完成后,我在本地先把PyTorch权重导出为ONNX格式,再通过推理引擎做了FP16量化,部署在一台带集成显卡的迷你主机上。这也符合大多数农业项目的现实——不可能在农田边上架一台工作站。
部署端我封装了一个极简的推理接口,输入一张叶片图片,输出三类信息:检测框坐标、类别标签、置信度;同时把同一叶片上的虫孔检测框数量汇总,按面积占比运算后输出轻度、中度、重度三级预警。前处理只用OpenCV的缩放和归一化,没有引入额外的复杂依赖。
实测推理速度方面,640×640输入在量化后的平均时延大概是8毫秒,足以支撑每秒十几帧的处理,完全满足巡检途中随手拍照即检的使用节奏。
5.2 实测中几个典型的翻车案例
第一次带到田间实测时,系统就出现了三类教科书式的误检,这里挨个复盘。
第一类是露水反光被识别为成虫。清晨叶片上的露珠在逆光下会形成高亮的边缘,频域对比注意力把这种强高频边缘误当成了目标特征,成虫类别误报率一度偏高。排查时发现主要问题出在训练集中的露珠样本太少,模型没有学到"光滑反光"与"虫体"的区别。
第二类是叶缘卷曲造成的阴影误检。叶片边缘卷起形成的弯弧阴影,形状上很像跳甲虫孔,模型在几乎所有卷叶样本上都输出了高置信度的虫孔框。这类问题在上一代模型里更难解,因为形状相似但频域特征不同,FFCM的对比机制没能完全抑制它,最后还是靠数据层面补了大量卷叶样本才压下去。
第三类是弱光下的整体漏检。傍晚光线不足时,叶片图像整体偏暗,虫孔对比度本来就不高,模型对虫孔的召回率明显下降。FP16量化在暗部区域丢失了一部分精度,这种问题在单纯训练层面很难根治。
5.3 针对性补救措施
露水误检的解法是在训练集里专门增加两个批次的露珠叶片数据,并且对这些样本追加"低饱和、高亮度边缘"的合成扰动;卷叶阴影误检则通过增加卷叶样本量、同时把虫孔类别的最小置信度阈值从0.25提高到0.35来解决,虽然稍微牺牲了一点召回,但田间应用更看重误检率;弱光漏检则用了图像自适应直方图均衡化作为前处理,把暗部细节提亮后再送入模型,实测召回率回升了约4个点。
还有一个部署阶段的建议:田间网络不稳,离线推理是刚需,所有推理链路的每一步都要对无网络环境做兼容。我第一次部署时因为自动更新组件在无网环境一直重试,白白浪费了大半天,后来干脆把所有依赖全部离线打包,现场零网络也能跑通。
6. 几点实操心得和接下来的方向
整套系统从立项到田间跑通,最深的体会是:农业视觉项目里,结构改进带来的收益,往往不如数据质量带来的收益大。FFCM和C3k2的组合确实把mAP@0.5:0.95拉高了整整10个点,但早期数据采集不规范时,再好的结构也救不回来;反过来,数据规范了之后,哪怕只用基线模型,也能达到可以接受的水平。所以我的建议是,如果你也打算复现这套方案,先把至少一周时间花在采集和标注规范上,再谈网络结构改造。
模型推理之外,目前还在做两件事:一是把检测结果自动换算成受害叶面积占比,对接生成施药建议表;二是尝试用连续多日检测的虫孔数量变化曲线做发生趋势预判。往长线看,这套检测框架也可以平移用到其他食叶害虫上,比如小菜蛾、跳甲相近类群,只要微调数据集和类别定义,就能快速形成新的监测能力。
最后分享一个实用的落地小技巧:不要只保存最优权重,训练过程中每隔20轮保存一个断点权重。我踩过不止一次坑,最优权重反而在验证集的某一次波动中被保存了下来,但实际田间表现还不如几十轮之前那个版本。多留几个候选权重,等田间实测之后再决定用哪个,比在验证集上一次定生死可靠得多。