简介:面向包含5229张图像的水稻害虫检测任务,这份数据集主要涵盖褐飞虱、绿叶蝉、叶夹、稻蝽、蛀干虫、轮生蛆等常见害虫类别,适用于农业植保、病虫害智能识别与算法模型训练等场景,帮助研发人员快速获取规范化的害虫标注数据。压缩包共2000个文件,全部为VOC格式的XML标注文件,包体约109.96MB,每个XML文件对应图像的类别标签与目标边界框坐标信息,可直接配合图片用于YOLO、SSD、Faster R-CNN等主流检测框架训练与评估。目前已有1221人学习下载,适合农业AI开发者、科研人员及目标检测初学者参考使用。标注按VOC标准组织,使用时可结合图像目录灵活拆分训练集与验证集,能显著节省人工标注成本,便于后续开展迁移学习、数据增强和不同模型效果对比,是一份实用的水稻害虫检测基础资源。
1. 拿到这份5229张图的VOC数据集,先别急着开训
做农田害虫检测的人,看到“水稻害虫数据集,5229张图,可检测褐飞虱绿叶蝉叶夹稻蝽蛀干虫轮生蛆,VOC标记”这种标题,第一反应往往是“图够多、类够全,能直接喂给模型了”。但我的实际经验是:图片数量只是第一步,VOC标记这份“说明书”才是决定你后面训练顺不顺利的关键。VOC格式能直接兼容一部分检测框架,但主流的训练流程里,你得先把它转成模型真正吃得到的格式,否则后面的数据加载、类别映射、增强策略全都得返工。
这个数据集覆盖了褐飞虱、绿叶蝉、叶夹(稻纵卷叶螟)、稻蝽、蛀干虫、轮生蛆六类,田间常见的“跳的、飞的和钻秆的”害虫基本都在。对做农业检测项目的人来说,它适合当起步数据,用来跑通数据预处理、模型训练和评估的完整链路;对正在学目标检测的读者,它也是一个能验证基础能力的实战样本。
我拿到这类数据时,最深的体会是:5229张图听着不少,但真实有效信息量完全取决于标注质量和类别分布。比如一张图里只有一个十几像素的褐飞虱,那它的标注价值就比一张有清晰大目标的高清图低很多。文章后面会专门讲这个问题。下面我把从解压、格式转换到训练、排查、部署验证的完整流程拆开,照着做就能把这套数据真正用起来。
2. 先看货再动手:六类害虫的VOC标注结构
2.1 六类害虫先对号入座,再谈怎么检测
要把模型训好,第一步不是跑代码,而是把每一个类别长什么样、在图片里占多大、容易和哪个类混淆摸清楚。这是我拿到任何数据集都会先做的事,不是看文档,而是随机抽几百张图用标注框叠在原图上肉眼扫一遍。下面是我按这类数据集的常见规律梳理的类别对照表:
| 标注名 | 中文俗称 | 在田间的典型表现 | 目标尺寸与难点 |
|---|---|---|---|
| brown_planthopper | 褐飞虱 | 体长3到5毫米,聚集在稻株下部叶鞘处,群居 | 极小目标,常只有十几到几十像素 |
| green_leafhopper | 绿叶蝉 | 体长4到6毫米,绿色,在叶片正面跳跃 | 小目标,颜色与叶片接近 |
| leaf_folder | 叶夹 / 稻纵卷叶螟 | 幼虫把叶片纵卷成筒状,虫体藏在卷叶内 | 框往往跨卷叶区域,形状不规则 |
| stink_bug | 稻蝽 | 绿色盾形,多在稻穗上吸食 | 中等大小,但常被稻穗遮挡 |
| stem_borer | 蛀干虫 | 幼虫钻入茎秆,蛀孔附近有虫粪 | 细长形,框容易标得不准 |
| rotary_maggot | 轮生蛆 | 危害心叶,被害状呈轮生样,幼虫隐蔽 | 小且隐蔽,漏标率偏高 |
这份表格不是从某个文档里抄来的,而是我在做农业检测项目时的通用归类方式。其中褐飞虱和绿叶蝉是整份数据里最容易出问题的两个类:它们都小、都带翅、颜色又接近,模型在低分辨率下很容易把它们混在一起。而蛀干虫和轮生蛆的标注框通常细长,如果标注时框得松,训练出来的预测框就会偏大或者偏斜。建议在动手前先按这个维度把每个类的难点列出来,后面调参时才知道问题出在哪。
2.2 VOC标注的XML结构:每个字段都会影响后面的训练
VOC格式的标注,每一张图对应一个XML文件,文件名和图片名一致。这份数据集的标注是Pascal VOC标准结构,和公开检测数据集里常见的标注格式同源。一个典型的标注文件长这样:
<annotation> <folder>rice_pests</folder> <filename>IMG_00412.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>brown_planthopper</name> <bndbox> <xmin>1420</xmin> <ymin>736</ymin> <xmax>1458</xmax> <ymax>772</ymax> </bndbox> </object> <object> <name>green_leafhopper</name> <bndbox> <xmin>320</xmin> <ymin>168</ymin> <xmax>344</xmax> <ymax>196</ymax> </bndbox> </object> </annotation>这个XML里几个字段的优先级不一定相同。size里的width和height是整个坐标归一化的基础,转换格式时所有坐标都要除以它,如果这里写的和实际图片分辨率对不上,后面所有框都会偏。object的name是类别标签,必须和类别字典严格一致,比如一个标成brown_planthopper、另一个标成Brown_planthopper,训练时就会因为类别数不一致直接报错。bndbox里的xmin、ymin、xmax、ymax是像素坐标,转换脚本要基于它们算中心点和宽高。
我的习惯是拿到数据后先写一个小脚本,把XML里的size字段和实际图片的尺寸做一次全量比对,找出两者不一致的文件,这种文件在转换阶段最容易出问题。这里的坐标越界问题,比如xmax大于width,也会在后面的转换和训练阶段暴露出来,第5章会专门展开排查方法。
2.3 目录组织与完整性检查:解压后先跑一遍脚本
数据集的zip包解压后,目录一般是images和Annotations两个文件夹并排躺着,或者每个文件夹下再按类别分组。开始转换前,先做一轮完整性检查和文件计数,这是最简单也最容易被跳过的步骤:
# 解压,注意不要在Windows上直接双击zip解压,避免中文路径和权限问题 unzip rice_pests_voc.zip -d ./rice_pests && cd ./rice_pests # 统计图片文件和XML文件数量,应该一致 echo "图片数量: $(ls ./images | wc -l)" echo "标注数量: $(ls ./Annotations | wc -l)" # 找出没有标注的图片,这些图片训练时会被跳过,也可能导致数据量虚高 for img in ./images/*.jpg; do base=$(basename "$img" .jpg) [ ! -f "./Annotations/$base.xml" ] && echo "缺标注: $base" done命令不长,但能提前暴露数据不完整的问题。比如5229张图,实际能同时找到图片和XML的可能只有5100张,剩下100多张要么是坏图,要么是空标注。如果直接拿去训练,日志里那些“missing annotation”的警告会拖慢调试速度,而且最终训练用的数据量和你以为的不一致,评估结果也就失真了。这个脚本跑完,我一般会把缺标注的图片全部移到unlabeled/目录里,不删除,因为负样本和背景样本后面可能还会用到。
提示:VOC的XML文件名必须和图片文件名完全一致,包括后缀大小写。
IMG_001.jpg配IMG_001.xml没问题,但IMG_001.JPG和img_001.xml会匹配不上,脚本里最好统一转成小写再比对。
3. 把VOC变成能直接训练的数据:转换、切分和数据增强
3.1 VOC转YOLO格式的Python脚本:一次跑完不返工
VOC格式本身是通用的,但主流检测框架里,YOLO系列用的是每张图一个txt文件、每行一条“类别 中心点x 中心点y 宽 高”的格式,坐标值已经归一化到0到1之间。我一般会写一个转换脚本,一次性把整个Annotations目录处理完,输出到labels目录,同时把类别写入一个字典文件,后续训练直接引用。
import os import xml.etree.ElementTree as ET # 类别映射:VOC里的name字符串 -> YOLO的类别ID CLASS_MAP = { "brown_planthopper": 0, "green_leafhopper": 1, "leaf_folder": 2, "stink_bug": 3, "stem_borer": 4, "rotary_maggot": 5, } annotations_dir = "./Annotations" images_dir = "./images" labels_dir = "./labels" os.makedirs(labels_dir, exist_ok=True) for xml_file in os.listdir(annotations_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(annotations_dir, xml_file)) root = tree.getroot() # 读取原始图片尺寸,坐标归一化依赖它 size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) # 顺便从XML里拿到文件名,保证输出txt和图片同名 filename = root.find("filename").text txt_name = os.path.splitext(filename)[0] + ".txt" lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAP: print(f"跳过未知类别 {name} 在 {xml_file}") continue label = CLASS_MAP[name] box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 对坐标做clip,防止越界导致训练时该样本被丢弃 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) # 归一化并转成YOLO需要的中心点+宽高格式 dw, dh = 1.0 / img_w, 1.0 / img_h cx = (xmin + xmax) / 2.0 * dw cy = (ymin + ymax) / 2.0 * dh bw = (xmax - xmin) * dw bh = (ymax - ymin) * dh lines.append(f"{label} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(labels_dir, txt_name), "w") as f: f.write("\n".join(lines)) print("转换完成, 共处理", len(os.listdir(labels_dir)), "个标注文件")脚本里有几个细节值得单独说。一是CLASS_MAP字典必须提前看一遍数据里的name值再定,不能想当然;有些数据集的类别名是中文拼音,比如bhf、lyc,和标准英文名对不上,转换脚本会直接跳过,训练时类别数就少了。二是坐标clip这一段是我吃过亏才加上的,曾有一份数据里某个框的xmax比图片宽度还大50像素,不裁掉的话那个样本在训练时会被框架直接丢掉,损失一个类别的正样本。三是输出txt文件命名必须和图片严格一一对应,YOLO的DataLoader是靠同名文件把图和标注配对在一起的。
3.2 训练集、验证集、测试集划分:固定随机种子才能复现
数据划分看起来是小事,但直接影响你后续对比实验有没有意义。常见做法是按7:2:1的比例随机分成训练集、验证集和测试集,其中测试集全流程只能碰一次,用来做最终的模型评估。我在划分时强调固定随机种子,因为换一次种子,模型性能可能波动一到两个点,届时就分不清是模型改进带来的提升还是数据划分带来的噪声。
import os import random random.seed(42) # 固定种子,确保每次运行结果一致 images = [f for f in os.listdir("./images") if f.endswith(".jpg")] random.shuffle(images) train_ratio, val_ratio = 0.7, 0.2 train_cut = int(len(images) * train_ratio) val_cut = int(len(images) * (train_ratio + val_ratio)) for split, split_files in [ ("train", images[:train_cut]), ("val", images[train_cut:val_cut]), ("test", images[val_cut:]), ]: os.makedirs(f"./datasets/{split}", exist_ok=True) # 这里只生成符号链接,不复制图片,省磁盘空间 for fname in split_files: src = os.path.realpath(os.path.join("./images", fname)) dst = os.path.join(f"./datasets/{split}", fname) if not os.path.exists(dst): os.symlink(src, dst) print(f"划分完成: train={train_cut}, val={val_cut - train_cut}, test={len(images) - val_cut}")划分时我建议做成两个独立步骤而不是一股脑复制文件:先写一个split.txt记录每个文件属于哪个集合,再按这个清单去建目录。这样后面想调整比例,不用重新跑一次完整脚本,只需要改split.txt里的映射关系。另外,按类别做分层抽样在害虫数据集里有价值,因为蛀干虫和轮生蛆的样本量可能远少于褐飞虱,如果随机打乱后验证集里这两类一只都没有,评估结果就无法反映真实表现。
3.3 数据增强:别把害虫糊没了
5229张图扣除掉切分和无效样本后,真正参与训练的图片可能在3500张左右。对一个六类目标检测任务来说,这个规模不算富裕,尤其是小目标类别。数据增强是弥补样本不足的主要手段,但增强策略要结合害虫的尺寸特点来定,不能照搬通用目标检测的设置。
我一般会关闭Mosaic这类会把图片缩放得特别狠的增强,或者把Mosaic的随机缩放范围限制在0.5到1.5倍。原因很直接:褐飞虱在原始图上可能只有20个像素,Mosaic拼接时再被缩到一半,就变成10个像素了,模型几乎不可能从这么低分辨率里学到有效特征。相比之下,随机左右翻转、HSV亮度扰动、随机裁剪这些更温和的手段,对害虫检测更友好。
# 以YOLOv8的增强参数为例,在训练yaml之外单独调节 # 我的常用配置: # hsv_h: 0.02 # 色相扰动小一点,避免绿色叶片色相产生大偏移 # hsv_s: 0.5 # 饱和度扰动,模拟早晚光照差异 # hsv_v: 0.4 # 明度扰动 # degrees: 10 # 小角度旋转,害虫姿态本身比较固定 # scale: 0.3 # 缩放限制,值太大会让小目标更小 # fliplr: 0.5 # 水平翻转 # flipud: 0.0 # 不用垂直翻转,因为田间拍摄角度基本固定这些参数如果只在视觉上凭感觉调,很容易翻车。我的建议是每次只动一两个参数,训练后看验证集mAP和PR曲线,用结果说话,而不是把一堆增强选项全部打开。
4. 训练参数与选型:让模型在5229张图上跑出可用结果
4.1 模型怎么选:YOLOv8n起步,m和s做对比
数据集规模决定了模型选型的上限。5229张图、六类目标,这个体量用不到大模型。我自己做农业检测项目时,起步通常会选YOLOv8n或YOLOv8s,原因是:小模型在数据量不足的场景下不容易过拟合,推理速度快,也方便后续部署到边缘设备。下面是一个选型对照表,按实际需求来挑:
| 模型 | 参数量 | 适合场景 | 在这个数据集上的预期 |
|---|---|---|---|
| YOLOv8n | 约3.2M | 快速原型验证、移动端部署 | mAP50能到0.6左右即可 |
| YOLOv8s | 约11.2M | 精度优先、算力足够 | 比n高2到4个点,但训练和推理更慢 |
| YOLOv8m | 约25.9M | 尝试冲更高精度 | 需要配合更强的增强策略防止过拟合 |
以上参数量是公开模型的常见量级,具体以你拉取的权重为准。我的建议是先用n把流程跑通,确认数据管线没问题,再训练s做对比。直接上m,一旦效果不好,很难判断是数据问题还是模型太大导致的过拟合。
4.2 训练参数配置:epoch、batch、imgsz怎么设
YOLO的官方命令行能覆盖大部分训练需求,但参数必须按这个数据集的实际特性调。下面是我的起步配置:
yolo train \ data=rice_pests.yaml \ model=yolov8n.pt \ epochs=150 \ batch=16 \ imgsz=640 \ patience=30 \ device=0几个关键参数为什么要这样设,展开说明一下。imgsz=640是平衡点,再小到512会丢失小目标细节,再大到960对显存要求高,而且这类害虫检测任务中绝大多数框都小于64像素,继续放大输入图收益有限。batch=16是在普通单卡上比较稳妥的值,如果显存只有8G,降到8;如果显存够大,调大batch通常能加快收敛。epochs=150配合patience=30的意思是,如果连续30个epoch验证集指标没有提升,训练提前结束,避免把验证集训过拟合。
训练前需要写一个数据集yaml,这是YOLO框架定位数据的依据:
# rice_pests.yaml path: ./datasets/rice_pests train: train val: val test: test nc: 6 names: 0: brown_planthopper 1: green_leafhopper 2: leaf_folder 3: stink_bug 4: stem_borer 5: rotary_maggot这里值得提醒的是path字段务必写绝对路径或相对当前工作目录的正确路径。很多人训练时报错“dataset not found”,不是yaml写错,而是path指向的目录不存在或者权限不对。我在第一次训练前会先单独打印一遍rice_pests.yaml里的每个路径,确认图片能读到再开训,省得浪费一晚上。
4.3 评估指标怎么看:mAP50、mAP50-95和混淆矩阵
训练结束后,runs/detect/train目录下会有weights、混淆矩阵、PR曲线和验证集结果。判断模型过不过关,我看三个数:mAP50、mAP50-95和验证集上的F1。对这个规模的数据集,mAP50达到0.6以上算及格,0.7以上算不错;mAP50-95能到0.4以上说明框的回归质量也还行。如果mAP50挺高但mAP50-95很低,说明模型虽然能定位大目标,但框的精度不够,常见于小目标和细长目标。
混淆矩阵是另一个要重点看的东西,它会直接告诉你褐飞虱和绿叶蝉到底混得有多严重。如果矩阵里这两个类之间有一块明显的深色区域,说明前面第2章担心的类间混淆真的发生了,这时候不要盲目加大训练轮次,而是回到数据层面去解决。
5. 避坑排查:四类真正让人翻车的常见问题
5.1 坑一:褐飞虱和绿叶蝉混淆,小目标加上近似外观
现象:训练完模型,验证集mAP50看着还行,但打开混淆矩阵发现褐飞虱和绿叶蝉互相误判的比例超过30%。在测试图上也能直观看到,模型把绿色的小虫子框成褐飞虱,或者反过来。
原因:两类害虫的形态本就接近,在低分辨率下几乎没有明显区分度。加上部分标注框画得偏松,把虫子旁边的稻叶纹理也圈进去了,模型学到的特征被背景干扰带偏。
解决:我一般会先做一轮标注检查,把原图上目标区域裁剪放大,逐一看标注框内到底是哪一种。如果整体图像分辨率不足以区分,就不要再强行分两类,直接把这两类合并成“稻飞虱类”或者“小型叶蝉类”,重新生成标注再训练。合并后类别数少一个,但单类的正样本数量增加,模型训练更容易稳定。对于标注框偏松的情况,可以写一个脚本把框向目标中心收缩10%到15%,减小背景干扰。
5.2 坑二:蛀干虫和轮生蛆样本太少,模型一直漏检
现象:训练20个epoch后,其余四类的recall都到0.8以上,蛀干虫只有0.4,轮生蛆甚至不到0.3。验证集里这两类的框,大多只检出框的一部分,要不就直接漏掉。
原因:整个数据集5229张图,如果蛀干虫和轮生蛆的有效实例数合计不到几百个,模型学不到足够的类别特征。而且这两类目标细长、隐蔽,本身检测难度就比褐飞虱要高,标注里的漏标和错标也会进一步压低recall。
解决:第一步是统计每类的实例数和有效图数,做到心里有数。如果蛀干虫的标注图确实只有一百多张,就不要指望靠训练参数把它救回来。常见做法是只保留这两类的标注区域做二次过采样训练:把包含蛀干虫的图片复制两三份,并在每次复制时做不同的随机裁剪和颜色扰动,变相增加它们在每个epoch里出现的次数。另一种做法是临时把这两类从损失权重上加权,但副作用是容易误检,我自己很少用。
5.3 坑三:XML坐标越界和空标注,训练时被框架静默丢弃
现象:训练日志里出现“WARNING removing images: 123”之类的提示,但训练还能正常跑完,只是最终mAP比预期低。后来一查,发现少了一百多张图参与训练。
原因:转换脚本没有处理坐标越界,或者XML里的坐标本身就是错的,比如xmin大于xmax、坐标值超过图像宽高。检测框架在加载标注时会校验坐标范围,非法样本直接跳过不参与训练,而且不会在日志里明确告诉你丢了哪些文件。
解决:回到第3章的转换脚本,加上坐标clip逻辑,并把越界的文件单独打印出来,集中检查。对于xmin大于xmax这种逻辑错误,clip修不了,只能定位到具体XML后手工修正或用附近框的平均值替换。另外,空标注文件(一张图只有背景没有任何害虫)YOLO会报错,这类图要么删除,要么归入一个单独的“背景”目录,只用于负样本增强时随机混入。
5.4 坑四:田间背景干扰和光照变化,误检比漏检更恼人
现象:测试时模型把稻叶边缘、水滴反光点甚至田埂上的杂草屑识别成害虫,置信度还不低。尤其是在强光直射的图片上,误检数量明显上升。
原因:这和我自己做田间采集时踩过的坑一致——训练图大多是近距离拍摄的高清夹取图,背景干净、光照均匀;但真实田间部署时,设备离虫体远,背景里全是被风吹动的叶片和复杂纹理。模型学到的高响应特征其实来自“小暗色斑块”这种视觉模式,而不是害虫本身的生物学特征。
解决:在训练时加入同一批场景但不含害虫的负样本图,让模型学会对这类背景输出“无目标”。另外,推理阶段把置信度阈值从默认的0.25升到0.4或0.45,能明显压掉一批边缘误检。如果误检仍然顽固,回到数据层面,把误检的图片从测试集里捞出来补标为正样本的“难例”,做一轮增量训练。
6. 部署前的最后验证:用测试集复现指标并调阈值
6.1 先跑测试集,再看PR曲线决定取舍
训练完成后,很多人会直接看验证集mAP就觉得可以交付了,这是实践中容易出问题的地方。验证集在训练过程中已经被用来做early stopping和调参,指标存在过拟合的可能性。正确的做法是拿出第3章划分时留出的测试集,这个测试集在整个训练过程中一次都没参与过模型选择,用它做最终评估才有说服力。命令很简单:
yolo predict model=runs/detect/train/weights/best.pt source=./datasets/test imgsz=640 conf=0.25 save_json=true跑完预测后,用val模式的save_json输出保存每张图的预测框,再和测试集标注计算一次mAP。这里的conf值我通常分别跑0.25和0.45两组,观察每个类别PR曲线在置信度变化时的表现。如果某类在0.25时recall高但precision很低,说明存在大量误检,届时应调高阈值;反之如果recall本身就低,那问题在模型训练,而不是阈值。
6.2 部署端的两处调整:置信度阈值与模型量化
我的习惯是,模型交付前一定会在目标设备上跑一遍真实推理,而不是只盯服务器上的指标。边缘盒子和手机端推理时,首先把置信度阈值调到在测试集上验证过的经验值,通常在0.35到0.5之间,宁漏检也不误报的场合取高值,尽量少漏的场合取低值。其次是考虑模型量化,把训练好的FP32权重转成INT8,推理速度和体积都能明显改善。量化前要准备一小批代表性图片做校准集,图片里要覆盖六类害虫和不同光照条件,量化后重新跑一遍测试集,如果mAP50下降超过3个点,就得考虑把某个层留在FP16精度,或者换一个量化策略。
去年有一个项目就卡在这里:模型在服务器上mAP50是0.68,部署到边缘盒子后跌到0.42,排查发现是量化校准集只用了二十多张干净背景图,没能覆盖田间复杂场景,回归校准后指标恢复到0.60以上。从那以后我就养成了习惯,任何部署测试都要在真实场景图或接近真实场景的测试集上做,校准集要按场景占比来凑,而不是随手挑几张。再往后,我还把一个容易误检的类从模型输出里单独拉出来看它的单类PR曲线,针对它单独调阈值,这比全局阈值更精细。这类数据的排查经验,就是在一次次“服务器上很好、现场不行”的落差里攒下来的。希望这些思路能帮到你,少走一段弯路。
本文还有配套的精品资源,点击获取