RegionKit Open Editor →
YOLO annotation format bounding box normalised coordinates object detection

YOLO Format Explained — Bounding Boxes, Classes, and the Coordinate System

A complete reference for the YOLO annotation format — how normalised coordinates work, the class ID system, directory structure, data.yaml, and how to convert to and from COCO. With working Python examples.

· RegionKit

YOLO’s annotation format is deceptively simple. One text file per image. Five numbers per object. Everything normalised. This reference covers exactly what those numbers mean, how the directory structure works, and what to watch out for when converting from other formats.

The per-line format

Each line in a YOLO .txt annotation file describes one annotated object:

<class_id> <x_center> <y_center> <width> <height>

All values are space-separated. The class ID is an integer. The four geometry values are floating-point numbers between 0 and 1.

Normalised coordinates

YOLO uses normalised coordinates — values divided by the image dimension. For a 640×480 image:

This means the same annotation file works regardless of what resolution your model processes the image at.

x_center — horizontal centre of the bounding box, as a fraction of image width. A box centred at pixel 320 in a 640-wide image is 0.5.

y_center — vertical centre, as a fraction of image height. A box centred at pixel 240 in a 480-tall image is 0.5.

width — box width as a fraction of image width. A 128-pixel-wide box in a 640-wide image is 0.2.

height — box height as a fraction of image height. A 64-pixel-tall box in a 480-tall image is 0.1333....

So a box covering the full image would be: 0 0.5 0.5 1.0 1.0

Converting pixel coordinates to YOLO

def bbox_to_yolo(x1, y1, x2, y2, img_w, img_h):
    """Convert pixel bounding box to YOLO normalised format."""
    cx = ((x1 + x2) / 2) / img_w
    cy = ((y1 + y2) / 2) / img_h
    w  = (x2 - x1) / img_w
    h  = (y2 - y1) / img_h
    return cx, cy, w, h

def yolo_to_bbox(cx, cy, w, h, img_w, img_h):
    """Convert YOLO normalised format to pixel bounding box."""
    x1 = int((cx - w/2) * img_w)
    y1 = int((cy - h/2) * img_h)
    x2 = int((cx + w/2) * img_w)
    y2 = int((cy + h/2) * img_h)
    return x1, y1, x2, y2

Class IDs and data.yaml

Class IDs are zero-indexed integers that map to class names via a data.yaml file. The class ID in the annotation and the position in the names list in data.yaml must match exactly.

Example data.yaml:

path: /datasets/my_dataset
train: images/train
val: images/val
test: images/test  # optional

nc: 3  # number of classes
names:
  0: person
  1: car
  2: bicycle

An annotation line 1 0.5 0.5 0.3 0.6 describes a car (class 1) centred at (0.5, 0.5) with width 0.3 and height 0.6.

Common mistake: class IDs in annotation files and the position in names must stay in sync. If you reorder your names list, all annotations referring to those class IDs become incorrect.

Directory structure

YOLO training scripts expect a specific directory layout:

dataset/
├── images/
│   ├── train/
│   │   ├── frame_001.jpg
│   │   └── frame_002.jpg
│   └── val/
│       └── frame_003.jpg
├── labels/
│   ├── train/
│   │   ├── frame_001.txt
│   │   └── frame_002.txt
│   └── val/
│       └── frame_003.txt
└── data.yaml

The labels directory mirrors the images directory exactly. Each .txt file has the same stem as the corresponding image file. Images with no annotations have an empty .txt file (or no file — behaviour depends on the framework version).

Handling images with no annotations

An image in the training set with no objects should have an empty .txt file. Some frameworks handle missing annotation files by assuming zero objects; others error. The safest practice is to include empty annotation files for all images in the dataset.

YOLO segmentation format (YOLOv8+)

YOLOv8 added polygon segmentation support. The format adds normalised polygon coordinates after the class ID:

<class_id> <x1> <y1> <x2> <y2> <x3> <y3> ...

Each (x, y) pair is a normalised polygon vertex. There’s no fixed vertex count — the polygon closes implicitly.

RegionKit exports polygon annotations in this format when you select YOLO TXT export.

Converting from COCO to YOLO

import json

def coco_to_yolo(coco_path, output_dir, img_w, img_h):
    with open(coco_path) as f:
        coco = json.load(f)

    cat_map = {c['id']: i for i, c in enumerate(coco['categories'])}

    for ann in coco['annotations']:
        img_id = ann['image_id']
        x, y, w, h = ann['bbox']  # COCO bbox: top-left x, y, width, height
        cx = (x + w/2) / img_w
        cy = (y + h/2) / img_h
        nw = w / img_w
        nh = h / img_h
        class_id = cat_map[ann['category_id']]

        fname = f"{output_dir}/{img_id:06d}.txt"
        with open(fname, 'a') as f:
            f.write(f"{class_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n")

Reading YOLO files in Python

def read_yolo_annotations(txt_path, img_w, img_h):
    annotations = []
    try:
        with open(txt_path) as f:
            for line in f:
                parts = line.strip().split()
                if not parts:
                    continue
                class_id = int(parts[0])
                cx, cy, w, h = map(float, parts[1:5])
                x1, y1, x2, y2 = yolo_to_bbox(cx, cy, w, h, img_w, img_h)
                annotations.append({
                    'class_id': class_id,
                    'bbox': [x1, y1, x2, y2],
                })
    except FileNotFoundError:
        pass  # image has no annotations
    return annotations

Exporting YOLO from RegionKit

RegionKit exports both bounding box and polygon YOLO annotations. Rectangle annotations become standard YOLO bounding box lines; polygon annotations become YOLO segmentation lines. The class label from the annotation’s label field is mapped to class IDs in order of first appearance.

Export: Export → YOLO TXT. The downloaded file is the annotation .txt for the loaded image.


Related: How to Annotate Images for YOLO Training · YOLO Annotation Tool Online

Try RegionKit — it's free

Browser-based ROI editor. No install, no account, no data upload.

Open the Editor