news 2026/10/1 19:35:03

芒果害虫检测数据集实战:VOC与YOLO双格式标注解析与YOLOv8训练

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
芒果害虫检测数据集实战:VOC与YOLO双格式标注解析与YOLOv8训练

简介:本资源为芒果害虫检测数据集,面向从事农业虫害识别、目标检测算法训练与课程实践的研究者及学生,可解决芒果种植场景下多类别害虫图像样本不足的问题。数据集同时提供Pascal VOC与YOLO两种标注格式,包含jpg图片及一一对应的xml、txt标注文件,标注类别共10类,涵盖Weevil、beetle、grasshopper、mango_hopper、mango_mealybug、moth、sawfly、slug、stem_borer、wasp等常见芒果害虫。压缩包为7z格式,共约2000个文件,以1999个xml标注文件和1个说明txt为主,整体约191.04MB,目录结构清晰,便于直接接入检测框架进行训练与验证。目前已有223人学习下载,适合需要快速构建虫害识别模型、开展对比实验或完成课程设计的读者参考使用。

1. 芒果害虫检测数据集:3575 张双格式标注,10 类虫害一次说清

做农业视觉检测的同行大概都有过这种经历:模型结构调了一周,最后卡在数据上——要么类别不齐,要么标注格式对不上训练脚本。这次拆的这份芒果害虫检测数据集,就是冲着这个痛点来的。它包含 3575 张 jpg 图片,每张图都配了 Pascal VOC 格式的 xml 和 YOLO 格式的 txt,标注类别 10 类,覆盖象甲、甲虫、蝗虫、芒果叶蝉、芒果粉蚧、蛾、叶蜂、蛞蝓、茎螟、黄蜂这些芒果园里常见的害虫。换句话说,拿到手就能直接喂给 YOLO 系列训练,也能用 VOC 工具链做二次清洗。适合谁?做果园虫情监测的算法同学、想练手目标检测的在校生、以及需要快速搭一个农业检测原型的工程师。下面从格式、目录、转换、训练、避坑一路拆到验证技巧,尽量把能抄的作业都写出来。

2. 双格式标注拆解:VOC 与 YOLO 到底怎么对应

2.1 为什么同一批图要存两份标注

先讲清楚一个容易混淆的点:这份数据集里 xml 和 txt 是同一批标注的两种表达,不是两套独立标注。VOC 的 xml 用绝对像素坐标记录xmin/ymin/xmax/ymax,而 YOLO 的 txt 用归一化后的class_id x_center y_center width height,数值都在 0 到 1 之间。之所以两份都留着,是因为不同训练框架吃的东西不一样:YOLOv5/v8 官方仓库默认读 txt,而很多老一些的检测代码、可视化脚本、标注复核工具仍然认 VOC。你如果只留一份,换框架时就得自己写转换,反而多一道出错环节。

从项目正文能看到文件命名规律,比如firc_mangopets_74.xml、firc_mangopets_1816.xml,前缀统一是firc_mangopets_,后面跟数字编号。这个编号就是图片和标注的对应键——firc_mangopets_74.jpg对应firc_mangopets_74.xml和firc_mangopets_74.txt。实操里最怕的就是图片和标注对不上号,所以拿到数据集第一件事不是急着训练,而是先做一次配对校验。

2.2 目录结构与文件清单核对

解压后典型的结构是这样(不同打包方式可能略有差异,以实际为准):

mango_pests/ ├── 使用前必读.txt ├── JPEGImages/ # 3575 张 jpg │ ├── firc_mangopets_74.jpg │ └── ... ├── Annotations/ # 3575 个 VOC xml │ ├── firc_mangopets_74.xml │ └── ... └── labels/ # 3575 个 YOLO txt ├── firc_mangopets_74.txt └── ...

先用一条命令核对三份文件数量是否一致,这是最省事的体检:

# 分别统计 jpg / xml / txt 数量,三个数字必须相等 find . -name "*.jpg" | wc -l find . -name "*.xml" | wc -l find . -name "*.txt" | wc -l

如果三个数字都是 3575,说明文件没丢。要是某个数字对不上,八成是解压时文件名编码出问题,或者压缩包本身有损坏,重新解压一次通常能解决。这一步花不了一分钟,但能挡掉后面一堆莫名其妙的报错。

2.3 类别名与 class_id 的映射关系

10 个类别名是英文的:Weevil、beetle、grasshopper、mango_hopper、mango_mealybug、moth、sawfly、slug、stem_borer、wasp。YOLO 的 txt 里存的是数字 id,所以你必须先确定 id 到名字的映射顺序。常见做法是建一个classes.txt,一行一个类别名,行号(从 0 开始)就是 class_id:

Weevil beetle grasshopper mango_hopper mango_mealybug moth sawfly slug stem_borer wasp

这里有个血泪经验:映射顺序一旦定了就不能改。我见过有人训练时用一套顺序,推理时又按字母序重排,结果模型把beetle认成Weevil,排查了半天才发现是类别表错位。建议把classes.txt和数据一起版本管理,训练、验证、部署全程共用同一份。

2.4 用脚本验证标注合法性

在正式训练前,写个小脚本扫一遍所有 txt,检查有没有越界坐标、空标注、类别 id 超范围的情况。这类脏数据不查出来,训练时 loss 会莫名其妙地抖。

import os # 类别总数,和 classes.txt 行数保持一致 NUM_CLASSES = 10 label_dir = "labels" bad_files = [] for name in os.listdir(label_dir): if not name.endswith(".txt"): continue path = os.path.join(label_dir, name) with open(path) as f: lines = [l.strip() for l in f if l.strip()] # 空标注文件:图片里没有目标,YOLO 允许,但值得记录 if not lines: bad_files.append((name, "empty")) continue for line in lines: parts = line.split() # YOLO 每行必须是 5 个字段 if len(parts) != 5: bad_files.append((name, "field_count")) break cid = int(parts[0]) coords = [float(x) for x in parts[1:]] # 类别 id 越界 或 归一化坐标不在 [0,1] if cid < 0 or cid >= NUM_CLASSES or any(c < 0 or c > 1 for c in coords): bad_files.append((name, "range")) break print(f"可疑文件数: {len(bad_files)}") for f in bad_files[:20]: print(f)

逻辑说明:逐行读每个 txt,先判字段数是否为 5,再判类别 id 是否落在[0, NUM_CLASSES),最后判四个归一化坐标是否都在 0 到 1 之间。参数上,NUM_CLASSES必须和你的类别表一致,写错这个数会把正常文件误判。跑完如果可疑文件是 0,就可以放心进入下一步;如果有,先人工看几张,判断是标注错误还是脚本判据太严。

3. 从 VOC 到 YOLO:转换脚本与坐标换算细节

3.1 VOC 与 YOLO 坐标的换算公式

虽然这份数据集已经给了 YOLO txt,但你还是得懂换算,因为一旦你要自己增补标注、或者拿 VOC 工具改完再转回来,就得手动算。VOC 给的是左上角(xmin, ymin)和右下角(xmax, ymax),图片宽W、高H。YOLO 要的是中心点和宽高,全部归一化:

x_center = (xmin + xmax) / 2 / W y_center = (ymin + ymax) / 2 / H width = (xmax - xmin) / W height = (ymax - ymin) / H

反过来,从 YOLO 还原 VOC:

xmin = (x_center - width / 2) * W ymin = (y_center - height / 2) * H xmax = (x_center + width / 2) * W ymax = (y_center + height / 2) * H

坑就在W和H上:必须用当前这张图的真实尺寸,不能拿一个固定值套所有图。芒果害虫数据集里图片尺寸不一定统一,用错尺寸会导致框整体偏移。

3.2 批量转换脚本(VOC 转 YOLO)

下面这个脚本把 Annotations 里的 xml 批量转成 YOLO txt,同时生成classes.txt。它用xml.etree解析,不依赖额外库。

import os import xml.etree.ElementTree as ET from PIL import Image # 类别顺序决定 class_id,务必和训练时一致 CLASSES = ["Weevil", "beetle", "grasshopper", "mango_hopper", "mango_mealybug", "moth", "sawfly", "slug", "stem_borer", "wasp"] cls_to_id = {c: i for i, c in enumerate(CLASSES)} xml_dir = "Annotations" img_dir = "JPEGImages" out_dir = "labels_from_voc" os.makedirs(out_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(".xml"): continue stem = os.path.splitext(xml_name)[0] tree = ET.parse(os.path.join(xml_dir, xml_name)) root = tree.getroot() # 用图片真实尺寸做归一化,缺图就跳过并记录 img_path = os.path.join(img_dir, stem + ".jpg") if not os.path.exists(img_path): print("缺图:", stem) continue W, H = Image.open(img_path).size lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in cls_to_id: print("未知类别:", name, "in", xml_name) continue 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) xc = (xmin + xmax) / 2 / W yc = (ymin + ymax) / 2 / H bw = (xmax - xmin) / W bh = (ymax - ymin) / H # 坐标裁剪到 [0,1],防止个别越界标注污染训练 xc, yc = min(max(xc, 0), 1), min(max(yc, 0), 1) bw, bh = min(max(bw, 0), 1), min(max(bh, 0), 1) lines.append(f"{cls_to_id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines))

逻辑说明:先按CLASSES建 id 映射,遍历每个 xml,用对应 jpg 的真实宽高做归一化,再按公式算中心点和宽高。参数上,CLASSES顺序就是 class_id 顺序,改它等于改标签含义;坐标裁剪那两行是保险,防止个别标注框超出图片边界导致训练异常。跑完把labels_from_voc和数据集自带的labels对比一下,如果内容基本一致,说明自带 txt 是可信的。

3.3 划分训练集与验证集

YOLO 训练需要train.txt和val.txt两个列表文件,每行是图片路径。按 8:2 划分:

import os import random img_dir = "JPEGImages" imgs = [f for f in os.listdir(img_dir) if f.endswith(".jpg")] random.seed(42) # 固定种子,保证每次划分一致 random.shuffle(imgs) split = int(len(imgs) * 0.8) train, val = imgs[:split], imgs[split:] for name, subset in [("train.txt", train), ("val.txt", val)]: with open(name, "w") as f: for im in subset: f.write(os.path.join(img_dir, im) + "\n") print("train:", len(train), "val:", len(val))

逻辑说明:random.seed(42)是关键,固定种子后每次跑划分结果都一样,方便复现实验。参数上,0.8 是训练比例,数据量 3575 张时验证集约 715 张,够评估用。如果某类样本特别少,建议改成按类别分层抽样,避免验证集里缺类。

4. 训练落地:YOLOv8 配置与参数怎么设

4.1 数据集 yaml 配置

YOLOv8 用一份 yaml 描述数据路径和类别。在项目根目录建mango.yaml:

path: /abs/path/to/mango_pests # 数据集根目录,写绝对路径最稳 train: train.txt val: val.txt 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: wasp

参数说明:path用绝对路径能避免相对路径在不同工作目录下失效;nc必须等于类别数 10;names的键值顺序必须和 txt 里的 class_id 严格对应。这三处任意一处错位,训练都不会报错,但模型学出来的类别是乱的,属于最隐蔽的坑。

4.2 启动训练与关键参数

yolo detect train \ data=mango.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/mango \ name=exp1

逐项说:model=yolov8n.pt是 nano 版预训练权重,3575 张图这个量级用 n 或 s 版就够,上大模型容易过拟合;imgsz=640是常见输入尺寸,显存吃紧可以降到 512;batch=16按显存调,8G 显存跑 640 一般能到 16;lr0=0.01是初始学习率,微调预训练模型时这个值比较稳;patience=20表示 20 轮没提升就早停,省时间。如果训练中 loss 一直不降,先别怀疑模型,回头查类别映射和标注质量。

4.3 训练过程该盯哪些指标

训练日志里重点看三个:box_loss、cls_loss、mAP50。box_loss管定位,cls_loss管分类,两者都应该整体下降。mAP50是 IoU 阈值 0.5 下的平均精度,是判断模型好坏的直接指标。如果box_loss降但mAP50不涨,通常是标注框质量差;如果cls_loss居高不下,多半是类别不平衡或映射错位。3575 张 10 类,平均每类 350 张左右,属于中等偏少,训练时建议开数据增强(YOLOv8 默认已开 mosaic、翻转等)。

4.4 推理与结果核对

训练完用验证集跑一次推理,肉眼核对几张:

yolo detect predict \ model=runs/mango/exp1/weights/best.pt \ source=JPEGImages \ conf=0.25 \ save=True

conf=0.25是置信度阈值,低于它的框不显示。核对时重点看两类错误:把mango_hopper和grasshopper搞混(这两类形态接近,是这份数据集的难点),以及小目标漏检(mango_mealybug通常很小)。如果混淆严重,可以考虑在类别名上做更细的区分,或者补充这两类的样本。

5. 避坑与排查:标注、映射、训练里最容易翻车的地方

5.1 图片与标注对不上号

现象:训练时提示找不到某张图的标注,或者 loss 异常高。原因:jpg、xml、txt 三者文件名前缀不一致,常见于解压时文件名被截断或编码转换。解决:跑一遍配对校验,用集合运算找出缺失项:

import os stems = lambda d, ext: {os.path.splitext(f)[0] for f in os.listdir(d) if f.endswith(ext)} img = stems("JPEGImages", ".jpg") xml = stems("Annotations", ".xml") txt = stems("labels", ".txt") print("缺xml:", img - xml) print("缺txt:", img - txt) print("多余xml:", xml - img)

三个差集都为空才算干净。

5.2 类别 id 与名字错位

现象:模型能检测到目标,但类别名全是错的,或者某类永远不出现。原因:classes.txt、yaml 里的names、txt 里的 id 三者顺序不一致。解决:以 txt 里的 id 为准,反查每个 id 对应的名字,确认三处一致。最稳的做法是只维护一份classes.txt,yaml 和转换脚本都从它读。

5.3 空标注文件被误删

现象:某些图片明明有虫,模型却学不会。原因:这些图的 txt 是空的(标注时漏标),有人清理数据时把空 txt 当垃圾删了,导致图片没有对应标注而被跳过。解决:空 txt 要保留,它表示"这张图无目标",是负样本,对降低误检有用。删了反而让模型没见过背景。

5.4 坐标越界导致训练崩溃

现象:训练几轮后 loss 变成 nan。原因:个别标注框的归一化坐标超出 [0,1],比如xmax大于图片宽度。解决:用 2.4 的校验脚本扫一遍,或者转换时统一做坐标裁剪(3.2 脚本里已经加了)。越界框不裁剪,梯度会爆。

5.5 验证集类别缺失

现象:mAP50波动大,某些类评估结果为空。原因:随机划分时某类样本全落进训练集,验证集里一个都没有。解决:改用分层抽样,保证每个类别在验证集里都有一定数量。样本少的类尤其要注意,必要时对这类做过采样。

6. 进阶技巧:用混淆矩阵定位难分类类别

训练跑通只是第一步,真正决定模型能不能上线的是对错误的分析。YOLOv8 训练结束会自动生成混淆矩阵(confusion_matrix.png),这张图比单看 mAP 有用得多。矩阵对角线是分对的,非对角线就是混淆。拿这份芒果害虫数据集来说,我一般会重点看mango_hopper和grasshopper那一格——这两类都是跳跃类昆虫,形态接近,混淆概率高。如果这一格数值明显,说明模型没学到区分特征,可以考虑三个方向:一是补充这两类的难例样本,二是检查标注时有没有把两者标混,三是适当提高输入分辨率,让小差异更容易被看到。

另一个实用技巧是按类别统计漏检和误检。YOLOv8 的验证输出里每个类都有 precision 和 recall,把 recall 低的类挑出来单独看。mango_mealybug这类小目标如果 recall 偏低,通常是输入尺寸不够,把imgsz从 640 提到 800 往往有改善,代价是显存和速度。下面这段代码可以快速把每个类的指标拉出来排序:

# 读取 YOLOv8 验证结果,按 recall 升序排列,优先处理差类 import pandas as pd # results.csv 在训练输出目录下,列名含 metrics/recall(B) 等 df = pd.read_csv("runs/mango/exp1/results.csv") df.columns = [c.strip() for c in df.columns] # 取最后一轮各类指标(需结合 val 输出的 per-class 结果,此处示意排序思路) print(df.tail(1).T.sort_values(by=df.tail(1).index[-1]))

逻辑说明:这段是示意如何从训练日志里定位薄弱类别,实际 per-class 指标建议直接看验证时打印的表格。参数上,重点盯 recall 而不是 precision——农业检测里漏检一只虫的代价通常比误报高,宁可多报也别漏报。

还有一个容易被忽略的点:验证集和训练集的分布要一致。如果训练集里某类都是近距离大目标,验证集里却是远景小目标,mAP 会虚低。划分数据时按拍摄场景分层,比纯随机更靠谱。我现在的习惯是,每次拿到新数据集,先花十分钟跑配对校验和标注合法性检查,再花五分钟看一眼类别分布,确认没问题才开训。这个流程帮我挡掉过好几次"训练半天发现数据是坏的"的翻车。从那以后我每次开训前都强制走一遍这套检查,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 19:34:53

Libero SoC FPGA开发全流程指南:从建工程到软硬件协同调试

1. Libero SoC到底是个什么东西先说结论&#xff1a;Libero SoC是Microchip&#xff08;原Microsemi&#xff09;家的FPGA全流程开发工具&#xff0c;从RTL设计、综合、布局布线、时序约束、仿真到生成烧写文件、在线调试&#xff0c;一条龙全包。你写Verilog也好、VHDL也好&am…

作者头像 李华
网站建设 2026/10/1 19:34:23

SpringBoot+Vue宿舍管理系统:床位状态流转与事务设计实战

简介&#xff1a;基于SpringBoot和Vue开发的学生宿舍管理系统&#xff0c;是一套面向计算机专业毕设学生、课程设计与期末大作业场景的完整项目资料。系统围绕宿舍管理实际业务展开&#xff0c;覆盖学生信息、宿舍分配、报修服务与费用统计等模块&#xff0c;能够直观展示从需求…

作者头像 李华
网站建设 2026/10/1 19:32:51

教师AI实操手册:从会用AI到用好AI的底层逻辑与核心方法

1. 从"会用AI"到"用好AI"&#xff1a;教师实操手册的底层逻辑很多老师第一次接触AI工具时&#xff0c;最典型的反应是两种&#xff1a;一种是"这东西太神奇了&#xff0c;什么都能干"&#xff0c;另一种是"试了一下&#xff0c;生成的东西没…

作者头像 李华
网站建设 2026/10/1 19:32:03

AI系统性能工程实战:从推理引擎到成本优化的全链路拆解

1. AI 系统性能工程的核心命题拆解 1.1 为什么“性能”在 AI 系统里是个完全不同的物种 传统后端系统的性能工程&#xff0c;核心指标无非是 QPS、P99 延迟、CPU 利用率、内存占用这几样&#xff0c;调优手段也相对成熟——加缓存、拆服务、异步化、连接池调参&#xff0c;一套…

作者头像 李华
网站建设 2026/10/1 19:32:03

AI系统性能工程实战:从指标拆解到全链路优化

1. AI 系统性能工程的核心命题&#xff1a;从单点优化到全链路治理 做 AI 系统性能工程这件事&#xff0c;最怕的就是把它当成单纯的“调参”或者“加机器”。我见过太多团队在模型上线之后发现延迟飙高、吞吐上不去、GPU 利用率长期趴在 30% 以下&#xff0c;第一反应就是“模…

作者头像 李华
网站建设 2026/10/1 19:32:00

DeepSeek Harness 开源工作台实战:从安装部署到技能扩展的完整指南

1. 从一句需求到看得见成果&#xff1a;这个工作台到底在解决什么 大多数人第一次接触 AI 工作台&#xff0c;脑子里浮现的画面是聊天框——你问一句&#xff0c;它答一句&#xff0c;聊完关掉&#xff0c;什么都没留下。这种模式在"随便问问"的场景下够用&#xff0…

作者头像 李华