The Pascal VOC format (XML files with <bndbox> tags) has been a cornerstone of computer vision research for over fifteen years.
However, modern deep learning frameworks — including Ultralytics YOLO (v8, v11, v12) — require per-image .txt files with normalized center coordinates.
This guide provides coordinate normalization formulas, a breakdown of VOC XML tags, and a zero-dependency Python script to convert a directory of XML files into YOLO label files. You still need matching images, a train/validation split, and a dataset YAML before training.
Need an instant in-browser conversion? Try our Pascal VOC to YOLO Converter Tool.
1. Pascal VOC XML vs. YOLO TXT Structure
Pascal VOC XML Example (image_001.xml)
<annotation>
<filename>image_001.jpg</filename>
<size>
<width>800</width>
<height>600</height>
<depth>3</depth>
</size>
<object>
<name>pedestrian</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>120</xmin>
<ymin>80</ymin>
<xmax>240</xmax>
<ymax>320</ymax>
</bndbox>
</object>
</annotation>
Corresponding YOLO TXT (image_001.txt)
0 0.225000 0.333333 0.150000 0.400000
2. Coordinate Conversion Mathematics
Pascal VOC uses corner coordinates: [xmin, ymin, xmax, ymax].
YOLO uses normalized center coordinates and dimensions: [x_center, y_center, width, height].
Given image dimensions $W$ (width) and $H$ (height):
x_center = (xmin + xmax) / (2 * W)
y_center = (ymin + ymax) / (2 * H)
width = (xmax - xmin) / W
height = (ymax - ymin) / H
3. Standalone Python Batch Converter Script
This script uses standard library modules (xml.etree.ElementTree and pathlib) to read XML files in one directory, build a class index, and generate YOLO labels plus a classes.txt reference file outside the labels folder. It does not copy images or create data.yaml.
#!/usr/bin/env python3
"""
voc_to_yolo.py
Converts Pascal VOC XML annotations to YOLO TXT format.
Zero external dependencies required.
"""
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import List, Dict, Tuple
def convert_voc_box(
size: Tuple[int, int],
box: Tuple[float, float, float, float]
) -> Tuple[float, float, float, float]:
"""Convert VOC xmin, ymin, xmax, ymax to YOLO center_x, center_y, width, height."""
dw = 1.0 / size[0]
dh = 1.0 / size[1]
xmin, ymin, xmax, ymax = box
# Calculate center and dimensions
x_center = (xmin + xmax) / 2.0
y_center = (ymin + ymax) / 2.0
w = xmax - xmin
h = ymax - ymin
# Normalize by image width and height
x_center = x_center * dw
w = w * dw
y_center = y_center * dh
h = h * dh
return x_center, y_center, w, h
def batch_convert_voc_to_yolo(
voc_xml_dir: str,
output_labels_dir: str,
classes_list: List[str] = None
):
xml_path = Path(voc_xml_dir)
out_path = Path(output_labels_dir)
out_path.mkdir(parents=True, exist_ok=True)
xml_files = list(xml_path.glob("*.xml"))
print(f"Found {len(xml_files)} VOC XML files in {xml_path}")
# Discover classes automatically if not provided
discovered_classes = set()
if classes_list is None:
for xml_file in xml_files:
tree = ET.parse(xml_file)
root = tree.getroot()
for obj in root.iter("object"):
cls_name = obj.find("name").text.strip()
discovered_classes.add(cls_name)
classes_list = sorted(list(discovered_classes))
class_to_id = {cls_name: idx for idx, cls_name in enumerate(classes_list)}
print(f"Classes ({len(classes_list)}): {class_to_id}")
total_boxes = 0
converted_files = 0
for xml_file in xml_files:
tree = ET.parse(xml_file)
root = tree.getroot()
# Extract image dimensions
size_node = root.find("size")
if size_node is None:
continue
width = int(size_node.find("width").text)
height = int(size_node.find("height").text)
if width <= 0 or height <= 0:
continue
yolo_lines = []
for obj in root.iter("object"):
cls_name = obj.find("name").text.strip()
if cls_name not in class_to_id:
continue
cls_id = class_to_id[cls_name]
xml_box = obj.find("bndbox")
xmin = float(xml_box.find("xmin").text)
ymin = float(xml_box.find("ymin").text)
xmax = float(xml_box.find("xmax").text)
ymax = float(xml_box.find("ymax").text)
# Clip corners before normalization; reject boxes outside the image.
xmin = max(0.0, min(width, xmin))
ymin = max(0.0, min(height, ymin))
xmax = max(0.0, min(width, xmax))
ymax = max(0.0, min(height, ymax))
if xmax <= xmin or ymax <= ymin:
continue
yolo_box = convert_voc_box((width, height), (xmin, ymin, xmax, ymax))
yolo_lines.append(f"{cls_id} {yolo_box[0]:.6f} {yolo_box[1]:.6f} {yolo_box[2]:.6f} {yolo_box[3]:.6f}")
total_boxes += 1
# Write output .txt
txt_filename = out_path / f"{xml_file.stem}.txt"
with open(txt_filename, "w", encoding="utf-8") as f:
f.write("\n".join(yolo_lines) + ("\n" if yolo_lines else ""))
converted_files += 1
# Keep the class list outside the labels folder.
with open(out_path.parent / "classes.txt", "w", encoding="utf-8") as f:
f.write("\n".join(classes_list) + "\n")
print(f"Successfully converted {converted_files} files with {total_boxes} boxes.")
print(f"Saved to: {out_path.resolve()}")
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
print("Usage: python voc_to_yolo.py <voc_xml_dir> <output_labels_dir>")
else:
batch_convert_voc_to_yolo(sys.argv[1], sys.argv[2])
4. Converting In-Browser Without Scripts
If you do not want to set up local Python environments or install command-line tools:
- Drag and drop your XML files or ZIP archive into the LabelOp Format Converter.
- The tool automatically detects image sizes, extracts class taxonomies, and normalizes all bounding boxes.
- Download the converted YOLO label files and class list. Pair each label with its source image, create separate train and validation folders, and write
data.yamlbefore training.
For an Ultralytics dataset, place matching image and label stems under images/train, labels/train, images/val, and labels/val. A minimal data.yaml is:
path: ./dataset
train: images/train
val: images/val
names:
0: pedestrian
Replace the class names with the order in classes.txt, and validate the split and boxes before training.
Summary
Migrating from Pascal VOC to YOLO is straightforward once coordinate normalization is applied. Use the script above for automated local pipelines or use LabelOp Tools for zero-setup browser conversions.