褐飞虱(brown planthopper)这类害虫,农业上习惯叫它“稻虱”,体长不过几毫米,一旦暴发就是成片枯死的“虱烧”景象。以前我们巡田主要靠人工,拿白瓷盘在稻丛中一拍,数虫口数量;水稻面积一大,人根本数不过来,等肉眼能明显看到枯黄,基本已经晚了。所以去年我们尝试用YOLOv9搭建了一套水稻害虫定点识别系统,类别列表里第一个就是brown-planthopper,之后陆续加上白背飞虱、稻纵卷叶螟、二化螟等。这篇内容就是把这套系统的完整落地过程记录下来,包括数据集怎么建、为什么选YOLOv9、训练调参时踩到的坑、模型怎么评估、最后怎么部署到田间的边缘设备。想用YOLOv9做小目标检测的朋友,或者正在做农业信息化项目的开发者,可以直接参考。
1. 认清目标:水稻害虫检测到底难在哪
1.1 检测对象本身就反人类
褐飞虱不是那种体型很大的虫子,成虫体长也就三四毫米,在图片上经常只有二十几个像素。它喜欢聚集在稻株基部,被叶片挡住,颜色又和水稻茎秆接近。更麻烦的是它的密度:暴发期一丛稻上可以有几十上百头,个体之间互相重叠,人眼都很难数清。
我们最初列出的检测类别就四个:
| 类别 | 常见体型 | 危害特点 | 检测难度 |
|---|---|---|---|
| brown-planthopper(褐飞虱) | 3~5mm | 刺吸汁液,成片枯死 | 小目标、高密度、拟色 |
| white-backed planthopper(白背飞虱) | 2~4mm | 苗期危害明显 | 小目标、高密度 |
| rice leaf roller(稻纵卷叶螟) | 蛾类翅展约15mm | 幼虫卷叶取食 | 成虫翅面纹理易与背景混淆 |
| striped stem borer(二化螟) | 成虫约12mm | 蛀茎造成白穗 | 样本少、活动隐蔽 |
表格里最让人头疼的其实就是褐飞虱。它既小又多,检测系统稍有漏检,统计出来的虫量变化趋势就会失真。
1.2 我们要的是“早期预警”,不是简单分类
这套系统不是做一个学术Demo,而是要回答几个实际问题:某个田块现在有没有害虫?数量大概是多少?比昨天是增还是减?所以模型不能只输出“这张图里有飞虱”,得给出位置框、类别和置信度,让后端可以按时间维度去累计和预警。
这也是为什么传统图像处理方法在这里走不通。我们用颜色阈值、边缘检测、HOG加SVM都试过,一到自然光下全部失效:害虫颜色和水稻茎叶太接近,阴影、水珠、泥土都会变成误检来源。深度学习目标检测,尤其是YOLO这类实时模型,成为实际可用的突破口。
2. 为什么是YOLOv9:PGI和GELAN不是噱头
2.1 从YOLOv8s换到YOLOv9c的动机
最早我们用YOLOv8s做了一版,跑起来很快,褐飞虱的漏检率却不理想。后来对比了YOLOv9的开源实现,在同样输入分辨率下,YOLOv9c对小目标的召回有明显改善。注意,这不是说YOLOv9全面碾压YOLOv8,而是对我们这类“目标小、背景杂、密度高”的场景,它的两个核心设计刚好打在痛点上。
2.2 PGI可编程梯度信息,通俗理解就是“备用输油管”
YOLOv9论文里提出的PGI(Programmable Gradient Information),核心解决的是深层网络训练时的信息丢失问题。神经网络反向传播时,梯度要一层一层往回传,网络越深,浅层特征能拿到的有效梯度就越少。小目标的信息本来就弱,传到浅层时可能已经没剩多少,模型自然学不好。
PGI的做法是引入一个辅助的可逆分支,把完整的梯度信息保留下来,再补给主分支。辅助分支只在训练时用,推理时不参与,所以不增加线上计算量。用大白话讲,主分支是个上课记笔记经常记不全的学生,PGI是那个用录音笔把老师所有话都录下来、课后再把重点讲给主分支听的同学。
这个机制对水稻害虫特别重要。因为褐飞虱出现在画面里的特征大多集中在浅层细节上,比如虫体轮廓、触角、翅膀纹理,这些细节能不能在训练时被强化,直接决定最终检测效果。
2.3 GELAN结构:把参数花在刀刃上
GELAN是YOLOv9里采用的网络结构,本质上是把跨层连接和高效计算模块组合起来,在增加网络表达能力的同时控制计算量。相比之前常见的CSP结构,GELAN在相同参数量下能拿到更好的精度,对部署也友好。
我们当时的选型对比如下:
| 模型 | 相对体量 | 特点 | 我们的选择 |
|---|---|---|---|
| YOLOv9t | 轻量 | 适合低算力设备 | 备用方案 |
| YOLOv9c | 中等 | 精度和速度折中最好 | 主力模型 |
| YOLOv9e | 大模型 | 精度上限高但部署成本大 | 仅作对比 |
最后训练主力放在YOLOv9c上,原因很简单:在边缘设备上要跑到实时帧率,不能只盯着精度。YOLOv9e在测试集上AP高一些,但换到低功耗板卡后速度掉得厉害,现场用不起来。
3. 数据集的命脉:采集、清洗和标注决定模型上限
3.1 田间图像怎么采集,直接决定系统能不能用
很多项目输在第一步:拿公开数据集或者网上图片凑数,训练出来一到田里就废。我们的做法是在试验田里定点采集,覆盖三个关键维度:角度、距离、光照。
角度上要有俯拍、斜拍和贴近稻丛的特写。距离上至少包含两个尺度:一种是监控摄像头视角的远景(害虫只占几十像素),一种是用长焦镜头贴近后的近景(害虫占上百像素)。光照上则分五个时间段:清晨露水期、上午、正午、傍晚、夜间补光。实测下来,正午高温时害虫会往稻丛底部躲,图片里露头的很少;傍晚和夜间才是活动高峰,所以夜间数据反而要重点采集。
如果你只是拿手机去田里随手拍,大概率会得到一堆模糊、过曝、角度单一的照片。我们的建议是:固定好采集设备,比如把相机架在三脚架上,对准稻丛基部,每天定时拍一段视频,再从视频中抽帧。这样获得的图片背景多样性足够,害虫姿态也更真实。
3.2 数据清洗:别让脏数据浪费训练时间
采集完原始素材后,不能直接送去标注。我们先用脚本做了两轮筛选:
- 拉普拉斯方差低于阈值的模糊帧直接丢弃;
- 画面中明显没有稻株或田块的空镜头丢掉;
- 连续视频里几乎一样的相邻帧,按时间间隔抽帧,避免重复样本过多。
这些处理虽然简单,但能避免模型在重复的背景上过拟合。最后我们保留了大约三万多张有效图像,其中大约六成含有目标。
3.3 标注规则:对密集小目标要提前定好规矩
标注这一步是整个项目里最花时间、也最决定上限的部分。我们用的是开源标注工具,类别名严格按配置文件来:
names: 0: brown-planthopper 1: white-backed-planthopper 2: rice-leaf-roller 3: striped-stem-borer对普通目标,标注原则是框紧贴虫体边缘,不为了迁就图像分辨率而故意放大。对褐飞虱这类密集目标,我们定了几条硬性规定:
- 遮挡超过一半、人眼无法判断边界的个体不标;
- 高密度重叠时,能拆分的尽量拆分,实在分不清的整簇只标一个框,并在后续检查中去掉这类样本;
- 宁可漏标,也不要标一个包含“半只虫加半片叶子”的框。
有人会问:漏标不会影响训练吗?实际上,模棱两可的标注对模型的干扰远大于漏标。让模型学到一个边界混乱的正样本,它会在推理时给出大量置信度虚高的错误框。
3.4 数据增强:针对小目标做“定向喂养”
YOLO训练管线里本来就有马赛克增强、HSV扰动、随机翻转,这些都要开。但对褐飞虱这种小目标,常规增强作用有限,最有效的是小目标复制粘贴:从近景图里把虫体切出来,经过随机缩放和旋转,贴到其他稻田背景图上。这样等于变相增加了小目标在训练中的出现次数。
一个小提醒:复制粘贴时要注意尺度。把一只三毫米的虫放大成三十毫米再贴上去,模型学到的还是错误的尺度概念。我们严格控制粘贴后的虫体尺寸和原图匹配,宁可贴小一点。
4. 训练复盘:参数、观测和试错记录
4.1 环境与训练命令
训练用的机器就是一块24G显存的GPU,PyTorch 2.x,CUDA 11.8,YOLOv9开源代码 clone 下来后按说明装依赖。数据集的配置长这样:
# rice_pest.yaml path: ./rice_pest_dataset train: images/train val: images/val names: 0: brown-planthopper 1: white-backed-planthopper 2: rice-leaf-roller 3: striped-stem-borer训练命令大致是:
python train.py \ --data rice_pest.yaml \ --cfg models/detect/yolov9-c.yaml \ --weights yolov9-c.pt \ --batch-size 16 \ --img-size 640 \ --epochs 300 \ --device 0注意,尽可能用在COCO上预训练过的yolov9-c.pt作为起点。随机初始化在小目标数据集上训练,前期收敛会慢很多。
4.2 输入分辨率的取舍
我们一开始图省事,训练时用--img-size 512,速度快了,但褐飞虱的AP直接崩了。原因很清楚:原本只有二三十像素的虫体,再缩放到512分辨率,连人眼都难辨认。后来老老实实改回640训练,验证时用1280再测一遍,小目标召回明显上涨。
如果你显存不够,优先减batch,不要减分辨率。褐飞虱检测这种任务,输入分辨率是比batch size更敏感的因素。
4.3 训练过程中的观察点
整个训练大约跑了300轮,有几个值得记录的观察:
- 前30轮mAP涨得很慢,尤其褐飞虱类别一直趴着不动,不要慌,预热阶段还没结束;
- 到150轮以后,其他类别的AP基本稳定,褐飞虱还在缓慢上升,说明小目标类别需要更长的训练周期;
- 我们一度在200轮后出现验证AP震荡,训练loss却一直降的典型过拟合信号。
针对过拟合,做法是增强里加上了随机擦除,并把余弦退火调度打开,收敛稳定了很多。
4.4 类别不均衡的补救
数据集里褐飞虱实例最多,占七成以上;二化螟最少,可能只有几百个实例。最初的模型在二化螟上AP不到0.5。
我们做了两件事:一是给少样本类别的损失加权,让模型“更在意”这些稀有类;二是对二化螟的样本做适度重复采样。这里要克制,不要无限制复制少样本图片,否则模型会把那几张图背下来,验证集上看不出来,到实际场景就露馅。我们最终每个少样本类别只做三倍以内的重复。
5. 评估指标拆开看:mAP漂亮不等于现场好用
5.1 别迷信单一mAP,要盯住召回
mAP50算是行业惯例,但我们自己评估时,更关注两个指标:褐飞虱类别的AP50,以及置信度调低后的召回率。原因前面说过,这套系统用于早期预警,漏一头迁入的褐飞虱,可能比误报十次更严重。宁可让模型多报,再由后端按“连续多帧报警”来过滤。
我们用验证集跑了F1曲线,按F1最大值选的置信度阈值,实际部署时又调低了一点。这个细节很多人忽略:训练代码里默认的置信度阈值通常是为了刷漂亮指标,不是为你现场业务设计的。
5.2 我们测试集上的实测数据
以下数据仅代表我们的测试集,不同场地、不同数据集会有明显差异,但可以大致感受一下:
| 类别 | AP50 | 主要错检/漏检原因 |
|---|---|---|
| brown-planthopper | 0.84 | 若虫颜色浅,贴在稻茎上难以辨认 |
| white-backed planthopper | 0.81 | 体型小,远景下特征不足 |
| rice leaf roller | 0.76 | 翅面花纹被压缩后与叶片纹理混淆 |
| striped stem borer | 0.71 | 样本少,夜间活动数据不够 |
总体mAP50在0.85上下。说实话,单看这个数字还行,但在真实视频里依然会见到漏检,这正是为什么不能只看汇总指标。
5.3 坏例分析带来的调整
我们把错检和漏检图像分批拉出来看,发现两个规律:
- 褐飞虱漏检集中在中午强光下,稻叶反光导致虫体轮廓消失;
- 误检集中在背景里有水珠、稻粒和蜘蛛网上。
针对反光,增强时加重了亮度扰动和曝光扰动;针对误检,部署时在后端加了ROI区域排除,稻田画面里只有稻丛区域参与检测,田埂和天空直接屏蔽。这一改动让每小时误报数下降了一个数量级。
6. 部署到田间的最后一步:模型压缩与边缘端推理
6.1 边缘设备选型
模型最终要放在田间,不可能拖一台服务器过去。我们的硬件方案是一块低功耗的Orin架构边缘板卡,整体功耗控制在25W以内,配普通监控摄像头和防水箱。摄像头装在稻丛上方约半米处,斜向下45度对准稻株基部,这样既能拍到褐飞虱出没的位置,又避免直视天空导致过曝。
如果你只是做验证,树莓派加摄像头也能跑起来,但帧率会很难看。褐飞虱检测需要至少15FPS才能实时预警,建议还是用带CUDA支持的边缘设备。
6.2 从PyTorch到ONNX再到TensorRT
导出步骤不复杂,命令大致是:
python export.py --weights best.pt --include onnx --img-size 640导出ONNX后,再用TensorRT工具链构建推理引擎。我们对比了几种运行方式的实测参考值:
| 运行方式 | 帧率参考 | 相对mAP50损失 |
|---|---|---|
| 服务器GPU,PyTorch FP32 | 约100+ | 基准 |
| 边缘板卡,PyTorch FP32 | 约10 | 几乎为0 |
| 边缘板卡,ONNX FP32 | 约14 | 几乎为0 |
| 边缘板卡,TensorRT FP16 | 约30 | 几乎为0 |
| 边缘板卡,TensorRT INT8 | 约45 | 约1%以内 |
虽然TensorRT INT8帧率最高,我们还是选了FP16。原因很简单:INT8对褐飞虱这种弱纹理小目标的精度损失不稳定,省出来的算力换来的收益,抵不过误检漏检率上升带来的现场投诉。如果你在部署时需要追求极致性能,建议先跑一遍自己的数据集对比,不要照搬别人的经验。
6.3 部署后的运行机制
模型只是系统的一部分。我们把推理结果送入一个简单的计数模块:按帧累计每个类别框的数量,计算单位时间内出现的目标次数,超过阈值就推送预警。由于单帧会有抖动误检,还加了帧间投票,连续三帧同一区域出现目标才确认报警。
这个机制非常土,但非常有效,把模型的独立性误检大幅度滤掉了。后来甚至发现,不用改模型,光靠这个后端逻辑,现场误报率就能从每小时多次降到每天一两次。
7. 回看整个项目:真正起作用的几个经验
7.1 数据比模型更值钱,尤其阴雨天数据
项目跑通以后,我们回头复盘,发现最大的一次精度提升不是来自换模型,而是来自补充了一批阴天和傍晚光照的数据。当时只加了大约三千张图,褐飞虱类别AP直接涨了约4个点。原因是傍晚正是褐飞虱取食活动高峰期,而原始数据这部分严重不足。
7.2 输出虫情指数,别让模型直接下防治结论
检测系统的职责是“看到什么”,不是“该怎么办”。我们把模型输出汇总成中长期的虫情指数,再交给植保人员结合气象、作物生育期做判断。千万不要在界面上写“建议立即用药”这种结论,模型没有能力理解田间综合因素,贸然给结论只会让系统失去信任。
7.3 建立误检漏检回流机制,模型要持续迭代
部署不是终点。我们每周抽一段现场视频,把模型预测结果和人工复核结果做比对,挑出错例补充到训练集里重训。三个版本迭代下来,褐飞虱和稻纵卷叶螟的稳定性明显提升。这个“数据回流”过程比调任何超参数都重要。
7.4 警惕物理极限:画面里只有几个像素时,什么模型都救不了
最后说一个很直白的体会:有很多用户以为把模型换大、把分辨率调高,就能把所有虫都检测出来。但褐飞虱远景下只有六到八个像素时,它已经不再是机器学习能解决的问题,而是物理成像问题。现场要保证检测效果,要么摄像头离稻丛更近,要么用特写镜头,要么配置灯光诱集装置把虫子引到固定区域再拍。设备机位设计到位,后面所有环节都会轻松很多。
如果让我重做一遍,我会先把采集设备的机位想清楚,而不是先把模型跑到50FPS。画面里的虫子只有八个像素时,任何算法都救不回来;把这个问题想透了,YOLOv9也好,其他模型也好,才能真正在稻田里稳定工作。