news 2026/10/6 3:14:18

10类水稻害虫检测数据集:VOC格式解析与YOLO训练实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
10类水稻害虫检测数据集:VOC格式解析与YOLO训练实战指南

简介:面向目标检测与农业害虫识别场景,这份水稻害虫检测数据集采用VOC标注格式,涵盖10个害虫类别,并附类别json字典与可视化脚本,可直接用作目标检测数据集。压缩包共2000个文件,以1999个XML标注为主,另含1个Python可视化脚本,整体约89.78MB,按train/val目录组织,内含images与labels子目录。训练集含6630张图像及对应XML标注,验证集有631张,图像为300×300 RGB,边界框完整,部分样本经四图拼接马赛克增强。json文件提供类别映射,可视化脚本无需修改即可随机绘制边界框,便于检查标注质量。目前已有327人学习/下载,适合算法工程师与科研人员使用。

1. 10类水稻害虫检测数据集:VOC标注格式为什么比COCO更适合做试验田

做目标检测的朋友应该都有过这种经历:从网上下载一个数据集,结果标注格式五花八门,有的给COCO的json,有的给YOLO的txt,还有的只给一堆没整理的XML文件。这个10类别水稻害虫检测数据集选用VOC标注格式,反而是一个很务实的选择——VOC格式虽然老,但胜在结构透明、可读性强、转换成本极低,特别适合用来做算法验证和模型选型。数据集包含训练集和验证集、类别json文件以及可视化脚本,基本做到了“拿到手就能开工”。这篇文章就围绕这套数据集,讲讲VOC格式怎么解析、训练集和验证集怎么划分、类别json文件怎么生成、可视化脚本怎么用,以及最后怎么喂给YOLO训练。

2. 先拆VOC标注格式:文件目录、XML字段与类别JSON的对应关系

2.1 目录结构一目了然:JPEGImages、Annotations、ImageSets/Main 谁管什么

VOC格式源自PASCAL VOC挑战赛,十几年下来已经成为目标检测领域最通用的标注格式之一。它的标准目录结构通常包含三个核心目录:JPEGImages存放原始图片,Annotations存放每张图片对应的XML标注文件,ImageSets/Main存放训练集和验证集的图片索引txt文件。这套水稻害虫数据集既然是VOC标注格式,大概率也是按照这个目录习惯来组织的。

XML文件是VOC格式的核心,它的结构大致长这样:

<annotation> <folder>JPEGImages</folder> <filename>rice_pest_0001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>稻飞虱</name> <bndbox> <xmin>256</xmin> <ymin>180</ymin> <xmax>512</xmax> <ymax>420</ymax> </bndbox> </object> </annotation>

从上面这段XML能读出几个关键信息:文件名、图片宽高、目标类别名称、以及目标在图片中的左上角和右下角坐标。bndbox里的四个值是像素坐标,注意xmax和ymax通常指框的右下角坐标,但有些工具生成时会在坐标上做“包含式”还是“排他式”的处理,也就是坐标是从1开始计数还是从0开始计数。别看这个差异小,转换到YOLO格式时如果处理不当,所有框都会偏移一个像素,训练时损失曲线会表现得很诡异。

2.2 用Python把VOC标注XML解析成JSON:代码与参数说明

拿到一套VOC格式数据集,第一步不是急着训练,而是先把所有XML文件解析出来,转成统一的数据结构。常见做法是用Python的xml.etree.ElementTree来解析,再汇总成JSON。解析脚本可以这样写:

import os import json import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree = ET.parse(xml_path) root = tree.getroot() filename = root.findtext('filename') size = root.find('size') width = int(size.findtext('width')) height = int(size.findtext('height')) objects = [] for obj in root.iter('object'): name = obj.findtext('name') box = obj.find('bndbox') xmin = int(float(box.findtext('xmin'))) ymin = int(float(box.findtext('ymin'))) xmax = int(float(box.findtext('xmax'))) ymax = int(float(box.findtext('ymax'))) objects.append({ 'category': name, 'bbox': [xmin, ymin, xmax, ymax] }) return { 'filename': filename, 'width': width, 'height': height, 'objects': objects } xml_dir = 'Annotations' all_annotations = {} for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue xml_path = os.path.join(xml_dir, xml_file) ann = parse_voc_xml(xml_path) all_annotations[xml_file.replace('.xml', '')] = ann with open('annotations.json', 'w', encoding='utf-8') as f: json.dump(all_annotations, f, ensure_ascii=False, indent=2)

这段代码的作用是把Annotations目录下所有XML解析成统一的字典结构,最后写成一个annotations.json文件。两个参数需要注意:ensure_ascii=False保证中文类别名在JSON里以可读的汉字形式存储,而不是被转义成\u7a3b之类的Unicode序列;indent=2让JSON文件有缩进,方便调试时用文本编辑器或json查询工具直接打开查看。如果后续要配合json格式文件下载、json查询函数之类的工具链,这个可读的JSON结构会省很多事。

解析完成后建议顺手做一次自检:批量统计每个XML里的object数量,如果发现大量XML的object数量为零,说明标注文件有问题,需要回到源头排查。这一步虽然简单,但能在训练前拦住最直接的坑。

3. 训练集与验证集划分:别按文件名硬切,先看类别分布

3.1 用统计脚本摸清10个类别的数量差距

拿到数据集先别急着切分,第一步是统计每个类别的目标数量。水稻害虫数据集里可能包含稻飞虱、二化螟、稻纵卷叶螟、稻瘟病等类别,不同类别的样本量往往差异很大。如果某个类别的标注框只有几十个,而另一个类别有几千个,训练出来的模型会严重偏向样本多的类别,少样本类别直接学不到有效特征。

统计脚本很简单,在解析JSON的基础上按类别计数即可:

import json from collections import Counter with open('annotations.json', 'r', encoding='utf-8') as f: anns = json.load(f) category_counter = Counter() image_counter = Counter() for img_id, ann in anns.items(): cats_in_img = set() for obj in ann['objects']: category_counter[obj['category']] += 1 cats_in_img.add(obj['category']) for c in cats_in_img: image_counter[c] += 1 print('按标注框数量统计:') for cat, cnt in category_counter.most_common(): print(f' {cat}: {cnt} 框') print('按包含该类别的图片数量统计:') for cat, cnt in image_counter.most_common(): print(f' {cat}: {cnt} 张图')

这段统计逻辑分两个维度:框数量和图片数量。框数量反映标注密度,图片数量反映样本覆盖度。如果某个类别框数量不少但图片数量很少,说明一张图里密集标注了多个同类别目标,这种情况下模型容易过拟合到特定场景。参数层面不需要额外调整,脚本输出结果后人工判断哪些类别是“穷类别”,在划分训练集和验证集时需要重点照顾。

3.2 按语义维度划分训练集和验证集:不按文件名随机切

很多教程教你按比例随机切分数据,比如train : val = 8 : 2,这个思路对常规数据集没问题,但水稻害虫数据有自己的特殊性。害虫图片通常按田间拍摄批次组织,同一时间、同一田块拍出来的照片背景高度相似,如果随机切分,训练集和验证集里会出现“同源图片”,验证集失去检验泛化能力的意义。

我一般的做法是先按filename前缀或者目录字段把图片分成不同的“采集批次”,在批次层面上划分训练集和验证集。比如一个批次拍到的水稻害虫图片有200张,可以把整个批次划入训练集,另一个批次的150张整体划入验证集。这样验证集面对的才是真正没见过的田间环境。如果数据集本身没有批次信息,就以图片的拍摄时间戳或文件名中的编号段为代理,尽量把连续编号的图片分到同一侧。

ImageSets/Main目录下的train.txt和val.txt在这种场景下就是按这个思路生成的,里面每一行是图片的文件名(不带扩展名)。VOC格式的训练代码会自动读取这两个文件来确定训练图片和验证图片的范围,不需要去改动XML内容。

3.3 类别JSON文件怎么生成:从XML里收集类别名并补全名称

类别JSON文件看起来不起眼,但在实际使用中非常重要。它提供了类别名称和类别索引之间的映射关系,目标检测训练框架在读取标注时需要使用这个映射关系,比如将类别映射到索引0-9,以便与模型的输出层匹配。一旦映射关系出错,比如类别索引不一致,训练和推理都可能出现严重的标签错乱问题。

生成类别JSON文件的代码主要实现两个功能:扫描所有标注XML,提取所有name字段的去重列表,然后建立映射关系并保存为JSON:

import os import json import xml.etree.ElementTree as ET categories = set() xml_dir = 'Annotations' for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() for obj in root.iter('object'): name = obj.findtext('name') categories.add(name) category_list = sorted(list(categories)) category_dict = {name: idx for idx, name in enumerate(category_list)} with open('categories.json', 'w', encoding='utf-8') as f: json.dump(category_dict, f, ensure_ascii=False, indent=2) print('类别数量:', len(category_list)) for name, idx in category_dict.items(): print(f' {idx}: {name}')

注意这里的sorted()操作,它让类别索引顺序稳定可复现,避免依赖XML文件的读取顺序。如果后面要合并多个数据集,这个稳定的顺序能避免索引错位。那个ensure_ascii=False我再次强调,中文类别名必须保留可读形式,否则后面调试模型输出时根本对不上号。

类别JSON文件还有个容易被忽略的用途:排查标注拼写错误。如果同一个害虫类别在标注XML里被写成了“稻飞虱”和“稻飞虱 ”(多了空格),或者“稻飞虱”和“褐飞虱”被混用,类别数量会虚高,模型训练时会把本应属于同一类别的目标当成不同类别来学习。统计类别列表时会很快发现问题,但在统计之前,如果你用了不严谨的脚本,可能还会把标点符号差别当作独立类别。

4. 可视化脚本:标注质量的第一道质检

4.1 可视化脚本应该包含的三个基本功能

很多人在数据集落地阶段会忽略可视化脚本的价值,直到训练出来的模型在验证集上表现不佳才开始怀疑标注质量。实际上,可视化脚本是投入训练前最便宜、最直接的质检手段。这套水稻害虫数据集自带的可视化脚本,一般来说应该覆盖三类功能:把标注框画到原图上、按类别着色并显示置信度(如果有的话)、支持随机抽样浏览多张图。

核心画框逻辑用OpenCV或Matplotlib都能实现,区别在于OpenCV更适合批量处理,Matplotlib更适合交互查看。以OpenCV为例,一个基本的画框脚本长这样:

import cv2 import json import os def draw_annotations(image_path, ann, category_dict): img = cv2.imread(image_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for obj in ann['objects']: cat = obj['category'] idx = category_dict.get(cat, -1) color = (0, 255, 0) if idx % 2 == 0 else (255, 0, 0) xmin, ymin, xmax, ymax = obj['bbox'] cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color, 2) cv2.putText(img, f'{cat}', (xmin, max(0, ymin - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return img with open('annotations.json', 'r', encoding='utf-8') as f: anns = json.load(f) with open('categories.json', 'r', encoding='utf-8') as f: category_dict = json.load(f) img_dir = 'JPEGImages' for img_id, ann in list(anns.items())[:20]: img_path = os.path.join(img_dir, ann['filename']) img = draw_annotations(img_path, ann, category_dict) cv2.imwrite(f'visual_check_{img_id}.png', cv2.cvtColor(img, cv2.COLOR_RGB2BGR))

这个脚本里cv2.rectangle的四个参数是坐标列表,颜色顺序按BGR处理,但可视化显示时需要转成RGB,代码里做了两次颜色转换避免出现红绿错位。有的新手会忽略OpenCV的BGR通道顺序,画出来的框颜色和预期完全相反。这里直接用固定颜色根据类别索引交替区分,当类别数量超过10个时建议改用预定义的颜色列表。

4.2 三个让可视化脚本翻车的具体场景

第一个常见问题是中文类别名在图上显示乱码。OpenCV自带的cv2.putText不支持中文显示,需要改用PIL在图片上绘制中文,或者直接用Matplotlib的plt.text。这是“玄学”级别的坑,排查半天都想不到问题在字体上。

第二个问题是坐标越界。个别标注框的坐标可能超出图片尺寸,比如xmax大于width,这时cv2.rectangle会报错,或者画出异常框。可视化脚本里应该对坐标做clip处理。

第三个问题是文件名不匹配。XML里写的filename和JPEGImages目录下实际文件名不一致,导致脚本找不到图片。这种情况通常在数据集复制移动、重命名时发生,需要脚本里加一个容错,比如按图片ID匹配而不是完全依赖XML里的filename字段。

4.3 避坑/常见问题/排查:VOC格式水稻害虫数据集从打开到训练前的高频问题

问题1:解析XML时报错提示“no element found”

  • 现象:批量解析Annotations目录时,某几个XML文件报错,程序中断。
  • 原因:XML文件被异常截断,常见于中断的数据集下载或不完整的标注导出。
  • 解决:在解析循环里加异常捕获,打印出具体文件名,把损坏文件单独挑出来重新获取或标注。注意不要“跳过坏文件就完事”,因为训练时ImageSets里的索引仍然会引用这张图,最终导致读取失败。

问题2:类别JSON文件生成后数量比预期多

  • 现象:明明数据集说明是10类,生成后类别列表有12类或更多。
  • 原因:类别名里混入了空格、全角字符、大小写差异,被程序识别为不同类别。
  • 解决:生成类别列表后人工过一遍,对类别名做strip处理,并将同义名称统一。可以用一个手工映射表把“稻飞虱”“稻飞虱 ”“稻飞虱”映射到同一个标准名。

问题3:可视化脚本画出来的框位置明显偏移

  • 现象:框整体向右下偏移,或者框的大小和物体实际轮廓明显不符。
  • 原因:XML里坐标是整数像素值,但图片被预处理过(比如resize或加黑边),导致坐标和当前图片尺寸不匹配。
  • 解决:画框前先检查图片实际尺寸与XML里size字段是否一致。如果图片被resize过,需要按比例缩放标注坐标。这个问题在数据增强场景下尤其常见,一旦做过缩放,VOC格式的绝对值坐标就要同步更新。

问题4:ImageSets里的train.txt和val.txt存在重复或缺失

  • 现象:训练时发现某张图既在train里又在val里,或者某张图两边都不在。
  • 原因:txt文件生成脚本的集合运算有误,或者手动编辑时误操作。
  • 解决:用Python做一次集合差集检查,确认train + val = 全部图片索引,且train ∩ val = 空集。这个校验只要两三行代码,但能拦住大量后续训练阶段的诡异问题。

问题5:损失曲线一开始就极小,但验证集mAP很低

  • 现象:训练启动后loss快速下降,感觉模型学得很快,但验证集精度上不去。
  • 原因:类别JSON文件的索引顺序与训练代码期望的顺序不一致,导致标注类别和模型输出类别完全错位。
  • 解决:先确认训练框架读取的类别顺序是从哪来的,明确它读的是categories.json里的值还是文件名排序。强烈建议将类别JSON文件打平成category_name id两列,并且在训练前用一两个样本做前向推理,人工确认预测类别和真实类别对得上。

5. 从VOC到YOLO训练:格式转换、训练验证与三种能力

5.1 用一段脚本把VOC转成YOLO txt:相对坐标和类别索引是重点

VOC格式虽然通用,但YOLO系列训练框架原生读不了VOC的XML,需要把坐标从像素绝对值转成相对图片尺寸的归一化值。转换公式是:x_center = (xmin + xmax) / 2 / width,y_center = (ymin + ymax) / 2 / height,w = (xmax - xmin) / width,h = (ymax - ymin) / height。四个值都归一化到0到1之间,类别用数字索引表示。

import os import json import cv2 def voc_to_yolo(ann, category_dict, output_path): img = cv2.imread(os.path.join('JPEGImages', ann['filename'])) h, w = img.shape[:2] lines = [] for obj in ann['objects']: cat_idx = category_dict[obj['category']] xmin, ymin, xmax, ymax = obj['bbox'] x_center = ((xmin + xmax) / 2) / w y_center = ((ymin + ymax) / 2) / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h lines.append(f'{cat_idx} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}') with open(output_path, 'w') as f: f.write('\n'.join(lines)) with open('categories.json', 'r', encoding='utf-8') as f: category_dict = json.load(f) with open('annotations.json', 'r', encoding='utf-8') as f: anns = json.load(f) yolo_label_dir = 'labels' os.makedirs(yolo_label_dir, exist_ok=True) for img_id, ann in anns.items(): voc_to_yolo(ann, category_dict, os.path.join(yolo_label_dir, f'{img_id}.txt'))

这段转换代码的坑在坐标边界:如果xmax是包含式坐标,转换时宽度应该用xmax - xmin还是xmax - xmin + 1,在不同的标注工具下会略有差异。我通常不做加1处理,因为对训练结果影响极小,但如果数据集中大量目标只有几个像素大小,这个差异会被放大,需要注意。

转换完成后还需要生成对应的images.txt索引文件列表,YOLO训练时会把图片路径列表交给data.yaml里的train和val字段,标注txt文件放在labels目录下且与图片同名,框架自动读取对应标注。

5.2 如何用训练损失曲线判断数据集的可用性:以yolov8为例

格式转换完成、数据划分好后就可以开始训练了。对于新手来说,可以先从配置YOLOv8环境开始,按照社区教程把依赖装好,然后跑这样一个最小命令:

yolo detect train data=rice_pest.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=16

rice_pest.yaml里的配置大致是:

train: images/train val: images/val nc: 10 names: ['稻飞虱', '二化螟', '稻纵卷叶螟', '稻瘟病', '纹枯病', '稻曲病', '褐飞虱', '白背飞虱', '稻水象甲', '粘虫']

这几个参数别直接照搬,要根据设备显存调整。batch=16在8G显存上跑640分辨率基本是上限,显存小就降到8或4。epochs=50用于快速验证数据集可行性,不必追求收敛。

训练过程重点看两类信号:训练损失曲线和验证集指标曲线。如果训练损失快速下降、验证损失在初期同步下降但随后回升,大概率是过拟合,说明数据集规模不够或者类别分布不均。如果验证损失从头到尾没有下降趋势,先别怪模型,回头检查标注坐标是否正确、类别索引有没有错位。这一步是检验前面所有数据处理工作的最终环节——即使标注文件解析再漂亮,训练结果不对就是不对。

我在实际项目中验证数据集可用性时,习惯从头训练一个微型模型,只训练30个epoch,然后用验证集做推理,人工看一批预测结果。这个“人工看结果”比看任何指标都更直接——用训练好的模型在验证集图片上画框,观察预测框是否贴近标注框。如果预测框位置大致准确、类别名称对上号,说明整个VOC数据集的解析、转换、类别映射链路是通的;如果预测结果完全混乱,就回到之前每一步排查数据。每当我换一套新数据集,这个流程都会先完整跑一遍,虽然费点时间,但比后面训练大模型才发现数据问题要省太多精力。希望这篇笔记能帮你少走一遍我走过的弯路,祝你的水稻害虫检测模型早日跑通。

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

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

从macOS迁移到Linux:备份失效与UI卡顿后的完整避坑指南

作为一个长期在 macOS 上写代码、做设计、剪视频的重度用户&#xff0c;我这次是真的被逼走了。不是没给过机会&#xff0c;各个大版本的所谓“丝滑”我没少体验&#xff0c;但自从某次外接硬盘上的 Time Machine 备份在恢复时被系统判定为“无可用备份”之后&#xff0c;我对它…

作者头像 李华
网站建设 2026/10/6 3:12:44

Java驱动包统一Modbus、Bacnet与OPC-UA,简化工业设备接入

简介&#xff1a;一套基于 Java 的物联网&#xff08;IOT&#xff09;通用驱动包设计源码&#xff0c;主要面向需要对接 Modbus-TCP、Bacnet、OPC-UA 等协议的 Java 开发者与系统集成商。驱动包以 SDK 方式组织&#xff0c;高度模块化&#xff0c;便于快速嵌入业务系统&#xf…

作者头像 李华
网站建设 2026/10/6 3:12:38

麒麟桌面系统V10-SP1用户组全攻略:权限配置与实操命令解析

用了好几年麒麟桌面系统&#xff0c;从V10早期版本一路用到现在V10-SP1 2503&#xff0c;日常给同事处理得最多的&#xff0c;除了软件装不上&#xff0c;就是各种权限说人话。报个“权限不足”&#xff0c;查来查去&#xff0c;十有八九是用户没进对用户组。这篇文章就把麒麟桌…

作者头像 李华
网站建设 2026/10/6 3:12:15

工业数采网关实战:MQTT与Modbus-RTU桥接RS485设备

先交代个背景。我最近把手头一个工业数采网关项目的 MQTT 上行链路做完了&#xff0c;上一篇写的是环境搭建和订阅发布的基础流程&#xff0c;这篇把最常被问到的问题展开聊&#xff1a;MQTT 消息到底怎么变成 RS485 串口上的一帧数据发给仪表&#xff0c;仪表回应回来的数据又…

作者头像 李华
网站建设 2026/10/6 3:12:03

半天上线AI Agent技能分享站:从架构到SEO的实战记录

事情得从办公室那声“老登”说起。我组里几个年轻人&#xff0c;平时管我这个工作十二年、现在主攻 AI 应用落地的人叫“老登程序员”。上个月我花半天时间把 agentskill.work 从空白仓库做到全量上线&#xff0c;回来之后他们再喊这个称呼&#xff0c;我答应得比谁都快。这个项…

作者头像 李华
网站建设 2026/10/6 3:11:31

Windows winsxs目录膨胀安全清理:组件存储与DISM排查修复

1. 一场"磁盘被神秘占满"的排查起点如果你也经历过这样一个场景——电脑 C 盘莫名从 80GB 可用变成 20GB&#xff0c;用各种管家类软件清理后只挤出了一两个 GB&#xff0c;用不了多久又满回去&#xff0c;打开"此电脑"选中所有文件看属性&#xff0c;却发…

作者头像 李华