尧图精选

水稻害虫VOC数据集转YOLO格式实战:从XML检查到小目标训练避坑

🕒 发布时间:2026/10/2 8:37:01 📁 来源:尧图网络
简介这套水稻害虫检测数据集面向目标检测与农业植保智能化应用覆盖褐飞虱、绿叶蝉、叶夹、稻蝽、蛀干虫、轮生蛆等常见害虫类别整体任务规模为5229张图像采用VOC格式进行标注可服务于虫情监测、防治决策等模型的训练与效果验证。压缩包共2000个文件全部为xml标注文件合计109.96MBxml中记录每个目标的类别与边界框坐标便于转换为YOLO、SSD等主流检测框架所需的标签结构。已有1218人学习下载。对于缺少田间标注数据的开发者这份资料可直接进行数据划分与格式转换减少人工采集和标注成本若后续需要扩展新害虫类别也可参考xml字段规范快速生成自定义检测数据集。1. 水稻害虫数据集 5229 张图VOC 标记的褐飞虱、绿叶蝉等类别能直接训练吗收到这份数据集后的第一反应很多人都是把 zip 解压后直接丢进训练脚本跑一把翻车概率不小。它标注的是水稻害虫检测场景共 5229 张图片格式为 VOC 标记XML描述里写了褐飞虱、绿叶蝉、叶夹、稻蝽、蛀干虫、轮生蛆六个类别。这些类别大多是稻株上的小虫或小虫危害状目标框偏小、单图目标数量多对数据质量的要求比常规目标检测更高。建议拿到 zip 后先做三件事统计 XML 里真实出现的name标签核对 XML 和 jpg 文件是否一一对应抽查坐标是否越界。这三步做完再决定训练参数顺序不能反。2. 从 VOC XML 看水稻害虫数据的组织六个类别、五个字段和一个命名坑2.1 XML 标注文件里的五个必读字段Pascal VOC 的标注文件是单个 XML和图片同名只是后缀从.jpg换成.xml。打开任意一份根节点是annotation里面依次有folder、filename、path、source、size然后是一组object。对训练脚本来说真正必须读对的字段是五个。filename决定这张标注对应哪张图片。字段值与实际 jpg 文件名不一致是转换流程里最常见的问题来源我见过两份来源不同的 XML 写同一个文件名导致后写的那份直接覆盖前一份。size给出width、height、depth坐标归一化时要用这组宽高做分母不少转换脚本只读bndbox不读size结果坐标系完全错乱。object是核心对象块每个目标一个节节点里的name是真实类别名。bndbox内四个子节点xmin、ymin、xmax、ymax是目标框的左上角和右下角坐标单位像素没有归一化。truncated表示目标被图片边界截断difficult表示难例这两个字段在第三方平台导出时多数为 0但转换时不能默认忽略要先确认自己的训练逻辑是否把难例单独加权。这个数据集的 XML 文件名以.rf.哈希结尾是典型图像标注平台导出产物XML 里的folder和path通常记录着原始目录。这两个字段对训练没有用处转换时必须剔除否则 YOLO 会把路径里的斜杠当成额外信息一起读进去轻则出现警告重则训练中断。2.2 文件名命名规律与标签统计文件名本身就是信息比如BROWN-PLANTHOPPER_original_bhopper--89--jpg_..._jpg.rf.3afd....xml可以拆出三层意思BROWN-PLANTHOPPER是工作区标签对应褐飞虱bhopper--89是采集现场的俗名和序号rf.3afd...是平台为规避同名冲突生成的指纹。文件名前缀可以参考但不能当作类别权威因为导出时经常会把多个来源的数据合并进同一批次。我一般会先对 XML 里的真实标签做一次全量去重再决定映射表。命令如下grep -h -o name.*/name annotations/*.xml \ | sed s/\/\?name//g \ | sort | uniq -c | sort -rngrep -h -o只输出匹配的标签内容而不带文件名sed去掉name和/name外壳uniq -c统计频次最后按次数降序排列。执行结果是一份能看清六类之间比例差异的清单同时也能发现不属于白名单的杂项标签。统计完标签再核对文件对应关系把 jpg 和 xml 按文件名主体取交集看两边各有多少孤立文件。常见做法是用find加comm但更省事的是让转换脚本把找不到 XML 的图片和找不到图片的 XML 全部打印出来一次跑完直接看日志。2.3 类别映射与白名单表根据资源标题这份数据覆盖褐飞虱、绿叶蝉、叶夹、稻蝽、蛀干虫、轮生蛆六类。中文名和 XML 里的英文标签之间需要一张映射表下表是常见方案实际名称以统计结果为准中文类别建议标签说明褐飞虱BROWN-PLANTHOPPER文件名与 XML 中均出现过稻蝽RICE-BUGS文件名与 XML 中均出现过绿叶蝉GREEN-LEAFHOPPER按 XML 实际值核对叶夹LEAF-FOLDER按 XML 实际值核对蛀干虫STEM-BORER按 XML 实际值核对轮生蛆WHORL-MAGGOT按 XML 实际值核对映射表的意义是给转换脚本设白名单。不在表里的标签打印日志后跳过而不是中断整个转换。最常见的命名噪音包括大小写不一致、连字符位置不一、单复数混用都要在这一层统一掉。映射关系一旦确定后续训练配置里的类别 ID 顺序就必须锁定训练到一半再改顺序模型输出含义会全部错位。3. 把 VOC 转成 YOLO 格式坐标归一化脚本、类别映射与五分钟自检3.1 为什么离线转换比运行时解析更省事YOLO 系列训练器接收的标注格式是每张图片一个 txt一行一个目标内容为类别id x_center y_center width height后四个数值要除以图片宽高归一化到 (0,1)。VOC 的 XML 给的是绝对像素坐标直接训练只有两条路一是在数据加载器里写 VOC 解析逻辑每次迭代都读 XML二是离线把所有 XML 转成 yolo txt。第一条路不是不能走但每次迭代重读文件坐标解析错误只会在训练中途冒出来定位成本高。离线转换能把全部解析问题提前到训练之前暴露这是数据准备里收益最高的一步。坐标小数位保留六位足够。对 640 分辨率图片来说0.000001 的归一化误差远小于一个像素模型感受野上区分不出来。类别 ID 从 0 开始递增不能从 1 开始YOLO 的损失函数和评估脚本都按 0 基计数。3.2 Python 转换脚本import xml.etree.ElementTree as ET from pathlib import Path voc_dir Path(annotations) yolo_dir Path(labels) yolo_dir.mkdir(exist_okTrue) CLASS_NAMES [ BROWN-PLANTHOPPER, # 褐飞虱 RICE-BUGS, # 稻蝽 # 其余类别名以 XML 统计结果为准列表顺序决定类别 ID ] def voc2yolo(xml_path: Path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) if size is None: print(f跳过 {xml_path.name}: 缺少 size 字段) return w float(size.find(width).text) h float(size.find(height).text) if w 0 or h 0: print(f跳过 {xml_path.name}: 非法图片尺寸) return lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_NAMES: print(f未映射类别 {name} {xml_path.name}) continue cls_id CLASS_NAMES.index(name) b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) if xmin xmax or ymin ymax: print(f无效框 {name} {xml_path.name}) continue x_c ((xmin xmax) / 2) / w y_c ((ymin ymax) / 2) / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {bw:.6f} {bh:.6f}) if lines: out yolo_dir / (xml_path.stem .txt) out.write_text(\n.join(lines), encodingutf-8) for xml_file in sorted(voc_dir.glob(*.xml)): voc2yolo(xml_file)脚本逻辑是先拿 size 再遍历所有 object。size字段缺失或宽高非法直接跳过这种文件进训练会引发除零name not in CLASS_NAMES是白名单过滤只打印日志不中断xmin xmax的判断用来防止宽高为负的框进入模型。最后一段if lines:很关键没有合法目标的 XML 不生成 txt 文件而是留到训练前统一清点。float()转换必不可少XML 里的坐标是文本不转类型直接除会报错。f-string里的.6f控制输出六位小数训练时可复现性更好。3.3 五分钟自检转换完成先做两组低技术含量检查不要直接开训。# 空标注统计数量应该与纯背景图数量吻合 find labels -name *.txt -empty | wc -l # 越界检查归一化坐标的四项都应落在 [0, 1] 区间 awk {for (i2; i5; i) if ($i0 || $i1) print FILENAME: $0} labels/*.txt | head -20第一条命令数出空 txt 文件数如果超过图片总数的 5%先回头确认 XML 结构多半是白名单漏了类别。第二条命令用 awk 遍历每行第 2 到第 5 个字段做区间判断出现越界说明 XML 里的size与实际图片分辨率不一致常见于二手标注平台的数据。两条都通过才进入数据划分。4. 训练前排查与避坑五个标注高频坑的处理方法4.1 标签统计结果和描述对不上现象统计出十几个name而资源描述只说六类。原因同一虫害在不同批次采集时用了别名或者平台在工作流里重建类目时生成了重复名称比如GreenLeafhopper和green-leaf-hopper被当成两个类别。解决以 XML 里出现频率最高的名称为规范标签把别名写进映射表统一到规范名。我每次都在转换脚本里保留未映射日志确认没有丢掉真正需要的类别再开训日志文件就是这个数据集的真实类别字典。4.2 目标框坐标越界现象第 3.3 节的 awk 检查出现1.034567或负数YOLO 训练时会直接报数据错误或静默丢弃该目标。原因粘贴图像后画布尺寸变了但标注框没有跟着偏移或者标注时拖框出了画布边缘。解决除法之前先把绝对像素坐标 clip 到[0, W]、[0, H]区间。更关键的是输出诊断日志对比原始框宽高和 clip 后的宽高损失比例超过 10% 的样本圈出来复查这类框即使不越界也往往是位置标错了。4.3 转换后出现大量空 txt现象脚本跑完labels目录里出现几十个空文件对应图片看起来也有虫。原因有些 XML 里的difficult1或truncated1在转换逻辑里被统一过滤掉了也有一部分是采集初期拍了纯背景图本就没有目标。解决先用空文件清单做人工抽查区分“该有框但没有”和“本来就没目标”两类。纯背景图可以单独保留成背景样本库在训练配置里以负样本形式参与该有框但没有的要回原始数据集补标注不能跳过。4.4 类别不均衡褐飞虱多、轮生蛆少现象统计结果显示某几类占了大半轮生蛆这类样本可能只有几十个直接训练时少数类几乎学不出来。原因田间采集生态分布就是如此褐飞虱常成簇出现蛀干虫和轮生蛆在稻株上很少单独露头。解决不要简单复制少数类图片那样过拟合风险极高。优先做法是按类别分层划分训练集和验证集保证验证集里每个类别都有代表训练时给少数类单独调分类损失权重观察每轮混淆矩阵里少数类的召回率变化比盲目复制图靠谱得多。4.5 小目标在预处理后消失现象轮生蛆这类目标在原图上可能只有十几个像素宽缩放到 640 分辨率后目标框面积占比降到百分之几以下训练出的模型几乎检测不到。原因归一化坐标本身没错但letterbox缩放过程把小目标框内像素抽样丢失小目标检测本身又很吃分辨率。解决先把imgsz从 640 调到 960再观察 mAP50 变化。增强阶段不要对这类图做重马赛克混叠mosaic 会进一步把小目标缩小关闭旋转和上下翻转保留水平翻转。小目标调试没有通用最优解每一轮只改一个变量才能知道是哪一步把小目标弄没了。5. 用 YOLOv8 验收这批 VOC 数据分层划分、训练配置与难例分析5.1 按类别分层划分 train 和 val数据划分用纯随机很容易出现某类只在训练集出现、验证集完全没有的情况。先按每个 txt 文件的主类别归档再对每个类别桶做比例切分import random from pathlib import Path from collections import defaultdict random.seed(42) txt_files [p for p in Path(labels).glob(*.txt) if p.stat().st_size 0] bucket defaultdict(list) for f in txt_files: cls open(f).readline().split()[0] # 取首个目标的类别 id 做分层依据 bucket[cls].append(f) train, val [], [] for cls, items in bucket.items(): random.shuffle(items) cut int(len(items) * 0.8) train items[:cut] val items[cut:] train.sort(), val.sort() Path(train.txt).write_text(\n.join(str(p) for p in train)) Path(val.txt).write_text(\n.join(str(p) for p in val))多标签文件按首个目标类别近似分层虽然粗但比纯随机安全。之后写一份rice_pests.yaml指定数据根目录、train/val 清单和六个类别名称直接交给训练脚本读取。5.2 训练配置与命令首次验证用轻量权重跑一轮先把数据链路跑通再谈精度yolo detect train \ datarice_pests.yaml \ modelyolov8n.pt \ imgsz640 \ epochs100 \ batch16imgsz先用 640遇到小目标类别表现太差再上 960。batch按显存调整16G 显存可以开到 32。训练完看results.csv里的mAP50和mAP50-95两条曲线水稻害虫这类小目标场景更关心mAP50不必强求高 IoU 指标。5.3 混淆矩阵和低置信度样本分析训练结束打开runs/detect/train下的confusion_matrix.pngVOC 数据最容易暴露的就是两类问题相邻类别互相错分说明标签噪声或类别边界不清晰某一整列偏淡说明该类别样本量太少。再对验证集做一轮推理把预测置信度低于 0.4 的结果存下来yolo detect predict modelruns/detect/train/weights/best.pt \ sourceval_images \ conf0.25 \ save_txtTrue save_confTrue低置信度样本里通常藏着两种问题目标太小被漏检或者标注框偏移导致 IoU 一直上不去。从那以后我每次拿到 zip 里带 VOC 标记的数据集都会强制走一遍标签统计、坐标越界检查和分层划分三件事再谈训练曲线。小目标检测里的玄学已经够多了能提前消掉的变量就别留给后处理希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →