YOLOv5害虫检测数据集:结构、标注与可视化全解析

发布时间:2026/9/12 9:47:03
YOLOv5害虫检测数据集:结构、标注与可视化全解析 简介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:(row1)*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. 训练前的数据体检与增强别急着 train4.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_okTrue) 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数据集的真实价值不在于有多少张图而在于你能否快速判断它是否适合当前任务、模型训练完能否解释得清楚表现好坏。把可视化、统计、校验这些日常工作沉淀成习惯比收集更多数据集更能提升项目的可维护性。本文还有配套的精品资源点击获取