
简介采用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 的 txt3575 张 jpg 对应 3575 个 xml、3575 个 txt10 个害虫类别从象甲、叶蝉到蛀茎虫都有覆盖。对做农业视觉的工程师来说拿到手省掉最枯燥的格式转换环节可以直接把图片路径和标签路径接进训练代码。新手能拿它熟悉 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 filenamefirc_mangopets_1152.jpg/filename size width640/width height480/height depth3/depth /size object namebeetle/name bndbox xmin120/xmin ymin80/ymin xmax200/xmax ymax160/ymax /bndbox /object /annotation这种标注方式对人不友好但对调试友好比如想直观确认某个框是否标错直接在图片上画矩形即可。xml 里的name存的是类别名称字符串而不是类别编号所以当你要转成 COCO 或 YOLO 格式时必须建立类别名称到索引的映射表这个映射表一旦错位训练出来的模型就会把甲虫认成蛾子。2.3 YOLO 格式的 TXT 为什么不带路径YOLO 训练时使用 txt 格式标注每行对应一个目标格式是class_id x_center y_center width height这里四个坐标值全部做了归一化用图片宽高去除。比如一张 640x480 的图中某个 beetle 的xmin120, ymin80, xmax200, ymax160换算成 YOLO 格式就是0 0.25 0.25 0.125 0.166666计算过程x_center (120 200) / 2 / 640 0.25y_center (80 160) / 2 / 480 0.25width (200 - 120) / 640 0.125height (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)这段代码把类别名称映射到 09 的索引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读取 xmlroot.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 全空。原因目录里存在无标注的图片或标注文件里存在没有对应图片的 txtYOLO 训练脚本会默认两边数量一致。解决在划分数据集之前先执行 3.2 节的校验脚本把不一致的文件单独移到quarantine目录不要直接删。有些图片可能是真的漏标删了就少一份样本移出去至少保住了原始数据。4.2 类别顺序错位模型把甲虫学成了象甲现象训练曲线一切正常但验证时几个类别的 precision 很低且互相混淆尤其是 beetle 和 Weevil 这种外形接近的类别。原因txt 里的类别编号是固定的 09但 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_okTrue) os.makedirs(f{dest_root}/labels/{split}, exist_okTrue) 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 \ datadataset/mango_pests/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0modelyolov8s.pt是使用 COCO 预训练权重热启动能显著提升收敛速度。imgsz640是默认值对芒果害虫这种小目标较多的场景我一般先跑 640看结果再决定是否上 768 或 1024。batch16取决于显卡显存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 个百分点。从那以后我每次拿到新数据集都会强制自己先跑一遍可视化抽查再进训练流程。别嫌麻烦十分钟的抽查能省下后面几轮无效训练的时间。希望这份操作记录能帮你在用这个芒果害虫检测数据集时少走弯路。本文还有配套的精品资源点击获取