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.
# 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)libreyolo predict model=LibreRFDETRs-obb.pt save=True \ source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpgfrom 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 boxfrom libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreRFDETRn-obb.pt")result = model(SAMPLE_IMAGE) print(result.obb.xywhr.shape)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 moreKnow 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.txtA 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:
path: datasettrain: images/trainval: images/valnames: 0: plane 1: shipNative 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
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)libreyolo train model=LibreRFDETRs-obb.pt data=my-obb-dataset.yaml \ epochs=50 imgsz=512 batch=8 lr0=1e-4# 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=512Training 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.
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"])libreyolo val model=LibreRFDETRs-obb.pt data=my-obb-dataset.yamllibreyolo val model=LibreRTDETRv2n-obb.pt data=my-obb-dataset.yamlmetrics/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
from libreyolo import LibreYOLO model = LibreYOLO("LibreRFDETRs-obb.pt")model.export(format="onnx", imgsz=512)libreyolo export model=LibreRFDETRs-obb.pt format=onnx imgsz=512# 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=1024from 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.