简介:采用Pascal VOC与YOLO双格式标注的芒果害虫检测数据集,内容覆盖10个常见害虫类别,包括象鼻虫、甲虫、蝗虫、粉蚧、蛾类、叶蜂、蛞蝓、茎蛀虫、黄蜂等,适用于目标检测模型训练与农业病虫害识别研究,尤其适合具备YOLO、Faster R-CNN等基础的中高级学习者。压缩包总计2000个文件,以1999个XML标注文件为主体,含TXT说明文档,整体体积约191MB,结构清晰便于直接导入训练流程。资源融合VOC与YOLO两种标注规范,既可用于Pascal VOC格式模型,也可切换至YOLO txt格式训练;配合说明文件,可快速完成数据划分、类别映射与模型迭代。对希望将标准标注格式落地到实际训练流程的读者而言,能省去格式转换与整理标注文件的步骤。目前已有224人浏览学习,是芒果虫害智能检测项目较为实用的数据集选择。
1. 把芒果害虫检测数据集用起来:VOC与YOLO双格式背后的真实价值
芒果园里的虫害监测,落到工程上最先卡住的往往不是模型选型,而是没有能直接喂进训练脚本的标注数据。这个芒果害虫检测数据集一边给的是 Pascal VOC 的 xml,一边给的是 YOLO 的 txt,3575 张 jpg 对应 3575 个 xml、3575 个 txt,10 个害虫类别从象甲、叶蝉到蛀茎虫都有覆盖。对做农业视觉的工程师来说,拿到手省掉最枯燥的格式转换环节,可以直接把图片路径和标签路径接进训练代码。新手能拿它熟悉 VOC 和 YOLO 两套标注体系怎么对应,熟手则可以省下整理数据的时间,把精力放在类别不均衡、小目标漏检这些真正影响精度的位置上。
2. 先摸清资源底细:解压 7z 之后该看什么
2.1 解压 7z 并确认图片、XML、TXT 三者对应关系
资源压缩包是 7z 格式,我一般先看压缩包里有没有目录结构,再决定解压方式。Linux 环境下最常用的是 p7zip 自带的命令行工具,解压命令很直接:
7z x firc_mangopets.7z -r如果不确定压缩包内结构,可以先列出内容:
7z l firc_mangopets.7z | head -50x代表解压并保留完整目录结构,-r是递归处理子目录,对于这种图片和标注混排的数据集能避免目录丢失。Windows 下用 7-Zip 图形界面右键解压即可,注意解压路径不要带中文,否则后续 Python 脚本读路径时容易碰到编码问题。
解压完成后,目录里能同时看到三类文件:jpg 图片、xml 标注和 txt 标注。关键的对应关系是文件名前缀一致,比如firc_mangopets_1152.jpg对应firc_mangopets_1152.xml和firc_mangopets_1152.txt。这种命名机制意味着后续做训练集、验证集划分时,只需要操作文件名前缀列表,不需要逐个复制文件。
2.2 看懂 VOC 格式的 XML 标签写法
Pascal VOC 的 xml 文件记录的是目标在图片里的绝对坐标,单位是像素。打开一个 xml 可以看到<size>节点里存着图片宽高,<object>节点里存着类别名称和<bndbox>的四个坐标值:xmin、ymin、xmax、ymax。
<annotation> <filename>firc_mangopets_1152.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>beetle</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>200</xmax> <ymax>160</ymax> </bndbox> </object> </annotation>这种标注方式对人不友好但对调试友好,比如想直观确认某个框是否标错,直接在图片上画矩形即可。xml 里的<name>存的是类别名称字符串,而不是类别编号,所以当你要转成 COCO 或 YOLO 格式时,必须建立类别名称到索引的映射表,这个映射表一旦错位,训练出来的模型就会把甲虫认成蛾子。
2.3 YOLO 格式的 TXT 为什么不带路径
YOLO 训练时使用 txt 格式标注,每行对应一个目标,格式是class_id x_center y_center width height,这里四个坐标值全部做了归一化,用图片宽高去除。比如一张 640x480 的图中,某个 beetle 的xmin=120, ymin=80, xmax=200, ymax=160,换算成 YOLO 格式就是:
0 0.25 0.25 0.125 0.166666计算过程:x_center = (120 + 200) / 2 / 640 = 0.25,y_center = (80 + 160) / 2 / 480 = 0.25,width = (200 - 120) / 640 = 0.125,height = (160 - 80) / 480 = 0.166666。
摘要里特意提到“不包含分割路径的 txt 文件”,意思是压缩包里的 txt 只存标注数据,不含图片路径信息,路径信息要靠训练脚本自己拼。这样做的好处是目录可以随便移动,不需要像某些数据集那样改一遍路径。坏处是如果你直接把整个目录挪到别处,而 data.yaml 里的路径没同步修改,训练会报找不到图片。
3. 把类别清点明白:10 类害虫的名字、索引与统计脚本
3.1 10 个类别名称与索引映射
数据集的 10 类分别是Weevil、beetle、grasshopper、mango_hopper、mango_mealybug、moth、sawfly、slug、stem_borer、wasp。名称里带mango_前缀的是芒果园里的主要害虫,比如芒果跳盲蝽和芒果粉蚧,另外几个是广义农业害虫。做映射时我习惯固定一个顺序文件,避免每次启动训练都重新排列:
class_names = [ "Weevil", "beetle", "grasshopper", "mango_hopper", "mango_mealybug", "moth", "sawfly", "slug", "stem_borer", "wasp", ] class_to_id = {name: idx for idx, name in enumerate(class_names)} print(class_to_id)这段代码把类别名称映射到 0~9 的索引,YOLO 训练时类别编号必须与这里完全一致。一个容易被忽略的细节是enumerate从 0 开始,数据集本身没有 0 号留空的问题,但如果你之前用过 COCO 格式的某些版本,可能已经习惯从 1 开始编号,在这里必须改回去。
3.2 统计图片数量、XML 数量、TXT 数量是否一致
拿到数据后第一件事不是开训练,而是写几行脚本确认三个数量能对齐。一个合格的检测数据集,jpg、xml、txt 数量应该完全一致,且文件名前缀一一对应。我常用下面的脚本做校验:
import os from collections import defaultdict root = "path/to/firc_mangopets" jpgs = set() xmls = set() txts = set() for f in os.listdir(root): if f.endswith(".jpg"): jpgs.add(f.rsplit(".", 1)[0]) elif f.endswith(".xml"): xmls.add(f.rsplit(".", 1)[0]) elif f.endswith(".txt"): txts.add(f.rsplit(".", 1)[0]) print("jpg count:", len(jpgs)) print("xml count:", len(xmls)) print("txt count:", len(txts)) missing_xml = jpgs - xmls missing_txt = jpgs - txts print("has jpg but no xml:", missing_xml) print("has jpg but no txt:", missing_txt)set 的去重特性天然适合这种文件名前缀统计。rsplit(".", 1)[0]的作用是从右侧切掉后缀,保留带前缀的名字。如果输出显示某个文件有 jpg 但没 xml,就要检查是不是解压过程丢失了标注文件。这个步骤虽然简单,但能避免训练到一半才发现数据缺失的尴尬。
3.3 从 XML 反推验证 TXT 坐标是否正确
VOC 转 YOLO 是常见操作,反过来用 VOC 校验 YOLO 更容易定位问题。把 xml 里的坐标读出来,再转成 YOLO 格式与 txt 内容对比,就能知道 txt 的归一化坐标是不是和 xml 一致。写一段轻量脚本来做这件事:
import xml.etree.ElementTree as ET def xml_to_yolo(xml_path, class_to_id): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") width = int(size.find("width").text) height = int(size.find("height").text) boxes = [] for obj in root.iter("object"): name = obj.find("name").text 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) x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height boxes.append((class_to_id[name], x_center, y_center, w, h)) return boxes xml_path = "firc_mangopets_1152.xml" boxes = xml_to_yolo(xml_path, class_to_id) for box in boxes: print(box)ET.parse读取 xml,root.iter("object")遍历所有目标,防止嵌套结构导致漏标。转换后的结果可以直接与对应 txt 里的每一行对比。如果发现某个框的类别名称不在映射表里,代码会直接抛出 KeyError,这种报错越早出现越好,说明数据里存在预定义类别之外的标签,需要重新清洗。
3.4 类别不均衡怎么看
农业害虫数据天然类别不均衡,芒果粉蚧可能大量出现,而茎蛀虫可能只有零星几张。统计类别分布能提前预判训练结果倾向。做法是先读全部 txt,统计每个类别编号出现次数:
import os from collections import Counter root = "path/to/firc_mangopets" counter = Counter() for f in os.listdir(root): if not f.endswith(".txt"): continue with open(os.path.join(root, f)) as fh: for line in fh: cls_id = int(line.strip().split()[0]) counter[cls_id] += 1 for cls_id in range(len(class_names)): name = class_names[cls_id] print(f"{name}: {counter[cls_id]}")看到分布之后,再决定要不要做类别重加权。常见的做法是给样本少的类别加大损失权重,或者在增强时对少样本类别做过采样。不要一上来就做随机裁剪和翻转,先把样本数量的账算明白。
4. 训练前的标注排查:五个最容易翻车的点
4.1 图片与标注文件不匹配,训练进程直接崩溃
现象:epoch 跑到一半报错AssertionError: The number of images does not match the number of labels,或者某个 batch 的 target 全空。
原因:目录里存在无标注的图片,或标注文件里存在没有对应图片的 txt,YOLO 训练脚本会默认两边数量一致。
解决:在划分数据集之前先执行 3.2 节的校验脚本,把不一致的文件单独移到quarantine目录,不要直接删。有些图片可能是真的漏标,删了就少一份样本;移出去至少保住了原始数据。
4.2 类别顺序错位,模型把甲虫学成了象甲
现象:训练曲线一切正常,但验证时几个类别的 precision 很低且互相混淆,尤其是 beetle 和 Weevil 这种外形接近的类别。
原因:txt 里的类别编号是固定的 0~9,但 data.yaml 里重新定义了顺序,或者你换用其他框架时又手动改了映射。VOC 的 xml 存的是字符串名字,txt 存的是数字索引,中间任何一步映射不一致都会造成标签错位。
解决:训练前加一条校验,随机抽 5 张图,把 txt 中的类别编号映射回名称,画在原图上人工核对。看到框上标的类别名称和判断一致,再放开批量训练。
4.3 坐标归一化后出现零宽零高的框
现象:训练日志中 loss 偶尔出现 NaN,用可视化脚本检查某几张图时,有的框宽度为 0。
原因:某些 xml 的xmin和xmax相同,或ymin等于ymax,这种退化框转成 YOLO 格式时 width 或 height 为 0,模型计算损失时分母变成零。
解决:在转换脚本里增加过滤条件,凡xmax - xmin <= 0或ymax - ymin <= 0的标注直接跳过,并记录到单独的bad_boxes.txt,方便后续回头检查标注源文件。
4.4 数据划分时不加随机种子,训练结果每次都不一样
现象:同一条配置,连续跑两次,最终 mAP 波动 2% 以上,你开始怀疑是模型玄学。
原因:训练集、验证集划分用的是random.sample但没有固定 seed,每次划分内容不同,模型训练遇到的数据分布自然不同。
解决:划分脚本里加random.seed(42),保持微调实验时训练集验证集恒定不变,这样才能对比不同超参数的真实效果。
4.5 解压路径带中文或空格,OpenCV 读不到图片
现象:训练程序启动正常,但cv2.imread返回空数组,日志里出现大量Could not read警告。
原因:Windows 下 opencv 的imread对中文路径支持不完整,解压目录带“芒果害虫”这类中文名就会读图失败。
解决:把整个数据集路径改成纯英文,例如E:/datasets/mango_pests_det/,同时数据集内部文件名不要改,保持firc_mangopets_前缀不变。这个问题看起来低级,但几乎每个项目都要踩一次。
5. 接到 YOLOv8 里训练:目录组织、data.yaml 与关键参数
5.1 把数据集整理成 YOLO 标准目录结构
YOLOv8 官方脚本期望的数据结构是images/train、images/val、labels/train、labels/val四层。现在数据集是平铺的,需要按比例拆分并移动文件。我习惯先写一个划分脚本:
import os import shutil import random random.seed(42) src_root = "path/to/firc_mangopets" dest_root = "dataset/mango_pests" train_ratio = 0.8 for split in ["train", "val"]: os.makedirs(f"{dest_root}/images/{split}", exist_ok=True) os.makedirs(f"{dest_root}/labels/{split}", exist_ok=True) files = [f for f in os.listdir(src_root) if f.endswith(".jpg")] random.shuffle(files) train_num = int(len(files) * train_ratio) train_files = files[:train_num] val_files = files[train_num:] for split, file_list in [("train", train_files), ("val", val_files)]: for jpg in file_list: stem = jpg.replace(".jpg", "") jpg_src = os.path.join(src_root, jpg) txt_src = os.path.join(src_root, stem + ".txt") jpg_dst = os.path.join(dest_root, "images", split, jpg) txt_dst = os.path.join(dest_root, "labels", split, stem + ".txt") shutil.copy(jpg_src, jpg_dst) shutil.copy(txt_src, txt_dst)random.seed(42)保证每次运行划分结果一致。train_ratio = 0.8表示 80% 用于训练、20% 用于验证,对 3575 张图来说验证集约 715 张,分布合理。使用shutil.copy而不是move,保留原始数据一份,划分错误时还能重新来。如果你要跑交叉验证,可以改成 5 折循环,把train_num按比例调低。
5.2 编写 data.yaml,注意路径别写错
YOLOv8 的配置只认两种路径形式:绝对路径,或者相对yaml文件所在目录的路径。最稳妥的方案是把data.yaml放在dataset/mango_pests下,然后写相对路径:
path: . train: images/train val: images/val names: 0: Weevil 1: beetle 2: grasshopper 3: mango_hopper 4: mango_mealybug 5: moth 6: sawfly 7: slug 8: stem_borer 9: wasp这里的path: .表示 yaml 文件所在目录就是数据根目录,train和val直接写相对路径。注意names的顺序必须与 3.1 节里的class_to_id保持一致,如果这里把 beetle 放成 0 而 txt 里 0 号是 Weevil,训练的模型会完全学歪。
5.3 启动训练:从默认参数开始再逐步调
YOLOv8 安装完成之后,命令行训练非常简单:
yolo detect train \ data=dataset/mango_pests/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0model=yolov8s.pt是使用 COCO 预训练权重热启动,能显著提升收敛速度。imgsz=640是默认值,对芒果害虫这种小目标较多的场景,我一般先跑 640,看结果再决定是否上 768 或 1024。batch=16取决于显卡显存,8G 显存跑 16 问题不大,4G 显存就降到 8 或 4。训练过程中重点看val/box_loss和mAP50-95曲线的趋势,如果 mAP 长时间不涨,首先怀疑类别顺序有没有写错。
提示:不要一开始就加 mosaic、mixup 这些增强,先拿原始数据跑通流程,确认数据集本身没有坑,再加增强调精度。
5.4 训练结果日志里看什么
训练结束后,runs/detect/train目录下会生成results.png和confusion_matrix.png。我通常先看验证集上的混淆矩阵,焦点是 beetle 和 Weevil 之间有没有系统性混淆,以及stem_borer这类少样本类别是不是被全部预测成背景。results.png里的mAP50值如果达到 0.7 以上,说明这个数据集训练出的模型具备基本可用性。
5.5 小目标漏检怎么调
芒果害虫里不少类别在照片上占比很小,比如叶片上的蚧虫可能只有十几个像素。遇到这种情况,我一般分三步走:第一步把imgsz调到 768,增加输入分辨率;第二步检查数据增强,看是否可以把 mosaic 的拼接概率调低,因为 mosaic 会拉伸小目标,反而让尺寸失真;第三步如果效果还不行,再换用 YOLOv8 自带的SAHI切片推理工具,把大图切成小块分别检测再拼回原图。不要一开始就换模型架构,先把数据侧参数试完。
6. 一个验证技巧:训练前花十分钟可视化抽查,比调一周参数都管用
有人会觉得可视化抽查不如多调几个 epoch 实在,但这个习惯帮我避过了好几次标注错位。做法很简单:从训练集随机抽 20 张图,把真实标注框画上去,再跑一次模型预测,把两者的框叠在一起对比。
我常用的一段可视化脚本长这样:
import cv2 import numpy as np def draw_yolo_label(image, txt_path, class_names): h, w = image.shape[:2] with open(txt_path) as f: for line in f: parts = line.strip().split() cls_id = int(parts[0]) x_center = float(parts[1]) * w y_center = float(parts[2]) * h box_w = float(parts[3]) * w box_h = float(parts[4]) * h x1 = int(x_center - box_w / 2) y1 = int(y_center - box_h / 2) x2 = int(x_center + box_w / 2) y2 = int(y_center + box_h / 2) cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(image, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return image img_path = "dataset/mango_pests/images/val/firc_mangopets_1816.jpg" txt_path = "dataset/mango_pests/labels/val/firc_mangopets_1816.txt" image = cv2.imread(img_path) image = draw_yolo_label(image, txt_path, class_names) cv2.imwrite("check.jpg", image)cv2.rectangle传入的坐标是绝对像素值,所以把 YOLO 的归一化坐标先乘回宽高,再做减半加半操作,这样框才能落在正确位置。cv2.putText里字号设置为 0.6,图片较小时文字不会糊成一片。输出check.jpg后拿到看图软件里扫一眼,重点检查框有没有明显偏移、类别名称是否合理。
有一次我抽查时发现某张图里的标注框比整只虫子大了一圈,位置倒是没偏,但框内包含了大量背景。这种标注如果进入训练,会让模型学到的目标边界松弛。手动修正了几十张后重跑,mAP50 直接提升近 3 个百分点。从那以后,我每次拿到新数据集都会强制自己先跑一遍可视化抽查再进训练流程。别嫌麻烦,十分钟的抽查能省下后面几轮无效训练的时间。
希望这份操作记录能帮你在用这个芒果害虫检测数据集时少走弯路。
本文还有配套的精品资源,点击获取