RF-DETR-nano — Bookshelf / book detector
Finetuned on Roboflow rateshelf/bookshelf-recognition-2 v1 (1 class: book), 10 epochs. Best metrics: {"val/ema_mAP_50": 0.9739392399787903, "val/ema_mAP_50_95": 0.8020401000976562, "val/mAP_50": 0.9727001786231995, "val/mAP_50_95": 0.7843878269195557, "val/mAP_75": 0.895616888999939}
A lightweight, real-time object detector that finds individual books (spines) on shelves. It is an RF-DETR-nano model fine-tuned on a public bookshelf dataset, intended as the detection stage of a shelf-monitoring pipeline for libraries: once books are localised, empty spans, linear-metre occupancy and per-shelf fill rates are derived geometrically from the boxes (no separate "empty space" class is needed).
- Task: object detection (bounding boxes) — a single effective class,
book - Base model: RF-DETR-nano (Roboflow, COCO-pretrained, Apache-2.0)
- Framework:
rfdetr==1.9.4(PyTorch) - Input resolution: 384 × 384
- License: Apache-2.0
Intended uses
Primary use. Detect books on shelf photos or video frames — bookstores, and, as the first step of a library shelf-monitoring workflow, library stacks. The detector emits book bounding boxes; downstream logic turns those boxes into:
- empty spans / gaps — measured between adjacent books on a shelf row (geometry, not a trained class);
- linear-metre occupancy — book-spine widths summed per row, scaled by a known shelf length;
- fill rate per shelf row — occupied width ÷ shelf span.
A live demo of this downstream pipeline is available at Geraldine/rf-detr-shelf-demo.
Out of scope.
- Reading call numbers / titles (OCR). This model does not read text; pair it with a dedicated OCR reader (e.g. PaddleOCR PP-OCRv5) on high-resolution shelf-end label crops.
- A trained
empty_gaporshelf_labelclass. Empty space is derived geometrically, which is more reliable than an open-vocabulary "empty shelf" class at typical shelf resolutions. - General book-cover retrieval or single-object recognition — this model localises spines in dense shelf scenes, not covers on a table.
How to use
Single image
from huggingface_hub import hf_hub_download
from rfdetr import RFDETRNano
from PIL import Image
import supervision as sv
ckpt = hf_hub_download("Geraldine/rf-detr-nano-bookshelf", "checkpoints/checkpoint_best_ema.pth")
model = RFDETRNano(pretrain_weights=ckpt)
model.optimize_for_inference()
image = Image.open("shelf.jpg")
detections = model.predict(image, threshold=0.5) # returns a supervision.Detections
annotated = sv.BoxAnnotator().annotate(image.copy(), detections)
annotated.save("shelf_annotated.jpg")
# detections.xyxy / .confidence feed the downstream gap & linear-metre logic
Video / camera (frame sampling)
Shelf monitoring does not need a live 30 fps stream — film an aisle, then sample frames:
import cv2
from PIL import Image
import supervision as sv
box = sv.BoxAnnotator()
frames = sv.get_video_frames_generator("aisle.mp4", stride=15) # ~1 frame / 0.5 s at 30 fps
for frame in frames:
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
det = model.predict(Image.fromarray(rgb), threshold=0.5)
# ... pass det to the geometry stage ...
CPU deployment with ONNX (no PyTorch)
For on-premise, GPU-free deployment, run the exported ONNX with onnxruntime — typically
2–4× faster than PyTorch on CPU, with no torch / rfdetr runtime dependency (only
numpy, onnxruntime and pillow).
A ready-to-use export is hosted in this repo:
onnx/rfdetr-nano-bookshelf.onnx.
To regenerate it yourself:
pip install -U onnx # up-to-date exporter — model.export() can fail on an old onnx
pip install "rfdetr[onnx]" # onnx export extras
from rfdetr import RFDETRNano
model = RFDETRNano(pretrain_weights="checkpoints/checkpoint_best_ema.pth")
model.export() # writes an .onnx into ./output — input resolution frozen at 384
Inference (numpy + onnxruntime only):
import numpy as np, onnxruntime as ort
from PIL import Image
from huggingface_hub import hf_hub_download
onnx_path = hf_hub_download("Geraldine/rf-detr-nano-bookshelf", "onnx/rfdetr-nano-bookshelf.onnx")
sess = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"])
RES = 384 # must match the exported resolution
MEAN = np.array([0.485, 0.456, 0.406], np.float32)
STD = np.array([0.229, 0.224, 0.225], np.float32)
def preprocess(path):
img = Image.open(path).convert("RGB")
W, H = img.size
x = np.asarray(img.resize((RES, RES), Image.BILINEAR), np.float32) / 255.0
x = (x - MEAN) / STD
return x.transpose(2, 0, 1)[None], (W, H) # NCHW float32, original size
x, (W, H) = preprocess("shelf.jpg")
# Match outputs BY NAME — RF-DETR does not guarantee positional order
out = {o.name: v for o, v in zip(sess.get_outputs(), sess.run(None, {"input": x}))}
boxes = out["dets"][0] # [num_queries, 4] cxcywh, normalized [0,1]
logits = out["labels"][0] # [num_queries, num_classes+1] raw logits
conf = (1.0 / (1.0 + np.exp(-logits))).max(1) # sigmoid; single effective class -> max activation
keep = conf > 0.5
boxes, conf = boxes[keep], conf[keep]
# cxcywh (normalized) -> xyxy (pixels in the original image)
cx, cy, w, h = boxes.T
xyxy = np.stack([(cx - w/2)*W, (cy - h/2)*H, (cx + w/2)*W, (cy + h/2)*H], axis=1)
# (xyxy, conf) is what the downstream gap / linear-metre stage consumes
Two things that silently break ONNX inference: using a resolution other than the 384 the model was exported at (boxes come out shifted/scaled), and reading
dets/labelsby position instead of by name. For a multi-class model, useargmaxover the class dimension and map indices to names; here everything isbook, so the max activation is enough.
CPU performance note. "Real-time" figures for RF-DETR are measured on GPU. On CPU, expect a few frames per second even in the nano size — fine for frame-sampled shelf surveys, not smooth live video.
Training data
- Source: Roboflow Universe
bookshelf-recognition-2(v1), COCO export, mirrored inGeraldine/shelf-photos-batch1. - Split: 4,624 training images / 77,468 boxes · 1,323 validation images / 22,360 boxes.
- Classes: the COCO export carries a Roboflow placeholder super-category
(
book-oCeY); the single effective detection class isbook. - License of the source data: released on Roboflow Universe under a permissive licence
(MIT for the parent bookshelf-recognition project) — confirm the exact licence on the
bookshelf-recognition-2project page before redistributing the data.
Training procedure
Fine-tuned with rfdetr==1.9.4 (scripts/train_nano_bookshelf.py, included in this repo).
| Hyperparameter | Value |
|---|---|
| Base model | RF-DETR-nano |
| Input resolution | 384 |
| Epochs | 10 |
| Batch size | 16 |
| Gradient accumulation | 1 |
| Learning rate | 1e-4 |
| Encoder learning rate | 1e-4 |
| EMA | enabled |
Live training metrics were logged to Geraldine/rf-detr-nano-bookshelf-trackio.
Three checkpoints are provided: checkpoint_best_ema.pth (recommended), checkpoint_best_regular.pth,
and checkpoint_best_total.pth.
Evaluation
Measured on the held-out validation split (1,323 human-annotated images) — unlike a pseudo-label baseline, these numbers reflect agreement with human ground truth.
| Metric | EMA checkpoint | Regular checkpoint |
|---|---|---|
| mAP@50 | 0.974 | 0.973 |
| mAP@50:95 | 0.802 | 0.784 |
| mAP@75 | 0.896 | — |
checkpoint_best_ema.pth is the recommended weight for inference.
Limitations & responsible use
- Domain gap. Training images are generic bookshelf/bookstore scenes. Real library stacks (Dewey/UDC labelling, specific furniture, lighting, tightly packed spines seen at an angle) differ. Expect a drop on your own shelves until you adapt the model on a small set of your own annotated photos.
- Single class. The model finds books only. Empty gaps, shelf boards and shelf-end labels are not detected — gaps come from geometry; labels/OCR require a separate stage.
- Resolution sensitivity. Detection degrades on low-resolution images (spines become indistinct). Shoot bays face-on, sharp, one bay per frame.
- Measurement is only as good as calibration. Linear-metre outputs depend on a correct pixel→metre scale (known shelf length, fixed camera distance, or a reference object).
Downstream pipeline
This detector is the first stage of a library shelf-monitoring tool
(collection moves, reshelving checks, linear-metre surveys). The geometry stage — row
clustering, gap measurement including shelf-end gaps, and linear-metre calibration — lives in the
demo Space Geraldine/rf-detr-shelf-demo.
Citation & acknowledgements
- RF-DETR — Roboflow, https://github.com/roboflow/rf-detr
- Training data —
bookshelf-recognition-2, Roboflow Universe
@software{rf_detr_nano_bookshelf,
author = {Geoffroy, Géraldine},
title = {RF-DETR-nano Bookshelf Detector},
year = {2026},
url = {https://huggingface.co/Geraldine/rf-detr-nano-bookshelf}
}
Model tree for Geraldine/rf-detr-nano-bookshelf
Base model
Roboflow/rf-detr-nanoDataset used to train Geraldine/rf-detr-nano-bookshelf
Space using Geraldine/rf-detr-nano-bookshelf 1
Evaluation results
- mAP@50 (EMA, validation) on bookshelf-recognition-2 (Roboflow Universe)self-reported0.974
- mAP@50:95 (EMA, validation) on bookshelf-recognition-2 (Roboflow Universe)self-reported0.802
- mAP@75 (EMA, validation) on bookshelf-recognition-2 (Roboflow Universe)self-reported0.896