Oriented detection

Oriented object detection locates each instance with a rotated rectangle rather than an axis-aligned one, so a tilted object is bounded tightly instead of by a box full of background. The task key is obb.

Definition

Oriented detection adds one number to a detection: the angle. Each instance gets a rotated rectangle, a class and a score. The gain is tightness. A ship at 45 degrees, a warehouse roof, a row of parked trucks: an axis-aligned box around any of them is mostly background, and two neighboring boxes overlap even when the objects do not. That is why the task is standard in aerial imagery and document layout, and why the reference dataset for it is DOTA.

obb is the canonical task key, and the -obb suffix in a checkpoint filename selects it, so task= is not needed when loading published weights.

predict() fills result.obb. .xywhr is the canonical (N, 5) form: center x, center y, width, height, and an angle in radians giving the rotation of the width side around the center. .conf and .cls carry the score and the class index into result.names, and .id a track id when tracking. .xyxyxyxy converts each row to its four corner points as (N, 4, 2) pixels, .xyxyxyxyn normalizes those corners, and .xyxy gives the enclosing axis-aligned box, which is what to use when downstream code only understands rectangles. result.boxes is filled as well, with the axis-aligned form.

Models

Two families serve this task, and which one to reach for depends on whether you need to train.

RF-DETR is the one that trains. It predicts, trains, validates and exports oriented boxes, and it ships published oriented checkpoints in four sizes, n, s, m and l. It needs its own extra, pip install "libreyolo[rfdetr]", and its model page carries the weights license and the provenance.

Read the section below on what those checkpoints actually predict before you plan around them.

RT-DETRv2 is the one with aerial weights. It publishes LibreRTDETRv2n-obb.pt through LibreRTDETRv2x-obb.pt, the official DOTA v1.0 single-scale checkpoints converted into LibreYOLO's format, covering DOTA's 15 classes at 1024 px. It needs no extra beyond the base package, the oriented graph is recognized from the checkpoint's own tensors, and prediction, validation and ONNX and TorchScript export are all supported. Training is not: the oriented task is inference only on that family, train() raises, and there is no transfer from its detection weights, which use a different backbone. Tracking and test-time augmentation are also unavailable for oriented boxes.

So: DOTA categories out of the box, RT-DETRv2. Your own oriented labels, RF-DETR.

Predict

Weights download from Hugging Face on first use and are cached locally.

Python
# Needs the rfdetr extra: pip install "libreyolo[rfdetr]"from libreyolo import LibreYOLO, SAMPLE_IMAGE # The -obb suffix in the filename selects the task, so no task# argument is needed.model = LibreYOLO("LibreRFDETRs-obb.pt")result = model(SAMPLE_IMAGE, save=True) obb = result.obbprint(obb.xywhr)   # (N, 5): center x, center y, width, height, radiansprint(obb.conf, obb.cls)
CLI
libreyolo predict model=LibreRFDETRs-obb.pt save=True \  source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpg
Corners instead of angles
from libreyolo import LibreYOLO, SAMPLE_IMAGE result = LibreYOLO("LibreRFDETRs-obb.pt")(SAMPLE_IMAGE)obb = result.obb print(obb.xyxyxyxy.shape)    # (N, 4, 2) corner points in pixelsprint(obb.xyxyxyxyn.shape)   # the same, normalizedprint(obb.xyxy.shape)        # (N, 4) enclosing axis-aligned box
A smaller checkpoint
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreRFDETRn-obb.pt")result = model(SAMPLE_IMAGE) print(result.obb.xywhr.shape)
RT-DETRv2
from libreyolo import LibreYOLO # DOTA v1.0 weights, 15 aerial classes at 1024 px. The oriented graph# is recognized from the checkpoint's own tensors, so no task argument.model = LibreYOLO("LibreRTDETRv2n-obb.pt")result = model("aerial.png", save=True) obb = result.obbprint(obb.xywhr)print(result.names)   # plane, ship, harbor, helicopter, and 11 more

Know what RF-DETR's published checkpoints are before you run them. Despite DOTA being the reference benchmark for this task, those weights were not trained on it. All four were initialized from the RF-DETR detection weights and fine-tuned on a single Roboflow Universe dataset of UAV footage, with six vehicle classes: bike, bus, car, other_vehicle, taxi and truck. Their model cards describe them as development weights, produced while validating oriented training support, and say they should not be read as production or benchmark-official weights.

In practice that means they are a working starting point for oriented boxes on vehicles seen from above, and for verifying that your pipeline runs end to end. Any other domain means training on your own oriented labels, and for the aerial categories DOTA is known for, the RT-DETRv2 checkpoints are the ones actually trained on that data. conf and max_det shape the output as they do for detection. See prediction for sources, streaming and result handling.

Dataset format

The layout is the detection layout: one .txt label file per image, found by swapping images for labels in the image path and changing the extension.

dataset/  data.yaml  images/    train/P0001.png    val/P0101.png  labels/    train/P0001.txt    val/P0101.txt

A row is exactly nine fields, a class index followed by four corner points in order:

<class_id> <x1> <y1> <x2> <y2> <x3> <y3> <x4> <y4>

The four points are normalized floats in [0, 1] and have to form a non-degenerate oriented rectangle. No angle is stored in the label file: the loader derives the canonical xywhr from the corners. The parser is strict by default and rejects out-of-range coordinates, while dataset and validation ingestion may first clip to [0, 1] for otherwise valid crop-boundary labels, then still reject degenerate boxes.

Row parsing is task-aware. Nine fields mean an oriented box only in obb mode; in segment mode the same row is read as a four-point polygon.

The YAML is the detection YAML:

yaml
path: datasettrain: images/trainval: images/valnames:  0: plane  1: ship

Native COCO JSON loads too, with an annotations mapping of split name to JSON file. Annotations are read in priority order: an obb field of eight pixel-space corners, an obb field of [cx, cy, w, h, angle] with the angle in radians, a segmentation polygon or RLE refit to its minimum-area rectangle, or a plain COCO bbox, which is treated as an axis-aligned rectangle and canonicalized to xywhr.

The canonical row parser is libreyolo.data.parse_yolo_obb_label_line.

Train

Python
from libreyolo import LibreYOLO # Continues from published oriented weights. data must point at a# dataset whose label rows carry four corners.model = LibreYOLO("LibreRFDETRs-obb.pt")model.train(data="my-obb-dataset.yaml", epochs=50, imgsz=512, batch=8, lr0=1e-4)
CLI
libreyolo train model=LibreRFDETRs-obb.pt data=my-obb-dataset.yaml \  epochs=50 imgsz=512 batch=8 lr0=1e-4
From detection weights
# Detection weights carry no angle prediction, so this is an explicit# transfer. Asking for task=obb is what authorizes it.libreyolo train model=LibreRFDETRs.pt data=my-obb-dataset.yaml \  task=obb epochs=50 imgsz=512

Training on this task means RF-DETR. Training continues from a published -obb checkpoint by default. Starting from detection weights is a deliberate transfer: those weights predict no angle, and passing task=obb is what authorizes the swap. Keep lr0 at or below 1e-4, as with the family's other tasks. RT-DETRv2's oriented checkpoints cannot be fine-tuned; use them as they are, or train an RF-DETR model on your own labels. See training for datasets, augmentation, multi-GPU and loggers.

Validate

val() returns a plain dictionary of metrics/ keys. Matching uses rotated IoU, computed between oriented rectangles rather than between their enclosing axis-aligned boxes, so a prediction with the right position and the wrong angle scores as a miss.

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreRFDETRs-obb.pt") # val() returns a plain dict, not an object.metrics = model.val(data="my-obb-dataset.yaml") print(metrics["metrics/mAP50-95"])print(metrics["metrics/mAP50"], metrics["metrics/mAP75"])print(metrics["metrics/precision"], metrics["metrics/recall"])
CLI
libreyolo val model=LibreRFDETRs-obb.pt data=my-obb-dataset.yaml
RT-DETRv2
libreyolo val model=LibreRTDETRv2n-obb.pt data=my-obb-dataset.yaml

metrics/mAP50-95 is mean average precision averaged over IoU thresholds 0.50 to 0.95 in steps of 0.05, and it is the headline number. Unlike the COCO path used by detection, this task honors iou_thresholds in the validation config, so the sweep can be changed. metrics/mAP50 and metrics/mAP75 are the single-threshold versions. metrics/precision and metrics/recall are real precision and recall at IoU 0.50, read at the loosest operating point: every prediction that survived the confidence threshold is counted, and that threshold defaults to 0.001 during validation. Raising conf therefore moves them, while the mAP figures, which use the whole precision-recall curve, stay put. Four of these repeat under an (OBB) suffix, metrics/mAP50-95(OBB), metrics/mAP50(OBB), metrics/precision(OBB) and metrics/recall(OBB), which is how a caller tells an oriented result from an axis-aligned one when both sit in the same table. metrics/mAP75 has no suffixed twin.

Two options do nothing on this task. save_json and save_plots are accepted and log a warning: oriented prediction dumps and validation plots are not implemented.

Export

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreRFDETRs-obb.pt")model.export(format="onnx", imgsz=512)
CLI
libreyolo export model=LibreRFDETRs-obb.pt format=onnx imgsz=512
RT-DETRv2
# ONNX and TorchScript are the validated targets here, at FP32,# batch 1, on a fixed 1024 by 1024 canvas.libreyolo export model=LibreRTDETRv2n-obb.pt format=onnx imgsz=1024libreyolo export model=LibreRTDETRv2n-obb.pt format=torchscript imgsz=1024
Use the exported file
from libreyolo import LibreYOLO, SAMPLE_IMAGE # The factory routes on the file suffix, so an exported artifact loads# like a checkpoint and returns the same Results object.model = LibreYOLO("LibreRFDETRs-obb.onnx")result = model(SAMPLE_IMAGE) print(result.obb.xywhr)

An exported artifact loads back through LibreYOLO() on its file suffix, so a .onnx or .engine file behaves like a checkpoint and returns the same Results. Format coverage differs by task on the same family, and the matrix on the model page is generated from the validated set and names the reason a target is unavailable. See export and deploy for the formats, their extras and their constraints.

Verified against LibreYOLO v1.5.0.