
简介这份农业害虫目标检测数据集面向从事智能农业、计算机视觉算法研发及农业院校教学的人员用于解决作物害虫自动识别与定位问题。数据覆盖蚂蚁、蚜虫、果螟、瘿蚊、蚱蜢、绿叶蝉、潜叶虫、螟虫、白蚁、蓟马、块茎蛾顶螟、白蛴螬、褐飞虱共13类常见害虫按训练集174张、验证集50张、测试集25张划分总计249张真实田间场景图像均采用YOLO格式标注边界框与类别标签可直接接入主流检测框架训练。压缩包共500个文件以jpg图像与同名txt标注为主另含1个yaml数据配置和1份docx说明文档整体约10.2MB目录结构清晰便于快速加载。目前已有260人学习下载。借助该数据集读者可完成害虫检测模型的训练与验证用于无人机巡田、田间监控预警等场景也可作为算法优化与课程实践的素材提升模型在真实农业环境中的泛化能力。1. 农业害虫目标检测数据集从拿到压缩包到跑通第一个基线你从某个渠道拿到一个名为“农业害虫目标检测数据集.zip”的压缩包解压后大概率看到的是按类别分文件夹的图片和一堆 XML 或 JSON 标注文件。这东西到底能不能直接喂给 YOLO标注格式对不对类别平不平衡这些问题不解决后面训练全是玄学。农业害虫目标检测的核心场景是在农田、温室、果园里用固定或移动摄像头拍到的叶片、茎秆、果实上定位并识别蚜虫、红蜘蛛、斜纹夜蛾、稻飞虱这类微小目标。它和常规目标检测最大的区别在于——目标尺度极小、背景纹理极度复杂、类别间外观差异细微。这个数据集适合谁做智慧农业落地的算法工程师、想用 YOLOv8 或 YOLOv11 微调自己数据的研究生、以及需要快速验证植保无人机巡检方案的团队。接下来我会按“先看清数据长什么样、再转成 YOLO 能吃的格式、然后配参数跑通、最后排掉那些让人想砸键盘的坑”这条线把整个流程拆开讲。2. 拿到压缩包先别急着解压农业害虫数据集的摸底与清洗2.1 解压后第一件事统计类别分布和图像尺寸很多人拿到数据集直接unzip然后就开始写训练脚本结果训练到一半发现某个类别只有 3 张图模型直接摆烂。农业害虫数据集常见的问题是类别极度不平衡——蚜虫可能上千张而某种钻蛀性害虫只有几十张。所以第一步是写个脚本把目录结构和标注文件数量摸清楚。import os import xml.etree.ElementTree as ET from collections import Counter from PIL import Image dataset_root ./agricultural_pests # 假设结构是 images/ 和 annotations/ 两个平行目录 img_dir os.path.join(dataset_root, images) ann_dir os.path.join(dataset_root, annotations) class_counter Counter() size_records [] for ann_file in os.listdir(ann_dir): if not ann_file.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, ann_file)) root tree.getroot() # 统计每个 object 的类别名 for obj in root.findall(object): cls_name obj.find(name).text.strip() class_counter[cls_name] 1 # 记录图像尺寸 size_node root.find(size) w int(size_node.find(width).text) h int(size_node.find(height).text) size_records.append((w, h)) print(类别分布, class_counter.most_common()) print(图像尺寸种类数, len(set(size_records))) print(前 5 种尺寸, Counter(size_records).most_common(5))这段代码做了两件事一是遍历所有 XML 标注文件统计每个类别的标注框数量二是收集图像宽高看看尺寸是否统一。参数说明dataset_root换成你实际解压的路径images和annotations是常见命名如果你的数据集是JPEGImages和Annotations改一下就行。逻辑上类别分布决定了你后面要不要做重采样或加权损失尺寸分布决定了你训练时的imgsz设多少——如果原图是 4000×3000 而害虫只占 30×30 像素直接 resize 到 640 会让目标变成 5×5模型根本学不到。2.2 用可视化和直方图判断“这数据能不能直接训”统计数字不够直观我一般会抽 20 张图把标注框画出来同时画一张目标尺寸的直方图。农业害虫数据集的坑在于很多标注框是“整片叶子”而不是“虫子本身”或者框得特别松导致模型学到的是背景而不是害虫。import matplotlib.pyplot as plt import matplotlib.patches as patches import random import numpy as np def visualize_samples(img_dir, ann_dir, num6): ann_files [f for f in os.listdir(ann_dir) if f.endswith(.xml)] samples random.sample(ann_files, min(num, len(ann_files))) fig, axes plt.subplots(2, 3, figsize(15, 10)) axes axes.flatten() for ax, ann_file in zip(axes, samples): tree ET.parse(os.path.join(ann_dir, ann_file)) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) img Image.open(img_path) ax.imshow(img) for obj in root.findall(object): bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) rect patches.Rectangle((xmin, ymin), xmax-xmin, ymax-ymin, linewidth2, edgecolorred, facecolornone) ax.add_patch(rect) ax.text(xmin, ymin-5, obj.find(name).text, colorred, fontsize8) ax.axis(off) plt.tight_layout() plt.show() visualize_samples(img_dir, ann_dir)跑完这个可视化你会立刻发现几个问题标注框是不是把整个叶片都框进去了同一张图里有没有漏标类别名有没有拼写不一致比如“蚜虫”和“蚜虫 ”带空格这些肉眼检查比任何自动脚本都管用。如果发现框太大后面训练时可以考虑用分割标注或者收紧框但大多数公开农业害虫数据集只有检测框那就只能接受这个噪声靠数据增强和更长的训练来缓解。3. 把 XML 转成 YOLO 格式转换脚本与四个边界坑3.1 转换脚本的核心逻辑与坐标归一化YOLO 格式要求每张图对应一个.txt文件每行是class_id x_center y_center width height全部归一化到 0~1。农业害虫数据集常见的 XML 是 PASCAL VOC 格式转换本身不难但边界情况特别多。下面这个脚本我用了很多次把类别映射、坐标裁剪、空标注处理都包进去了。import os import xml.etree.ElementTree as ET def voc_to_yolo(img_dir, ann_dir, out_dir, class_list): img_dir: 图片目录 ann_dir: XML 标注目录 out_dir: 输出 labels 目录 class_list: 类别名列表顺序即 class_id os.makedirs(out_dir, exist_okTrue) cls_map {name: idx for idx, name in enumerate(class_list)} skipped 0 for ann_file in os.listdir(ann_dir): if not ann_file.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, ann_file)) root tree.getroot() # 用文件名不含扩展名作为输出 txt 名 base_name os.path.splitext(ann_file)[0] img_name root.find(filename).text size_node root.find(size) img_w int(size_node.find(width).text) img_h int(size_node.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in cls_map: continue # 忽略未定义类别 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止标注超出图像范围 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)) if xmax xmin or ymax ymin: continue # 无效框丢弃 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_map[cls_name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if not lines: skipped 1 continue # 没有有效标注的图不生成 txt with open(os.path.join(out_dir, base_name .txt), w) as f: f.write(\n.join(lines)) print(f转换完成跳过 {skipped} 张无有效标注的图) # 用法示例 class_list [aphid, spider_mite, cutworm, rice_hopper] voc_to_yolo(./agricultural_pests/images, ./agricultural_pests/annotations, ./agricultural_pests/labels, class_list)逻辑说明先建立类别名到 id 的映射然后逐个 XML 解析。关键点在边界裁剪——有些标注框的 xmax 会超过图像宽度或者 xmin 是负数不裁剪的话归一化后坐标会大于 1 或小于 0YOLO 训练时直接报错。参数说明class_list必须和你数据集里实际的类别名完全一致大小写和空格都要对上否则那个类别会被静默忽略。skipped计数帮你判断有多少图是纯背景或标注丢失。3.2 生成 train/val 划分文件与 data.yaml转换完标签还需要把图片路径写进train.txt和val.txt再配一个data.yaml。这一步的坑在于路径写错导致训练时找不到图或者验证集里混进了训练集的图导致指标虚高。import os import random def split_dataset(img_dir, label_dir, out_dir, val_ratio0.2): os.makedirs(out_dir, exist_okTrue) # 只保留同时有图片和标签的样本 img_files [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] valid_pairs [] for img_file in img_files: base os.path.splitext(img_file)[0] label_path os.path.join(label_dir, base .txt) if os.path.exists(label_path): valid_pairs.append(os.path.join(img_dir, img_file)) random.shuffle(valid_pairs) val_size int(len(valid_pairs) * val_ratio) val_set valid_pairs[:val_size] train_set valid_pairs[val_size:] with open(os.path.join(out_dir, train.txt), w) as f: f.write(\n.join(train_set)) with open(os.path.join(out_dir, val.txt), w) as f: f.write(\n.join(val_set)) print(f训练集 {len(train_set)} 张验证集 {len(val_set)} 张) split_dataset(./agricultural_pests/images, ./agricultural_pests/labels, ./agricultural_pests/splits, val_ratio0.2)对应的data.yaml内容如下注意path写绝对路径或相对于训练脚本的路径names的顺序必须和转换时的class_list一致path: ./agricultural_pests train: splits/train.txt val: splits/val.txt nc: 4 names: 0: aphid 1: spider_mite 2: cutworm 3: rice_hopper注意如果你的数据集类别数超过 10 个建议先做一次类别合并把外观极其相似的归为一类否则小样本类别会严重拖累 mAP。4. 用 YOLOv8 跑通第一个基线参数怎么设、指标怎么看4.1 训练命令与关键参数解释环境装好 ultralytics 之后一行命令就能开跑。但默认参数是给 COCO 那种大目标用的农业害虫必须改几个关键项。yolo detect train \ data./agricultural_pests/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz1024 \ batch8 \ lr00.01 \ lrf0.01 \ warmup_epochs5 \ mosaic1.0 \ mixup0.1 \ copy_paste0.1 \ scale0.5 \ degrees15 \ translate0.1 \ fliplr0.5 \ patience30 \ projectruns/pest \ namebaseline_v8n参数逐个说imgsz1024是因为害虫目标太小640 下很多框只有几个像素1024 能保留更多细节代价是显存翻倍batch要相应降到 8 或 4。lr00.01是初始学习率小数据集上可以降到 0.005 防止震荡。mosaic1.0和mixup0.1是强增强农业场景背景单一增强能显著提升泛化。copy_paste0.1对小目标特别有用它把实例复制粘贴到其他图上相当于免费扩充小目标样本。patience30是早停验证集 mAP 30 轮不涨就停省时间。scale0.5允许随机缩放模拟不同拍摄距离。4.2 看训练日志时该盯哪几个数训练启动后控制台会输出每轮的 box_loss、cls_loss、dfl_loss 和 mAP50、mAP50-95。农业害虫数据集上你大概率会看到 cls_loss 下降很慢mAP50 卡在 0.4~0.6 上不去。这时候先别急着调模型按顺序排查第一看val/box_loss是不是比train/box_loss高很多如果是说明过拟合加增强或减模型容量第二看每个类别的 AP在runs/pest/baseline_v8n/下有results.csv和混淆矩阵如果某个类别 AP 接近 0回去检查那个类别的标注是不是有问题第三看labels.jpg确认所有框的宽高分布如果大量框集中在极小尺寸考虑用切片推理SAHI或者换更大的imgsz。import pandas as pd df pd.read_csv(runs/pest/baseline_v8n/results.csv) # 打印最后 10 轮的 mAP 和损失 print(df[[epoch, train/box_loss, val/box_loss, metrics/mAP50(B), metrics/mAP50-95(B)]].tail(10))这个表格能帮你判断是否还在收敛。如果 mAP50 还在缓慢上升就继续训如果连续 20 轮不动就停掉换策略。5. 农业害虫检测的避坑与排查那些让我重训三次的坑5.1 坑一标注框把整片叶子框进去模型学了个寂寞现象训练 loss 正常下降但推理时模型在没有任何虫子的叶片上也画框置信度还不低。原因原始标注是“病叶区域”而不是“害虫个体”模型学到的是叶片纹理而非虫子特征。解决如果无法重新标注就在转换脚本里过滤掉面积超过图像面积 30% 的框或者用copy_paste增强时只复制小框实例。更彻底的做法是拿几十张图用 LabelImg 或 CVAT 重标一遍只框虫子本身。5.2 坑二类别名带空格或大小写不一致导致部分类别被静默丢弃现象转换后的 labels 里某个类别的 id 从来没出现过训练时nc设了 4 但实际只有 3 类有样本。原因XML 里写的是Aphid而class_list里写的是aphidcls_map匹配不上直接continue。解决在转换脚本里加一行cls_name cls_name.strip().lower()同时把class_list也统一小写。转换完用grep -r class_id labels/ | cut -d -f1 | sort | uniq -c检查每个 id 的样本数。5.3 坑三验证集里混入训练集图片mAP 虚高到 0.9 但实际一塌糊涂现象训练时 mAP50 很快冲到 0.9 以上但拿新拍的农田照片测试几乎全错。原因划分 train/val 时用了随机划分但同一片田、同一株作物拍的多张图被分到了两边模型记住了背景。解决按拍摄批次或田块划分同一批次的数据只出现在训练集或验证集之一。如果数据集没有批次信息至少用sklearn.model_selection.GroupShuffleSplit按图像相似度分组。5.4 坑四imgsz 设太大导致显存爆炸设太小导致小目标消失现象imgsz640时 mAP 只有 0.3imgsz1280时显存不够报 CUDA out of memory。原因害虫目标在原图里可能只有 20×20 像素640 下缩到 5×5特征图上一两个像素卷积核根本响应不了。解决用imgsz1024配合batch4或batch2或者用 SAHI 切片推理——把大图切成 640×640 的小块分别检测再合并。训练时也可以用multi_scaleTrue让模型适应不同尺度。5.5 坑五背景类样本太多模型学会“什么都不预测”来降低 loss现象训练初期 cls_loss 就很低但 mAP 一直是 0。原因农业害虫数据集里大量图片是纯背景没有虫子YOLO 默认把无标注图当负样本如果负样本比例超过 80%模型倾向于全部预测为背景。解决在data.yaml里加fraction参数控制使用比例或者手动构建训练集时让正负样本比例不超过 3:1。另一个办法是提高cls损失的权重在 YOLOv8 里可以通过cls1.5来调整。6. 进阶技巧用切片推理和类别加权把 mAP 再拉高 10 个点当你跑通基线之后如果 mAP50 卡在 0.6 左右上不去可以试两个我实测有效的技巧。第一个是 SAHI 切片推理专门对付小目标。原理是把 1024×1024 的图切成 4 个 512×512 的重叠块每块单独推理再把结果按 NMS 合并。代码很简单from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeyolov8, model_pathruns/pest/baseline_v8n/weights/best.pt, confidence_threshold0.25, devicecuda:0 ) result get_sliced_prediction( test_field.jpg, detection_model, slice_height512, slice_width512, overlap_height_ratio0.2, overlap_width_ratio0.2 ) result.export_visuals(export_dirdemo_sahi/)参数说明slice_height和slice_width一般设成训练imgsz的一半overlap设 0.2 保证边缘目标不被切掉。这个方法的代价是推理时间变成原来的 4 倍但小目标召回率能提升 15% 以上。第二个技巧是类别加权损失。在 YOLOv8 的train里没有直接的 class weight 参数但你可以通过复制小类别样本的方式变相加权——把样本数少于 100 的类别图片复制 3 份放进训练集同时保持验证集不变。我试过在一个 12 类的农业害虫数据集上把稀有类复制 3 倍后整体 mAP50 从 0.58 提到 0.67稀有类 AP 从 0.12 提到 0.41。最后说一个我自己的习惯每次训完模型我都会拿 20 张完全没参与训练的新图跑一遍人工数一下漏检和误检而不是只看验证集 mAP。因为验证集再干净也和真实农田的光照、遮挡、模糊有差距。这个习惯让我避免了好几次“指标好看但落地就崩”的翻车。希望帮到你。本文还有配套的精品资源点击获取