简介面向目标检测与农业害虫识别场景此数据集提供10类水稻害虫的VOC格式标注包含训练集与验证集并附类别json字典和可视化脚本可直接用于模型训练与评估。压缩包共2000个文件以XML标注文件为主1999个另有一个无需修改即可运行的Python可视化脚本整体大小约89.78MB。已有326人学习下载。图像为300×300分辨率RGB图片边界框完整且每张含多个目标部分样本经四图融合增强目录按train/val划分各含images与labels子文件夹json字典便于类别映射可视化脚本能随机读取图片绘制边界框并保存整体结构清晰可接入YOLO等主流框架有效降低水稻害虫检测的数据准备成本。1. 10类水稻害虫检测数据集VOC格式看着简单真正决定模型上限的是标注审计从网上下过目标检测数据集的人基本都有同一种经历解压出来目录齐全一训练却发现mAP是零。标签能打开但类别id对不上可视化脚本能跑一半框却画在背景上。这类问题在水稻害虫检测上尤其致命——害虫目标小、密度高、同类姿态差异大标注歪上几个像素模型学到的就是噪声。标题里这套10类别VOC数据集训练集验证集类别json可视化脚本看起来常规却是把数据从“能下载”变成“能训练”的完整链条。这篇文章就按这条链条拆开讲VOC格式怎么读、json怎么对齐、可视化怎么审计、转YOLO训练时最常踩的坑在哪里。适合正在做农业植保检测的工程师也适合第一次训练自己数据集的新手。2. VOC标注格式与数据集构成先读懂目录和xml再谈模型2.1 VOC标注格式为什么仍是农业数据集的主流选择农业植保场景的检测数据大多是小团队采集、分批标注很难一步到位做成大规模COCO格式。VOC格式至今被广泛使用是因为它足够“透明”每张jpg对应一个同名xml文件xml里用纯文本记录了图片尺寸、目标类别和像素坐标任何语言都能解析而且肉眼可读。相比之下YOLO的txt标注里只有归一化浮点数一旦坐标出错靠肉眼几乎无法定位问题。另一个原因是标注工具兼容性。LabelImg、labelme、roboflow导出都支持VOC田间作业人员转标注任务时培训成本比COCO的json低得多。xml里还保留了truncated和difficult这类字段虽然多数检测训练不会直接用但它们能在数据清洗阶段帮我们筛掉“截断严重”和“难以辨认”的样本这一点在害虫数据集里非常实用。需要明确一点VOC格式本身不包含类别清单。xml里object的name字段既是类别名也是唯一标识同一个类在不同xml里写错大小写或多了空格会被当成两个类。这就是标题里专门带上“类别json文件”的原因——json在这里起的是锁死类别字典的作用而不是给模型提供额外特征。先把这一点想清楚后面转YOLO格式时才不会乱。2.2 10类水稻害虫的类别体系与边界划分标题没有给出完整的10类清单但水稻害虫检测数据集按常见做法会覆盖这几类二化螟、三化螟、稻飞虱、稻纵卷叶螟、稻水象甲、稻蝗、稻蓟马、稻瘿蚊、稻秆蝇、稻负泥虫。这10类覆盖了螟虫类、刺吸类、食叶类和钻蛀类主要危害类型是植保站和农药企业做虫情监测时最关心的对象。类别体系设计上最大的坑是近似种混淆。二化螟和三化螟成虫外观接近稻飞虱和叶蝉在低龄若虫阶段几乎无法区分稻蓟马更是只有一毫米级大小。如果标注人员没有植保背景很容易把同一张图里的不同虫态标成两个类。做法上我会在标注规范里强制要求以成虫和典型若虫为标注对象虫态不明的不标同一目标同时存在多个虫态时以数量最多的虫态为准。类别json的排序也要固定比如按危害部位分组排列而不是字母排序否则转换脚本读出来的类别顺序容易和txt标注错位。2.3 训练集与验证集的目录组织方式标题明确包含训练集和验证集。拿到手后我一般会先按下面结构重新组织一遍让后续脚本无脑可用rice_pest/ ├── annotations/ │ ├── train/ │ │ ├── rice_001.xml │ │ ├── rice_002.xml │ │ └── ... │ └── val/ │ ├── rice_101.xml │ └── ... ├── images/ │ ├── train/ │ │ ├── rice_001.jpg │ │ └── ... │ └── val/ │ ├── rice_101.jpg │ └── ... ├── classes.json ├── visualize.py └── README.md这种“annotation与image按同名拆分”的布局是VOC数据集的常见形态。训练集和验证集的比例在害虫这类样本不均衡的任务里我建议控制在8:2到9:1之间不要为了验证集好看而切走太多数据。更重要的是按“田块/拍摄session”划分而不是简单随机切分——同一个田块连续拍摄的帧高度相似随机拆分会造成验证集“虚高”这点后面避坑章节还会细讲。目录对齐是从数据集到训练的第一道关。xml里的filename字段经常和真实图片名不一致有的带路径前缀有的扩展名大小写混乱有的是jpg写成了jpeg。因此一切脚本都不该信任xml里的filename而要自己拿着图片目录建一个stem索引表。这个习惯能帮你在处理任何VOC数据集时少踩一半的坑。3. classes.json与可视化脚本训练前先做一次标注质量审计3.1 读懂classes.json的两种常见设计标题里的“类别json文件”有固定作用但json内部结构并不统一。常见做法有两种第一种是字典映射形式形如{二化螟: 0, 稻飞虱: 1, ...}第二种是列表形式形如[{id: 0, name: 二化螟}, {id: 1, name: 稻飞虱}]。前者简洁后者保留了扩展信息更像COCO风格。问题在于很多数据集作者只给其中一种而可视化脚本默认读另一种于是“json键名对不上”“读不到name字段”这类报错成了高频翻车现场。我写脚本时习惯写一个兼容读取函数不管数据结构长什么样都能拿到类别名字典import json from pathlib import Path def load_class_map(json_path: str) - dict: 读取类别json统一返回 {name: id} 映射。 with open(json_path, r, encodingutf-8-sig) as f: data json.load(f) if isinstance(data, dict): # 形式一: {二化螟: 0, 稻飞虱: 1} return {k: int(v) for k, v in data.items()} if isinstance(data, list): # 形式二: [{id: 0, name: 二化螟}, ...] return {item[name]: int(item[id]) for item in data} raise ValueError(f不支持的json结构: {type(data)})这段代码做了两件事用utf-8-sig打开文件兼容带BOM的Windows记事本保存格式用类型判断处理dict和list两种常见结构。name到id的映射是后续一切转换的唯一数据源不要在训练脚本里再写一份手打类别表。参数说明json_path指向classes.json返回值里映射关系必须与xml里的name标签一致。如果json里是brown_planthopper这类英文名而xml里写的是稻飞虱需要先统一命名否则转换后类别错位。3.2 用可视化脚本把标注框画出来肉眼审核可视化脚本是这套数据集里最容易被人忽略的价值点。很多人拿到数据集直接转格式训练等模型跑完才对着混淆矩阵猜数据问题。正确顺序是训练前先把标注画在原图上肉眼扫一遍。一个能用的可视化脚本通常长这样import argparse import json import xml.etree.ElementTree as ET from pathlib import Path import cv2 def parse_xml(xml_path: Path, class_map: dict): root ET.parse(str(xml_path)).getroot() boxes [] for obj in root.iter(object): name obj.findtext(name).strip() if name not in class_map: print(f警告: {xml_path.name} 含未知类别 {name}) continue bnd obj.find(bndbox) xmin int(float(bnd.findtext(xmin))) ymin int(float(bnd.findtext(ymin))) xmax int(float(bnd.findtext(xmax))) ymax int(float(bnd.findtext(ymax))) boxes.append((name, xmin, ymin, xmax, ymax)) return boxes def main(): ap argparse.ArgumentParser() ap.add_argument(--img_dir, requiredTrue, help图片目录) ap.add_argument(--xml_dir, requiredTrue, helpXML目录) ap.add_argument(--class_json, requiredTrue, help类别json路径) ap.add_argument(--out_dir, defaultvisual_check, help输出目录) ap.add_argument(--show, actionstore_true, help逐张弹窗预览) args ap.parse_args() class_map json.load(open(args.class_json, encodingutf-8-sig)) out_dir Path(args.out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) img_files [p for p in Path(args.img_dir).glob(*.jpg)] img_files [p for p in Path(args.img_dir).glob(*.jpeg)] img_files [p for p in Path(args.img_dir).glob(*.png)] for img_path in img_files: xml_path Path(args.xml_dir) / (img_path.stem .xml) if not xml_path.exists(): print(f跳过: {img_path.name} 缺少xml) continue boxes parse_xml(xml_path, class_map) img cv2.imread(str(img_path)) for name, xmin, ymin, xmax, ymax in boxes: cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, name, (xmin, max(0, ymin - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) out_path out_dir / img_path.name cv2.imwrite(str(out_path), img) if args.show: cv2.imshow(check, img) cv2.waitKey(0) if __name__ __main__: main()这段脚本按图片stem找xml不信任xml内的filename字段对未知类别只警告不崩溃方便一次跑完整个目录。运行方式python visualize.py --img_dir rice_pest/images/train --xml_dir rice_pest/annotations/train --class_json rice_pest/classes.json --out_dir check_output参数说明--show开启弹窗预览适合抽查--out_dir批量输出带框图片适合快速浏览。害虫数据集中小目标多画框后一定要放大看建议把输出图按“每张图的目标数”排序查看目标数越多的图越容易暴露标注问题。3.3 可视化之后重点审计的四类问题第一框与目标边缘是否贴合。害虫身体细长很多标注框为了省事把虫体连同周边茎叶一起框进去IoU只有0.5左右这会直接拉低模型定位精度。第二类别是否错标。二化螟蛾子颜色偏灰褐三化螟偏黄白屏幕上一眼能分辨但标注员连续工作两小时后很容易疲劳错标可视化能把这类错误成批找出来。第三小目标是否有遗漏。稻蓟马、稻飞虱若虫只有几十个像素漏标率最高。可视化时重点看那些“有大框无小框”的密集区域。第四是否存在重叠框。同一目标被标了两遍常见于多人协作标注。重叠框会让模型在训练时收到互相矛盾的梯度。这四类问题靠肉眼扫一遍可视化图就能发现比任何自动化检查都直接。4. 把VOC转成YOLO格式转换脚本、data.yaml与训练参数4.1 为什么yolov8训练前必须转换格式yolov8和yolov5原生的训练管线默认读取Ultralytics格式每个图片对应一个同名txt文件txt里每行是“类别id 归一化中心x 归一化中心y 归一化宽 归一化高”。坐标全部是0~1的浮点数不保留像素坐标和difficult标记。这意味着VOC的xml必须经过一次转换否则数据进不了模型。网上现成的xml转txt脚本很多但大部分没处理三个关键问题目标框坐标超出图像边界、xml里存在没有bndbox的残缺object、类别名称与json不一致。这些问题在害虫数据里很常见因为田间照片可能经过裁剪或拼接标注工具又偶尔会写出xmax比图片宽还大的非法框。所以我建议自己维护转换脚本把输入检查做在源头。4.2 一个带边界保护的XML转TXT脚本下面这个脚本针对水稻害虫数据做了几个加固直接按“annotations目录→labels目录”批量转换import argparse import json import xml.etree.ElementTree as ET from pathlib import Path def convert_one(xml_path: Path, class_map: dict, out_txt: Path, img_width: int, img_height: int): 转换单个xml为YOLO txt坐标越界时按边界裁剪。 root ET.parse(str(xml_path)).getroot() lines [] for obj in root.iter(object): name obj.findtext(name).strip() if name not in class_map: print(f跳过未知类别 {name} 在 {xml_path.name}) continue bnd obj.find(bndbox) if bnd is None: continue xmin float(bnd.findtext(xmin)) ymin float(bnd.findtext(ymin)) xmax float(bnd.findtext(xmax)) ymax float(bnd.findtext(ymax)) # 边界保护防止标注框超出图片尺寸 xmin max(0.0, min(xmin, img_width - 1)) xmax max(0.0, min(xmax, img_width - 1)) ymin max(0.0, min(ymin, img_height - 1)) ymax max(0.0, min(ymax, img_height - 1)) if xmax xmin or ymax ymin: print(f忽略非法框 {name} 在 {xml_path.name}) continue # 转归一化中心坐标 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height # 防止浮点误差导致坐标略超1.0 x_center min(x_center, 1.0) y_center min(y_center, 1.0) cls_id class_map[name] lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_txt.parent.mkdir(parentsTrue, exist_okTrue) out_txt.write_text(\n.join(lines), encodingutf-8) def main(): ap argparse.ArgumentParser(descriptionVOC XML 转 YOLO TXT) ap.add_argument(--xml_dir, requiredTrue) ap.add_argument(--label_dir, requiredTrue) ap.add_argument(--img_dir, requiredTrue) ap.add_argument(--class_json, requiredTrue) args ap.parse_args() class_map load_class_map(args.class_json) img_dir Path(args.img_dir) xml_dir Path(args.xml_dir) label_dir Path(args.label_dir) for xml_path in xml_dir.glob(*.xml): # 从xml同级或图片目录读取尺寸 img_path img_dir / (xml_path.stem .jpg) if not img_path.exists(): img_path img_dir / (xml_path.stem .jpeg) if not img_path.exists(): img_path img_dir / (xml_path.stem .png) if not img_path.exists(): print(f跳过 {xml_path.name}: 找不到图片) continue import cv2 h, w cv2.imread(str(img_path)).shape[:2] out_txt label_dir / (xml_path.stem .txt) convert_one(xml_path, class_map, out_txt, w, h) if __name__ __main__: main()这段脚本的核心设计是坐标裁剪加非法框检查避免yolo训练时xywh出现负数或超界用图片实际尺寸做归一化分母而不是信任xml里的size节点——实测中xml的width和height经常和真实图片尺寸不一致尤其当图片被标注工具压缩过。运行方式python voc2yolo.py --xml_dir rice_pest/annotations/train --label_dir rice_pest/labels/train --img_dir rice_pest/images/train --class_json rice_pest/classes.json转换完建议随手抽查几个txt确认每行都是“整数 四个小数”的结构。常见问题是有些转换脚本把类别id从1开始编号导致第一类永远学习不到训练出来的模型漏检率奇高。4.3 data.yaml的写法与关键参数转换完标注还要写一份data.yaml告诉yolo框架数据在哪、有几类。下面这份配置以yolov8为参照同样适用于yolov5# rice_pest.yaml path: ./rice_pest # 数据集根目录相对路径或绝对路径 train: images/train # 训练图片目录 val: images/val # 验证图片目录 nc: 10 # 类别数必须与classes.json一致 names: # 顺序必须与classes.json的id一致 0: 二化螟 1: 三化螟 2: 稻飞虱 3: 稻纵卷叶螟 4: 稻水象甲 5: 稻蝗 6: 稻蓟马 7: 稻瘿蚊 8: 稻秆蝇 9: 稻负泥虫这里最容易出错的是names顺序。yolov8的txt标注里存的是类别iddata.yaml的names列表按下标对应类别名。假设classes.json里二化螟对应0那names的第0项必须也是二化螟两者任何一边不一致都会让训练在完全错误的目标上优化。训练命令可以直接用yolov8的cli工具yolo detect train datarice_pest.yaml modelyolov8s.pt epochs120 imgsz640 batch16 lr00.01 workers8参数建议按这个基准调整epochs不要低于100害虫类内差异大训练不足容易欠拟合imgsz640是通用默认值如果图里稻蓟马这类小目标占比高可升到1280但显存占用和训练时间会翻倍batch按显存调12G显存跑yolov8s建议8~16workers在Windows上调小到2否则dataloader容易报错。训练结束后用验证集评估yolo detect val datarice_pest.yaml modelruns/detect/train/weights/best.pt验证命令会输出每个类别的precision、recall和mAP。先看每个类别的AP再调参数不要只看总mAP。5. 避坑与排查VOC数据集训练最常见的5个翻车现场5.1 现象训练loss不降验证mAP0.5恒为0原因分析转换脚本里类别id和xml里的name对不上比如按字母序排序生成了id而classes.json按害虫危害部位分组排序两类顺序完全不同。模型从头到尾都在用一个错误映射学不到有效信息。解决办法强制以classes.json为唯一数据源转换脚本不要自己做排序。在convert_one函数里加一行断言检查class_map里的name集合与xml里出现的name集合是否一致不一致立即报错并打印缺失项。这一步能拦住90%的类别错位问题。5.2 现象可视化脚本报“找不到文件”训练时提示Dataset is empty原因分析xml里的filename和实际图片文件名不匹配。有的标注工具写的是带前缀的路径有的把jpg写成了jpeg还有的图片名是1.jpg而xml文件名是1_1.xml。可视化脚本和转换脚本都按stem匹配时就会扑空。解决办法不要依赖filename字段统一用Path(img_path).stem去xml目录里找同名xml。如果图片名和xml名整体对不上说明数据集在打包时已经损坏这时只能按xml的filename去重新映射图片或者用图片内容哈希做匹配没有捷径。5.3 现象json.load报编码错误或类别名在可视化图上显示乱码原因分析数据集作者在Windows下用记事本保存json默认编码是GBK或者带BOM另一些json里混入了中文引号。python的json.load默认按utf-8解析遇到GBK直接抛错。解决办法读json统一用encodingutf-8-sig这能同时兼容带BOM和不带BOM的utf-8。如果还报错用chardet探测文件编码再把文件转成utf-8重存。另外脚本入口要加# -*- coding: utf-8 -*-终端也切到utf-8环境避免中文类别名在Linux和Windows两个平台间来回踩坑。5.4 现象某张图有目标但txt文件为空训练时该图被当成背景原因分析xml里object节点缺失bndbox子节点或所有框经过边界裁剪后变成非法框被脚本过滤掉了。这种情况常见于标注过程中误删框体坐标只留了类别名。解决办法转换脚本里遇到“有object但无bndbox”的情况不要静默跳过要单独输出到errored.txt清单。训练前检查清单把这类xml从标注目录移出或者人工补齐坐标。空txt本身没问题yolov8会把空txt当负样本但如果这张图本该有目标模型就会学到“这里没虫”的错误信号。5.5 现象验证集mAP虚高实田间测试一塌糊涂原因分析训练集和验证集出现重叠。最常见的是按文件名随机拆分而同一田块连续拍摄的几十帧画面高度相似被拆到了不同集合里验证时模型相当于“看过答案再考试”。另一个来源是augmented图片没隔离离线增强出来的图片被同时放进了train和val。解决办法写一个检查脚本比对训练集和验证集的文件stem列表找出同时出现在两边的图片名。更合理的划分方式是按“拍摄时间田块编号”分组一组整体进train或整体进val不做逐张随机切分。数据集目录里如果带了采集日志优先按日志字段划分。6. 更进一步数据增强、轻量模型选型与田间验收标准6.1 离线增强怎么做效果最好水稻害虫检测的难点集中在目标小、背景杂、光照多变。在线增强里mosaic和随机HSV抖动是默认项此外我习惯加两项种子级增强随机亮度扰动和随机遮挡模拟。亮度扰动要同时调高调低模拟早晚田间光照差异遮挡模拟可以用随机矩形块遮掉部分虫体强迫模型学习局部特征。离线增强不要无脑复制二化螟这类样本少时把原始图做水平翻转、旋转30度、加高斯噪声各扩一份就够了。6.2 模型选型建议这类数据集我推荐从yolov8n或yolov8s起步不要一上来跑yolov8x。稻蓟马和稻飞虱若虫目标小但并不是模型越大效果越好小模型配合1280输入分辨率往往比大模型配640更实际。如果要做田间边缘端部署训练完把模型转成ONNX再量化成FP16体积能压到十几MB推理耗时在Jetson上可以到实时水平。6.3 验收标准别只看总mAP最后要有验收底线至少让验证集mAP0.5达到0.85以上且逐类AP里没有低于0.6的类别。我最早做水稻害虫检测时总mAP接近0.9但稻蓟马那一类AP只有0.2装在田间的设备对小型害虫几乎完全漏检。后来把可视化脚本输出的图片逐张重查发现是标注框普遍偏大目标只占框面积的六成模型被背景噪声带偏了。重标那一类后AP直接翻倍。数据干净这件事永远比模型结构更值得投入时间。这套做法是我踩了多次坑之后沉淀下来的希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
AI音乐生成提示词模板库:12种风格+四层结构,从随机抽卡到精准配乐 1. 为什么我要花两周时间整理这套AI音乐提示词库去年年底我开始用AI音乐生成工具做短视频配乐,一开始觉得这东西太简单了——输入“欢快的钢琴曲”不就完了?结果生成出来的东西要么像八音盒卡带,要么像MIDI铃声大杂烩,根本没法用。… · 2026/9/25 10:37:09
Atlas 300V 24G部署YOLO模型全攻略:从ONNX转换到推理性能优化 1. Atlas 300V 24G是谁:先纠正一个常见误读最近被问得最多的问题就是"Atlas 300V 24G是不是运算加速卡",很多人把它当成一张类似游戏显卡的东西,或者以为它跟GPU一样插上就能跑PyTorch。这个误读直接影响后续所有部署决策——如果你… · 2026/9/25 10:37:09
AI辅助PLC编程实战:从复制粘贴到提示词驱动,老工程师的避坑指南 说实话,前两年AI技术刚火起来那阵,我是真没当回事。干了十年PLC,天天跟接触器、继电器、扫描周期、梯形图打交道,总觉得AI写网页、写Python跟我有什么关系?直到上个月接了个项目,三十多个电动阀门的启停逻辑… · 2026/9/25 10:36:57
nvim-tree.lua 贡献开发指南:质量检查、帮助文档生成与 Pull Request 规范全解 开发工具 【免费下载链接】nvim-tree.lua A file explorer tree for neovim written in lua 项目地址: https://gitcode.com/gh_mirrors/nv/nvim-tree.lua 点击查看 免费下载 导读:本文以 nvim-tree.lua 仓库的 CONTRIBUTING.md 为主体,系统… · 2026/9/25 11:07:30
Atlas 300V部署YOLO全流程:昇腾推理卡环境搭建与模型转换实战 “Atlas部署YOLO”这六个字,是我最近在好几个AI相关的社群里见得最多的一句话。点进去一看,问的人大多一脸迷茫,手里刚好有一张Atlas 300V Pro(24G)推理卡,或者是公司刚采购了一批昇腾设备,领导… · 2026/9/25 11:07:23
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37