FAQ

Formats

Pascal VOC XML: the format you will still meet in 2027

One XML file per image, corner-based boxes, a difficult flag that changes your metrics. Why older datasets and annotation tools still emit it.

Pascal VOC's challenge ended over a decade ago, but its annotation format did not. Plenty of annotation tools still write it by default, and plenty of older datasets have never been converted, so it remains something you have to be able to read.

One XML file per image

Where COCO puts an entire dataset in one JSON document, VOC puts each image's annotations in its own XML file named after the image. That makes it pleasant to edit by hand and to diff in version control, and unpleasant to load at scale — a hundred thousand images means a hundred thousand file opens.

Boxes are corner-based absolute pixels: xmin, ymin, xmax, ymax. The original VOC data is one-indexed, meaning a box flush against the left edge has xmin of 1 rather than 0. Most modern tools write zero-indexed values anyway. The one-pixel difference rarely matters for training and does matter if you are trying to reproduce a published number exactly.

Annotations/000001.xml
<annotation>
  <folder>VOC2012</folder>
  <filename>000001.jpg</filename>
  <size>
    <width>640</width><height>480</height><depth>3</depth>
  </size>
  <segmented>0</segmented>
  <object>
    <name>car</name>
    <pose>Frontal</pose>
    <truncated>0</truncated>
    <difficult>0</difficult>
    <bndbox>
      <xmin>132</xmin><ymin>205</ymin><xmax>204</xmax><ymax>295</ymax>
    </bndbox>
  </object>
</annotation>
An XML listing with comments: annotation, folder, filename 000089.jpg, size with width 348, height 281 and depth 3, then an object named ship with pose, truncated and difficult tags and a bndbox of xmin 124, ymin 62, xmax 137, ymax 84.
The start of one Pascal VOC annotation from the SAR Ship Detection Dataset's official release of 2021: the image file and its size, then one object element per ship with its class name, flags and a bounding box stored as two corner points (xmin, ymin, xmax, ymax) in absolute pixels. Image (panel b, cropped): Figure 4b of the article · CC BY 4.0 · from Zhang et al. 2021, Remote Sensing, “SAR Ship Detection Dataset (SSDD): Official Release and Comprehensive Data Analysis”, doi:10.3390/rs13183690
The same object in three conventions. A corner/centre mix-up shifts a box by half its size, so a wrong conversion looks fine on small objects and is badly wrong on large ones.

difficult changes your numbers, truncated does not

Two per-object flags matter, and one of them carries real evaluation weight. difficult marks an object the annotators judged unreasonably hard — tiny, heavily occluded, barely visible. The official VOC protocol excludes difficult objects from evaluation entirely: a detection on one is neither a true nor a false positive.

This is why the same model can report two different mAP figures on the same VOC data. If your pipeline treats difficult objects as ordinary ground truth, you are measuring something harder than the published benchmark and your numbers will look worse than they are. Decide which convention you are using, and write it down next to the result.

truncated means the object runs off the edge of the image. It does not affect standard evaluation, but it is useful for filtering when you are building a clean set.

A precision against recall plot titled 'Class: object, AP: 26.84%'. A jagged blue precision line ends near recall 0.47; red dots mark the interpolated precision at recall 0, 0.1 and so on to 1, at 1.0, about 0.67, about 0.43 for 0.2 to 0.4, and 0 from 0.5 upwards.
One set of detections scored by the 11-point rule VOC used before 2010: precision is read at recall 0, 0.1 and so on to 1 (red dots), giving AP 26.84%. Interpolating through every point, as VOC has done since, gives 24.56% for the same curve. Record which protocol a VOC number used, alongside how difficult objects were treated. Image: rafaelpadilla/Object-Detection-Metrics · MIT repository

Converting VOC to COCO

The conversion is mechanical: walk the XML files, collect a class list, assign ids, and translate corners to [x, y, w, h]. The decisions that matter are what you do with difficult and whether you are correcting the one-indexing.

voc_to_coco.py
import os, glob, json, sys
import xml.etree.ElementTree as ET

voc_dir, out_file = sys.argv[1], sys.argv[2]
keep_difficult = "--keep-difficult" in sys.argv

images, annotations, cat_ids = [], [], {}
ann_id = 0

for img_id, xf in enumerate(sorted(glob.glob(os.path.join(voc_dir, "*.xml"))), 1):
    r = ET.parse(xf).getroot()
    size = r.find("size")
    W, H = int(size.find("width").text), int(size.find("height").text)
    images.append({"id": img_id, "file_name": r.find("filename").text,
                   "width": W, "height": H})

    for obj in r.findall("object"):
        diff = int((obj.findtext("difficult") or "0"))
        if diff and not keep_difficult:
            continue
        name = obj.find("name").text.strip()
        cat_ids.setdefault(name, len(cat_ids) + 1)
        b = obj.find("bndbox")
        # VOC is 1-indexed; subtract 1 to land in COCO's 0-indexed pixel space
        x0 = float(b.find("xmin").text) - 1
        y0 = float(b.find("ymin").text) - 1
        x1 = float(b.find("xmax").text)
        y1 = float(b.find("ymax").text)
        x0, y0 = max(0.0, x0), max(0.0, y0)
        x1, y1 = min(float(W), x1), min(float(H), y1)
        w, h = x1 - x0, y1 - y0
        if w <= 0 or h <= 0:
            continue
        ann_id += 1
        annotations.append({"id": ann_id, "image_id": img_id,
                            "category_id": cat_ids[name],
                            "bbox": [x0, y0, w, h], "area": w * h,
                            "iscrowd": 0, "voc_difficult": diff})

json.dump({"info": {"description": f"converted from {voc_dir}"},
           "images": images, "annotations": annotations,
           "categories": [{"id": i, "name": n} for n, i in cat_ids.items()]},
          open(out_file, "w"), indent=1)
print(f"{len(images)} images, {len(annotations)} annotations, {len(cat_ids)} classes")
VOC numbers pixels from 1 and counts both end pixels, so the example box from xmin 132 to xmax 204 is 73 pixels wide. In COCO's zero-based coordinates it runs from the left edge of pixel 132 (x = 131) to the right edge of pixel 204 (x = 204), which is why the converter subtracts 1 from xmin and ymin but not from xmax and ymax.
Seven small panels on black backgrounds, each containing a handful of rectangular objects. Every panel draws ground-truth boxes in green and detected boxes in red, with a confidence percentage printed beside each detection, and the panels are labelled as separate sample images.
Ground truth against detections across a set of images. The VOC difficult flag quietly changes which of these mismatches count against you; truncated does not affect standard evaluation. Image: rafaelpadilla/Object-Detection-Metrics · MIT repository

Where you will still run into it

Three places, mainly. Annotation tools — several desktop labelling applications write VOC XML as their native or default export, so data coming back from a manual labelling pass often arrives this way. Older published datasets, which were released against the VOC protocol and never restructured. And internal pipelines built when VOC was the default, which keep emitting it because everything downstream expects it.

None of that is a reason to adopt it for something new. For a new dataset, COCO for detection and segmentation or a columnar format for scale will both serve you better. VOC is a format to read fluently, not one to choose.

Two masks of the same drawn shapes on black, side by side. Left, the class mask: a rectangle with a rectangular hole, a small square and an irregular blob with a hole are all the same blue, and a triangle is olive green. Right, the instance mask: the same four shapes in four different colours, dark red, navy, green and olive yellow, one per object.
Masks exported from CVAT in its Segmentation Mask format, which its documentation describes as PASCAL VOC masks. The class mask paints every object of a class one colour, so the three blue shapes are indistinguishable; the instance mask gives each object its own. These are the SegmentationClass and SegmentationObject PNGs of a VOC export, read by colour, and the XML carries none of this geometry. Image: cvat-ai/cvat · MIT repository

Questions people ask

Is VOC XML one-indexed or zero-indexed?

The original VOC releases are one-indexed. Most contemporary annotation tools write zero-indexed coordinates into the same schema without saying so. If you need to reproduce a published VOC number, check a box that touches the image edge: an xmin of 1 suggests the original convention, 0 suggests the modern one, but LabelImg, the classic desktop VOC tool, clamps any coordinate below 1 to 1, so its files show 1 at the edge too.

Does VOC support segmentation masks?

Indirectly. The segmented flag says whether a mask exists, and the masks live as separate PNG files in SegmentationClass and SegmentationObject directories, indexed by colour. The XML itself carries no polygon data.

What does the pose field mean?

It records the object's rough orientation — values like Frontal, Rear, Left, Right or Unspecified. It was part of the original challenge's annotation richness and is ignored by essentially all modern training code, but it survives in files and can be a useful filter.

Which is better for a new project, VOC or COCO?

COCO, in nearly every case. It handles segmentation natively, supports keypoints and captions over the same images, keeps the whole dataset in one document, and has far wider loader support. Choose VOC only when a tool you cannot replace demands it.

Stop hunting. Start searching.

Tens of thousands of curated datasets, searchable in plain language — with the licence and size checked before you download anything.

Search Dataset Finder →