简介:菲律宾水稻褐飞虱成虫目标检测数据集面向农业AI、智能植保与科研人员,提供真实田间环境下褐飞虱成虫的标注图像,用于训练和验证YOLO等主流目标检测模型,支撑虫情实时监测、精准施药和病虫害防治研究。数据集共2000个文件,包含1458个txt标注文件、540个jpg现场图片、1个yaml配置及1个docx说明文档,压缩包大小56.36MB,目录结构便于直接接入YOLO训练流程。该批标注按训练、验证、测试划分为941、304、213份,类别为褐飞虱成虫,所有样本均经农业专家校验,覆盖水稻不同生长期,具备光照变化、植株遮挡等复杂场景,可直接用于模型快速训练与效果评估。目前已有197人学习使用,适合需要构建农业害虫识别系统的开发者、农业院校师生及从事AI+农业研究的团队参考使用。
1. 菲律宾水稻褐飞虱成虫目标检测数据集:从ZIP包到可部署检测模型的完整链路
第一次拿到菲律宾水稻褐飞虱成虫目标检测数据集这份资源时,我差点把它当成一个解压就能直接训练的常规目标检测包。直到把标注框可视化之后才发现,一张水稻图像里少则几十、多则上百个只有几十像素的褐色小点,才是这份数据真正要考验你的地方。它适合目标检测入门阶段结束、准备往小目标检测和农业植保方向深挖的人,也适合需要在真实田间图像上做虫口计数算法的工程师。能不能把这组数据从zip包变成一份稳定可用的训练集,更多取决于你对目录结构、标签格式和样本分布的判断,而不取决于盲目堆模型。这篇笔记把我整理数据、做校验、转格式、选参数和踩坑的整套流程按实操顺序写出来,你可以直接照着走。
2. 解剖数据集:目录结构、标注格式与密集场景的真实特征
很多人在褐飞虱检测数据集上翻车,不是模型不够好,而是压根没先搞清楚压缩包里标的是什么、怎么标的。目标检测数据集的目录组织方式千差万别,有的按VOC格式放,有的用单个JSON文件承载全部标注,有的干脆是YOLO风格的txt。先花半小时把数据结构和标注字段读明白,比急着跑训练更值。
2.1 压缩包内部常见目录形态:VOC、COCO 与 YOLO 标签的实际区别
常见做法是根目录下平铺images、annotations、classes.txt这几个条目,但也要有心理准备,对方可能按照VOC那一套老结构组织:JPEGImages放原图,Annotations放每个XML文件,ImageSets/Main里再放训练验证划分。COCO格式则把所有标注集中到一个annotations.json里,通过images和annotations两个数组描述图像路径、类别映射和每个目标的bounding box。还有的数据集直接采用YOLO目录,images里是图片,labels里是对应的txt标注。
拿到压缩包的第一件事是看目录树,不要凭经验猜。
unzip -q 菲律宾水稻褐飞虱成虫目标检测数据集.zip -d bph_dataset find bph_dataset -maxdepth 2 | sort | head -50这里unzip -q表示安静模式解压,不打印每个文件的解压过程;-d bph_dataset指定解压目标目录,避免文件散落一地。后面用find查看目录结构,maxdepth 2表示只往下看两层,防止输出刷屏。
执行后重点看三样东西:是否有独立的标注目录、是否提供了类别名文件、图片与标注是否同名。如果发现annotations目录里既有XML又有JSON,需要人工判断到底哪一份是完整的、哪一份是中途生成的临时产物。按我的习惯,会优先采用能逐一对应到图片的文件格式,因为单个标识文件损坏时可以单独处理,而所有标注塞进一个JSON时,一个字段错了往往影响整个训练集。
2.2 标签字段逐个讲:bbox、类别ID与坐标体系的确认方法
VOC格式的XML标注文件是这个样子的:
<annotation> <filename>BPH_20231012_014.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>adult</name> <bndbox> <xmin>476</xmin> <ymin>312</ymin> <xmax>492</xmax> <ymax>326</ymax> </bndbox> </object> </annotation>这里xmin、ymin是目标框左上角的绝对像素坐标,xmax、ymax是右下角的绝对像素坐标,坐标系原点在图像左上角。<name>是类别名,取决于这份数据集把褐飞虱成虫定义成什么字符串。
COCO格式的标注则以JSON数组形式存储,一个目标对应一条记录,bbox字段是[x, y, width, height],同样是绝对像素坐标,但表示方式和VOC不同。YOLO格式的txt文件每行一串数字,依次是类别ID、目标中心点横坐标、中心点纵坐标、宽度、高度,其中坐标值都归一化到0到1之间。三种格式转换时最容易掉坑的是类别ID从0开始还是从1开始,COCO的类别ID经常从1开始,YOLO从0开始,搞错一次全盘皆输。
| 标注格式 | 坐标定义 | 是否归一化 | 每个目标的信息来源 |
|---|---|---|---|
| VOC XML | xmin, ymin, xmax, ymax | 否,绝对像素 | 一个object节点对应一个目标 |
| COCO JSON | x, y, width, height | 否,绝对像素 | annotations数组里的一条记录 |
| YOLO txt | center_x, center_y, width, height | 是,除以图像宽高 | 一行对应一个目标 |
建议先解压一个标注文件打开看,再对照表2-1确认坐标表示,最后写一段小脚本抽样验证,不要先把所有标注一股脑转成训练格式。
2.3 密集目标才是分水岭:一张图上百个框意味着什么
褐飞虱成虫个体小,稻田暴发期稻株茎基部常常叠着好几只虫。一张普通分辨率的田间照片里,成虫目标经常只有几十个像素,远小于常规目标检测数据集里的目标尺度。更麻烦的是,虫体聚集在一起形成密集遮挡,两个目标挨得极近,标注框之间大量交叠。
这种密集场景对模型训练有两个直接影响。第一,基于anchor的检测器在分配正样本时,同一个anchor可能同时和两个相邻目标有较高IoU,训练库只能把它分给IoU最大的那个目标,如果标注框本身不够精确,就会产生训练信号震荡。第二,密集目标对NMS后处理非常敏感,一个置信度稍低的框很容易被相邻高分框抑制掉,导致召回率下降。所以这一步你就要建立心理预期:这份数据集的难点不是“认不认得出来”,而是“这么多小目标怎么一个个框出来”,后续所有参数设置都要围绕这一点展开。
3. 解压后的第一道工序:校验、统计与可视化复查
数据集到手之后直接跑训练是最容易翻车的做法。压缩包可能在中转时损坏,标注文件可能有缺失,坐标可能和图像尺寸对不上,这些问题在训练阶段才会暴露,但排查成本已经高了好几倍。我每次拿到数据集都会按“完整性校验、样本统计、可视化复查”三步走一遍,花一两个小时,能省下后面几天的黑匣子调试。
3.1 完整性校验:用testzip和哈希值确认压缩包没坏
网盘下载、邮件转发、移动硬盘拷贝,任何环节都可能让zip包产生损坏。先用Python跑一遍CRC校验,是最快的兜底手段。
import zipfile import hashlib zip_path = "菲律宾水稻褐飞虱成虫目标检测数据集.zip" with zipfile.ZipFile(zip_path, "r") as zf: bad_file = zf.testzip() if bad_file: print("损坏文件:", bad_file) else: print("所有zip条目CRC校验通过") sha1 = hashlib.sha1() with open(zip_path, "rb") as f: for chunk in iter(lambda: f.read(1024 * 1024), b""): sha1.update(chunk) print("文件SHA1:", sha1.hexdigest())testzip()会逐个解压校验包内文件的CRC值,返回第一个损坏文件的文件名;如果全部正常则返回None。哈希计算放在后面,是为了给整个压缩包留一个唯一指纹。以后再从其他地方拿到同名文件,对比SHA1就知道是不是同一个文件。这里分块读取文件,每次读1MB,避免一次性把大文件全部载入内存。
3.2 统计样本与标注分布:一个脚本看清数据全貌
不知道训练集里有多少张图、多少个标注框、类别分布怎么样,就直接定训练策略,等于闭眼开车。我一般会写一个只解析标签、不读图像的统计脚本,速度快,也不占内存。
import os import glob import numpy as np from xml.etree import ElementTree as ET from collections import Counter ann_dir = "./bph_dataset/annotations" img_dir = "./bph_dataset/images" imgs = glob.glob(os.path.join(img_dir, "*.jpg")) + glob.glob(os.path.join(img_dir, "*.png")) anns = glob.glob(os.path.join(ann_dir, "*.xml")) print("图像数量:", len(imgs)) print("标注文件数量:", len(anns)) box_counts = [] class_counter = Counter() for ann in anns: root = ET.parse(ann).getroot() n = 0 for obj in root.iter("object"): name_node = obj.find("name") if name_node is None: continue class_counter[name_node.text.strip()] += 1 n += 1 box_counts.append(n) arr = np.array(box_counts) print("每图框数 均值=%.2f 中位数=%.1f 最大值=%d" % (arr.mean(), np.median(arr), arr.max())) print("类别统计:", dict(class_counter))这段代码先统计图像数和标注文件数,再遍历每个XML里的object节点,统计类别出现次数以及每张图有多少个框。np.median比均值更能反映典型样本情况,因为如果存在个别几百个框的密集图,均值会被明显拉高。跑完建议看一眼两项指标:有没有大量图像一个框都没有,以及每图框数的中位数和最大值是否相差悬殊。前者说明数据里混入了纯背景图,后者说明密集场景占比较高,训练时需要专门处理。
3.3 可视化复查:把标注框画回图像上,错位一眼可见
文本统计只能说明数量,不能说明坐标是否正确。坐标与图像错位是数据集中最常见的低级问题,原因通常是标注工具导出时的尺寸换算和原图不一致,或者是图片被压缩过而XML里的size字段没更新。画框抽查是最快的验证方法。
import os import cv2 from xml.etree import ElementTree as ET def draw_voc_boxes(img_path, xml_path, out_dir): img = cv2.imread(str(img_path)) if img is None: print("无法读取图片:", img_path) return h, w = img.shape[:2] root = ET.parse(xml_path).getroot() for obj in root.iter("object"): name = obj.find("name").text bnd = obj.find("bndbox") xmin = int(float(bnd.find("xmin").text)) ymin = int(float(bnd.find("ymin").text)) xmax = int(float(bnd.find("xmax").text)) ymax = int(float(bnd.find("ymax").text)) xmin = max(0, min(xmin, w - 1)) ymin = max(0, min(ymin, h - 1)) xmax = max(0, min(xmax, w - 1)) ymax = max(0, min(ymax, h - 1)) if xmax <= xmin or ymax <= ymin: continue cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, name, (xmin, max(0, ymin - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) os.makedirs(out_dir, exist_ok=True) out_path = os.path.join(out_dir, os.path.basename(img_path)) cv2.imwrite(out_path, img) print("已保存:", out_path)这里读取图像后先取实际高度和宽度,再把XML里的坐标裁剪到图像边界范围内,避免画出的框超出画布导致绘图异常。cv2.putText里的字体大小0.5和线宽1适合小图标注,如果要放到大图上查看,可以适当调大。建议从不同子目录随机挑10到20张图进行可视化,重点看密集区域:框是否包住整只虫、是否明显偏向叶片一侧、是否存在一个框同时框住多只虫的情况。如果大量存在最后一种,需要回到数据源头确认标注规范。
4. 格式转换、参数设定与第一次训练:把标签变成可用的检测模型
数据校验通过之后,进入真正的训练环节。这里最稳妥的顺序是先把标注转成目标格式,再用小模型跑一轮冒烟测试确认流程,最后再加大输入尺寸和训练轮次。直接开大模型全参训练,一旦管道报错,排查成本极高。
4.1 标注格式转换:VOC/XML转YOLO的脚本与边界处理
YOLO系列训练库最常用的格式是每张图像对应一个txt文件,每行一个目标,内容是类别ID、归一化中心点坐标和归一化宽高。下面这段代码把VOC XML转成YOLO txt,我在实际项目中经常复用。
import os import glob import xml.etree.ElementTree as ET from pathlib import Path CLASSES = ["adult"] # 以数据集提供的classes.txt为准,可多类别 def voc_to_yolo(xml_path, img_w, img_h): lines = [] tree = ET.parse(xml_path) root = tree.getroot() for obj in root.iter("object"): name = obj.find("name").text.strip() if name not in CLASSES: continue cid = CLASSES.index(name) bnd = obj.find("bndbox") xmin = int(float(bnd.find("xmin").text)) ymin = int(float(bnd.find("ymin").text)) xmax = int(float(bnd.find("xmax").text)) ymax = int(float(bnd.find("ymax").text)) xmin = max(0, min(xmin, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) xmax = max(0, min(xmax, img_w - 1)) ymax = max(0, min(ymax, img_h - 1)) if xmax <= xmin or ymax <= ymin: continue cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h bw = (xmax - xmin) / img_w bh = (ymax - ymin) / img_h lines.append(f"{cid} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") return "\n".join(lines) out_dir = Path("yolo_labels") out_dir.mkdir(exist_ok=True) for xml_path in glob.glob("bph_dataset/annotations/*.xml"): root = ET.parse(xml_path).getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) name = Path(xml_path).stem txt = voc_to_yolo(xml_path, w, h) (out_dir / f"{name}.txt").write_text(txt, encoding="utf-8")两个关键参数值得说明。img_w和img_h来自XML里的size字段,而不是从图像文件实时读取,目的是避免逐张解码大图拖慢速度;但如果怀疑XML中的尺寸和实际图像不一致,就需要先做一次核对。第二个关键是边界裁剪:标注框偶尔会越过图像边界,如果不处理,归一化中心点和宽高可能出现负数或大于1的值,训练时轻则警告,重则让损失直接变成NaN。
转换完成后,还要准备一份数据配置文件说明训练集、验证集和类别名的对应关系。常见做法是在数据目录下放一个YAML文件:
path: ./bph_dataset train: images/train val: images/val names: 0: adult这里train和val的相对路径默认以path为基准,names的键值顺序必须和转换脚本里的CLASSES一致,两者错位会造成标签错乱。
4.2 第一次训练的合理参数:输入尺寸、batch size、epochs 和学习率
对于褐飞虱这种小目标密集检测场景,输入尺寸怎么定,优先级比模型选型还高。我一般会先用640的输入尺寸跑一个10轮的小实验,确认数据管道没有低级错误,然后正式训练时把输入尺寸提到1280或至少1024。输入尺寸越大,小目标在特征图上的有效像素越多,但显存占用也随之增加,需要同步调整batch size。
| 参数 | 建议值 | 说明 |
|---|---|---|
| imgsz | 1280,显存不足降到1024 | 小目标需要更大输入,但不要盲目到1536 |
| batch | 8到16,以显卡显存上限为准 | 显存不够时优先降batch而不是降imgsz |
| epochs | 100到200 | 小目标收敛较慢,50轮以内往往看不到最佳效果 |
| lr0 | 0.01(SGD类)或0.001(AdamW类) | 不同训练库默认值差别大,以默认优先 |
| mosaic | 关闭或调低 | 密集小目标经过mosaic缩小后更难识别 |
训练命令以你实际安装的YOLO训练库为准,常见写法如下:
yolo detect train data=bph.yaml model=yolov8s.pt epochs=120 imgsz=1280 batch=8 project=./runs这里model=yolov8s.pt表示用小规模的预训练权重作为起点,project指定训练日志和权重输出目录。第一次训练结束后,不要只看mAP,建议打开训练日志里的loss曲线和验证集预测图,确认模型真的框住了密集虫体,而不是只学会了把背景压成低置信度。
4.3 模型选型:单阶段和两阶段的真实取舍
很多人会在YOLO和Faster R-CNN之间纠结。我的判断标准是:项目要落地,优先YOLO,因为部署生态最完整,从训练到导出ONNX再到端侧推理,链路顺畅;如果追求极致的小目标召回率,并且推理速度要求不高,Faster R-CNN系列有优势。对这份数据集来说,单用YOLO在小目标密集区域容易漏,单用Faster R-CNN则可能慢到没法做实时监测。
实际工程里常见做法是“YOLO跑检测,滑窗切片做补充”。把大图切成有重叠的patch,每个patch分别检测,再合并结果,能显著缓解小目标漏检。切片尺寸一般取512或640,重叠率取20%到25%,合并时用NMS把重叠区域里同一个目标的重复框抑制掉。这套组合在实际项目中往往比换更大的骨干网络更有效。
5. 避坑与排查:这份数据从解压到收敛的五个常见坑
以下五个问题是我在类似数据上反复遇到过的。每一条按“现象、原因、解决”写清楚,遇到时可以直接对号入座。
5.1 文件名对不上,训练时批量FileNotFoundError
现象:数据校验阶段没发现异常,但训练开始后日志里连续出现某个图片文件找不到的报错,或者损失曲线完全不下降。
原因:压缩包里的图片和标注文件经过多次转发后,文件名被改动。常见情况是图片被加上了“副本”“_1”这类后缀,而XML文件名保持原名;也可能是部分标注文件存在而对应图片缺失。这种情况在Windows系统之间传包时特别常见。
解决:写一个脚本快速核对图片和标注文件的stem是否一一对应,把没有配对的文件单独列出来。
from pathlib import Path root = Path("./bph_dataset") for ann in (root / "annotations").glob("*.xml"): stem = ann.stem candidates = [ root / "images" / f"{stem}.jpg", root / "images" / f"{stem}.png", root / "images" / f"{stem}.jpeg", ] if not any(p.exists() for p in candidates): print("缺少对应图片:", stem)这里直接对文件主名做匹配,.glob只扫描当前目录,速度快,足够发现问题。如果发现大量不配对,宁可先写脚本批量重命名,也不要在训练阶段靠报错来逐步修。
5.2 小目标漏检严重,召回率始终上不去
现象:训练跑完,mAP50看着还可以,但实际统计一张密集虫图上被检测出的目标数,明显少于人工数出来的数量。
原因:输入尺寸太小,加上网络下采样倍数高,小目标在深层特征图上只剩一两个像素,特征信息基本丢失。尤其对于只有几十像素的褐飞虱成虫,640的输入尺寸远远不够。
解决:优先提高输入尺寸到1280,或者在推理阶段把大图切成小块分别检测再合并。另一个血泪经验是不要为了追求精确框而把置信度阈值调得过高,密集场景下正确目标的置信度本身普遍偏低,阈值设到0.5以上容易把真目标全部筛掉。建议先输出所有预测框,观察置信度分布再定阈值。
5.3 标注定义混乱,模型把背景物当目标
现象:训练时loss能降下来,但验证集上预测框经常落在枯叶、稻粒或水面反光上,可视化结果看得很不舒服。
原因:标注阶段没有严格统一“成虫”的标准,标注员把部分枯黄稻叶碎片、干瘪虫尸甚至泥点也框了进去。这类噪声目标在特征是上本身就和成虫有相似纹理,模型很难自动区分。
解决:回到标注文件层面做一次人工复核。对置信度高但类别错误的典型样本,把误标框对应的标注信息直接剔除;对无法明确判断的对象,与其硬归到某一类,不如丢掉。宁可少几十个框,也不要让模型学会错误的特征。
5.4 中文路径和编码问题导致读取失败
现象:训练脚本在Windows下运行正常,换到Linux服务器后图片读取报错;或者解压后文件名变成一串乱码,XML解析时直接UnicodeDecodeError。
原因:压缩包内部文件名在生成时采用GBK编码,而Python在很多环境下默认按UTF-8处理文件名,两边对不上就出现乱码。另一些情况是zip包内包含中文目录深度过大,训练库对路径字符集处理不统一。
解决:最省事的办法是解压后立刻把所有文件夹和文件重命名为纯英文,彻底和中文路径切割。如果压缩包内已经是乱码,优先用unzip工具加参数处理。一个简化的重命名方案是用Python遍历一遍旧目录,把非ASCII字符替换为拼音或序号。不要侥幸等到训练时报错再回头处理,路径问题越早解决越省事。
5.5 背景图和无目标样本过多,模型忽略了目标本身
现象:训练时loss下降很快,但mAP曲线波动大;模型对真实验证集大量输出低置信度预测,看起来像什么都没学会。
原因:数据集里存在一部分完全没有褐飞虱的水稻图像,或者有的图只有一个目标,其余大部分区域是稻叶背景。模型在训练过程中发现“直接预测背景就能把损失压得很低”,于是对稀疏目标样本的学习动力变弱。
解决:采样策略上把这类低目标密度的样本降权,或者在每个batch的组成中控制空背景图占比。另一个有效手段是采用Focal Loss这类能抑制易分类样本损失的损失函数。如果数据集本身提供了验证集划分,还要注意验证集里不要混入大量空背景图,否则即便模型没学好,mAP也会因为假阴性大量减少而显得虚高。
6. 从检测框到虫口密度:三个进阶做法让模型真正落地
检测框本身不是终点,农业场景里最终要的是虫口密度。这里给你三个我常用的进阶手段。
6.1 用迁移学习补上田间差异
这份数据集的拍摄条件和你实际部署的农田大概率不同,光照角度、水稻品种、拍摄设备都会影响效果。我一般会先用它训练出基础权重,再在当地示范区拍几百张照片做增量微调。采集时不用贪多,把“密集、遮挡、逆光、手机广角”这些难例场景尽量覆盖住,每类几十张就有效果。
6.2 把检测框折算成虫口密度要定好口径
统计密度时,先定两个东西,一个是置信度阈值,一个是统计单位。同类模型在同一批图上,阈值从0.1改到0.3,计数结果可能相差百分之三四十。我通常的做法是按农技站要求的单位面积或稻丛数来折算,先固定阈值,再计算每丛平均虫量。判断阈值是否合适,就看验证集上人工计数和模型计数的相对偏差能不能稳定在可接受范围。
6.3 维护一个坏例样本池,持续回填
模型部署后不是一劳永逸,水稻生长阶段变化会让叶片颜色和背景纹理持续变化。我习惯每两周抽20张现场照片,把模型漏检和误检的典型图单独归档,重新标注后做一次增量训练。这个坏例池越到后期越值钱,比一次性采集几千张新图有效得多。
有一回我为了压低漏检,把置信度阈值一路调到0.05,结果计数立刻膨胀,每丛上百只虫,明显不符合实际。从那次之后,我都是先人工定阈值,再让模型去适配阈值,而不是反过来让阈值迁就模型。做好阈值界定、坏例回填和密度折算这三个闭环,这份数据集才算真正用出了价值。希望帮到你。
本文还有配套的精品资源,点击获取