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_gap or shelf_label class. 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 / labels by position instead of by name. For a multi-class model, use argmax over the class dimension and map indices to names; here everything is book, 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 in Geraldine/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 is book.
  • 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-2 project 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

@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}
}
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

Model tree for Geraldine/rf-detr-nano-bookshelf

Quantized
(1)
this model

Dataset 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-reported
    0.974
  • mAP@50:95 (EMA, validation) on bookshelf-recognition-2 (Roboflow Universe)
    self-reported
    0.802
  • mAP@75 (EMA, validation) on bookshelf-recognition-2 (Roboflow Universe)
    self-reported
    0.896