首页/新闻资讯/正文详情

VOC转YOLO格式:目标检测数据集转换脚本与实操避坑指南

发布时间:2026/9/24 18:28:41 来源:云帆数科 栏目:资讯中心
VOC转YOLO格式:目标检测数据集转换脚本与实操避坑指南
简介将VOC格式数据集转换为YOLO格式并完成训练集与测试集划分的Python实现面向计算机、电子信息工程、数学等专业的本专科学生适用于课程设计、期末大作业、毕业设计中的目标检测数据预处理环节。压缩包内共2个文件均为.py脚本整体大小约2KB结构非常精简一个脚本负责把PASCAL VOC的XML标注解析并映射为YOLO的txt标签另一个脚本负责按设定比例随机划分训练集与测试集并自动生成对应的文件路径清单。代码采用参数化编程思路输入输出路径、类别列表、划分比例等关键参数均可灵活修改同时辅以清晰注释和运行结果示例已经过实际数据测试可直接复用或二次开发。目前已有560人学习下载适合需要批量转换VOC标注、搭建YOLO训练数据集的初学者参考也可作为快速完成数据准备工作的实用小工具。1. 把 VOC 数据集喂给 YOLO 训练先过格式转换这道坎做过目标检测的人都知道LabelImg 标注完的数据是 VOC 格式每个图片对应一个 XML 文件里面是object标签包着bndbox坐标。但 YOLO 训练要的不是 XML而是每个图片对应一个同名 TXT里面写归一化坐标和类别 id。这个转换卡住了很多刚接触 YOLO 的人尤其是毕设和课程设计阶段数据集标注完才发现格式对不上训练脚本一跑就报错。这份资源解决的正是这个问题两个 Python 脚本voc_to_yolo.py负责把 VOC 的 XML 标注转成 YOLO 的 TXT 标注cutdata.py负责把图片和标注按比例拆成训练集与测试集外加一份说明文档把参数讲清楚。适合正在做目标检测课程设计、期末大作业或者第一次用 YOLOv5/YOLOv8 训练自己数据集的同学。两个脚本都是参数化写法改路径和比例就能用不用改代码逻辑。2. 格式差异与转换原理为什么非转不可以及坐标公式怎么来的2.1 VOC 与 YOLO 标注的本质区别VOC 格式的 XML 记录的是目标框的绝对像素坐标也就是xmin、ymin、xmax、ymax四个整数单位是像素坐标原点是图片左上角。而 YOLO 格式的 TXT 每行只写五个数class_id x_center y_center width height其中后四个都是相对图片宽高的归一化浮点数取值范围在 0 到 1 之间。归一化的目的很简单不管训练时输入图片被 resize 成 640 还是 416归一化坐标都不用重新计算。两个格式的关键差异有三个。第一是类别表示方式VOC 的 XML 里存的是类别名字符串比如nameperson/name而 YOLO 只认数字 id0、1、2……这个 id 和你定义的类别顺序严格绑定。第二是坐标语义VOC 给的是左上角和右下角两个点YOLO 给的是中心点加宽高。第三是存储粒度VOC 是一个 XML 管一张图的全部目标YOLO 是每个目标一行一个 TXT 管一张图。所以转换脚本要做的事就是解析 XML 拿到每个目标的名字和 bndbox 坐标把名字映射成数字 id再把(xmin, ymin, xmax, ymax)换算成(x_center, y_center, width, height)并除以图片宽高。换算公式如下x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height2.2 为什么转换脚本要拆成两个文件而不是合成一个很多初学者会问转换和划分数据能不能写在一个脚本里跑完。功能上完全可以但这个资源选择拆成两个脚本是有讲究的。voc_to_yolo.py处理的是数据和标注的关系cutdata.py处理的是文件在磁盘上的组织方式两者职责不同分开写的好处是你在调试数据格式问题时不需要关心目录怎么分在调整训练集测试集比例时不需要重新跑一遍转换逻辑。实际使用中我一般也是这个习惯。先单独跑转换抽几张图出来看一眼 TXT 内容对不对确认坐标值没有超出 0~1 的范围再做数据划分。如果合成一个脚本一旦转换环节有隐蔽 bug划分完的数据得全部重新生成浪费时间。2.3 两个脚本的依赖与运行环境两个脚本都是纯 Python 实现不需要安装 OpenCV 或者 NumPy 之类的大型依赖只用到了标准库里的xml.etree.ElementTree用于解析 XML、os和shutil用于文件操作、random用于随机划分。这意味着任何一个装了 Python 3 的机器都能直接跑不涉及虚拟环境配置和包管理器安装的坑。脚本里用的os.makedirs、os.listdir、shutil.copy都是 Python 文件操作的常见函数。提示脚本不需要 GPU 也不需要 CUDA纯数据预处理任务CPU 跑完就结束几分钟的事情。3. 拿到资源先改参数脚本核心参数与配置方式3.1 voc_to_yolo.py 的参数清单与含义脚本最上方定义了几个待配置参数改完直接运行即可。拿到的压缩包解压后用任意文本编辑器打开voc_to_yolo.py你会看到类似下面的配置区# 必改参数 # VOC 格式标注文件所在的目录通常名为 Annotations xml_folder VOCdevkit/VOC2007/Annotations # 图片文件所在的目录通常名为 JPEGImages img_folder VOCdevkit/VOC2007/JPEGImages # 转换后 YOLO 标注文件的输出目录建议新建 labels 文件夹 output_folder VOCdevkit/VOC2007/labels # 类别列表顺序非常重要和你的训练配置保持一致 classes [person, car, bicycle, dog, cat] # 参数说明xml_folder指向存放所有 XML 标注文件的目录脚本会遍历这个目录下的所有.xml文件。img_folder是原始图片目录脚本需要读取图片尺寸来完成归一化计算所以这个路径不能省。output_folder是转换结果输出目录每个 XML 会生成一个同名的.txt文件到这个目录。重点是classes列表。列表的索引就是类别 id索引 0 对应person索引 1 对应car以此类推。这个顺序必须和你后续训练时用的data.yaml里的类别顺序完全一致否则会出现类别错位模型以为 0 是 car但实际数据里 0 是 person训练出来的模型全乱套。3.2 cutdata.py 的参数与目录组织逻辑cutdata.py的作用是把转换好的图片和 TXT 标注按比例拆成训练集和测试集。它需要知道图片在哪、标注在哪、输出到哪以及切分比例。配置区通常长这样# 必改参数 # 原始图片目录和 voc_to_yolo.py 的 img_folder 保持一致 images_path VOCdevkit/VOC2007/JPEGImages # 转换好的 TXT 标注目录和 voc_to_yolo.py 的 output_folder 保持一致 labels_path VOCdevkit/VOC2007/labels # 划分后输出目录会自动创建 train/images、train/labels 等子目录 output_path VOCdevkit/VOC2007/yolo_data # 测试集比例0.2 表示 20% 的数据作为测试集 test_ratio 0.2 # 随机种子固定后每次划分结果一致方便复现 random_seed 42 # 参数说明整个划分逻辑是先把images_path下的所有图片文件名提取出来按test_ratio比例随机选出一部分作为测试集剩下的作为训练集。选好之后把图片和对应的 TXT 标注分别复制到输出目录的train/images、train/labels、test/images、test/labels四个子目录下。random_seed这个参数值得多说一句。固定随机种子可以保证每次运行脚本得到的划分结果完全一致这在调试时特别有用。你调一次参数发现结果不对改完代码再跑一次如果训练集和第一次跑的完全一样问题就出在别的地方如果每次跑划分结果都在变你根本没法定位是划分问题还是训练问题。所以我强烈建议自己用的时候也固定住种子。提示输出目录结构按训练和测试分大类每类下面再分 images 和 labels 两个子目录这个组织方式也是 YOLOv5 和 YOLOv8 官方仓库默认支持的目录结构后面训练的时候直接指定--data参数指向这个目录即可。4. 转换与划分的实现细节核心代码逐段拆解4.1 XML 解析与坐标提取voc_to_yolo.py的第一步是解析 XML。核心逻辑是用ElementTree加载 XML 文件遍历所有object节点每个节点对应一个目标框。代码如下import xml.etree.ElementTree as ET def parse_xml(xml_file): tree ET.parse(xml_file) root tree.getroot() # 读取图片尺寸VOC 的 XML 里 size 标签存了宽高 size root.find(size) img_width int(size.find(width).text) img_height int(size.find(height).text) img_depth int(size.find(depth).text) objects [] for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) objects.append({ name: name, xmin: xmin, ymin: ymin, xmax: xmax, ymax: ymax }) return img_width, img_height, objects这段代码的逻辑很直白先定位size标签拿到图片宽高再遍历object标签提取每个目标的类别名和边界框坐标。这里有个细节值得注意xmin、xmax等坐标在 XML 里虽然是整数但读进来转成float而不是int。原因是后面计算归一化坐标时要除图片宽高如果这里用int除法结果是精确的没问题但有些标注工具可能输出浮点坐标提前转float能避免类型错误。4.2 坐标转换与归一化的核心公式拿到原始坐标后就要按前面说的公式做归一化转换。这步是转换脚本的核心直接决定 TXT 里面的数值能不能被 YOLO 正确解析def convert_to_yolo(img_width, img_height, obj): # obj 是一个包含 xmin, ymin, xmax, ymax 的字典 xmin, ymin obj[xmin], obj[ymin] xmax, ymax obj[xmax], obj[ymax] # 计算中心点和宽高 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height return x_center, y_center, width, height有几个点需要说明。分母必须用img_width和img_height也就是整张图的宽高不能用某个目标的宽高做分母。中心点坐标是(xmin xmax) / 2再除以图宽这个顺序不能反过来。还有一种常见错误是把x_center写成(xmin xmax) / 2.0 / img_height也就是除错了分母这种错误不会让程序崩溃但 TXT 里的数值是错的训练时 loss 会异常大模型收敛不了属于比较隐蔽的坑。写 TXT 文件时注意格式YOLO 对分隔符不敏感空格和 tab 都能用但建议用空格统一。每行五个数值类别 id 在最前面其余四个按x_center y_center width height的顺序排列这个顺序不能搞混。4.3 类别名映射到数字 id 的处理YOLO 的 TXT 里不存类别名字符串只存数字 id。映射逻辑是在转换时把 XML 里的name和预定义的classes列表做匹配def class_to_id(name, classes): # 在类别列表里查找该名字的索引找不到就报错 if name not in classes: raise ValueError(f未知类别: {name}请检查 classes 列表是否完整) return classes.index(name) # 转换主循环中调用 class_id class_to_id(obj[name], classes)这里有个重要的设计决策遇到类别列表里不存在的名字时直接抛异常而不是跳过。很多转换脚本遇到未知类别会打印一行警告然后跳过这个目标但这会静默丢掉标注导致训练数据缺失。抛异常的价值在于让你立刻发现问题——如果你的数据集里有 10 个类别但 classes 列表只写了 8 个运行时会直接报错告诉你漏写了什么。写代码时注意classes.index(name)是线性查找类别数量几十个以内完全没性能问题不需要做特殊优化。如果类别上百个可以提前建一个{名字: id}的字典用查表方式但常见数据集类别数都在 80 个以内直接 index 就行。4.4 数据划分的逻辑实现cutdata.py的划分逻辑用到的是 Python 标准库的random.sample一次性抽取测试集文件名避免手动 shuffle 加切片时出错import random import os import shutil def split_dataset(images_path, test_ratio, random_seed): # 拿到所有图片文件名 all_images [f for f in os.listdir(images_path) if f.endswith((.jpg, .png, .jpeg))] all_images.sort() # 排序保证顺序稳定 random.seed(random_seed) # 按比例抽取测试集sample 不会重复抽取 test_count int(len(all_images) * test_ratio) test_images random.sample(all_images, test_count) # 剩下的就是训练集 train_images [f for f in all_images if f not in test_images] return train_images, test_images这段代码有三个设计点值得说明。先sort()再划分保证不同机器上跑出来的结果一致虽然随机种子已经保证了随机序列一致但os.listdir返回的文件顺序在不同系统上可能不一样排序可以再上一层保险。random.sample采样结果不会重复不需要自己判断去重。判断一个文件是训练集还是测试集用的是列表推导式里的not in测试集一般只有几百个文件线性查找的性能可以忽略。划分后的文件复制可以用shutil.copy2而不是shutil.copy区别是copy2会保留文件的修改时间和元数据便于后续检查哪些文件是最近生成的# 复制到训练集目录 for img in train_images: shutil.copy2(os.path.join(images_path, img), os.path.join(train_img_dir, img)) shutil.copy2(os.path.join(labels_path, img.replace(.jpg, .txt)), os.path.join(train_lbl_dir, img.replace(.jpg, .txt))) # 测试集同理只是目标目录换成 test复制标注时用了img.replace(.jpg, .txt)的方式推导标注文件名这要求图片后缀统一。如果你的数据集里混合了.jpg和.png建议先统一后缀规则或者在代码里加一个后缀映射函数。5. 避坑与常见问题排查六个我自己踩过的坑5.1 提示图片尺寸读取失败XML 里没有size标签现象运行voc_to_yolo.py时报错提示NoneType object has no attribute find定位到size root.find(size)这一行。原因部分 VOC 数据集在标注时没有写入size信息尤其是一些从网上下载的改名版数据集XML 里只有object没有size。代码里直接对root.find(size)的结果调用find(width)结果为空自然报错。解决给size做一个判空校验为空时用cv2.imread读取图片实际尺寸兜底或者直接跳过这个 XML 并打印警告。我一般建议第二种方案宁可少一张图也不要给训练数据喂错误尺寸。5.2 转换后 TXT 里出现大于 1 的坐标值现象转换完成后打开 TXT发现里面有的数值超过 1比如0.87 1.23 0.44 0.56。原因归一化公式里分子算错。常见的是x_center (xmin xmax) / 2之后忘了再除以img_width或者除的时候把img_width和img_height弄反了。还有一种可能是标注框本身超出了图片边界xmax比img_width还大。解决先检查公式确认除了两次。再检查原始 XML 里的坐标值用读图工具打开对应图片看标注框是否越界。YOLO 训练时坐标值大于 1 不会报错但会严重影响训练效果属于必须排查的问题。5.3 类别 id 错位模型把人和车搞混现象训练正常收敛loss 也降下来了但推理的时候person类别的框打出的标签是car。原因voc_to_yolo.py的classes列表顺序和训练时data.yaml里的names顺序不一致。比如转换时classes [person, car]训练时names {0: car, 1: person}模型学到的人的特征被打上了 0 号标签而 0 号标签对应的是 car。解决转换脚本里的classes列表和训练配置里的names列表必须严格一致最好的办法是直接复制粘贴而不是重新手打一遍顺序。固定用同一个配置文件管理类别列表避免两处维护。5.4 划分后训练集图片和标注数量对不上现象跑完cutdata.py后train/images下有 100 张图但train/labels下只有 95 个 TXT 文件。原因原始数据里有几张图片没有对应的 XML 标注转换时没有生成 TXT。cutdata.py只按图片文件名做划分不管标注是否存在导致图片被复制过去但标注缺失。解决在cutdata.py里加一个校验逻辑复制标注前先检查对应 TXT 文件是否存在不存在就跳过这张图片并打印提示。如果这张图确实没有目标也可以生成一个空 TXT 文件YOLO 支持空标注但建议直接丢弃这种图。5.5 图片后缀不统一.jpg和.JPG混用现象脚本跑完有些标注文件没有被复制到对应目录日志里没有任何报错。原因cutdata.py里用img.replace(.jpg, .txt)推导标注文件名但原始图片里恰有.JPG后缀的文件替换后变成.JPG.txt和voc_to_yolo.py生成的.txt文件名对不上。解决在预处理阶段先统一图片后缀全部转成小写.jpg。用 Python 批量os.rename或者干脆在读取文件列表时只保留小写后缀的文件把大写后缀的单独纳为警告项。5.6 固定随机种子后两次划分结果仍然不同现象random_seed 42设了但连续跑两次cutdata.py得到的训练集和测试集名单不一样。原因代码里只对random.sample做了random.seed(random_seed)但random.sample之前如果有其他代码调用了random模块的全局状态会影响采样结果。更常见的是文件列表没有sort()os.listdir顺序不固定导致列表顺序变了。解决把random.seed(random_seed)放在os.listdir之后、random.sample之前执行且确保文件列表先sort()。如果还不行用random.Random(random_seed)创建一个独立的随机实例不污染全局状态。6. 进阶用法转换结果回画验证与难例挖掘转换脚本跑通只是第一步真正能省时间的是转换完成后的验证环节。我每次转完格式都会做一件事把 TXT 里的归一化坐标读出来换算回像素坐标画到原图上生成一张带框的验证图。这个操作能一次性发现坐标错位、归一化除错、类别映射乱套等所有问题。import cv2 def verify_txt(image_path, txt_path, classes): img cv2.imread(image_path) h, w img.shape[:2] with open(txt_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() cls_id int(parts[0]) x_center float(parts[1]) * w y_center float(parts[2]) * h box_w float(parts[3]) * w box_h float(parts[4]) * h xmin int(x_center - box_w / 2) ymin int(y_center - box_h / 2) xmax int(x_center box_w / 2) ymax int(y_center box_h / 2) color (0, 255, 0) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color, 2) cv2.putText(img, classes[cls_id], (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow(verify, img) cv2.waitKey(0)这段验证代码的核心是把归一化坐标反向换算回像素坐标乘回宽高即可。验证时重点看三个地方框是否紧贴目标物体边缘、类别标签是否正确、框是否越界出图片。跑完所有验证图确认没问题数据才可以拿去训练。另一个值得做的进阶操作是难例挖掘。训练完一轮后把测试集里预测置信度低的样本找出来单独分析是标注质量差还是目标太小太模糊。如果是标注问题用 LabelImg 修正后重新转换不需要改任何脚本代码直接重新跑一遍voc_to_yolo.py和cutdata.py就行。这个流程走下来数据质量基本就能和公开数据集的水平看齐。从那以后我每次转换完都会强制走一遍回画验证流程不管数据集多小这个步骤不跳过。框画出来对了后面训练才有意义。希望这个资源和这套验证流程能帮到你。本文还有配套的精品资源点击获取

相关推荐

OpenCV模板匹配车牌识别:从原理到实战的完整实现与避坑指南
OpenCV模板匹配车牌识别:从原理到实战的完整实现与避坑指南

简介:基于 OpenCV 模板匹配的车牌识别项目,定位为计算机视觉方向的 Python 毕业设计/课程设计参考实现,面向具备 Python 基础、希望快速搭建车牌识别 Demo 的高校学生。项目提供完整 GUI 界面,软件环境为 Python 3.8 与 OpenCV 4.… · 2026/9/24 18:28:41

Mac读写NTFS移动硬盘全攻略:三种方案搞定只读问题
Mac读写NTFS移动硬盘全攻略:三种方案搞定只读问题

用Mac的人碰到NTFS格式的移动硬盘,几乎都会卡在同一句提示上:“这个磁盘是只读的”。NTFS是Windows最常用的文件系统,而macOS默认用的是APFS/HFS,苹果虽然自带了一套NTFS读取能力,却把写入功能藏了起来,导致… · 2026/9/24 18:28:41

oh-my-pi 十分钟上手:把 IDE 能力接进终端的 AI 编程智能体,从安装到完成一次代码审查
oh-my-pi 十分钟上手:把 IDE 能力接进终端的 AI 编程智能体,从安装到完成一次代码审查

oh-my-pi 十分钟上手:把 IDE 能力接进终端的 AI 编程智能体,从安装到完成一次代码审查 【免费下载链接】oh-my-pi ⌥ Coding agent with the IDE wired in 项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi oh-my-pi(命令行… · 2026/9/24 18:28:41

二手房房价预测Python实战:数据清洗到机器学习建模全流程
二手房房价预测Python实战:数据清洗到机器学习建模全流程

简介:基于链家网二手房交易数据,这套项目源码覆盖从数据爬取、清洗、分析到房价预测的完整流程。项目获导师认可并以98分通过毕业设计答辩,适合计算机相关专业正在准备毕业设计、课程设计或期末大作业的学生,也适合需要真实项目练… · 2026/9/24 19:08:44

DNS欺骗攻击原理与防御:从ARP劫持到抓包取证
DNS欺骗攻击原理与防御:从ARP劫持到抓包取证

1. 攻击目标、测试场景与武器选择做安全测试这几年,我一直觉得 DNS 欺骗是个被低估的入口。很多人把注意力放在 Web 漏洞、系统漏洞上,却忽略了“地址解析”这个最基础的环节。一旦域名解析被改写了,用户访问的网站、下载的文件、输入的账号密… · 2026/9/24 19:08:44

千元内降噪耳机横评:通勤与长途场景实测选购指南
千元内降噪耳机横评:通勤与长途场景实测选购指南

每天早高峰挤地铁的时候,我都在想一个问题:到底是车厢里的报站声更让人烦躁,还是旁边那位外放短视频的大哥更让人崩溃?后来我发现答案都不对,最让人崩溃的是你花了小一千买了个降噪耳机,结果戴上去之后&… · 2026/9/24 19:08:44

Java学生选课系统如何保证并发数据一致性?从表结构到事务实战
Java学生选课系统如何保证并发数据一致性?从表结构到事务实战

简介:基于Java的学生选课系统压缩包是一套前后端分离的应用源码,面向需要处理课程数据管理、选课排课与权限分配的高校实训、课程设计或小型教务场景,适合具备一定Java与Vue基础的开发者参考。系统后端采用Spring Boot,前端基于Vu… · 2026/9/24 19:08:44

宽带FWM波长转换模块设计:相位匹配、器件选型与测试实战
宽带FWM波长转换模块设计:相位匹配、器件选型与测试实战

做宽带FWM产品这几年,最大的感受是:网上能找到的理论一堆,但真正动手搭系统、调相位匹配、换器件、处理测试数据的时候,坑比想象中多得多。不少同事、同行拿着论文里的参数直接选型,结果做出来的样机效率低、带宽窄、指… · 2026/9/24 19:08:38

云端 GPU 图形调试:何时需要 VNC 图形入口,而不是只停留在 SSH?
云端 GPU 图形调试:何时需要 VNC 图形入口,而不是只停留在 SSH?

云端 GPU 上跑图形类、视频类或其他需要窗口反馈的任务时,一个很常见的误区是: 已经能 SSH 进去,是不是就说明远程调试入口已经解决了? 不一定。 这里真正需要区分的,并不是“SSH 和 VNC 谁更好”,而是当前… · 2026/9/24 19:08:31

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码