coco-rfdetr-small

An RF-DETR Small model that detects the 80 COCO categories, exported to ONNX. Small and fast enough for realtime inference on a phone or in the browser on WebGPU. Built for dashradar.app. The weights are Roboflow's COCO checkpoint and nothing is trained here, so what this repo adds is the export and the contract around it. The architecture is described in arXiv:2511.09554.

Sample detection: a cobbled street scene with a person, a bicycle, a backpack, a car and a traffic light each boxed and labelled with a confidence score

Files

File Precision Size Use
onnx/model_fp16.onnx fp16 weights, fp32 I/O 57.4 MB Recommended, WebGPU (onnxruntime-web)
onnx/model.onnx fp32 114.4 MB Reference build

Both files share one I/O signature, so the same pre and post processing works for either. The fp16 build is mixed precision. Its GridSample nodes stay at fp32, because onnxruntime-web's fp16 GridSample shader is broken on WebGPU and fails by returning wrong boxes rather than by erroring.

The source checkpoint is not republished here. It is Roboflow's, it is public, and it is stable at https://storage.googleapis.com/rfdetr/small_coco/checkpoint_best_regular.pth. RFDETRSmall() downloads it for you. If you want it in transformers form, use stevenbucaille/rf-detr-small, which is the same checkpoint converted, and which these exports were verified against.

Input and output

  • input: [1, 3, 512, 512] float32 NCHW. Resolution is fixed.
  • dets: [1, 300, 4] float32, boxes in cxcywh normalized 0..1.
  • labels: [1, 300, 91] float32, raw logits. Apply sigmoid.

The 300 is the query count. Read it off the tensor length rather than hardcoding it, so a future build that emits a different number still decodes.

No NMS is baked in. RF-DETR's set loss usually makes it unnecessary. Add a light dedup if duplicate boxes show up.

Preprocessing: largest centered square of the RGB image, bilinear resize to 512x512, scale to [0, 1], ImageNet mean [0.485, 0.456, 0.406] and std [0.229, 0.224, 0.225], NCHW. The cost of the crop is field of view. A 16:9 frame loses about 44% of its width.

Decoding: the class head is 91 wide, which is COCO's raw category id space and not a dense 80. Index 0 is COCO's N/A background slot and is never read. The 80 real categories sit sparsely across indices 1 to 90, with ten N/A gaps where COCO retired a category id. Assuming a dense 0..79 table reads the wrong label for most of the classes. So sigmoid the logits, and per query take the highest scoring index that the model's own names map actually names. One box gets one label. Boxes are normalized to the cropped square, not the original frame, so map them back through the crop origin.

Metadata: each build carries its label map, input contract, source checkpoint and release tag in ONNX metadata_props. Read them with ort.InferenceSession(path).get_modelmeta().custom_metadata_map, or open the file in Netron. The keys follow the convention Ultralytics' YOLO exporter set, so the values are Python reprs rather than JSON. Parse names with ast.literal_eval and you get 80 entries keyed by integer logit index, sparse across 1 to 90 to match the head. That map is the only machine readable record of what a slot means, so read it from the file instead of shipping your own table.

Confidence tracks apparent size, not distance. The same object scores far higher in a tight native-resolution crop than it does when a full frame is downscaled into 512x512, because the second view leaves it a few pixels wide. For distant objects use a tighter crop, not a lower threshold.

Usage

import ast
import numpy as np, onnxruntime as ort
from PIL import Image

MEAN = np.array([0.485, 0.456, 0.406], np.float32)
STD = np.array([0.229, 0.224, 0.225], np.float32)
THRESHOLD = 0.35  # the upstream example's value, not tuned. See below.

img = Image.open("input.jpg").convert("RGB")
W, H = img.size
side = min(W, H)
x0, y0 = (W - side) // 2, (H - side) // 2
square = img.crop((x0, y0, x0 + side, y0 + side)).resize((512, 512), Image.BILINEAR)
x = ((np.asarray(square, np.float32) / 255.0 - MEAN) / STD).transpose(2, 0, 1)[None]

sess = ort.InferenceSession("onnx/model_fp16.onnx", providers=["CPUExecutionProvider"])
out = dict(zip([o.name for o in sess.get_outputs()], sess.run(None, {sess.get_inputs()[0].name: x})))
names = ast.literal_eval(sess.get_modelmeta().custom_metadata_map["names"])

named = np.array(sorted(names))              # the 80 logit slots that mean something
prob = 1 / (1 + np.exp(-out["labels"][0]))   # [queries, 91]
best = named[prob[:, named].argmax(1)]
score = prob[:, named].max(1)

cx, cy, bw, bh = out["dets"][0].T
xyxy = np.stack([x0 + (cx - bw / 2) * side, y0 + (cy - bh / 2) * side,
                 x0 + (cx + bw / 2) * side, y0 + (cy + bh / 2) * side], axis=1)

for q in np.argsort(-score):
    if score[q] < THRESHOLD:
        break
    print(f"{names[int(best[q])]} {score[q]:.2f} at {xyxy[q].round().astype(int).tolist()}")

Threshold

This repo publishes an untuned model. No confidence sweep was run here, and there is no dataset behind these tags to sweep against. The snippet above uses 0.35, which is the value the upstream model card's usage example keeps detections above. It is an example's value, not the cut any metric was reported at, and it is not tuned for any particular deployment. It is a place to start.

If a release ever does measure a threshold, it goes in the CHANGELOG entry for that version, so read the entry for the revision you pin. Tune against your own images either way. The right cut depends on whether a false positive or a miss costs you more, and this repo cannot know that.

Using it in dashradar.app

dashradar.app can add a model by pasting a Hugging Face URL. Paste the URL of the build itself, not the repo page:

https://huggingface.co/tuxracer/coco-rfdetr-small/resolve/v1.0/onnx/model_fp16.onnx

A bare repo URL does not work here. Given one, the app looks for a single ONNX file in the repo and gives up when it finds more than one, and this repo publishes two. Naming the file in the URL settles it.

Name a tag rather than main. Either way the stored entry is immutable, since the app resolves a main URL to the commit sha before saving it. The reason to use a tag is that it is a readable release name you can match against a CHANGELOG entry later. A sha tells you nothing.

Intended use and limitations

General purpose object detection over the 80 COCO categories, on single images or realtime video.

It is trained on COCO and inherits COCO's biases. The categories are the ones COCO chose to label, the images are the scenes COCO collected, and quality drops on anything outside that distribution. Accuracy is uneven across classes, and small or heavily occluded objects are the weakest case. The center crop means anything outside the middle square of a wide frame is never seen.

Do not rely on it for safety-critical decisions. A missed detection is a normal outcome rather than a bug.

Attribution

reference/test.jpg is COCO val2017 image id 577932, released under the Attribution License (CC BY 2.0). The original is http://farm5.staticflickr.com/4019/5159956078_f820c56d6f_z.jpg. It is center-cropped from 640x543 and resized to 512x512, so it is exactly the model input and needs no preprocessing.

The model is RF-DETR by Roboflow, trained by them on COCO. stevenbucaille/rf-detr-small is the transformers conversion these exports were checked against.

Citation

Cite the architecture, not this repo. The export adds no research.

@misc{robinson2026rfdetrneuralarchitecturesearch,
      title={RF-DETR: Neural Architecture Search for Real-Time Detection Transformers},
      author={Isaac Robinson and Peter Robicheaux and Matvei Popov and Deva Ramanan and Neehar Peri},
      year={2026},
      eprint={2511.09554},
      archivePrefix={arXiv},
      primaryClass={cs.CV},
      url={https://arxiv.org/abs/2511.09554},
}

License

Apache 2.0, matching upstream RF-DETR.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Paper for tuxracer/coco-rfdetr-small