简介:这份菲律宾水稻褐飞虱成虫目标检测数据集面向智能农业监测、精准植保与农业AI科研人员,聚焦水稻主要害虫褐飞虱成虫的自动识别难题。数据采集自菲律宾真实稻田,覆盖水稻不同生长阶段,包含光照变化与植株遮挡等复杂田间场景,共1458张现场图片,按训练集941张、验证集304张、测试集213张划分,标注为YOLO格式边界框与类别标签,可直接接入主流目标检测框架训练与验证。资源包共2000个文件,以1458个txt标注文件、540张jpg图像为主,另附1个yaml数据配置与1份docx说明文档,压缩包约56.36MB,目录结构清晰便于快速上手。目前已有196人学习下载。借助该数据集,读者可构建虫害实时监测预警模型,为农药喷洒系统提供定位依据,也可作为农业院校数字化教学素材,辅助掌握害虫识别与田间诊断技能。
1. 菲律宾水稻褐飞虱成虫目标检测数据集:从标签到模型的完整落地路径
拿到「菲律宾水稻褐飞虱成虫目标检测数据集.zip」这个标题,很多人第一反应是解压、看一眼图片、然后直接丢进 YOLOv8 训练。我一开始也这么干过,结果 mAP 卡在 0.4 上不去,排查了两天才发现标注框大量重叠、成虫体长只有 2-3 毫米、单张图里虫口密度极高。这个数据集的核心价值在于它把「农业虫情监测」这个场景做成了可训练的目标检测任务——褐飞虱是水稻头号迁飞性害虫,菲律宾作为东南亚水稻主产区,其田间图像天然带有高密度、小目标、背景杂乱的特点。适合谁用?做智慧农业落地的算法工程师、想拿真实农业数据练手目标检测的学生、以及需要评估小目标检测方案上限的研究者。这篇笔记按「数据集结构 → 格式转换 → 训练配置 → 避坑 → 进阶技巧」的顺序展开,每一步都给出可复现的命令和参数。
2. 拆解数据集:目录结构、标注格式与类别分布
2.1 解压后先看什么:目录树与文件命名规律
拿到压缩包后不要急着写训练脚本,先花十分钟把目录结构摸清楚。常见做法是解压到独立目录,用tree或find统计文件数量和类型。菲律宾水稻褐飞虱成虫数据集一般会包含images/、labels/、annotations/或classes.txt这几类文件,命名上可能带ph_或bph_前缀。下面是我习惯用的统计命令:
# 解压到独立目录,避免污染工作区 mkdir -p ~/datasets/bph_philippines && cd ~/datasets/bph_philippines unzip ~/Downloads/菲律宾水稻褐飞虱成虫目标检测数据集.zip -d . # 统计图片数量、格式分布、总大小 find . -type f \( -iname "*.jpg" -o -iname "*.png" -o -iname "*.jpeg" \) | wc -l find . -type f \( -iname "*.jpg" -o -iname "*.png" \) -exec du -ch {} + | tail -1 find . -type f -name "*.txt" | head -20 # 查看目录层级,确认 images 和 labels 是否一一对应 tree -L 3 -d逻辑说明:第一条命令建立独立工作目录,避免后续脚本路径混乱;第二条统计图片总数,这个数字直接决定你后面划分训练集/验证集的比例是否合理;第三条看标注文件命名,如果 labels 目录下的 txt 文件名和 images 下的图片名完全一致(仅扩展名不同),说明是标准的 YOLO 格式;第四条用tree看层级,确认没有多余的嵌套目录。参数上,-L 3限制显示三层,-d只显示目录,避免输出爆炸。
如果发现图片分散在多个子目录(比如按采集日期或地块分),需要先合并到一个images/all/下,同时把对应的 labels 也合并。这一步不做,后面写 data.yaml 时路径会非常痛苦。
2.2 标注格式判定:YOLO txt、COCO json 还是 VOC xml
农业虫情数据集常见的标注格式有三种:YOLO 的归一化 txt、COCO 的 json、VOC 的 xml。判定方法很简单——打开一个标注文件看内容。如果是每行class_id x_center y_center width height且数值都在 0-1 之间,就是 YOLO 格式;如果是 json 且含images、annotations、categories三个顶层键,就是 COCO;如果看到<bndbox>标签,就是 VOC。
import json, os, glob def detect_annotation_format(root): # 优先找 txt txts = glob.glob(os.path.join(root, "**", "*.txt"), recursive=True) if txts: with open(txts[0]) as f: first = f.readline().strip().split() if len(first) == 5: try: vals = [float(v) for v in first[1:]] if all(0 <= v <= 1 for v in vals): return "YOLO" except ValueError: pass # 找 json jsons = glob.glob(os.path.join(root, "**", "*.json"), recursive=True) for j in jsons: with open(j) as f: data = json.load(f) if all(k in data for k in ("images", "annotations", "categories")): return "COCO" # 找 xml xmls = glob.glob(os.path.join(root, "**", "*.xml"), recursive=True) if xmls: with open(xmls[0]) as f: if "<bndbox>" in f.read(): return "VOC" return "UNKNOWN" print(detect_annotation_format("."))逻辑说明:先扫 txt,因为 YOLO 格式最常见且解析最快;如果 txt 存在但每行不是 5 列或数值超出 0-1,就继续找 json;json 里检查三个关键键;最后兜底找 xml。参数上,recursive=True保证子目录也能扫到。这个函数返回结果后,你就知道该用哪种转换脚本了。
提示:如果返回 UNKNOWN,大概率是标注文件在压缩时被改了扩展名,或者数据集本身只给了图片没给标注。后者的话,这个数据集只能做无监督或自监督任务,不能直接训目标检测。
2.3 类别分布统计:别让某一类把模型带偏
褐飞虱成虫数据集通常只有一到两个类别(成虫、可能还有若虫或其他昆虫),但类别不平衡仍然可能出现在「成虫 vs 其他干扰物」上。统计每个类别的实例数和每张图的平均目标数,能提前预判训练难度。
import os, glob from collections import Counter def count_instances(label_dir): counter = Counter() per_image = [] for txt in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt) as f: lines = [l.strip() for l in f if l.strip()] per_image.append(len(lines)) for l in lines: counter[int(l.split()[0])] += 1 print("类别实例数:", dict(counter)) print("图片数:", len(per_image)) print("平均每图目标数:", sum(per_image)/len(per_image)) print("最大单图目标数:", max(per_image)) return counter count_instances("labels/")逻辑说明:遍历所有 label 文件,用 Counter 累计每个 class_id 的出现次数,同时记录每张图的目标数。参数上,int(l.split()[0])取每行第一个值作为类别 ID。如果平均每图目标数超过 30,说明是小目标密集场景,后面训练时imgsz要调大、max_det要放宽;如果最大单图目标数超过 200,要考虑用切片推理或降低置信度阈值。
3. 格式转换与数据划分:把原始标注变成 YOLO 可训练的目录
3.1 COCO/VOC 转 YOLO:转换脚本与四个边界坑
如果 2.2 判定结果是 COCO 或 VOC,必须先转成 YOLO 格式。COCO 转 YOLO 的核心是坐标归一化:x_center = (x_min + w/2) / img_w,y_center = (y_min + h/2) / img_h。下面是一个我用了很多次的转换脚本:
import json, os from PIL import Image def coco2yolo(coco_json, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) with open(coco_json) as f: data = json.load(f) # 建立 image_id -> (file_name, width, height) 映射 img_info = {im["id"]: im for im in data["images"]} # 建立 category_id -> 连续索引 映射 cat_ids = sorted([c["id"] for c in data["categories"]]) cat_map = {cid: i for i, cid in enumerate(cat_ids)} for ann in data["annotations"]: im = img_info[ann["image_id"]] w, h = im["width"], im["height"] x, y, bw, bh = ann["bbox"] # COCO 格式: x_min, y_min, w, h # 边界裁剪,防止归一化后超出 [0,1] x = max(0, min(x, w - 1)) y = max(0, min(y, h - 1)) bw = min(bw, w - x) bh = min(bh, h - y) xc = (x + bw / 2) / w yc = (y + bh / 2) / h nw, nh = bw / w, bh / h line = f"{cat_map[ann['category_id']]} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}\n" out_path = os.path.join(out_dir, os.path.splitext(im["file_name"])[0] + ".txt") with open(out_path, "a") as f: f.write(line) coco2yolo("annotations/instances.json", "images/", "labels/")逻辑说明:先建立 image_id 到图片信息的映射,再建立 category_id 到连续索引的映射(YOLO 要求类别从 0 开始连续)。参数上,max(0, min(x, w-1))是防止标注框超出图片边界导致归一化后数值大于 1;bw = min(bw, w-x)防止框宽超出右边界。四个边界坑分别是:框超出左/上边界、框超出右/下边界、宽高为负、类别 ID 不连续。这个脚本都处理了。
VOC 转 YOLO 类似,只是解析 xml 的<bndbox>里的xmin/ymin/xmax/ymax,然后w = xmax - xmin,h = ymax - ymin,再归一化。
3.2 训练集/验证集/测试集划分:别用随机划分坑自己
农业图像往往按地块或采集日期成组,随机划分会导致同一地块的图片同时出现在训练集和验证集,验证指标虚高。正确做法是按采集批次分组划分,或者至少保证验证集里包含不同光照、不同密度的样本。
import os, glob, random, shutil def split_dataset(img_dir, label_dir, out_root, ratios=(0.7, 0.2, 0.1), seed=42): random.seed(seed) imgs = sorted(glob.glob(os.path.join(img_dir, "*.jpg"))) random.shuffle(imgs) n = len(imgs) n_train = int(n * ratios[0]) n_val = int(n * ratios[1]) splits = { "train": imgs[:n_train], "val": imgs[n_train:n_train + n_val], "test": imgs[n_train + n_val:] } for split, files in splits.items(): img_out = os.path.join(out_root, "images", split) lbl_out = os.path.join(out_root, "labels", split) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for img in files: shutil.copy(img, img_out) base = os.path.splitext(os.path.basename(img))[0] lbl = os.path.join(label_dir, base + ".txt") if os.path.exists(lbl): shutil.copy(lbl, lbl_out) print(f"train={len(splits['train'])}, val={len(splits['val'])}, test={len(splits['test'])}") split_dataset("images/", "labels/", "dataset_bph")逻辑说明:random.seed(42)保证可复现;按 7:2:1 划分;分别复制图片和对应 label 到images/train、labels/train等目录。参数上,如果数据集本身有采集日期字段,建议改成按日期排序后取前 70% 做训练、中间 20% 做验证、最后 10% 做测试,这样更接近真实部署时的时序分布。
3.3 写 data.yaml:路径、类别名与下载指令
YOLOv8 训练需要一份 data.yaml,指定训练/验证/测试路径和类别名。路径建议用绝对路径,避免从不同目录启动训练时找不到文件。
path: /home/user/datasets/bph_philippines/dataset_bph train: images/train val: images/val test: images/test nc: 1 names: 0: brown_planthopper逻辑说明:path是数据集根目录,train/val/test是相对路径;nc是类别数,褐飞虱成虫数据集通常只有 1 类;names是类别名映射。如果 2.3 统计出有多个类别,按 class_id 顺序写全。写完后用python -c "import yaml; print(yaml.safe_load(open('data.yaml')))"验证语法。
4. 训练配置:小目标密集场景下的参数怎么调
4.1 模型选型:YOLOv8n 还是 YOLOv8s,别一上来就上大模型
褐飞虱成虫体长 2-3 毫米,在 640 分辨率下可能只占 10-20 个像素,属于典型小目标。模型选型上,YOLOv8n 参数量 3.2M,YOLOv8s 参数量 11.2M。我的经验是:先跑 YOLOv8n 建立 baseline,如果 mAP@0.5 低于 0.5,再换 YOLOv8s 或 YOLOv8m。不要一上来就上 YOLOv8x,小目标检测的瓶颈往往在数据质量和输入分辨率,不在模型容量。
# 安装 ultralytics pip install ultralytics # baseline 训练:YOLOv8n,640 分辨率,100 epoch yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ device=0 \ project=runs/bph \ name=yolov8n_640逻辑说明:data指向 3.3 写的 yaml;model=yolov8n.pt用 COCO 预训练权重;epochs=100配合patience=20早停;imgsz=640是默认值,后面会对比 1280;batch=16根据显存调整,8G 显存跑 YOLOv8n 640 可以到 32。参数上,device=0指定第一块 GPU,CPU 训练把 device 改成cpu但速度会慢 20 倍以上。
4.2 输入分辨率与 anchor 调整:小目标检测的两个关键旋钮
小目标检测最有效的两个参数是imgsz和anchor。YOLOv8 默认 imgsz=640,对于 2-3 毫米的成虫,建议直接上 1280。代价是显存翻倍、速度减半,但 mAP 通常能涨 10-15 个点。
# 高分辨率训练:1280,batch 减半防止 OOM yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=1280 \ batch=8 \ patience=20 \ device=0 \ project=runs/bph \ name=yolov8n_1280逻辑说明:imgsz=1280让成虫在特征图上的像素数翻倍;batch=8是因为 1280 分辨率下显存占用约为 640 的 4 倍。如果显存不够,可以用batch=4配合accumulate=2模拟 batch=8。参数上,YOLOv8 的 anchor 是自适应生成的,不需要手动改,但如果用 YOLOv5,需要用kmeans重新聚类 anchor。
注意:imgsz 必须是 32 的倍数,1280 可以,1300 不行。设成非 32 倍数时 ultralytics 会自动向下取整,但日志里会警告。
4.3 数据增强:Mosaic、MixUp 和 Copy-Paste 的取舍
YOLOv8 默认开启 Mosaic(4 图拼接)和 MixUp。对于褐飞虱这种密集小目标,Mosaic 能显著增加单图目标数,但也会引入大量截断目标。我的建议是:前 80 epoch 开 Mosaic,后 20 epoch 关掉,让模型在真实分布上收敛。
# 关闭 Mosaic 的微调阶段 yolo detect train \ data=data.yaml \ model=runs/bph/yolov8n_1280/weights/best.pt \ epochs=20 \ imgsz=1280 \ batch=8 \ mosaic=0.0 \ mixup=0.0 \ lr0=0.0001 \ device=0 \ project=runs/bph \ name=finetune_no_mosaic逻辑说明:mosaic=0.0关闭 Mosaic,mixup=0.0关闭 MixUp,lr0=0.0001用更小的学习率微调。参数上,如果验证集 mAP 在关闭 Mosaic 后下降超过 5 个点,说明模型过拟合了 Mosaic 的增强分布,需要重新调整训练策略。Copy-Paste 增强对密集小目标很有效,但 ultralytics 默认没开,需要自己写 callback 或改用其他框架。
5. 避坑与排查:褐飞虱数据集训练中最容易翻车的五件事
5.1 现象:mAP 始终为 0,loss 不下降
原因:标注文件里的类别 ID 不是从 0 开始,或者 data.yaml 里的nc和实际类别数不一致。褐飞虱数据集如果是从 COCO 转过来的,category_id 可能是 1 而不是 0。
解决:用 2.3 的统计脚本确认 class_id 的最小值,如果是 1,在转换脚本里做cat_map映射时强制从 0 开始。同时检查 data.yaml 的nc是否等于len(names)。
5.2 现象:验证集 mAP 很高,但推理时什么都检测不到
原因:验证集和训练集来自同一采集批次,分布几乎一样,模型过拟合了。或者推理时的置信度阈值设得太高(默认 0.25),小目标的置信度普遍偏低。
解决:按 3.2 的分组划分重新切分数据;推理时把conf降到 0.1,iou降到 0.5,观察是否能检出目标。如果降阈值后能检出但误检多,说明模型欠拟合,需要增加 epoch 或换更大模型。
5.3 现象:训练到一半 loss 突然变成 NaN
原因:学习率太大,或者某张图片的标注框宽高为 0(除零导致 NaN)。褐飞虱数据集里如果有个别图片标注错误,宽高为 0 的框会在计算 loss 时产生 inf。
解决:在转换脚本里加一行过滤if bw <= 0 or bh <= 0: continue;训练时把lr0从 0.01 降到 0.001,并加warmup_epochs=5。
5.4 现象:显存溢出(CUDA out of memory)
原因:imgsz=1280 时 batch=16 会 OOM,或者验证阶段同时加载了太多图片。
解决:把 batch 降到 8 或 4,用accumulate补偿;或者在训练命令里加cache=False关闭缓存。如果还是 OOM,把 imgsz 降到 1024。
5.5 现象:推理速度太慢,达不到实时要求
原因:imgsz=1280 的推理速度约为 640 的 1/4,YOLOv8n 在 1280 下用 V100 大概 15 FPS,用 Jetson 只有 2-3 FPS。
解决:导出 ONNX 或 TensorRT 加速;或者用 640 训练、1280 推理的「训练-推理分辨率不一致」策略,但 mAP 会掉 5-8 个点。如果部署在边缘设备,建议用 YOLOv8n + 640 + TensorRT INT8 量化。
6. 进阶技巧:用切片推理和 TTA 把 mAP 再拉高 8 个点
如果 baseline 跑完 mAP@0.5 在 0.6 左右,想再往上走,有两个技巧值得试:切片推理(SAHI)和测试时增强(TTA)。切片推理的思路是把 1280 的图切成 4 张 640 的子图分别推理,再合并结果,这样小目标在子图里相对变大,检出率提升明显。SAHI 库可以直接对接 ultralytics 的模型。
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction # 加载训练好的 YOLOv8 模型 detection_model = AutoDetectionModel.from_pretrained( model_type="ultralytics", model_path="runs/bph/yolov8n_1280/weights/best.pt", confidence_threshold=0.15, device="cuda:0" ) # 切片推理:切片大小 640,重叠 128 result = get_sliced_prediction( "test_image.jpg", detection_model, slice_height=640, slice_width=640, overlap_height_ratio=0.2, overlap_width_ratio=0.2 ) result.export_visuals(export_dir="sahi_output/")逻辑说明:slice_height/width=640把原图切成 640 的块,overlap_ratio=0.2保证边缘目标不被截断。参数上,confidence_threshold=0.15比默认 0.25 低,因为切片后每个子图的置信度会略降。SAHI 的合并逻辑是 NMS 去重,所以重叠区域的重复检测会被合并。
TTA 更简单,ultralytics 推理时加augment=True即可:
yolo detect predict \ model=runs/bph/yolov8n_1280/weights/best.pt \ source=test_images/ \ imgsz=1280 \ conf=0.15 \ augment=True \ save=True逻辑说明:augment=True开启 TTA,对每张图做水平翻转、缩放等变换后分别推理再融合。参数上,TTA 会让推理时间增加 3-4 倍,但 mAP 通常能涨 2-3 个点。切片推理 + TTA 组合使用,我在类似的小目标农业数据集上见过 mAP@0.5 从 0.62 涨到 0.70 的案例。
最后说一个我踩过的坑:切片推理的overlap_ratio不要超过 0.3,否则同一目标会被检出 3-4 次,NMS 合并后反而引入误检。我一般用 0.2,在速度和精度之间取平衡。另外,SAHI 的推理结果导出后,建议用 5.2 的方法在验证集上跑一遍,确认 mAP 确实涨了再上生产。希望帮到你。
本文还有配套的精品资源,点击获取