1. 项目概述与目标场景
这款基于YOLOv9的水稻害虫检测系统,是我在农业智能化方向上完整跑通的一个实战项目,核心检测对象是水稻种植过程中最难防的一类小型刺吸式害虫,也就是褐飞虱(brown-planthopper)。整套系统的检测标签定义为包含['brown-planthopper', 'rice-leaf-folder', 'yellow-rice-borer', 'green-leafhopper']等类别的组合,但工程上优先对褐飞虱做高精度识别,因为它的爆发性和隐蔽性最强。系统基于YOLOv9目标检测框架搭建,覆盖图像采集、数据标注、模型训练、部署推理全流程,能够在田间实拍照片、无人机俯拍画面和监控视频流中自动圈出害虫个体,输出类别、置信度和定位坐标。
这个系统解决的问题非常具体:传统人工查虫需要植保人员蹲在田埂上,扒开水稻中下部茎秆逐株检查,劳动强度大、效率低,而且褐飞虱成虫体长只有3到5毫米,肉眼识别全靠经验,漏检率很高。我在整个项目周期里最深的感受是,做农业视觉检测和做通用物体检测完全是两码事,害虫目标小、背景复杂、光照变化剧烈,每一个环节都可能出现常规教程中不会提到的坑。这篇文章就把我从数据集制作到模型训练再到部署推理的完整过程拆开来讲,中间包含大量踩坑记录和调参经验,特别适合正在用YOLOv9做农业目标检测、准备开发病虫害识别系统,或者想复现一套可落地水稻害虫检测方案的同学参考。
1.1 为什么选YOLOv9而不是其他版本
YOLOv9是YOLO系列里一个标志性版本,它提出了可编程梯度信息(PGI)和广义高效层聚合网络(GELAN)两大核心结构。简单解释一下:传统深层网络在反向传播时,梯度信息逐层传递会衰减,浅层网络学不到足够的特征,导致小目标漏检;PGI通过辅助可逆分支全程保留梯度信息,让网络前几层也能获得充足监督信号,这对褐飞虱这种小目标非常关键。GELAN则是把跨层特征融合和轻量化设计结合,在同等算力下获得更好的特征表达能力。
在实际对比中,我用同样的数据和训练配置分别跑过YOLOv8和YOLOv9,最终YOLOv9在褐飞虱这个小类别上的mAP50比YOLOv8高出大约4到6个百分点,尤其对密集分布、体重叠的虫群场景,置信度整体更稳。作为参考,如果硬件条件受限或推理延迟要求很高,YOLOv8的轻量版也是备选,但我最终坚持使用YOLOv9,因为水稻害虫检测的核心矛盾是“虫小且杂”,精度优先级高于推理速度,部署环境又是普通工控机即可满足,算力压力不大。
1.2 褐飞虱的分类难点
褐飞虱的检测难点主要在三个方面:第一,虫体颜色与水稻茎秆颜色相近,都是褐黄色系,深度学习模型容易把背景纹理误判为害虫;第二,虫体小且常聚集在水稻基部,需要高分辨率输入和优秀的特征提取能力;第三,田间拍摄经常出现叶片遮挡、逆光阴影和露水反光,图像质量参差不齐,对数据集的多样性和模型的泛化能力要求极高。
为了解决这些难点,我在数据层面不是简单堆数量,而是有意识地平衡了不同光照、不同生育期水稻、不同拍摄距离下的样本比例,并且专门补充了一批褐飞虱聚集爆发的密集场景图。后文会详细讲数据清洗和标注策略,这里是先建立整体认知:褐飞虱检测的本质,是在强干扰背景下对大量小目标做精确分类和定位,所有技术选型都要围绕这个核心展开。
2. 系统整体架构与方案选型
系统的整体架构分为四层:数据采集层、标注存储层、模型训练层和应用推理层。数据采集层负责从田间获取图像和视频,来源包括植保人员手机拍摄、固定监控摄像头抓拍、无人机巡航拍摄以及公开农业数据集筛选;标注存储层使用LabelImg等工具生成YOLO格式的TXT标注文件,并统一管理图像与标签的对应关系;模型训练层基于Ultralytics框架完成YOLOv9的训练、验证和导出;应用推理层则通过Python脚本和Flask服务对外提供检测能力,支持单张图片检测和视频流检测。
选型上我没有选择复杂的分布式训练架构,而是采用单机单卡训练方案,因为这类型项目的数据规模通常在几千到几万张,单卡RTX系列显卡已经能完成百轮级别的训练,没有必要引入分布式成本。推理侧则更看重稳定性,所以我保留了一份ONNX导出模型作为备用,防止PyTorch环境在目标部署机上出问题时无路可退。
2.1 检测流程设计
检测流程按以下步骤组织:输入图像首先经过预处理,包括尺寸缩放、归一化和色彩空间转换;随后送入YOLOv9主干网络提取特征,经颈部网络聚合多尺度信息,最后在检测头输出预测框与类别概率。通过置信度阈值和非极大值抑制(NMS)过滤冗余检测框,输出最终结果。
这里有一个容易忽视的细节:褐飞虱常与稻飞虱若虫混发,养殖过程中叶片上也可能沾有泥点,这些干扰物在特征空间上距离害虫很近。我在流程中额外增加了一个置信度分档逻辑,对于置信度在0.3到0.5之间的检测框不直接丢弃,而是标记为“待复核”,后续操作人员可以二次确认。这个设计虽然增加了一点人工成本,但显著降低了漏报风险,在农业场景中漏报的代价远高于虚警。
2.2 硬件与软件环境
训练环境配置为双路CPU加单张24GB显存的GPU,系统使用Ubuntu,CUDA与PyTorch版本需匹配。推理部署机采用常见的工控机,处理器为低功耗四核,内存16GB,无独立GPU,依靠ONNX Runtime的CPU推理也能跑出单张图片1到2秒的延迟,满足田间离线检测需求。
软件依赖上,我使用Python 3.9作为基础环境,通过virtualenv管理项目依赖,避免污染系统环境。核心依赖包括ultralytics、torch、torchvision、opencv-python、numpy、labelimg、onnx、onnxruntime和flask。这里提醒一句,YOLOv9需要较高版本的ultralytics支持,安装时不要图省事直接装默认版本,务必查看官方文档确认对应关系。
3. 数据集构建与标注要点
可以说,整个项目的成败有一半取决于数据集质量。水稻害虫检测不像人脸检测那样有大量现成公开数据集,褐飞虱的标注数据尤其稀缺。我最初从公开渠道收集到约3000张水稻害虫图片,其中干净可用的褐飞虱样本只有不到800张,远远不够训练一个稳定模型。
为了补足数据,我在早晚两个时段去实验田拍摄,早晚的光线比较柔和,叶片反光少,同时褐飞虱活动更频繁,容易拍到清晰个体。拍摄时使用手机微距镜头和单反长焦镜头各拍一批,手机负责近距离特写,长焦负责模拟监控视角的中远距离画面。最终三个月积累下6000多张有效图片,经过筛选和清洗后,保留了4000余张作为训练基线,同时从其他农业公开数据集中挑选了2000张包含飞虱、叶蝉等近似虫态的图片作为辅助训练数据。
3.1 数据采集的多样性策略
数据多样性直接决定模型的泛化能力,我在采集时有意识地覆盖了以下维度:水稻品种方面,包括籼稻和粳稻;生育期方面,包括分蘖期、拔节期、抽穗期和灌浆期;拍摄距离方面,包含10厘米级特写、30到50厘米中景和1米以上远景;光照条件方面,包含晴天直射、阴天漫射、早晚逆光和人工补光。每个维度至少覆盖50到100张样本,避免模型偏向某一类场景。
另一个关键策略是密集程度分级。褐飞虱在水稻上的分布极不均匀,有时一丛水稻只有零星几只,有时整个茎秆密密麻麻布满虫体。我所标注的数据按每丛水稻上的虫口数量分为低密度(1到5只)、中密度(6到20只)和高密度(20只以上)三档,各档都保持足够样本量。这样训练出来的模型不会只擅长识别孤立个体,也不会对拥挤场景直接失效。
3.2 标注工具与标签规范
标注工具使用LabelImg,它操作简单,支持Pascal VOC和YOLO两种导出格式,我直接使用YOLO格式,省去后续格式转换。标注时遵循固定的规范:褐飞虱只标注成虫,若虫不标;被遮挡超过一半的个体不标;画面模糊到无法确认是否为褐飞虱的个体不标;同一图像内同一虫体出现两次时,只标更清晰的那一次。
框选边界的标准是紧贴虫体轮廓,比常见的目标检测标注稍微收紧一点。因为褐飞虱通常聚集在一起,如果框选得太大,会把相邻个体的特征混入训练样本中,导致模型无法区分边界,反而降低定位精度。我反复校验了对边界标注的理解,这里可以说一个例子:如果一只褐飞虱趴在叶鞘上,翅膀收拢,框应该刚好包住头胸腹三部分,而不包含叶鞘的背景像素。
3.3 数据清洗与类别整理
初始标注完成后,我执行了严格的数据清洗:删除标注框小于15像素的样本、删除重复图片、校正明显标错的类别、剔除分辨率低于800像素的模糊图片。清洗后最终得到约5500张可用图片,共有约12800个有效标注框,其中褐飞虱标注框约6400个,占比一半。
类别整理上,系统最终包含四个检测类别:褐飞虱、稻纵卷叶螟、二化螟和绿叶蝉。这里我特别说明一个容易踩的坑:不要把所有水稻害虫都堆到一个类别里,因为不同害虫的防治策略完全不同。褐飞虱需要用吡蚜酮类药剂,稻纵卷叶螟需要卵寄生蜂配合生物农药,混在一起会让模型输出结果对植保决策毫无帮助。类别设计要跟着应用目标走,而不是跟着标注难度走。
4. YOLOv9模型训练过程
训练环节是最容易出现“跑不起来”的地方,很多新手在这一步卡住一整天。先讲环境配置,再讲训练参数,最后讲我踩过的坑和调参经历。
4.1 环境配置与依赖安装
首先创建虚拟环境:
python -m venv venv source venv/bin/activate pip install --upgrade pip pip install ultralytics torch torchvision安装完成后验证CUDA是否可用:
import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果输出True和正确的显卡型号,说明GPU环境正常。这里我遇到过两个典型问题:一是镜像源下载过慢,使用国内镜像源后问题解决;二是PyTorch版本与CUDA版本不匹配导致无法调用GPU,排查后通过卸载重装对应版本的torch解决。建议安装前先确认显卡驱动支持的CUDA版本,再选择对应的PyTorch版本。
4.2 数据配置与标签转换
数据配置需要准备一个YAML文件,Ultralytics框架会读取该文件确定数据集路径和类别信息。以下是我使用的数据集配置,文件名命名为rice_pest.yaml:
path: ./rice_pest_dataset train: images/train val: images/val nc: 4 names: 0: brown-planthopper 1: rice-leaf-folder 2: yellow-rice-borer 3: green-leafhopper对应的数据集目录结构如下:
rice_pest_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/每个图片对应一个相同文件名的TXT文件,内容格式为“类别ID 中心点x 中心点y 宽度 高度”,所有数值都归一化到0到1之间。如果你的原始标注是Pascal VOC格式的XML文件,需要先转换成YOLO格式,可以利用脚本自动完成转换。
4.3 训练参数与调参经验
训练入口用Ultralytics提供的Python API写训练脚本,比命令行方式更灵活,方便记录和恢复训练进度。下面是我使用的训练脚本:
from ultralytics import YOLO model = YOLO('yolov9c.yaml') results = model.train( data='rice_pest.yaml', epochs=200, imgsz=640, batch=16, lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, warmup_momentum=0.8, box=7.5, cls=0.5, dfl=1.5, patience=30, workers=8, device=0, project='rice_pest_train', name='yolov9c_ricepest', augment=True, val=True, save_period=10 )几个关键参数的解释:imgsz选择640,考虑到褐飞虱是小目标,图像尺寸太小会导致特征丢失,但太大又增加显存占用和训练时间,640是目前平衡点;batch选择16,这是在24GB显存下能够稳定运行的上限,如果显存不足,需要减小batch而不是强行放大imgsz;lr0是初始学习率,训练数据量不大时0.01比较稳,太低会收敛缓慢,太高会振荡。
我训练过程中另外设置了几项数据增强参数,包括随机翻转、色彩抖动、Mosaic增强等,这些在Ultralytics中默认开启。对农业图像来说,色彩抖动尤其有用,因为田间光照变化大,适当增强色彩扰动可以提升模型对不同光照条件的适应能力,我在保持默认augment的基础上关闭了复制粘贴增强,避免小目标重复采样带来的过拟合问题。
4.4 训练过程中的监控与调整
训练时我借助TensorBoard实时监控loss曲线。一个正常的训练过程,box_loss、cls_loss和dfl_loss都应当平稳下降,验证集的mAP50逐步上升。如果发现val_loss在某一轮后不降反升,而train_loss继续下降,说明模型开始过拟合。此时早停机制会自动触发,patience设置为30轮比较合适,在训练集不太大的情况下可以接受。
针对褐飞虱小目标较多的问题,我在后30轮训练里进一步降低了学习率,让模型在小步长下继续微调。这个做法相当于二次精调,通常在最后一个阶段能给小目标类别提升2到3个mAP点数。训练完成后,最佳模型权重保存在project/name/weights/best.pt路径下,我习惯再跑一次验证集评估并保存多尺度评估结果,方便后续对比。
5. 部署与推理实现
训练完成的模型如果不落地部署,始终停留在Demo阶段。我这里的部署目标是在无GPU的工控机上跑推理,兼容图片和视频两种输入,同时提供一个简单的HTTP接口给前端Web页面调用。
5.1 模型导出与格式选择
首先将PyTorch模型导出为ONNX格式,这样可以脱离PyTorch环境运行,也能在CPU上获得更稳定的性能:
from ultralytics import YOLO model = YOLO('best.pt') model.export(format='onnx', imgsz=640, opset=12)导出后得到best.onnx文件,我另外使用ONNX Simplifier做了一次结构简化,减少冗余节点,提升推理速度。对比实测,简化后的ONNX模型单张CPU推理时间从2.1秒缩短到1.4秒左右,效果明显。
如果不方便安装ONNX Runtime,也可以直接保留PyTorch模型进行推理,但需要确认部署机上有正确的PyTorch环境,且依赖版本一致,否则可能出现加载失败或算子不支持的问题。使用ONNX的最大优势是环境依赖干净,部署机只需要安装onnxruntime和opencv-python两个包即可。
5.2 单张图片检测脚本
推理脚本如下:
import cv2 import onnxruntime as ort import numpy as np session = ort.InferenceSession('best.onnx') input_name = session.get_inputs()[0].name output_name = session.get_outputs()[0].name def preprocess(img): img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img = img.astype(np.float32) / 255.0 img = np.transpose(img, (2, 0, 1)) img = np.expand_dims(img, axis=0) return img img = cv2.imread('field_001.jpg') input_tensor = preprocess(img) outputs = session.run([output_name], {input_name: input_tensor})这里输出的原始张量包含预测框、置信度和类别概率,需要进一步解析,解析流程与YOLOv5的通用后处理类似,包括置信度过滤和NMS去重。我更推荐用Ultralytics自带的推理接口做快速验证,但生产环境中的后处理逻辑我会自己实现,便于按项目需求调整过滤阈值和输出格式。
5.3 视频流实时检测
视频流检测是农业物联网中更常见的需求,通过在田间部署监控摄像头,定时抓帧执行检测。我用OpenCV读取视频流,每隔一帧执行一次检测,间隔时间取决于检测单帧耗时。在CPU推理环境下,单帧1.4秒意味着无法执行连续视频流的实时检测,因此我选择每隔5秒抓取一帧,保证检测覆盖的同时留出推理时间。
检测结果按时间戳存储为JSON记录,内容包括检测类别、置信度、坐标和抓帧时间。这样积累的数据可以进一步分析虫口密度变化趋势,为精准喷药提供参考。这个环节我建议同时保存原始帧的缩略图,方便人工核对,避免模型误检造成的数据污染。
5.4 部署性能优化
CPU推理场景下,性能瓶颈主要在卷积计算,优化空间有限但仍有几个技巧。第一,缩小输入尺寸并搭配动态尺寸推理,比如将长边限制在640、短边等比缩放,减少无效填充带来的计算浪费;第二,使用半精度推理,但注意CPU推理的半精度加速取决于芯片指令集,不是所有部署机都支持;第三,将检测任务拆分为多个线程,主线程抓帧,推理线程并行计算,降低帧间隔波动。
另一个有效优化是模型蒸馏,用原始YOLOv9模型的输出作为伪标签,训练一个更小规模的学生模型。在我实测中,轻量级的学生模型在CPU上的单帧推理时间降到0.6秒,mAP50下降不超过3个百分点,性价比极高。但这需要额外的工作量和调试周期,如果你对部署机性能没信心,可以重点考虑这条路线。
6. 常见问题与排查技巧实录
这部分整理我在整个项目过程中遇到的最典型的几个问题,每个问题都附上了排查思路和解决方案,直接可以当速查表用。
6.1 训练不收敛或loss出现NaN
训练一开始loss就出现NaN,通常由以下原因造成:数据集中存在异常标注(比如某一张图的标签坐标超出图像边界)、学习率设置过大、batch中包含空图像。排查步骤是先检查标注数据,用脚本绘制所有锚框并人眼抽查,确认坐标值是否在0到1之间。如果标注正常,再调低学习率到0.001重新启动训练。
我实际遇到的问题就是某一张手机拍摄的图片包含EXIF旋转信息,OpenCV读取时没有自动校正方向,导致标注框与图像方向不一致,模型认为每个标框都匹配错误。解决办法是在预处理阶段统一按照EXIF方向旋转图像,并在标注前完成旋转校正。这个坑很容易被忽视,因为只有少数图片触发,表现为训练loss整体下降但某个类别mAP始终偏低。
6.2 褐飞虱小目标检测效果差
小目标漏检集中表现为大量单只褐飞虱未被检出,或者检出的置信度较低。我从三个角度解决:一是增大输入分辨率到960,虽然训练速度变慢,但小目标的特征保留明显更充分,实测mAP50提升3到5个百分点;二是增强微小目标的采样概率,在数据增强参数中提高Mosaic的参与比例,让同一场景里不同尺度目标更多样化;三是使用多尺度训练,Ultralytics支持在训练时随机使用不同尺寸的输入,测试时固定为640,这个策略对多尺度目标都有帮助。
在参数调整之外,我更建议从数据层面补充大量近距离微距照片。因为模型对小目标的识别上限,本质上由训练数据中的目标尺度分布决定。如果训练样本里大多数褐飞虱框都在50像素以上,模型自然很难捡回只有30像素的小目标。我后期增加了500多张微距特写后,低置信度漏检情况明显减少。
6.3 检测结果虚警率高
虚警的典型表现是把稻叶上的泥斑、水滴反光或其他小型昆虫误判为褐飞虱。原因有两类:训练数据中背景干扰样本不足,模型没有学到足够的“负样本”信息;或者置信度阈值设置过低。我首先将默认置信度阈值从0.25提高到0.4,虚警率下降一半,但漏检略有增加。随后我调整了训练数据比例,刻意加入更多无褐飞虱但有泥斑、水滴和蜘蛛网的水稻图片,让模型在训练阶段就见过这些干扰物,最终虚警率回落到可接受范围。
如果你在部署后发现特定区域的虚警率始终很高,可以用错误检测日志反查图片,把误检样本收集起来重新加入训练集,形成持续迭代的闭环。这个方法听起来简单,却是提升农业检测系统可用性最有效的手段之一。
6.4 CPU推理速度慢
无GPU的部署环境推理速度慢是普遍现象。我用过的最有效方案是轻量网络蒸馏,其次是ONNX动态尺寸和半精度推理。如果两者都不想引入,也可以考虑调整部署方案,比如增加一台带有入门级GPU的边缘计算设备,成本控制在3000元以内,推理时间能从1.4秒降到100毫秒左右,对于需要实时响应的场景基本是决策因素。
部署前建议务必确认CPU是否支持相应指令集,部分老工控机不支持新版AVX指令,ONNX Runtime会自动退回到低效实现,性能损失严重。这个查一下型号和指令集资料就能确认,属于比较容易忽略的硬件兼容性问题。我遇到过一次工控机CPU不支持FMA指令,推理时间翻倍,最终通过换用Runtime版本和关闭部分算子优化缓解。
7. 实操心得与后续扩展方向
项目从数据采集到最终部署,前后大约花了四个月时间,最终验证集上的褐飞虱类别mAP50达到0.776,整体模型mAP50为0.802,在实验田的实拍图片上基本可以做到每10张抽检图只有1到2张出现漏检或误检。这个指标距离完美还很远,但它已经从“人工查虫靠眼力”跃升到“机器辅助查虫”的水平,植保人员的效率提升了数倍。
我个人在实际操作中有两点体会比较深:第一,农业检测项目的时间分配要倒过来,很多人把精力花在训练调参上,实际上数据集构建与清洗在项目中至少占70%以上的时间。没有一张高质量的标注图,再先进的YOLOv9也难以发挥作用。第二,不要盲目追求检测精度的一个最优数值,要把系统放到真实场景里评估。我的模型在室内标准光线下表现很好,一上田间强光环境虚警率就会上升,后来通过加入真实环境数据重新迭代才稳定下来。这类问题只有现场测试才能暴露。
后续扩展方向其实很清晰:一是持续迭代检测模型,增加更多害虫类别和不同地理区域的样本,逐步建立覆盖全生育期的水稻害虫知识库;二是在检测结果基础上结合气象和虫情灯诱捕数据,建立虫害爆发趋势预测模型,把系统从“看见害虫”升级为“预见虫害”;三是开发轻量级移动端App,让植保人员直接用手机拍照上传检测,降低使用门槛。我目前正在做第三个方向,把ONNX模型通过NCNN框架移植到手机端,初步测试单张检测延迟在300到500毫秒之间,基本可以接受。
最后再分享一个小技巧:做农业检测项目时,除了把模型指标记录下来,记得同步记录每张测试图片的拍摄时间、天气、水稻生育期和虫口密度信息。这些元数据在后续分析误检原因、评估模型短板和构建预测模型时非常值钱,千万不要偷懒省略。我到后期复盘时才发现很多早期测试图片的基础信息已经丢失,想要追溯某个误检样本的环境因素却无从下手,只能重新补拍,教训相当深刻。如果你现在正准备启动类似项目,强烈建议从第一天就建立完整的采样登记表。