YOLO数据集格式转换实战:PASCAL VOC XML与YOLO TXT互转详解 1. 为什么需要数据集格式转换第一次用YOLO训练模型时我就被各种数据集格式搞晕了。当时用LabelImg标注了2000多张图片默认生成的是PASCAL VOC格式的XML文件但YOLO需要的却是TXT格式。后来才知道这是计算机视觉领域最常见的两种标注格式PASCAL VOC XML每个图片对应一个XML文件记录图片尺寸、物体类别和边界框的绝对坐标像素值YOLO TXT每个图片对应一个TXT文件每行记录一个物体的类别编号和归一化后的中心坐标/宽高这两种格式最大的区别在于坐标表示方式。VOC用绝对像素值比如xmin100/xmin而YOLO用相对值比如0.45表示中心点横坐标是图片宽度的45%。我在第一次转换时就因为没注意这个区别导致模型完全学不会。2. 格式详解与对比2.1 PASCAL VOC XML结构解析用LabelImg标注后生成的典型XML文件长这样annotation filenameIMG_001.jpg/filename size width800/width height600/height depth3/depth /size object namecat/name bndbox xmin100/xmin ymin200/ymin xmax300/xmax ymax400/ymax /bndbox /object /annotation关键字段说明size图片的宽、高、通道数object每个检测对象一个节点bndbox用左上(xmin,ymin)和右下(xmax,ymax)坐标表示边界框2.2 YOLO TXT格式详解对应的YOLO格式TXT文件内容如下0 0.25 0.5 0.25 0.333这行数据表示0类别ID对应cat0.25中心点x坐标/图片宽度0.5中心点y坐标/图片高度0.25框宽度/图片宽度0.333框高度/图片高度2.3 核心差异对比表特征PASCAL VOCYOLO文件格式XMLTXT坐标类型绝对像素值归一化相对值框表示法左上右下坐标中心点宽高多物体处理多个object节点每行一个物体可视化难度容易含图片尺寸需要原始图片尺寸3. XML转TXT实战3.1 转换原理与公式转换的核心是坐标归一化def convert(size, box): # size (width, height) dw 1./size[0] dh 1./size[1] x (box[0] box[1])/2.0 # 计算中心点x y (box[2] box[3])/2.0 # 计算中心点y w box[1] - box[0] # 计算宽度 h box[3] - box[2] # 计算高度 x x * dw # 归一化 w w * dw y y * dh h h * dh return (x, y, w, h)3.2 完整转换代码import xml.etree.ElementTree as ET import os classes [cat, dog] # 你的类别列表 def convert_annotation(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() with open(txt_path, w) as f: size root.find(size) w int(size.find(width).text) h int(size.find(height).text) for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue cls_id classes.index(cls) xmlbox obj.find(bndbox) b (float(xmlbox.find(xmin).text), float(xmlbox.find(xmax).text), float(xmlbox.find(ymin).text), float(xmlbox.find(ymax).text)) bb convert((w,h), b) f.write(f{cls_id} { .join([str(a) for a in bb])}\n) # 批量转换 xml_dir Annotations/ txt_dir labels/ os.makedirs(txt_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): txt_file xml_file.replace(.xml, .txt) convert_annotation( os.path.join(xml_dir, xml_file), os.path.join(txt_dir, txt_file) )3.3 常见问题解决类别不匹配确保XML中的类别都在classes列表中否则会跳过坐标越界转换后检查数值是否在0-1之间图片尺寸缺失有些标注工具可能不生成size节点需要手动添加4. TXT转XML实战4.1 反向转换原理当需要数据增强或可视化时需要将YOLO格式转回VOC格式。关键步骤读取图片获取原始尺寸将归一化坐标还原为绝对坐标x_center float(parts[1]) * width y_center float(parts[2]) * height box_width float(parts[3]) * width box_height float(parts[4]) * height xmin int(x_center - box_width/2) xmax int(x_center box_width/2) ymin int(y_center - box_height/2) ymax int(y_center box_height/2)4.2 完整转换代码import cv2 from xml.dom.minidom import Document def txt_to_xml(txt_path, img_path, xml_path, class_list): img cv2.imread(img_path) h, w, d img.shape doc Document() annotation doc.createElement(annotation) doc.appendChild(annotation) # 添加文件信息 filename doc.createElement(filename) filename.appendChild(doc.createTextNode(os.path.basename(img_path))) annotation.appendChild(filename) # 添加尺寸信息 size doc.createElement(size) for tag, val in [(width, w), (height, h), (depth, d)]: elem doc.createElement(tag) elem.appendChild(doc.createTextNode(str(val))) size.appendChild(elem) annotation.appendChild(size) # 处理每个标注 with open(txt_path) as f: for line in f.readlines(): parts line.strip().split() obj doc.createElement(object) # 类别 name doc.createElement(name) name.appendChild(doc.createTextNode(class_list[int(parts[0])])) obj.appendChild(name) # 边界框 bndbox doc.createElement(bndbox) coords [xmin, ymin, xmax, ymax] values convert_yolo_to_voc(w, h, list(map(float, parts[1:5]))) for c, v in zip(coords, values): elem doc.createElement(c) elem.appendChild(doc.createTextNode(str(int(v)))) bndbox.appendChild(elem) obj.appendChild(bndbox) annotation.appendChild(obj) # 保存XML with open(xml_path, w) as f: doc.writexml(f, indent, addindent\t, newl\n, encodingUTF-8)4.3 转换后验证技巧用LabelImg打开生成的XML检查框位置是否正确比较转换前后的目标数量是否一致特别检查边缘位置的框如xmin0或xmaxwidth5. 实际项目经验分享去年在做一个工业质检项目时客户提供了VOC格式的数据集但我们需要用YOLOv5训练。转换过程中踩过几个坑类别ID不连续客户给的类别是[OK, NG, Critical]但标注文件里直接用0/1/2。解决方案是建立明确的类别映射表。图片尺寸不一致有些图片实际尺寸和XML里的size不一致。后来加了校验代码img cv2.imread(img_path) assert img.shape[0] h and img.shape[1] w, 尺寸不匹配特殊字符处理XML中的特殊字符如, 会导致解析失败。现在我的代码里都会加from xml.sax.saxutils import escape name escape(obj.find(name).text)建议在转换完成后用这个脚本快速检查数据质量import matplotlib.pyplot as plt import matplotlib.patches as patches def visualize(img_path, txt_path, class_names): img plt.imread(img_path) fig, ax plt.subplots(1) ax.imshow(img) with open(txt_path) as f: for line in f: cls_id, x, y, w, h map(float, line.split()) rect patches.Rectangle( ((x-w/2)*img.shape[1], (y-h/2)*img.shape[0]), w*img.shape[1], h*img.shape[0], linewidth2, edgecolorr, facecolornone) ax.add_patch(rect) plt.text((x-w/2)*img.shape[1], (y-h/2)*img.shape[0]-10, class_names[int(cls_id)], colorwhite, bboxdict(facecolorred, alpha0.5)) plt.show()