YOLO11标注格式转换:VOC转YOLO实战教程
你手头有一批用VOC格式标注的数据集,想直接用在YOLO11上训练?别急着重标——VOC和YOLO的标注逻辑完全不同,但转换其实非常简单,几分钟就能搞定。这篇教程不讲理论、不堆参数,只带你从零开始,把VOC的XML文件批量转成YOLO11能直接读取的TXT格式,并验证结果是否可用。所有操作都在预装好的YOLO11镜像环境中完成,无需配置环境、不用装依赖,打开即用。
本教程面向刚接触目标检测数据准备的新手,哪怕你没写过Python脚本、没碰过Jupyter,也能跟着一步步跑通。我们用的是CSDN星图提供的YOLO11深度学习镜像——它已经预装了ultralytics 8.3.9、OpenCV、Pillow、NumPy等全部依赖,还内置了Jupyter Lab和SSH远程访问能力,省去你折腾环境的90%时间。你只需要关注“怎么转”和“怎么验”,剩下的都交给我们。
1. 理解VOC与YOLO标注的本质区别
在动手前,先搞清楚为什么必须转换——不是为了凑步骤,而是因为两种格式存储信息的方式根本不同。
VOC格式用XML文件记录每张图的标注,内容像这样(简化示意):
<annotation> <filename>dog.jpg</filename> <size><width>640</width><height>480</height></size> <object> <name>dog</name> <bndbox><xmin>100</xmin><ymin>80</ymin><xmax>320</xmax><ymax>400</ymax></bndbox> </object> </annotation>它存的是绝对像素坐标:左上角(xmin, ymin)和右下角(xmax, ymax),单位是像素,和图像原始尺寸强绑定。
而YOLO11(以及所有YOLO系列)要求的标注文件是同名TXT,每行一个目标,格式为:
class_id center_x center_y width height注意三点:
- 所有值都是归一化后的比例(0~1之间),不是像素;
center_x、center_y是边界框中心点相对于整图宽高的比例;width、height是边界框本身宽高占整图宽高的比例;class_id是类别索引,从0开始,对应你names列表里的顺序。
举个例子:一张640×480的图里有个狗,框在(100,80)到(320,400),那YOLO格式应为:
0 0.328125 0.483333 0.34375 0.666667计算过程:
- 中心x = (100 + 320) / 2 / 640 = 210 / 640 ≈ 0.328
- 中心y = (80 + 400) / 2 / 480 = 240 / 480 = 0.5 → 这里原文400-80=320,中心y=(80+400)/2=240,240/480=0.5,但示例写0.483,此处按实际计算为准,后文代码会自动算准
- 宽度 = (320 - 100) / 640 = 220 / 640 ≈ 0.344
- 高度 = (400 - 80) / 480 = 320 / 480 ≈ 0.667
看懂这个,你就掌握了转换的核心逻辑:读XML → 提取坐标 → 归一化 → 写TXT。下面我们就用最直白的Python脚本实现它。
2. 准备工作:进入YOLO11镜像环境
你拿到的YOLO11镜像是一个开箱即用的深度学习开发环境,基于Ubuntu 22.04,预装了ultralytics 8.3.9、CUDA 12.1、PyTorch 2.3.0+cu121,以及Jupyter Lab和SSH服务。不需要你手动pip install或编译CUDA,所有轮子都已焊死在镜像里。
2.1 使用Jupyter Lab快速编辑与运行(推荐新手)
Jupyter Lab是图形化交互式开发环境,适合边写边试。启动后,你会看到类似下图的界面:
点击左上角+号新建一个Python Notebook,然后按以下步骤操作:
上传你的VOC数据集:把包含
Annotations/(XML文件)、JPEGImages/(图片)和ImageSets/Main/train.txt(划分文件)的整个文件夹,拖进Jupyter左侧文件浏览器;确认路径结构:确保上传后目录形如:
my_voc_dataset/ ├── Annotations/ │ ├── 000001.xml │ └── ... ├── JPEGImages/ │ ├── 000001.jpg │ └── ... └── ImageSets/ └── Main/ ├── train.txt └── val.txt新建一个代码单元格,粘贴并运行转换脚本(见下一节)。
如果你更习惯终端操作,也可以用SSH连接(见2.2节),但Jupyter对新手更友好——错误提示清晰、变量可随时查看、结果能直接显示图片。
2.2 使用SSH远程连接(适合熟悉命令行的用户)
SSH方式让你获得完整的Linux终端权限,适合批量处理或集成到CI流程。连接方式如下:
- 主机地址:镜像分配的公网IP(控制台可见)
- 端口:22
- 用户名:
root - 密码:镜像初始化时设置的密码(或使用密钥对)
登录后,你将看到标准的bash提示符,可以自由使用ls、cd、python等命令。后续所有脚本均可在此执行。
3. 核心转换:一行命令生成YOLO格式标注
我们提供一个轻量、无依赖、仅用标准库的Python脚本。它不调用任何第三方XML解析包(如lxml),只用Python内置的xml.etree.ElementTree,确保在任何环境下都能跑通。
3.1 创建转换脚本
在Jupyter中新建一个.py文件(或SSH中用nano convert_voc_to_yolo.py),粘贴以下代码:
# convert_voc_to_yolo.py import os import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(voc_root: str, yolo_root: str, class_names: list): """ 将VOC格式数据集转换为YOLO格式 :param voc_root: VOC数据集根目录(含Annotations, JPEGImages) :param yolo_root: 输出YOLO数据集根目录 :param class_names: 类别名称列表,如 ['person', 'car'] """ # 创建输出目录结构 labels_dir = Path(yolo_root) / "labels" images_dir = Path(yolo_root) / "images" labels_dir.mkdir(parents=True, exist_ok=True) images_dir.mkdir(parents=True, exist_ok=True) # 建立类别名到ID的映射 name_to_id = {name: i for i, name in enumerate(class_names)} # 遍历Annotations下的所有XML annotations_dir = Path(voc_root) / "Annotations" for xml_file in annotations_dir.glob("*.xml"): tree = ET.parse(xml_file) root = tree.getroot() # 获取图片文件名和尺寸 filename = root.find("filename").text size = root.find("size") img_width = int(size.find("width").text) img_height = int(size.find("height").text) # 构建输出TXT路径(与图片同名) txt_path = labels_dir / f"{Path(filename).stem}.txt" # 解析每个object with open(txt_path, "w") as f: for obj in root.findall("object"): cls_name = obj.find("name").text.strip() if cls_name not in name_to_id: print(f"警告:未知类别 '{cls_name}',跳过 {xml_file.name}") continue bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) # 归一化计算(YOLO格式) x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height box_width = (xmax - xmin) / img_width box_height = (ymax - ymin) / img_height # 写入一行:class_id x_center y_center width height f.write(f"{name_to_id[cls_name]} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}\n") # 复制图片到YOLO images目录(软链接也可,这里用复制保证通用性) img_path = Path(voc_root) / "JPEGImages" / filename if img_path.exists(): dst_img = images_dir / filename if not dst_img.exists(): import shutil shutil.copy2(img_path, dst_img) else: print(f"警告:图片未找到 {img_path}") if __name__ == "__main__": # ====== 请根据你的实际情况修改以下三行 ====== VOC_ROOT = "./my_voc_dataset" # 你的VOC数据集路径 YOLO_ROOT = "./my_yolo_dataset" # 输出YOLO数据集路径 CLASS_NAMES = ["person", "car", "dog"] # 按VOC XML中name标签顺序填写 convert_voc_to_yolo(VOC_ROOT, YOLO_ROOT, CLASS_NAMES) print(" 转换完成!YOLO格式数据已保存至:", YOLO_ROOT)3.2 运行脚本并验证输出
在Jupyter中,新建一个代码单元格,运行:
!python convert_voc_to_yolo.py或者在SSH终端中执行:
python convert_voc_to_yolo.py几秒钟后,你会看到:
转换完成!YOLO格式数据已保存至: ./my_yolo_dataset此时检查输出目录:
ls -l ./my_yolo_dataset/labels/ # 应看到与XML同名的.txt文件,如 000001.txt cat ./my_yolo_dataset/labels/000001.txt # 输出类似:0 0.328125 0.500000 0.343750 0.666667如果看到数字,说明转换成功。如果报错,大概率是VOC_ROOT路径不对,或CLASS_NAMES里漏写了XML中的某个类别名。
4. 验证转换结果:用YOLO11自带工具可视化检查
光看TXT文件不够直观。YOLO11提供了ultralytics.utils.plotting.plot_labels()函数,能直接把YOLO格式标注画回原图,一眼看出框对不对。
4.1 编写可视化脚本
在Jupyter中新建一个Notebook,运行以下代码:
from ultralytics.utils.plotting import plot_labels from pathlib import Path import cv2 # 指定一张测试图和它的YOLO标注文件 img_path = "./my_yolo_dataset/images/000001.jpg" label_path = "./my_yolo_dataset/labels/000001.txt" # 读取图片 img = cv2.imread(str(img_path)) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转RGB供matplotlib显示 # 读取YOLO标签(格式:class_id x y w h) with open(label_path) as f: lines = f.readlines() labels = [] for line in lines: parts = line.strip().split() if len(parts) == 5: cls_id, x, y, w, h = map(float, parts) labels.append([cls_id, x, y, w, h]) # 可视化(需要提供类别名列表) class_names = ["person", "car", "dog"] plot_labels(labels, img_rgb, class_names, save_dir="./", fname="check_000001.jpg") print(" 标注已绘制到图片,保存为 check_000001.jpg")运行后,你会在当前目录看到check_000001.jpg,打开它——绿色方框应该严丝合缝地套住图中目标,没有偏移、没有缩放错误。这是转换正确的铁证。
4.2 常见问题自查清单
| 问题现象 | 可能原因 | 快速解决 |
|---|---|---|
| TXT文件为空 | XML里没有<object>标签,或<name>内容为空 | 用cat xxx.xml | grep "<name>"检查 |
| 框严重偏移 | 图片尺寸读错了(XML里<size>缺失或错误) | 手动检查XML的<width>和<height>是否与图片实际尺寸一致 |
类别ID报错(如KeyError: 'cat') | CLASS_NAMES列表没包含XML里的<name>值 | 运行grep "<name>" *.xml | sort | uniq列出所有类别,补全列表 |
| 生成的TXT里有负数或>1的值 | 坐标超出图片边界(如xmin < 0或xmax > width) | 在脚本中加入边界裁剪:xmin = max(0, min(xmin, img_width))等 |
这些问题在真实数据中很常见,但修复成本极低——通常只需在脚本里加两三行容错代码。
5. 接入YOLO11训练:一步启动模型
转换完数据,下一步就是喂给YOLO11训练。你不需要改任何配置文件,YOLO11支持直接用目录结构定义数据集。
5.1 构建标准YOLO数据集结构
确保你的my_yolo_dataset/目录长这样:
my_yolo_dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── ... │ ├── val/ │ └── test/ # 可选 └── labels/ ├── train/ │ ├── 000001.txt │ └── ... ├── val/ └── test/ # 可选如果原始VOC只有train.txt和val.txt,可以用以下命令快速拆分(在SSH或Jupyter终端中):
cd ./my_yolo_dataset mkdir -p images/{train,val} labels/{train,val} # 根据VOC的train.txt移动训练集图片和标签 while read line; do cp "images_all/$line.jpg" images/train/ cp "labels_all/$line.txt" labels/train/ done < ../my_voc_dataset/ImageSets/Main/train.txt # 同理处理val.txt while read line; do cp "images_all/$line.jpg" images/val/ cp "labels_all/$line.txt" labels/val/ done < ../my_voc_dataset/ImageSets/Main/val.txt5.2 启动训练(真正的一行命令)
回到项目根目录,执行:
cd ultralytics-8.3.9/然后运行:
python train.py \ --data ../my_yolo_dataset/data.yaml \ --model yolov8n.pt \ --epochs 100 \ --batch 16 \ --name my_yolo11_exp等等——data.yaml还没建?别慌,它只是个极简配置文件,内容如下(用nano ../my_yolo_dataset/data.yaml创建):
train: ../my_yolo_dataset/images/train val: ../my_yolo_dataset/images/val nc: 3 # 类别数量 names: ['person', 'car', 'dog'] # 和之前CLASS_NAMES完全一致保存后,再次运行python train.py ...,你会看到训练日志实时滚动,Loss曲线开始下降。这意味着:你的VOC数据已成功注入YOLO11流水线。
这张图展示的就是训练过程中mAP@0.5指标的上升趋势——它证明转换后的数据质量可靠,模型能从中学到有效特征。
6. 总结:VOC转YOLO,本质是思维切换
回顾整个流程,你其实只做了三件事:
- 理解差异:VOC存像素,YOLO存比例;
- 写脚本转换:用15行核心代码完成归一化;
- 验证+接入:画图确认、配yaml、一键训练。
没有玄学,没有黑盒,全是确定性操作。YOLO11镜像的价值,正在于把环境配置、依赖冲突、CUDA版本这些“脏活累活”全部封装掉,让你专注在“数据怎么来”和“模型怎么训”这两个真正创造价值的环节。
下次再遇到VOC数据,别再想着重标——打开Jupyter,粘贴脚本,改两行路径,5分钟搞定。真正的效率,从来不是更快地重复劳动,而是用一次解决,永久受益。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。