RT-DETR

A detection transformer built for real-time inference: it decodes a fixed set of queries rather than a dense grid, so it runs no NMS. LibreYOLO carries three versions of it, told apart by the checkpoint you load, and version 2 also serves oriented boxes.

Tasks
detection, oriented boxes
Sizes
rtdetr: r18, r34, r50, r50m, r101, l, x at 640 px; rtdetrv2: r18, r34, r50, r50m, r101 at 640 px; rtdetrv4: s, m, l, x at 640 px
Install
pip install libreyolo
Support tier
Core, since v1.1.0. Core trainable detectors: features follow the flagships in the same release wave.
Upstream
RT-DETR, RT-DETRv2 and RT-DETRv4 by Baidu (versions 1 and 2), Peking University and Tsinghua University (version 4), Apache-2.0. Paper, source
Licenses
Code Apache-2.0, weights Apache-2.0. Commercial use

Install

RT-DETR needs no optional extra. Everything it imports is in the base install, and the rtdetr extra is a stable name that adds nothing to it.

bash
pip install libreyolo

Adapter fine-tuning with lora=True is the exception, and needs the lora extra.

bash
pip install "libreyolo[lora]"

Predict

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

Python
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreRTDETRr18.pt")result = model(SAMPLE_IMAGE, save=True) for box in result.boxes:    print(box.cls, box.conf, box.xyxy)
CLI
libreyolo predict model=LibreRTDETRr18.pt save=True \  source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpg
Video
from libreyolo import LibreYOLO # The version is part of the file name, and the factory routes on the# checkpoint, so all three load the same way.model = LibreYOLO("LibreRTDETRv4s.pt") # Any source the library accepts: file, folder, URL, webcam index,# RTSP stream, or a .streams listfor result in model.predict("clip.mp4", stream=True, save=True):    print(len(result.boxes))
Oriented boxes
from libreyolo import LibreYOLO # Version 2 only. The -obb suffix selects the task, and the checkpoint# is recognized as oriented from its own tensors, so no task argument# is needed. These weights are DOTA v1.0, 15 aerial classes at 1024 px.model = LibreYOLO("LibreRTDETRv2n-obb.pt")result = model("aerial.png", save=True) obb = result.obbprint(obb.xywhr)     # (N, 5): cx, cy, w, h, radiansprint(obb.xyxyxyxy)  # the same rows as four corner pointsprint(result.boxes.xyxy)  # enclosing axis-aligned boxes
Oriented boxes, CLI
libreyolo predict model=LibreRTDETRv2n-obb.pt source=aerial.png save=True

The returned Results object is the one every family returns, so swapping in a different detector is a one line change. conf and max_det filter a top-k decode over queries and classes; there is no NMS step to tune, and iou is accepted but unused. An oriented checkpoint fills result.obb natively and also fills result.boxes with the enclosing axis-aligned rectangles. See prediction for sources, streaming and result handling.

Variants

Three versions, two tasks between them, and the size codes do not run in a single series. Version 1 names its sizes after the backbone, ResNet or HGNetv2. Version 2 reuses the ResNet names only: version 1 already ships the two HGNetv2 sizes, and version 2's results there were close enough that LibreYOLO publishes no duplicate weights for them. Version 4 uses a plain letter series, which collides with version 1's HGNetv2 names, so a size code on its own does not identify a model. The version is written into the checkpoint file name.

CheckpointInput (px)mAP 50-95Params (M)
LibreRTDETRl64055.832.93
LibreRTDETRr10164056.876.56
LibreRTDETRr1864049.720.18
LibreRTDETRr3464052.231.44
LibreRTDETRr5064055.942.89
LibreRTDETRr50m64053.836.59
LibreRTDETRx64057.967.37
LibreRTDETRv2r10164056.876.56
LibreRTDETRv2r1864050.820.18
LibreRTDETRv2r3464053.231.44
LibreRTDETRv2r5064055.742.89
LibreRTDETRv2r50m64054.836.59
LibreRTDETRv4l64057.831.24
LibreRTDETRv4m64056.519.59
LibreRTDETRv4s64052.810.32
LibreRTDETRv4x64060.062.62

COCO val2017, 500 images. Measured by the LibreYOLO benchmark harness and published on Vision Analysis, where latency across hardware and runtimes is compared and the full run records live.

Version 2 keeps version 1's architecture and state dict layout and changes how the deformable attention samples, which is why the two are told apart by the metadata in the checkpoint rather than by shape. Version 4 is a different lineage: it reuses D-FINE's architecture and trainer, and its weights come from distilling a DINOv3 vision foundation model teacher into an HGNetv2 student. In LibreYOLO LibreRTDETRv4 is a subclass of LibreDFINE with the mask head pinned off, so it stays detection only.

Oriented boxes on version 2

Version 2 is the one version that carries a second task. Its supported tasks are detect and obb, and the two do not share a graph or a size series. Detection uses the ResNet sizes at 640 px; oriented detection uses an HGNetv2 series, n, s, m, l and x, at 1024 px, and the input size resolves per task rather than per family. A checkpoint is recognized as oriented from its own tensors, by the five-coordinate box heads and the version 2 sampling parameters, so -obb weights load into the oriented graph without a task argument and a mismatch between the two is a hard error rather than a silent reinterpretation.

The published files are LibreRTDETRv2n-obb.pt through LibreRTDETRv2x-obb.pt. They are the official DOTA v1.0 single-scale checkpoints converted into LibreYOLO's format, 15 aerial classes from plane and ship through harbor and helicopter, and their class names are stamped into the checkpoint. Unlike the detection side, the oriented task is inference only: prediction, validation and export work, and train() on an oriented model raises. Tracking and test-time augmentation do not support oriented boxes either. Oriented detection covers the task, the label format and the metrics.

Train

Training starts from a published checkpoint. pretrained is accepted and then dropped on all three versions, so pretrained=False does not give you a randomly initialized model. Everything in this section is about detection: version 2's oriented task is inference only, and there is no transfer path from detection weights to it, because the two use different backbones.

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreRTDETRr18.pt") # coco128.yaml downloads a 128-image sample on first use. Point `data`# at your own dataset YAML for a real run.model.train(data="coco128.yaml", epochs=50, batch=4, lr0=1e-4)
CLI
libreyolo train model=LibreRTDETRr18.pt data=coco128.yaml \  epochs=50 batch=4 lr0=1e-4
LoRA
# Needs the lora extra: pip install "libreyolo[lora]"from libreyolo import LibreYOLO model = LibreYOLO("LibreRTDETRr18.pt")model.train(data="coco128.yaml", epochs=50, lora=True)
Multi-GPU
libreyolo train model=LibreRTDETRr18.pt data=coco128.yaml \  epochs=50 device=0,1

Learning rate is the argument to get right, and each version carries its own default rather than the library-wide one. The Python train() signature reads it from that version's training config, and the CLI resolves the same value when lr0 is not passed. Versions 1 and 2 also take lr_backbone and default it to a twentieth of lr0, following the original recipe; version 4 runs through the D-FINE trainer, which scales the backbone parameter group with backbone_lr_mult instead.

Leave imgsz at the checkpoint's native size unless you have a reason to change it. Validation and prediction at other sizes work, with one residual: a rectangular size whose token count matches the native size still reuses an embedding built for the wrong aspect ratio.

See training for datasets, augmentation, multi-GPU and loggers.

RT-DETRv4 enables amp=True with amp_dtype="float16" by default. Pass amp=False for FP32.

Validate

val() returns a dictionary of metrics/ keys covering precision, recall, mAP 50 and mAP 50-95, measured against any dataset in the format you trained on.

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreRTDETRr18.pt") # val() returns a plain dict, not an objectmetrics = model.val(data="coco128.yaml") print(metrics["metrics/mAP50-95"])print(metrics["metrics/mAP50"])print(metrics["metrics/precision"], metrics["metrics/recall"])
CLI
libreyolo val model=LibreRTDETRr18.pt data=coco128.yaml
Against COCO
# coco-val-only.yaml fetches the 5000 val2017 images and skips the# training set. It carries an embedded download script, so it needs# explicit permission unless the dataset is already local.libreyolo val model=LibreRTDETRr18.pt data=coco-val-only.yaml \  allow_download_scripts=True
Oriented boxes
from libreyolo import LibreYOLO # Oriented validation matches with rotated IoU, so a prediction in the# right place at the wrong angle counts as a miss.model = LibreYOLO("LibreRTDETRv2n-obb.pt")metrics = model.val(data="my-obb-dataset.yaml") print(metrics["metrics/mAP50-95(OBB)"])print(metrics["metrics/mAP50(OBB)"])

The rows in the benchmark table above come from the LibreYOLO benchmark harness; the note under that table records which dataset produced them and links the run records.

Oriented validation runs through the same call and reports the same keys, plus four repeated under an (OBB) suffix. Matching uses rotated IoU rather than the IoU of the enclosing rectangles, so an angle error is a miss. augment=True is rejected on this task.

Export

TaskONNXTorchScriptExecuTorchTensorRTOpenVINOPaddleMNNRKNNncnnTFLiteCoreMLCore AI
DetectionDetection to ONNX: supportedDetection to TorchScript: supportedDetection to ExecuTorch: supportedDetection to TensorRT: supportedDetection to OpenVINO: supportedDetection to Paddle: not supportedDetection to MNN: supportedDetection to RKNN: not supportedDetection to ncnn: not supportedDetection to TFLite: not supportedDetection to CoreML: not supportedDetection to Core AI: supported
Oriented boxesOriented boxes to ONNX: supportedOriented boxes to TorchScript: supportedOriented boxes to ExecuTorch: supportedOriented boxes to TensorRT: supportedOriented boxes to OpenVINO: supportedOriented boxes to Paddle: not supportedOriented boxes to MNN: not supportedOriented boxes to RKNN: not supportedOriented boxes to ncnn: not supportedOriented boxes to TFLite: not supportedOriented boxes to CoreML: not supportedOriented boxes to Core AI: not supported

The matrix covers the lineage as one page: where the three versions disagree about a format, the cell shows the weakest of the three, so nothing here is oversold for whichever version you load. The oriented row belongs to version 2 alone. ONNX and TorchScript are validated there, at FP32, batch 1 and a fixed 1024 by 1024 canvas; OpenVINO, TensorRT and ExecuTorch convert and reload but have not met raw-output parity across the full query set, so the top boxes agree to a fraction of a pixel while the tail drifts.

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.

Python
# Needs the onnx extra: pip install "libreyolo[onnx]"from libreyolo import LibreYOLO model = LibreYOLO("LibreRTDETRr18.pt")path = model.export(format="onnx")print(path)
CLI
libreyolo export model=LibreRTDETRr18.pt format=onnx
Oriented boxes
# ONNX and TorchScript are the validated targets for the oriented task,# 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 any checkpoint and returns the same Results object.model = LibreYOLO("LibreRTDETRr18.onnx")result = model(SAMPLE_IMAGE) print(result.boxes.xyxy)

ONNX inputs and outputs

LibreRTDETR and LibreRTDETRv2

Tensorimages float32 [batch, 3, height, width]
ColorRGB
Scalingpixel / 255
Normalizationnone
Resizestretch to width x height (no padding, aspect ratio not kept), bilinear

Square input only. Export rejects a non-square imgsz, and height equals width, 640 by default.

LibreRTDETR also exports at other square sizes, such as 512. LibreRTDETRv2 exports at 640 only: any other imgsz fails during export with a tensor size mismatch.

Stretch the whole image to width x height with cv2.resize and INTER_LINEAR on the 8-bit RGB image. There is no letterbox and no padding, so the aspect ratio is not kept.

Then divide by 255. There is no mean or std normalization.

Detection, default export
OutputShapeMeaning
pred_logits[batch, queries, nc]Raw class logits per query, sigmoid not applied. 300 queries and 80 classes for the COCO checkpoints.
pred_boxes[batch, queries, 4]Boxes as cx, cy, w, h, normalized 0 to 1 of the input.

Postprocessing

  1. Apply sigmoid to pred_logits.
  2. Flatten the scores to queries x nc and take the top max_det (300 by default) over all queries x classes. For flat index k, the query is k // nc and the class is k % nc. One query can yield several classes.
  3. Keep the entries whose score is strictly above the confidence threshold (0.25 by default). There is no NMS, so the IoU threshold is not used.
  4. Convert the query's box to x1, y1, x2, y2 with x1 = cx - w / 2, y1 = cy - h / 2, x2 = cx + w / 2, y2 = cy + h / 2.
  5. Map boxes back to the original image: multiply x by the original width and y by the original height. The input was stretched, so no pad offset applies. LibreYOLO does not clip these boxes.
  6. nms=True is not available for this family: export raises NotImplementedError.

LibreRTDETRv2 OBB

Tensorimages float32 [batch, 3, height, width]
ColorRGB
Scalingpixel / 255
Normalizationnone
Resizecustom, Pillow bilinear
How to reproduce Pillow's bilinear resize

LibreYOLO resizes with Pillow's BILINEAR filter (Image.resize). It is not OpenCV INTER_LINEAR or INTER_AREA: when shrinking, Pillow widens the filter by the shrink factor, and OpenCV's resizes change detections and move boxes by several pixels. To reproduce it without Pillow, resize one axis, round to 8 bits, then resize the other axis and round again. Per axis, let s = in_size / out_size and f = max(s, 1). Output pixel i has center c = (i + 0.5) * s and reads source pixels j from int(c - f + 0.5) to int(c + f + 0.5) - 1, clipped to the image, each with weight max(0, 1 - |j + 0.5 - c| / f), divided by the sum of the weights. Pillow works in fixed point and picks the axis order from the image shape, so float weights can differ from it by one level on a few pixels. That does not change the results in our checks.

Square input only, 1024 x 1024. Export rejects a non-square imgsz, and any other square size fails during export with a tensor size mismatch.

Scale by r = min(height / h, width / w) and resize to (round(w * r), round(h * r)). This rounds to the nearest integer instead of truncating. An exact half goes to the even integer, as Python round does.

Paste the resized image in the top-left corner of a canvas filled with 0. The padding is on the right and bottom only.

On the test image, cv2.resize with INTER_LINEAR or INTER_AREA changes the detection count and moves boxes by more than 2 px.

Pillow computes each pass in fixed point: it rounds every weight to an integer with 22 fractional bits (round half away from zero), sums weight x pixel in integers, adds 2^21, shifts right by 22 and clips to 0 to 255.

Use that integer arithmetic for this model. Float weights differ from Pillow by a level on a few pixels, and on the test image that turns one box by 0.2 rad.

Divide the whole canvas by 255 after the resize. There is no mean or std normalization.

Oriented boxes, default export
OutputShapeMeaning
pred_logits[batch, queries, nc]Raw class logits per query, sigmoid not applied. 300 queries and 15 DOTA classes for the released checkpoints.
pred_boxes[batch, queries, 5]Rotated boxes as cx, cy, w, h, a. cx and w are normalized 0 to 1 of the input width, cy and h of the input height, both on the padded canvas. a is the angle divided by pi, 0 to 1.

Postprocessing

  1. Apply sigmoid to pred_logits.
  2. Flatten the scores to queries x nc and take the top max_det (300 by default) over all queries x classes. For flat index k, the query is k // nc and the class is k % nc. One query can yield several classes.
  3. Keep the entries whose score is strictly above the confidence threshold (0.25 by default). There is no NMS, rotated or axis-aligned, so the IoU threshold is not used.
  4. Map boxes back to the original image: multiply cx and w by the input width, cy and h by the input height, then divide all four by r. The padding is on the right and bottom, so no offset applies. LibreYOLO does not clip these boxes.
  5. The angle in radians is a x pi. LibreYOLO reports each box as cx, cy, w, h, angle in original-image pixels and radians.
  6. The corners are (cx + dx cos(angle) - dy sin(angle), cy + dx sin(angle) + dy cos(angle)) for (dx, dy) = (-w / 2, -h / 2), (w / 2, -h / 2), (w / 2, h / 2), (-w / 2, h / 2). With y pointing down, a positive angle turns the box clockwise on screen.
  7. LibreYOLO's axis-aligned box for a detection is the smallest box that encloses its four corners.
  8. nms=True is not available for this family: export raises NotImplementedError.

LibreRTDETRv4

Tensorimages float32 [batch, 3, height, width]
ColorRGB
Scalingpixel / 255
Normalizationnone
Resizestretch to width x height (no padding, aspect ratio not kept), Pillow bilinear
How to reproduce Pillow's bilinear resize

LibreYOLO resizes with Pillow's BILINEAR filter (Image.resize). It is not OpenCV INTER_LINEAR or INTER_AREA: when shrinking, Pillow widens the filter by the shrink factor, and OpenCV's resizes change detections and move boxes by several pixels. To reproduce it without Pillow, resize one axis, round to 8 bits, then resize the other axis and round again. Per axis, let s = in_size / out_size and f = max(s, 1). Output pixel i has center c = (i + 0.5) * s and reads source pixels j from int(c - f + 0.5) to int(c + f + 0.5) - 1, clipped to the image, each with weight max(0, 1 - |j + 0.5 - c| / f), divided by the sum of the weights. Pillow works in fixed point and picks the axis order from the image shape, so float weights can differ from it by one level on a few pixels. That does not change the results in our checks.

Square input only. Export rejects a non-square imgsz, and height equals width, 640 by default. Other square sizes, such as 512, also export.

Batch size 1 only. The graph declares a dynamic batch axis, but batches of 2 or more fail in 1.7.0.

Divide by 255 after the resize. There is no mean or std normalization.

Detection, default export
OutputShapeMeaning
pred_logits[batch, queries, nc]Raw class logits per query, sigmoid not applied. 300 queries and 80 classes for the COCO checkpoints.
pred_boxes[batch, queries, 4]Boxes as cx, cy, w, h, normalized 0 to 1 of the input.

Postprocessing

  1. Apply sigmoid to pred_logits.
  2. Flatten the scores to queries x nc and take the top max_det (300 by default) over all queries x classes. For flat index k, the query is k // nc and the class is k % nc. One query can yield several classes.
  3. Keep the entries whose score is strictly above the confidence threshold (0.25 by default). There is no NMS, so the IoU threshold is not used.
  4. Convert the query's box to x1, y1, x2, y2 with x1 = cx - w / 2, y1 = cy - h / 2, x2 = cx + w / 2, y2 = cy + h / 2.
  5. Map boxes back to the original image: multiply x by the original width and y by the original height. The input was stretched, so no pad offset applies. LibreYOLO does not clip these boxes.
  6. nms=True is not available for this family: export raises NotImplementedError.

To check your own implementation, run the same file through LibreYOLO: LibreYOLO("model.onnx").predict("image.jpg") returns the reference results. Verified with LibreYOLO 1.7.0.

Checkpoints

Every published weight file for this family.

FileInput (px)Weights license
Detection
LibreRTDETRr34.pt640apache-2.0
LibreRTDETRr18.pt640apache-2.0
LibreRTDETRr50.pt640apache-2.0
LibreRTDETRr50m.pt640apache-2.0
LibreRTDETRr101.pt640apache-2.0
LibreRTDETRl.pt640apache-2.0
LibreRTDETRx.pt640apache-2.0
LibreRTDETRv2r18.pt640apache-2.0
LibreRTDETRv2r34.pt640apache-2.0
LibreRTDETRv2r50m.pt640apache-2.0
LibreRTDETRv2r50.pt640apache-2.0
LibreRTDETRv2r101.pt640apache-2.0
LibreRTDETRv4s.pt640apache-2.0
LibreRTDETRv4m.pt640apache-2.0
LibreRTDETRv4l.pt640apache-2.0
LibreRTDETRv4x.pt640apache-2.0
Oriented boxes
LibreRTDETRv2n-obb.ptapache-2.0
LibreRTDETRv2s-obb.pt640apache-2.0
LibreRTDETRv2m-obb.pt640apache-2.0
LibreRTDETRv2l-obb.pt640apache-2.0
LibreRTDETRv2x-obb.pt640apache-2.0

Every file above exists in the LibreYOLO org today and downloads on first use.

The file name carries the version, then the size, then the task. Detection weights are LibreRTDETR<size>.pt, LibreRTDETRv2<size>.pt and LibreRTDETRv4<size>.pt, all at 640 px. Oriented weights exist for version 2 only and add the task suffix, LibreRTDETRv2n-obb.pt through LibreRTDETRv2x-obb.pt, all at 1024 px and trained on DOTA v1.0 rather than COCO.

Licensing

Check the license on the Hugging Face repository of the specific weights you download. Every checkpoint in the LibreYOLO org carries one, and they are not always the same across a family. That repository is the authoritative source; the summary below describes what applied when this page was last verified.

This is a description of the licenses involved, not legal advice. If the answer matters commercially, read the licenses yourself and take your own counsel.

Original work
RT-DETR, RT-DETRv2 and RT-DETRv4, Baidu (versions 1 and 2), Peking University and Tsinghua University (version 4)
Upstream license
Apache-2.0
LibreYOLO code
MIT
Weights
Apache-2.0, republished at huggingface.co/LibreYOLO
Interpretation
Apache-2.0 is a permissive license, so these weights can be used in commercial and closed-source products. It asks you to keep its license text and attribution notices with any copy of the weights you redistribute, and it grants a patent license. It places no obligation on your own application code, and weights you train yourself on your own data are yours. All three versions carry it, across two repositories and three papers: RT-DETR and RT-DETRv2 at github.com/lyuwenyu/RT-DETR, and RT-DETRv4 at github.com/RT-DETRs/RT-DETRv4, which is cited separately (arXiv 2510.25257). RT-DETRv4 distills from a DINOv3 teacher while training only; the released student weights hold no DINOv3 parameters, and the tensors that fed the teacher are dropped when a checkpoint is converted, so Meta's DINOv3 license does not reach them.

Citation

@misc{lv2023detrs,
      title={DETRs Beat YOLOs on Real-time Object Detection},
      author={Yian Zhao and Wenyu Lv and Shangliang Xu and Jinman Wei and Guanzhong Wang and Qingqing Dang and Yi Liu and Jie Chen},
      year={2023},
      eprint={2304.08069},
      archivePrefix={arXiv},
      primaryClass={cs.CV}
}

@misc{lv2024rtdetrv2improvedbaselinebagoffreebies,
      title={RT-DETRv2: Improved Baseline with Bag-of-Freebies for Real-Time Detection Transformer}, 
      author={Wenyu Lv and Yian Zhao and Qinyao Chang and Kui Huang and Guanzhong Wang and Yi Liu},
      year={2024},
      eprint={2407.17140},
      archivePrefix={arXiv},
      primaryClass={cs.CV},
      url={https://arxiv.org/abs/2407.17140}, 
}

Copied from the authors' citation block at github.com/lyuwenyu/RT-DETR#citation.

The block above is what the authors publish for versions 1 and 2 detection. Version 2's oriented weights have a third upstream, the Apache-2.0 RiO-DETR repository at github.com/RicePasteM/RiO-DETR, which is where the DOTA checkpoints come from; cite that project if you used one. Version 4 is a separate paper by a different group and has its own citation block at github.com/RT-DETRs/RT-DETRv4; cite that one if you used a version 4 checkpoint.

Verified against LibreYOLO v1.6.0. Support tables, checkpoints and benchmark numbers on this page are generated from the released library and the published weights, not written by hand.