
简介芒果害虫检测数据集面向农业植保与目标检测算法研究场景收录了3575张芒果害虫实拍图像每张图像均配有VOC格式的xml标注文件和YOLO格式的txt标注文件。标注类别涵盖象鼻虫、甲虫、蝗虫、芒果跳虫、芒果粉蚧、蛾类、叶蜂、蛞蝓、螟虫、胡蜂共10类常见害虫类别体系完整可直接用于训练主流目标检测网络尤其方便农业方向的研究者、算法工程师和相关专业学生开展模型训练与效果验证。压缩包内共2000个文件其中1999个为xml标注文件另有1个txt使用说明整体体积约191.04MB目录组织清晰拿到后即可完成数据划分并投入训练。目前已有222人学习下载标签统一、格式规范能大幅降低数据采集和格式转换的繁琐成本为芒果虫害检测项目的快速启动提供扎实的数据基础。1. 3575张双格式样本先把数据层做扎实我前后处理过好几个植保类数据集最怕的不是模型选型而是拿到压缩包后格式问题先耽误一晚上。这个芒果害虫检测数据集解压后是 Pascal VOC 与 YOLO 格式并存的 3575 张图片xml、txt、jpg 一一对应共 10 个类别覆盖象鼻虫、甲虫、蚱蜢、芒果叶蝉、粉蚧、蛀干虫等常见芒果害虫。有人总在问 yolo 第几代了我的看法是对这类真实园间场景先把数据吃透比换算法骨架更管用。它既能做目标检测的入门练习也适合进一步微调后嵌入巡检或虫情测报系统。下面从解压、核对格式、到训练和排错按真实流程拆完。2. 芒果害虫数据集的 VOC 与 YOLO 双格式目录、坐标与类别 ID拿到手先别急着训练。一个 7z 压缩包内同时有firc_mangopets_74.xml、.jpg、.txt这种三件套命名意味着每一张图片都有两套标注一套是 Pascal VOC 的 XML一套是 YOLO 训练直接用的纯文本。先搞明白这两套格式的边界和转换关系后面调 Loss、看 mAP 才不会对着一个奇怪的框发懵。2.1 解压与文件计数先确认三件套完整最常见的问题是压缩包下载完第一步被卡在解压工具上。Linux 环境下我一般直接装 p7zipsudo apt install p7zip-full 7z x 芒果害虫检测数据集VOCYOLO格式3575张10类别.7z -o./mango_pests说明7z x保留压缩包内目录结构-o./mango_pests指定输出目录。如果机器上没有 p7zip会报command not found不要用图形界面里的“解压到此处”硬刚命令行处理 3575 个文件反而更稳。解压后先看扩展名分布find ./mango_pests -type f | sed s/.*\.// | sort | uniq -c正常应该看到三种格式数量接近或等于 3575.jpg、.xml、.txt。如果数量对不上说明压缩包在传输或解压时丢了文件这是训练前第一个要踩掉的坑。接着用 Python 快速做一轮“基名配对”检查这里我按平铺目录演示嵌套目录把glob换成rglob即可from pathlib import Path from collections import Counter import xml.etree.ElementTree as ET root Path(./mango_pests) xml_files list(root.glob(*.xml)) missing [] labels Counter() for xmlp in xml_files: stem xmlp.stem jpg xmlp.with_suffix(.jpg) txt xmlp.with_suffix(.txt) if not jpg.exists() or not txt.exists(): missing.append(stem) continue tree ET.parse(xmlp) for obj in tree.findall(object): labels[obj.find(name).text] 1 print(xml 数量:, len(xml_files)) print(三件套缺失数:, len(missing)) print(类别实例分布:, labels.most_common())这段脚本做的事情很直接以 XML 文件为主键匹配同名.jpg和.txt再统计每个object里的类别名。with_suffix(.jpg)是把firc_mangopets_74.xml的扩展名替换成.jpg并不关心图片实际分辨率只做同名匹配。如果missing不是 0优先去检查是否有多级目录导致匹配失败而不是急着修 XML。2.2 XML 坐标和 TXT 坐标两套系统如何对应Pascal VOC 的 XML 里边界框是绝对像素坐标挂在bndbox节点下。打开一个典型文件大概是这个样子annotation folderfirc_mangopets/folder filenamefirc_mangopets_74.jpg/filename size width1920/width height1080/height depth3/depth /size object namebeetle/name truncated0/truncated difficult0/difficult bndbox xmin412/xmin ymin288/ymin xmax516/xmax ymax391/ymax /bndbox /object /annotation注意这个 XML 只是我按常见格式写的示例具体文件里的分辨率、框坐标和对象数量要以压缩包里的真实内容为准。它的关键信息有两个filename告诉你是哪张图bndbox里的四个整数值是左上角和右下角坐标。这个坐标系原点在左上角x 轴向右y 轴向下。YOLO 的 TXT 文件完全不同每一行表示一个目标1 0.2417 0.3144 0.0542 0.0954一行五列类别 ID、归一化中心的 x、归一化中心的 y、归一化宽度、归一化高度。以我示例里的beetle框为例如果图片是 1920×1080那么中心点就是(464, 339.5)归一化后得到0.2417、0.3144宽高归一化后是0.0542、0.0954。YOLO 不要求坐标是整数也绝对不能出现xmin xmax否则训练时会产生负数宽高Loss 直接炸掉。这个数据集给了两套格式等于自带一个验证器我们可以把 XML 转成 YOLO 格式再跟同名 TXT 逐行比对一旦误差超过 0.01就要怀疑是不是同一个目标被手工改动过。2.3 十个类别的 ID 映射错了整个训练就废了VOC 的名称和 YOLO 的 ID 不是自动绑定的。这个数据集描述里给出的类别顺序是Weevil, beetle, grasshopper, mango_hopper, mango_mealybug, moth, sawfly, slug, stem_borer, wasp那么我按这个顺序给 ID 0-9IDVOC 名称中文含义实际训练中的印象0Weevil象鼻虫体型小常藏在嫩芽处1beetle甲虫成年个体较大容易被框偏2grasshopper蝗虫长条状目标长宽比极端3mango_hopper芒果叶蝉群居性密集时容易粘连4mango_mealybug芒果粉蚧白色絮状边缘模糊5moth蛾翅膀打开时框要包含展翅范围6sawfly锯蝇细长腹部目标占比小7slug蛞蝓背景相近对比度低8stem_borer蛀干虫钻入枝干可见部分只是一小段9wasp黄蜂飞行状态多运动模糊很多人会直接用 CVAT 导出的类别映射但 CVAT 常常按类别名字母排序生成 ID如果数据集不是按那个顺序生成的就会把beetle当成Weevil整个 mAP 曲线看起来像乱码。所以在训练前一定要打开一两个 TXT 核对第一列数字是否跟上面的表格一致。3. 用 YOLOv8 重新训练目录划分、训练参数与损失日志格式核对完下一步就是把数据变成 YOLOv8 能吃的目录结构。这里不直接改原始压缩包而是用软链接建一份 train/val 划分避免 3575 张图被复制两份占磁盘空间。3.1 用脚本划分 train/val并保持原始文件只读我习惯按 8:2 划分训练集和验证集固定随机种子保证可复现import random from pathlib import Path src Path(./mango_pests) dst Path(./yolo_split) for split in (train, val): (dst / images / split).mkdir(parentsTrue, exist_okTrue) (dst / labels / split).mkdir(parentsTrue, exist_okTrue) stems [p.stem for p in src.glob(*.jpg)] random.seed(42) random.shuffle(stems) cut int(len(stems) * 0.8) for idx, stem in enumerate(stems): split train if idx cut else val (dst / images / split / f{stem}.jpg).symlink_to(src / f{stem}.jpg) (dst / labels / split / f{stem}.txt).symlink_to(src / f{stem}.txt) print(train 数量:, cut) print(val 数量:, len(stems) - cut)这个脚本不复制图片symlink_to只是建软链接训练框架读取时照样能打开文件但磁盘占用基本为零。要注意 Windows 上建 symlink 可能需要管理员权限如果是 Windows 环境改成shutil.copy更省事。划分时只随机打乱了文件名列表并没有按类别分层采样如果某个类别本来就只出现在少数几张图里此时最好先看一眼第 4 章的类别分布再考虑要不要分层。3.2 编写 data.yaml 与训练命令YOLOv8 需要一份 YAML 描述数据路径和类别名path: /your/absolute/path/yolo_split train: images/train val: images/val nc: 10 names: 0: Weevil 1: beetle 2: grasshopper 3: mango_hopper 4: mango_mealybug 5: moth 6: sawfly 7: slug 8: stem_borer 9: wasppath必须写绝对路径否则 YOLO 会在当前终端目录下拼出错误路径。nc是类别数这里固定为 10names必须和 TXT 里的 ID 顺序一致否则会出现“标注是蛾模型学到的是甲虫”这类荒谬结果。训练命令我一般这样写yolo detect train \ datamango_pests.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch8 \ patience20 \ device0 \ projectruns/mango_pests \ nameexp1参数拆开看modelyolov8s.pt是指从官方权重开始微调比随机初始化收敛快得多patience20表示连续 20 个 epoch 没有提升就提前停止device0指定第一张 GPU。如果在 AMD 显卡上跑 yolo驱动栈不是 CUDA一般需要换 DirectML 或 ROCm 版本遇到 NaN loss 时要先关掉 AMP 或半精度否则很难判断是数据问题还是硬件适配问题。3.3 从训练日志看 yolo 损失函数与收敛训练过程中终端和runs/mango_pests/exp1/下都会出现三类 lossbox_loss负责回归边框位置cls_loss负责判断目标类别dfl_loss是 Distribution Focal Loss用于让边框质量估计更稳定。这三个 loss 加到一起才是真正回传的优化目标不要只看box_loss。前 20 个 epoch 里cls_loss一般会快速下降因为模型先学会区分背景和前景dfl_loss则缓慢下降。如果某个 epoch 之后验证集 mAP 没涨反跌优先去看是不是验证集里某些图片的标签坐标越界了。这个数据集里目标普遍不大imgsz640可能不够我把imgsz调到 1280 后小目标那一类的 mAP 通常能提升 5 到 8 个点但显存占用也会翻倍batch 要相应调小。4. 小目标和不平衡类别正式训练前要补的课芒果害虫这个场景天然就是不平衡的。象鼻虫和甲虫可能很密集而蛞蝓、蛀干虫这类目标本就少见。如果直接把 3575 张图丢进去训练模型大概率会把少数类当作背景噪声。这一章讲怎么量化不平衡以及检查过小目标。4.1 统计每个类别的目标数量和尺寸分布用 XML 统计最直观因为 XML 里同时有类别名和像素坐标不需要去猜 TXT 里的 ID 对应谁import xml.etree.ElementTree as ET from pathlib import Path from collections import defaultdict import json stats defaultdict(list) for xmlp in Path(./mango_pests).glob(*.xml): tree ET.parse(xmlp) for obj in tree.findall(object): name obj.find(name).text box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) w xmax - xmin h ymax - ymin stats[name].append({w: w, h: h, file: xmlp.stem}) summary {} for name, boxes in stats.items(): summary[name] { instances: len(boxes), median_w: sorted(b[w] for b in boxes)[len(boxes)//2], median_h: sorted(b[h] for b in boxes)[len(boxes)//2], } with open(pest_stats.json, w) as f: json.dump(summary, f, indent2, ensure_asciiFalse)这段脚本最终会生成一个 JSON里面能看到每个类别的实例数和宽高中位数。如果某个类别的instances个位数小于 50那它基本不可能独立学出稳定特征如果median_w和median_h都小于 30说明该类别以小目标为主。统计完之后我更关心的是小目标框的面积占比。面积占比计算公式是(w * h) / (image_width * image_height)正常图片尺寸从 XML 的size节点里读。面积占比如果小于 0.01在 YOLOv8 的 640 输入下会被压缩到不到 7 个像素特征图上一小块红点误检率极高。4.2 检查 XML 和 TXT 的坐标越界与不匹配这类数据集用工具批量生成时最容易出现三种问题第一XML 里xmin/xmax超出了size给出的宽高第二TXT 里的归一化坐标有负数或大于 1 的值第三两份标注生成时间不同同一个目标的框在 XML 和 TXT 上相差超过 1%。前两种问题直接导致训练 Loss 变成 NaN第三种问题会导致验证时模型“明明检测对了但 mAP 被错误标注压下去”。我一般先把越界项过滤出来python - EOF import xml.etree.ElementTree as ET from pathlib import Path for xmlp in Path(./mango_pests).glob(*.xml): tree ET.parse(xmlp) size tree.find(size) w int(size.find(width).text) h int(size.find(height).text) for obj in tree.findall(object): box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) if x1 0 or y1 0 or x2 w or y2 h: print(xmlp.stem, x1, y1, x2, y2, size:, w, h) EOF这个命令式的python -直接读取标准输入不需要额外建文件。如果输出为空说明 VOC 坐标没有越界。真正麻烦的是 XML 和 TXT 不一致需要把 XML 转成归一化坐标后跟 TXT 逐列做减法。这类不一致往往是后处理脚本用了不同的图像尺寸导致的比如 XML 记录的 width 是 1920但 YOLO TXT 是按压缩后的 1280 图片生成的所有 x 中心点都会偏差。4.3 针对小目标的数据增强策略确认目标偏小以后不要盲目加hsv色域增强。色域增强对叶片背景和昆虫同色系的问题有帮助但不能把小目标变大。更有效的做法是提高imgsz或者开启 YOLOv8 内置的mosaic和copy_paste。后者会把一个图中的目标贴到另一张图上相当于合成新样本比较适合密集小目标场景。要注意的是YOLOv8 的mosaic默认是开启的但它会在每个 epoch 里把四张图拼成一张如果原始 TXT 里框的坐标是准确的mosaic 之后模型看到的目标数量会变多。对于少数类我还会单独把包含该类别的图片挑选出来在训练集中复制增强后一节就给出这个最直接的水平翻转脚本。5. 最后一个技巧少数类水平翻转增强与训练/验证分离这一节只讲一个实用的增强技巧对包含尾类别sawfly、slug、wasp的图片做水平翻转生成额外的训练样本。水平翻转在目标检测里是最安全的几何变换因为框的宽高不变只需要把中心点 x 坐标改成1 - xc标签错误率几乎为零。我写过一个增强脚本只处理 YOLO 格式的 TXT不碰 XMLimport shutil from pathlib import Path src_img Path(./mango_pests) dst_img Path(./mango_pests_aug/images/train) dst_txt Path(./mango_pests_aug/labels/train) dst_img.mkdir(parentsTrue, exist_okTrue) dst_txt.mkdir(parentsTrue, exist_okTrue) # 按第二章的类别 ID 映射sawfly6, slug7, wasp9 boost_ids {6, 7, 9} count 0 for txtp in src_img.glob(*.txt): lines txtp.read_text().strip().splitlines() if not lines: continue ids [int(l.split()[0]) for l in lines] if not any(i in boost_ids for i in ids): continue stem txtp.stem imgp txtp.with_suffix(.jpg) if not imgp.exists(): continue shutil.copy(imgp, dst_img / f{stem}_hf.jpg) new_lines [] for l in lines: parts l.split() cid int(parts[0]) xc, yc, w, h map(float, parts[1:]) new_lines.append(f{cid} {1.0 - xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) (dst_txt / f{stem}_hf.txt).write_text(\n.join(new_lines) \n) count 1 print(生成增强样本数:, count)脚本逻辑分四步先扫描所有 TXT判断该图是否至少包含一个少数类 ID满足条件就复制原图并重命名加_hf后缀然后把每一行的xc改写成1 - xcw、h不动最后把新 TXT 写到增强目录。boost_ids是一个集合不要在循环里反复改它如果你要增强moth或stem_borer直接往集合里加对应 ID。这里有两点必须提醒。第一增强样本只能放进训练集验证集必须保持原始分布否则验证指标会虚高第二复制图片时不要用shutil.move原始数据一旦被移动后续想恢复原始分桶就麻烦了。最后把yolo_split的训练目录和mango_pests_aug的训练目录合并重新指向data.yaml里的train路径再跑一遍yolo detect train你会发现尾类别虽然实例数仍然少但至少每一轮都能看到几个正样本Loss 不会在某个 step 之后完全静止。这一步做完再去调imgsz、mosaic和cls_loss权重才算把这份 3575 张双格式数据集真正用到实处。本文还有配套的精品资源点击获取