简介:YOLO格式的农作物害虫检测数据集,面向计算机视觉入门者与农业智能应用开发者,解决害虫检测任务中数据标注与格式转换的痛点。数据集涵盖蝗虫、苍蝇、水果蛾等4类常见害虫,图像为640×640高分辨率RGB图,每张含多个完整边界框,采用YOLO相对坐标标注,且已按YOLOv5目录结构划分好训练集与验证集,下载后可直接用于模型训练。资源包共2000个文件,含1999个txt标注文件与1个Python可视化脚本,压缩包大小约68.29MB;脚本可对任意输入图片绘制边界框,直观预览标注效果。已有687人学习下载。除了完整的数据集划分与类别文件,这份资源还提供开箱即用的可视化脚本,无需修改代码即可快速检查标注质量,适合研究者、开发者直接接入农业害虫检测项目,显著节省数据预处理时间。
1. 先看这个数据集到底卡在哪个环节
做目标检测的人大多有过这种经历:模型结构写得再漂亮,训练时发现数据集标注错位、类别文件缺失、图片尺寸不统一,光是清洗数据就耗掉半天。农业场景的害虫检测尤其如此——田间拍摄的图片背景复杂,害虫尺寸小、姿态多变,如果数据本身没有按 YOLO 的目录规范整理,后面每一步都在还债。这个数据集的价值在于它是"拆好了"的:训练集 2859 张、验证集 266 张,图片统一为 640×640 的 RGB,标注文件是 YOLO 相对坐标,带着类别 class 文件和可视化脚本,拿到手就能跑。它适合两类人:一是刚学 YOLO 系列、想找个干净数据集走通训练全流程的初学者;二是做农业植保或智慧农业项目、需要快速验证模型的工程师。与其自己爬图、标注、清洗,不如先基于这套数据把流程跑通。
2. 目录结构与 YOLO 标签格式:先弄懂数据怎么组织
2.1 YOLOv5 标准目录的对应关系
拿到数据集后,第一步是确认目录结构是否真的符合 YOLOv5 的预期。YOLOv5 训练时默认读取datasets根目录下的images和labels两个子目录,且要求图片与标注文件一一对应,文件名前缀必须一致。这个数据集的结构大致如下:
datasets/ ├── images/ │ ├── train/ # 2859 张 .jpg │ └── val/ # 266 张 .jpg/.jpeg ├── labels/ │ ├── train/ # 2859 个 .txt │ └── val/ # 266 个 .txt ├── classes.txt # 4 个类别 └── show.py # 可视化脚本提示:YOLOv5 的
datasets目录期望的是images和labels平行结构,类别文件实际上不需要放在 datasets 里,训练时通过data.yaml指定类别名即可。
这里有个容易踩坑的细节:很多第三方标注工具导出的是_jpg.rf.xxx.txt这类带随机后缀的文件名。比如LocustImage13_jpg.rf.b8e1781004ea27fc5a4b3b15f3e8e67a.txt,对应的图片必须是LocustImage13_jpg.rf.b8e1781004ea27fc5a4b3b15f3e8e67a.jpg。如果图片文件名和标签文件名不匹配,YOLOv5 训练时会静默跳过,导致实际参与训练的图片变少。建议拿到数据后先跑一遍脚本核对:
import os img_dir = 'datasets/images/train' label_dir = 'datasets/labels/train' img_names = {os.path.splitext(f)[0] for f in os.listdir(img_dir)} label_names = {os.path.splitext(f)[0] for f in os.listdir(label_dir)} print(f'图片数: {len(img_names)}, 标签数: {len(label_names)}') print(f'有图无标签: {len(img_names - label_names)}') print(f'有标签无图: {len(label_names - img_names)}')这段脚本的原理很直接:利用splitext去掉扩展名,用集合的差集找出不匹配的文件。对于有 2859 张图的数据集,跑完应该输出两个数字都是 0。如果出现数量对不上的情况,先用这个脚本定位是哪些文件出了问题。
2.2 YOLO 格式的五列数值到底代表什么
标注文件每一行代表一个目标,格式为:
class_id x_centre y_centre width height类别 ID 从 0 开始计数。坐标和宽高都是相对值,即除以图片宽高后的归一化结果,范围在 0 到 1 之间。拿一个具体的标注文件举例:
0 0.628906 0.335938 0.085938 0.101563 1 0.398438 0.719204 0.062500 0.087121 2 0.588281 0.765625 0.103125 0.125000需要注意两个数值上的坑:一是x_centre + width/2可能超过 1,这说明边界框超出图片右边界,训练时不会报错但会降低 AP;二是width或height为 0,这是标注错误,会导致 loss 变成 NaN。可以用下面的代码做一次体检:
import os import numpy as np label_dir = 'datasets/labels/train' issues = [] for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: issues.append((fname, '列数不为5')) continue cls, x, y, w, h = float(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if w == 0 or h == 0: issues.append((fname, '宽或高为0')) if x + w/2 > 1 or y + h/2 > 1: issues.append((fname, '边界框超出图片边界')) print(f'共发现 {len(issues)} 个问题') for issue in issues[:10]: print(issue)这段脚本遍历所有标签文件,逐行解析五个数值,检查列数、宽高是否为 0、边界框是否越界。issues列表里记录文件名和问题类型,打印前 10 条即可快速定位数据质量问题。如果是新项目自己标注数据,这一步应该作为标准流程固定下来。
2.3 classes.txt 与 data.yaml 的对应关系
数据集的classes.txt文件内容为 4 个类别名称,每行一个。训练时需要一个data.yaml文件来关联图片路径和类别名,典型内容如下:
train: datasets/images/train val: datasets/images/val nc: 4 names: ['Locust', 'GallFly', 'FruitMoth', 'Other']nc表示类别数,names的顺序必须与标注文件里的 class_id 对应。如果类别顺序写反,模型训练出来的预测结果就会张冠李戴——检测框位置是对的,但类别标签全错位。建议从classes.txt读取类别名,用脚本自动生成data.yaml,避免手写出错。
3. show.py 可视化脚本:验证标注质量的关键工具
3.1 脚本设计逻辑解析
数据集附带了一个可视化脚本show.py,核心功能是读取一张图片和对应的 txt 标注文件,绘制边界框和类别名称后保存到当前目录。整个流程分为几步:读取图片、解析标签文件、逐框绘制、保存结果。用 OpenCV 实现的话,绘制部分的核心逻辑如下:
import cv2 import os img_path = 'datasets/images/val/FruitMothImage240_jpeg.rf.287a04aab016439844b33545f0b1f4e1.jpg' label_path = img_path.replace('images', 'labels').replace('.jpg', '.txt') # 如果图片是 .jpeg 结尾,需要额外处理 label_path = os.path.splitext(label_path)[0] + '.txt' img = cv2.imread(img_path) h, w = img.shape[:2] colors = [(0, 0, 255), (0, 255, 0), (255, 0, 0), (0, 255, 255)] class_names = ['Locust', 'GallFly', 'FruitMoth', 'Other'] with open(label_path) as f: for line in f: cls_id, x_c, y_c, bw, bh = map(float, line.strip().split()) x1 = int((x_c - bw/2) * w) y1 = int((y_c - bh/2) * h) x2 = int((x_c + bw/2) * w) y2 = int((y_c + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors[int(cls_id)], 2) cv2.putText(img, class_names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors[int(cls_id)], 2) cv2.imwrite('visualized_result.jpg', img) print(f'可视化结果已保存,图片尺寸: {w}x{h}')代码里的核心逻辑是坐标转换:YOLO 格式存的是归一化后的中心点和宽高,要转成像素坐标才能用 OpenCV 绘制。x1 = (x_c - bw/2) * w把相对坐标还原成左上角像素坐标,x2 = (x_c + bw/2) * w还原右下角。cv2.rectangle的参数分别是图片、左上角坐标、右下角坐标、颜色、线宽。
3.2 实际运行与常见问题排查
脚本在项目根目录下直接运行即可:
python show.py如果脚本默认读取的是固定路径的图片,比如写死的datasets/images/train/xxx.jpg,那当前目录下会生成一个带边界框的新图片。这里有几个常见问题值得留意:
一是 .jpg 和 .jpeg 扩展名的差异。数据集中同时存在.jpg和.jpeg文件,如果脚本用.replace('.jpg', '.txt')做路径替换,遇到.jpeg文件时替换不生效,标签文件路径就错了。正确做法是用os.path.splitext先分离扩展名,再拼接.txt。
二是 OpenCV 默认读取通道顺序是 BGR,如果数据集图片里有彩色的害虫(比如绿色的蝗虫),绘制出来的边界框颜色可能和预期有偏差,但这对验证任务不影响,不用额外处理。
三是 GTK 版本报错。有些环境跑cv2.imshow会报 GTK 相关的错,这个脚本用的是cv2.imwrite而不是imshow,正好避开了这个问题,也比较适合在远程服务器上跑。
提示:如果可视化后发现某个类别的框特别多或者特别少,大概率是类别不均衡,而不是标注错误。可以先跑一遍统计再下结论。
3.3 把可视化脚本改造成批量检查工具
单张可视化只能确认格式对不对,没法整体评估标注质量。做项目时我会把它改造成批量版本,随机抽 100 张图,画完框后拼接成一个大图方便快速浏览:
import cv2 import os import random img_dir = 'datasets/images/train' label_dir = 'datasets/labels/train' sample_count = 100 all_imgs = [f for f in os.listdir(img_dir) if f.endswith(('.jpg', '.jpeg'))] sample = random.sample(all_imgs, min(sample_count, len(all_imgs))) thumbnails = [] for fname in sample: img = cv2.imread(os.path.join(img_dir, fname)) h, w = img.shape[:2] label_path = os.path.join(label_dir, os.path.splitext(fname)[0] + '.txt') with open(label_path) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id, x_c, y_c, bw, bh = map(float, parts) x1 = int((x_c - bw/2) * w) y1 = int((y_c - bh/2) * h) x2 = int((x_c + bw/2) * w) y2 = int((y_c + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) thumbnails.append(cv2.resize(img, (320, 320))) # 拼成 10x10 网格 grid = [] for row in range(10): grid_row = cv2.hconcat(thumbnails[row*10:(row+1)*10]) grid.append(grid_row) grid_img = cv2.vconcat(grid) cv2.imwrite('batch_visualization.jpg', grid_img)这段代码的逻辑是:先随机抽 100 张图,逐张绘制边界框,缩放成统一的 320×320 缩略图,再用vconcat和hconcat拼成 10×10 的网格图。缺点是如果原始图片宽高比差异大,缩放后会变形,但对检查标注是否对齐到目标边缘已经足够。批量可视化比单张检查能更快发现系统性标注问题,比如某一类害虫的框普遍偏大或偏小。
4. 训练前的数据体检与增强:别急着 train
4.1 类别分布统计:知道模型会偏向谁
一个常见误区是拿到数据先训练,跑完看 mAP 不理想才开始找原因。其实类别不均衡问题在训练前就可以发现。统计每个类别的目标数量用下面的脚本:
import os from collections import Counter label_dir = 'datasets/labels/train' class_counter = Counter() box_count = 0 for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) class_counter[cls_id] += 1 box_count += 1 total_images = len(os.listdir(label_dir)) print(f'图片总数: {total_images}') print(f'边界框总数: {box_count}') print(f'平均每张图片目标数: {box_count / total_images:.2f}') print('各类别目标数:') for cls_id in sorted(class_counter.keys()): ratio = class_counter[cls_id] / box_count * 100 print(f' 类别 {cls_id}: {class_counter[cls_id]} 个 ({ratio:.1f}%)')训练集 2859 张图对应 2859 个标签文件,每张图的标注目标数量不一。如果统计发现某一个类别的目标数明显偏少,比如不到另一个类别的十分之一,那么模型训练时梯度更新会被多数类主导,少数类的 AP 会明显偏低。这时需要考虑针对性处理,而不是直接调模型。
提示:YOLO 的损失函数是综合分类损失、置信度损失和定位损失的加权和。当一个类别的样本太少时,定位损失里该类别的贡献可忽略不计,模型对少数类边界框的回归精度会显著下降。
4.2 基于数据增强的类别均衡策略
针对类别不均衡,常见的处理手法有离线复制少数类样本到训练集里复制几份,或者做在线增强。YOLOv5 自带的在线增强已经比较强大,包括马赛克增强、随机仿射变换、HSV 色域扰动等。但如果是复制少数类样本,需要注意数据泄漏的风险。合理的增强策略应该这样设计:
# augment.py - 对类别不均衡做针对性增强 import cv2 import os import numpy as np label_dir = 'datasets/labels/train' img_dir = 'datasets/images/train' target_class = 3 # 需要增强的少数类 # 找出包含目标类别的图片 target_imgs = [] for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: lines = f.readlines() for line in lines: if line.startswith(str(target_class)): target_imgs.append(fname) break print(f'包含类别 {target_class} 的图片数: {len(target_imgs)}') # 对每张图做水平翻转并更新标签 output_dir = 'datasets/images/train_augmented' os.makedirs(output_dir, exist_ok=True) for fname in os.listdir(img_dir): label_path = os.path.join(label_dir, os.path.splitext(fname)[0] + '.txt') if not os.path.exists(label_path): continue with open(label_path) as f: labels = [line.strip() for line in f if line.strip()] has_target = any(line.startswith(str(target_class)) for line in labels) if not has_target: continue img = cv2.imread(os.path.join(img_dir, fname)) if img is None: continue h, w = img.shape[:2] flipped_img = cv2.flip(img, 1) # 翻转后 x 坐标变为 1 - x flipped_labels = [] for line in labels: parts = line.split() parts[1] = str(1 - float(parts[1])) flipped_labels.append(' '.join(parts)) base_name = os.path.splitext(fname)[0] cv2.imwrite(os.path.join(output_dir, f'{base_name}_flip.jpg'), flipped_img) with open(os.path.join(label_dir, f'{base_name}_flip.txt'), 'w') as f: f.write('\n'.join(flipped_labels)) print('增强完成')这段代码的核心逻辑是水平翻转并更新标签坐标,parts[1] = str(1 - float(parts[1]))是最关键的一行——水平翻转后,归一化 x 中心点坐标由 x 变为 1-x,如果忘了这一行,增强数据的边界框位置就全错了。这种做法对多数类不产生影响,只针对少数类做定向扩增。
4.3 小目标检测的观察方法
害虫检测场景中不少目标比较小。640×640 的图片上,一只蝗虫可能只占 30×30 像素。YOLOv5 在推理阶段有三个尺度的输出,分别对应下采样 8 倍、16 倍、32 倍的特征图。小目标主要由 8 倍下采样的特征图负责。如果你的数据集里小目标占比高,可以观察训练日志中的 P3 指标——YOLOv5 的训练输出中P3对应的是最小尺度的检测头的精确率。
从标注数据分析目标尺寸分布的代码:
import os import numpy as np label_dir = 'datasets/labels/train' sizes = [] for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue w, h = float(parts[3]), float(parts[4]) sizes.append((w, h)) sizes = np.array(sizes) area = sizes[:, 0] * sizes[:, 1] * 640 * 640 # 换算成像素面积 small = (area < 32*32).sum() medium = ((area >= 32*32) & (area < 96*96)).sum() large = (area >= 96*96).sum() print(f'小目标 (<32x32): {small} ({small/len(area)*100:.1f}%)') print(f'中目标 (32x32~96x96): {medium} ({medium/len(area)*100:.1f}%)') print(f'大目标 (>96x96): {large} ({large/len(area)*100:.1f}%)')这里的换算逻辑是:标注文件里的 w 和 h 是相对值,乘以 640 得到像素尺寸。COCO 的评价协议以小目标为 32×32 以下,中目标为 96×96 以下。如果小目标占比超过三四成,训练时应该考虑使用更高的输入分辨率,或调整 anchor 尺寸,而不是直接改损失函数。YOLOv5 在models/yolov5s.yaml里的anchors是 COCO 数据集上的聚类结果,害虫数据集上的目标尺寸分布与 COCO 差异明显时,建议用 k-means 重算 anchor。
5. 边界框的精度验证:把可视化当标尺
5.1 框是否贴合目标的判断标准
可视化脚本有一个隐含用途:检查边界框是否紧贴目标边缘。做数据检查时我一般按这样的标准判断标注质量:边界框与目标边缘的间隙小于目标尺寸的 5% 算合格;框超过目标边缘的 10% 以上说明标注膨胀。如果数据集里有大量这样的膨胀框,训练时 IoU 阈值取 0.5 可能不敏感,但 mAP 计算时会受到影响。
可以用代码批量计算边界框在图片上占的面积比,快速判断是否有异常数据:
import os img_dir = 'datasets/images/train' label_dir = 'datasets/labels/train' for fname in os.listdir(img_dir): base = os.path.splitext(fname)[0] label_path = os.path.join(label_dir, base + '.txt') if not os.path.exists(label_path): print(f'缺少标签文件: {fname}') continue with open(label_path) as f: lines = f.readlines() # 检查是否有目标超过图片面积的一半 for line in lines: parts = line.strip().split() if len(parts) != 5: continue w, h = float(parts[3]), float(parts[4]) area_ratio = w * h if area_ratio > 0.5: print(f'边界框面积异常: {fname}, 面积占比 {area_ratio:.3f}')这个脚本遍历训练集所有图片,计算每个边界框的面积占比。一张 640×640 的图片里,正常害虫目标的面积占比一般不会超过 0.3。超过 0.5 可能是标注错误或图片本身有问题,需要重点检查。
5.2 利用可视化脚本反向调整训练参数
可视化不仅用于检查标注,还能辅助调整训练参数。YOLOv5 的hyp.yaml里有一组增强参数,当可视化结果暴露某类问题时,对应的调整策略是不同的:
| 问题 | 可视化观察到的现象 | 对应调整 |
|---|---|---|
| 小目标漏检 | 小害虫边界框只有半个在目标上 | 增大img_size到 960,调小anchor_t接受更多 anchor |
| 密集场景误检 | 多只害虫的框重叠且类别相同 | 调低conf_thres,检查 NMS 的 IoU 阈值 |
| 类别混淆 | 相邻两类害虫的框交替出现在同一区域 | 检查训练集里两类样本的数量比,考虑做类别加权 |
| 定位偏移 | 边界框中心偏离害虫身体中心 | 调低box_loss系数,调高cls_loss |
注意:
hyp.yaml里的box和cls系数是损失函数的权重,不是固定不变的。如果你发现模型对害虫的定位整体偏差,可以尝试把box系数调小而不是调大,这听起来反直觉,但实际效果取决于各类别标注质量的对比。
5.3 把可视化脚本积累成数据集维护习惯
做这类数据集项目最大的体会是:可视化脚本其实是数据集健康度的晴雨表。每隔几天跑一次批量可视化,对比之前的结果,能发现很多训练日志里看不出来的问题。我自己常用的做法是每周跑一次 100 张图的抽样可视化,保存成带日期的文件名,连续几周对比,观察有没有新增的标注问题或者数据分布漂移。
这个数据集附带的show.py是一个起点,建议在它的基础上增加两个小功能:一是把可视化结果里的类别名显示改成class_id + 类别名的形式,便于快速对照类别文件;二是支持通过命令行参数传入图片路径,而不是改代码里的常量。改完后,整个数据检查流程就变成了一条命令行工具链。
python show.py --img datasets/images/val/LocustImage13_jpg.rf.b8e1781004ea27fc5a4b3b15f3e8e67a.jpg --label datasets/labels/val/LocustImage13_jpg.rf.b8e1781004ea27fc5a4b3b15f3e8e67a.txt --output check.jpg数据集的真实价值不在于有多少张图,而在于你能否快速判断它是否适合当前任务、模型训练完能否解释得清楚表现好坏。把可视化、统计、校验这些日常工作沉淀成习惯,比收集更多数据集更能提升项目的可维护性。
本文还有配套的精品资源,点击获取