Detect Small Objects¶
Detect small objects with RF-DETR, YOLO, or your preferred model using tiled
inference in Supervision and ml-pipes. The examples use
RF-DETR through
Inference, comparing full-image detection
with InferenceSlicer.
Baseline Detection¶
Small object detection in high-resolution images presents challenges due to the objects' size relative to the image resolution.
Running a standard detection model on the full image establishes a baseline for comparison. Load your chosen model, pass the image through it, and convert the results into a Detections object. This baseline reveals how many small objects the model misses at native resolution, motivating the sliced inference approach shown later.
from ml_pipes.core import Pipeline
from ml_pipes.standard import Recall, Select, Store
from ml_pipes.supervision import BoxAnnotator, Detections, ImageToArray, LabelAnnotator, PlotImage
from ml_pipes.supervision.inference import RoboflowInference
from ml_pipes.vision import Decode, LoadFile
pipeline = Pipeline(
[
LoadFile(),
Decode(),
ImageToArray(),
Store("source_image"),
RoboflowInference(model_id="rfdetr-medium"),
Select(0),
Detections.FromInference(),
Recall("source_image", prepend=True),
BoxAnnotator(),
LabelAnnotator(show_class=True, show_confidence=True),
PlotImage(at=0),
]
)
annotated_image, detections = pipeline("<SOURCE_IMAGE_PATH>")
import cv2
import supervision as sv
from inference import get_model
model = get_model(model_id="rfdetr-medium")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = box_annotator.annotate(
scene=image,
detections=detections,
)
annotated_image = label_annotator.annotate(
scene=annotated_image,
detections=detections,
)

Input Resolution¶
Higher model input resolution can improve small-object detection at a speed and memory cost. This is model-specific tuning; see the upstream input-resolution comparison.
Inference Slicer¶
InferenceSlicer processes high-resolution images by dividing them into smaller segments, detecting objects within each, and aggregating the results.
overlap_wh is measured in pixels, not percentages. Increase it when objects
span tile boundaries, at the cost of more inference. The upstream reference
also describes alternative overlap filters.
from ml_pipes.core import Pipeline
from ml_pipes.standard import Gather, Pick, Recall, Scatter, Select, Store
from ml_pipes.supervision import BoxAnnotator, Detections, ImageToArray, LabelAnnotator, PlotImage
from ml_pipes.supervision.inference import RoboflowInference
from ml_pipes.vision import Decode, LoadFile, Tile
pipeline = Pipeline(
[
LoadFile(),
Decode(),
Store("source_image"),
Tile(slice_wh=(640, 640), overlap_wh=(100, 100)),
Store("tile_rects", source=1),
Pick(0),
Scatter(max_concurrency=1),
RoboflowInference(model_id="rfdetr-medium"),
Select(0),
Detections.FromInference(),
Gather(),
Recall("tile_rects"),
Detections.Stitch(),
Detections.NMS(threshold=0.5),
Store("detections"),
Recall("source_image"),
Pick(1),
ImageToArray(),
Recall("detections"),
BoxAnnotator(),
LabelAnnotator(show_class=True, show_confidence=True),
PlotImage(at=0),
]
)
annotated_image, detections = pipeline("<SOURCE_IMAGE_PATH>")
import cv2
import numpy as np
import supervision as sv
from inference import get_model
model = get_model(model_id="rfdetr-medium")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback=callback)
detections = slicer(image)
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = box_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)

Small Object Segmentation¶
InferenceSlicer can perform segmentation tasks too, using the same tile and
NMS settings as above. To opt into compact masks,
set compact_masks=True in the conversion stage or callback.
from ml_pipes.core import Pipeline
from ml_pipes.standard import Gather, Pick, Recall, Scatter, Select, Store
from ml_pipes.supervision import Detections, ImageToArray, LabelAnnotator, MaskAnnotator, PlotImage
from ml_pipes.supervision.inference import RoboflowInference
from ml_pipes.vision import Decode, LoadFile, Tile
pipeline = Pipeline(
[
LoadFile(),
Decode(),
Store("source_image"),
Tile(slice_wh=(640, 640), overlap_wh=(100, 100)),
Store("tile_rects", source=1),
Pick(0),
Scatter(max_concurrency=1),
RoboflowInference(model_id="rfdetr-seg-medium"),
Select(0),
Detections.FromInference(),
Gather(),
Recall("tile_rects"),
Detections.Stitch(),
Detections.NMS(threshold=0.5),
Store("detections"),
Recall("source_image"),
Pick(1),
ImageToArray(),
Recall("detections"),
MaskAnnotator(),
LabelAnnotator(show_class=True, show_confidence=True),
PlotImage(at=0),
]
)
annotated_image, detections = pipeline("<SOURCE_IMAGE_PATH>")
import cv2
import numpy as np
import supervision as sv
from inference import get_model
model = get_model(model_id="rfdetr-seg-medium")
image = cv2.imread("<SOURCE_IMAGE_PATH>")
def callback(image_slice: np.ndarray) -> sv.Detections:
results = model.infer(image_slice)[0]
return sv.Detections.from_inference(results)
slicer = sv.InferenceSlicer(callback=callback)
detections = slicer(image)
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator()
annotated_image = mask_annotator.annotate(
scene=image, detections=detections)
annotated_image = label_annotator.annotate(
scene=annotated_image, detections=detections)

Author¶
- Piotr Skalski — Computer Vision Engineer, Roboflow