Supervision is an open-source Python library by Roboflow that gives you the reusable building blocks for computer vision applications. It takes the raw output of any detection, segmentation, keypoint, or classification model and turns it into a single, consistent object you can annotate, filter, track, count, export, and benchmark — without rewriting glue code for every model you try.
This guide is task-oriented. Start with the Quick Start, install the package, learn
the one object every workflow revolves around (sv.Detections), then jump to the
task you need: drawing boxes and masks, filtering predictions, tracking objects in
video, counting in zones, processing datasets, or measuring model accuracy.
Note
Supervision is model-agnostic. It does not run inference itself — you bring a model (Ultralytics YOLO, Roboflow Inference, Hugging Face Transformers, SAM, Detectron2, MMDetection, a VLM, and more) and Supervision handles everything that happens to the predictions afterward.
1. Quick Start
If you already have a detection model, here is the complete loop: run inference, convert to Supervision's format, and draw the results.
import cv2
import supervision as sv
from ultralytics import YOLO
# 1. Run any model
model = YOLO("yolov8n.pt")
image = cv2.imread("dog.jpeg")
results = model(image)[0]
# 2. Convert the output into a unified Detections object
detections = sv.Detections.from_ultralytics(results)
# 3. Draw the predictions
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated = box_annotator.annotate(scene=image.copy(), detections=detections)
annotated = label_annotator.annotate(scene=annotated, detections=detections)
sv.plot_image(annotated)
That is the core pattern for every model Supervision supports. Only line 2 changes:
swap from_ultralytics for from_inference, from_transformers, from_sam, and so
on. Everything downstream — annotators, filters, trackers, zones, metrics — works the
same regardless of which model produced the predictions.
Tip
Don't have a test image? Download the one used throughout the Roboflow tutorials:
wget https://media.roboflow.com/notebooks/examples/dog.jpeg
2. Installation
Supervision runs on Python 3.9 or newer (3.9–3.14 are officially supported) on macOS, Windows, and Linux.
pip (recommended)
pip install supervision
uv
uv pip install supervision
For a uv-managed project:
uv add supervision
poetry
poetry add supervision
conda / mamba
conda install -c conda-forge supervision
Optional extras
Some features pull in extra dependencies. Install them only when you need them:
| Extra | Command | Adds |
|---|---|---|
| Metrics | pip install "supervision[metrics]" |
pandas, required only for tabular metric output |
Note
The legacy
headlessanddesktopinstall targets were removed.pip install supervision[headless]still works — it installs the base library and harmlessly warns that the extra no longer exists. The base package is already headless-friendly.
Install from source (development)
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -e "."
Verify the installation
import supervision as sv
print(sv.__version__)
0.29.0
Important
Supervision installs CPU dependencies only (NumPy, OpenCV, Pillow, SciPy, Matplotlib). It does not install PyTorch, Ultralytics, or any model framework — you choose and install those yourself based on the model you want to run.
3. The Detections Object (Core Concept)
Almost everything in Supervision flows through one class: sv.Detections. It is a
container that normalizes the output of any supported model into the same set of
NumPy arrays.
| Attribute | Shape | Meaning |
|---|---|---|
xyxy |
(n, 4) |
Bounding boxes as [x1, y1, x2, y2] in pixels |
mask |
(n, H, W) or None |
Boolean segmentation masks, when available |
confidence |
(n,) or None |
Per-detection confidence scores |
class_id |
(n,) or None |
Integer class IDs |
tracker_id |
(n,) or None |
Persistent IDs assigned by a tracker |
data |
dict |
Extra per-detection fields, e.g. data["class_name"] |
metadata |
dict |
Collection-level info (video name, timestamp, …) |
You rarely build a Detections by hand. Instead you use a from_* connector for
your model.
Connectors — load predictions from any model
| Method | Source framework |
|---|---|
Detections.from_inference(result) |
Roboflow Inference |
Detections.from_ultralytics(result) |
Ultralytics YOLO (detect, segment, OBB) |
Detections.from_transformers(result, id2label=...) |
Hugging Face Transformers |
Detections.from_sam(result) |
Segment Anything Model |
Detections.from_sam3(result, resolution_wh=...) |
SAM 3 |
Detections.from_detectron2(result) |
Detectron2 |
Detections.from_mmdetection(result) |
MMDetection / MMYOLO |
Detections.from_yolo_nas(result) |
YOLO-NAS (super-gradients) |
Detections.from_yolov5(result) |
YOLOv5 |
Detections.from_deepsparse(result) |
Neural Magic DeepSparse |
Detections.from_paddledet(result) |
PaddleDetection |
Detections.from_tensorflow(result, resolution_wh=...) |
TensorFlow Hub |
Detections.from_ncnn(result) |
NCNN |
Detections.from_easyocr(result) |
EasyOCR |
Detections.from_azure_analyze_image(result) |
Azure AI Vision 4.0 |
Detections.from_vlm(vlm=..., result=..., ...) |
Vision-language models (see §11) |
Tip
Every connector returns the same
Detectionstype. This is the whole point of Supervision: write your annotation, tracking, and counting logic once, then change models freely by changing only the connector line.
Useful properties and methods
| Member | Returns | Notes |
|---|---|---|
len(detections) |
int |
Number of detections |
detections.area |
(n,) |
Mask area if masks present, else box area |
detections.box_area |
(n,) |
Bounding-box area in pixels |
detections.box_aspect_ratio |
(n,) |
Width / height of each box |
detections.get_anchors_coordinates(anchor) |
(n, 2) |
Anchor points (see Position) |
detections.with_nms(threshold=0.5) |
Detections |
Non-maximum suppression |
detections.with_nmm(threshold=0.5) |
Detections |
Non-maximum merge |
Detections.merge([d1, d2, ...]) |
Detections |
Concatenate multiple objects |
Detections.empty() |
Detections |
An empty object |
detections.is_empty() |
bool |
True when there are no detections |
detections["class_name"] |
array | Access a data field directly |
4. Detect and Annotate
Supervision ships a rich catalog of annotators — composable objects that each
draw one kind of visual overlay. They all share the same call shape:
annotator.annotate(scene=image, detections=detections), and each returns the
modified image so you can chain them.
Boxes and labels
Ultralytics
import cv2
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
image = cv2.imread("dog.jpeg")
results = model(image)[0]
detections = sv.Detections.from_ultralytics(results)
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated = box_annotator.annotate(scene=image, detections=detections)
annotated = label_annotator.annotate(scene=annotated, detections=detections)
Inference
import cv2
import supervision as sv
from inference import get_model
model = get_model(model_id="yolov8n-640")
image = cv2.imread("dog.jpeg")
results = model.infer(image)[0]
detections = sv.Detections.from_inference(results)
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
annotated = box_annotator.annotate(scene=image, detections=detections)
annotated = label_annotator.annotate(scene=annotated, detections=detections)
Custom labels
By default LabelAnnotator prints each detection's class name (falling back to class
ID, then index). Pass your own labels list to override:
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence
in zip(detections["class_name"], detections.confidence)
]
annotated = box_annotator.annotate(scene=image, detections=detections)
annotated = label_annotator.annotate(
scene=annotated, detections=detections, labels=labels)
Segmentation masks
For a segmentation model, swap BoxAnnotator for MaskAnnotator — it is a drop-in
replacement.
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER_OF_MASS)
annotated = mask_annotator.annotate(scene=image.copy(), detections=detections)
annotated = label_annotator.annotate(scene=annotated, detections=detections)
The annotator catalog
| Annotator | Draws |
|---|---|
BoxAnnotator |
Bounding-box rectangles |
RoundBoxAnnotator |
Boxes with rounded corners |
BoxCornerAnnotator |
Corner brackets only |
OrientedBoxAnnotator |
Rotated (OBB) boxes |
ColorAnnotator |
Semi-transparent filled boxes |
MaskAnnotator |
Segmentation masks |
PolygonAnnotator |
Mask outlines as polygons |
HaloAnnotator |
Soft glow around masks |
EllipseAnnotator |
Ellipse under each object (sports-style) |
CircleAnnotator |
Circle around each object |
DotAnnotator |
A dot at an anchor point |
TriangleAnnotator |
A triangle marker above each object |
LabelAnnotator |
Text labels with background |
RichLabelAnnotator |
Labels with custom (e.g. non-Latin) fonts |
IconAnnotator |
A custom PNG icon per detection |
BlurAnnotator |
Blurs the detected regions |
PixelateAnnotator |
Pixelates the detected regions |
HeatMapAnnotator |
Accumulated heatmap over a video |
TraceAnnotator |
Motion trails for tracked objects |
PercentageBarAnnotator |
A progress-style bar per detection |
CropAnnotator |
Magnified crops of each detection |
BackgroundOverlayAnnotator |
Dims everything outside the detections |
ComparisonAnnotator |
Side-by-side comparison of two detection sets |
Common annotator options
Most annotators accept the same core constructor arguments:
| Argument | Default | Meaning |
|---|---|---|
color |
ColorPalette.DEFAULT |
A single Color, a ColorPalette, or a hex string |
thickness |
2 |
Line thickness in pixels |
color_lookup |
ColorLookup.CLASS |
How colors map to objects: INDEX, CLASS, or TRACK |
MaskAnnotator and ColorAnnotator add opacity (default 0.5). LabelAnnotator
adds text_color, text_scale, text_padding, text_position, border_radius,
and smart_position (spreads overlapping labels apart).
Tip
Set
color_lookup=sv.ColorLookup.TRACKonce you are tracking objects, so every object keeps the same color across all frames of a video.
5. Filter Detections
Detections supports NumPy-style boolean indexing. This is the fastest, most
expressive way to refine predictions — no helper methods to memorize.
import numpy as np
import supervision as sv
# Keep only class 0
detections = detections[detections.class_id == 0]
# Keep a set of classes
detections = detections[np.isin(detections.class_id, [0, 2, 3])]
# Confidence threshold
detections = detections[detections.confidence > 0.5]
# Minimum pixel area
detections = detections[detections.area > 1000]
# Relative area (drop objects larger than 80% of the frame)
h, w, _ = image.shape
detections = detections[(detections.area / (h * w)) < 0.8]
# Box dimensions
box_w = detections.xyxy[:, 2] - detections.xyxy[:, 0]
box_h = detections.xyxy[:, 3] - detections.xyxy[:, 1]
detections = detections[(box_w > 200) & (box_h > 200)]
Combine any conditions with & (and) / | (or):
detections = detections[(detections.confidence > 0.7) & (detections.class_id == 2)]
Remove duplicates with NMS
detections = detections.with_nms(threshold=0.5)
Note
detections.areareturns mask area when masks are present and box area otherwise. Usedetections.box_areawhen you always want bounding-box area, anddetections.box_aspect_ratioto filter by shape.
6. Track Objects in Video
Tracking assigns a persistent ID to each object so you can follow it across
frames. Supervision processes video through sv.process_video, which calls your
per-frame callback.
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
tracker = sv.ByteTrack()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
trace_annotator = sv.TraceAnnotator()
def callback(frame: np.ndarray, index: int) -> np.ndarray:
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
detections = tracker.update_with_detections(detections)
labels = [
f"#{tracker_id} {class_name}"
for class_name, tracker_id
in zip(detections["class_name"], detections.tracker_id)
]
annotated = box_annotator.annotate(frame.copy(), detections=detections)
annotated = label_annotator.annotate(annotated, detections=detections, labels=labels)
return trace_annotator.annotate(annotated, detections=detections)
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
TraceAnnotator overlays each tracked object's recent path, making movement patterns
easy to see.
Warning
sv.ByteTrackis deprecated sincesupervision-0.28.0and will be removed insupervision-0.30.0. The recommended replacement isByteTrackTrackerfrom the separatetrackerspackage (pip install trackers). In the new package the update method is namedupdate()instead ofupdate_with_detections().
ByteTrack tuning
| Parameter | Default | Effect |
|---|---|---|
track_activation_threshold |
0.25 |
Min confidence to start a track |
lost_track_buffer |
30 |
Frames to keep a lost track alive (occlusion handling) |
minimum_matching_threshold |
0.8 |
Track-to-detection matching strictness |
frame_rate |
30 |
Video FPS (used to scale the lost-track buffer) |
minimum_consecutive_frames |
1 |
Frames an object must persist to become a valid track |
Smoothing shaky boxes
Wrap the tracker output in DetectionsSmoother to average box coordinates over the
last few frames:
smoother = sv.DetectionsSmoother() # default length = 5 frames
detections = tracker.update_with_detections(detections)
detections = smoother.update_with_detections(detections)
Caution
DetectionsSmootherrequirestracker_id(run a tracker first) and is not compatible with segmentation masks.
7. Count Objects in Zones
Polygon zones
A PolygonZone tests which detections fall inside an arbitrary region. Its
trigger() method returns a boolean mask you can use to filter or count.
import numpy as np
import supervision as sv
polygon = np.array([[100, 200], [200, 100], [300, 200], [200, 300]])
zone = sv.PolygonZone(polygon=polygon)
mask = zone.trigger(detections=detections) # boolean array, one per detection
detections_in_zone = detections[mask]
count = zone.current_count # number currently inside
Draw the zone and its live count with PolygonZoneAnnotator:
zone_annotator = sv.PolygonZoneAnnotator(
zone=zone,
color=sv.Color.RED,
thickness=4,
)
frame = zone_annotator.annotate(scene=frame)
Tip
Generate polygon coordinates visually with the free PolygonZone web tool — upload a frame, click the region, and copy the NumPy array it produces.
Note
By default a detection counts as "inside" when its bottom-center point is in the polygon. Change which anchor(s) are tested with
PolygonZone(polygon=..., triggering_anchors=(sv.Position.CENTER,)).
Line zones (crossing counts)
A LineZone counts objects that cross a line, separately tallying each direction.
Line crossing needs tracker_id, so run a tracker first.
start = sv.Point(x=0, y=500)
end = sv.Point(x=1280, y=500)
line_zone = sv.LineZone(start=start, end=end)
# inside the per-frame callback, after tracking:
crossed_in, crossed_out = line_zone.trigger(detections)
print(line_zone.in_count, line_zone.out_count)
LineZone also exposes per-class tallies via in_count_per_class and
out_count_per_class. Render the line and counts with sv.LineZoneAnnotator
(or LineZoneAnnotatorMulticlass for per-class display).
8. Detect Small Objects (Inference Slicing)
Small objects in high-resolution images are easy for a model to miss. InferenceSlicer
applies the SAHI technique: it cuts the image into overlapping tiles, runs your
model on each tile, and merges the results.
import cv2
import numpy as np
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8x.pt")
image = cv2.imread("high_res.jpg")
def callback(image_slice: np.ndarray) -> sv.Detections:
result = model(image_slice)[0]
return sv.Detections.from_ultralytics(result)
slicer = sv.InferenceSlicer(callback=callback)
detections = slicer(image)
| Parameter | Default | Meaning |
|---|---|---|
slice_wh |
640 |
Tile size (w, h); an int sets both |
overlap_wh |
100 |
Overlap between tiles in pixels |
overlap_filter |
NON_MAX_SUPPRESSION |
How to merge overlaps: NON_MAX_SUPPRESSION, NON_MAX_MERGE, or NONE |
iou_threshold |
0.5 |
IoU used when filtering merged overlaps |
thread_workers |
1 |
Parallel tiles (see caution below) |
compact_masks |
False |
Keep masks run-length encoded to avoid OOM on huge images |
The same slicer works for segmentation — just return masks from your callback and use
MaskAnnotator to draw them.
Warning
overlap_whis measured in pixels, not as a percentage. Increase it when objects are large relative to the tile or often land on tile borders; decrease it for speed.
Caution
When using oriented bounding boxes (OBB), Supervision automatically falls back to sequential slice processing even if
thread_workers > 1, because many OBB backends are not thread-safe. You will see a one-time warning when this happens.
9. Process Datasets
sv.DetectionDataset loads, converts, splits, merges, and saves object-detection
datasets in YOLO, COCO, and Pascal VOC formats.
Load
COCO
ds = sv.DetectionDataset.from_coco(
images_directory_path="dataset/train",
annotations_path="dataset/train/_annotations.coco.json",
)
YOLO
ds = sv.DetectionDataset.from_yolo(
images_directory_path="dataset/train/images",
annotations_directory_path="dataset/train/labels",
data_yaml_path="dataset/data.yaml",
)
Pascal VOC
ds = sv.DetectionDataset.from_pascal_voc(
images_directory_path="dataset/train/images",
annotations_directory_path="dataset/train/labels",
)
Iterate (images load lazily)
for image_path, image, annotations in ds:
... # annotations is a Detections object
print(ds.classes) # ['person', 'bicycle', 'car', ...]
print(len(ds))
Split, merge, and convert
# Split — returns exactly TWO datasets
train_ds, test_ds = ds.split(split_ratio=0.8, shuffle=True)
# Merge several into one
combined = sv.DetectionDataset.merge([ds_a, ds_b, ds_c])
# Convert format by loading one and saving as another
sv.DetectionDataset.from_yolo(...).as_pascal_voc(
images_directory_path="out/images",
annotations_directory_path="out/labels",
)
Export with ds.as_coco(...), ds.as_yolo(...), or ds.as_pascal_voc(...).
Note
split()returns two datasets (train and test). To get a validation set as well, split one of the results again, e.g.valid_ds, test_ds = test_ds.split(split_ratio=0.5).
Tip
Supervision has no built-in augmentation pipeline. Pair it with Albumentations: feed
annotations.xyxyaspascal_voc-format boxes, then rebuild the annotation withdataclasses.replace(annotations, xyxy=...).
10. Benchmark a Model
Supervision computes the standard detection metrics so you can compare models numerically, not just visually.
Mean Average Precision (mAP)
from supervision.metrics import MeanAveragePrecision, MetricTarget
map_metric = MeanAveragePrecision(metric_target=MetricTarget.BOXES)
map_result = map_metric.update(predictions_list, targets_list).compute()
print(map_result) # mAP @ 50:95, @ 50, @ 75, per class, and by object size
map_result.plot()
MetricTarget selects what to evaluate: BOXES, MASKS, or ORIENTED_BOUNDING_BOXES.
mAP is reported at IoU 0.50:0.95 (the headline number), plus mAP@50 and mAP@75, and is
broken down by small / medium / large objects.
F1, Precision, Recall, mAR
The same update(...).compute() pattern applies to F1Score, Precision, Recall,
and MeanAverageRecall, all importable from supervision.metrics.
from supervision.metrics import F1Score, MetricTarget
f1_result = F1Score(metric_target=MetricTarget.BOXES) \
.update(predictions_list, targets_list).compute()
print(f1_result)
Confusion matrix
import supervision as sv
cm = sv.ConfusionMatrix.from_detections(
predictions=predictions_list,
targets=targets_list,
classes=dataset.classes,
)
cm.plot()
Tip
Benchmark on a test set the model never saw during training. Using the training or validation set produces unrealistically optimistic numbers.
11. Keypoints, Classification, and VLMs
Keypoint detection
sv.KeyPoints is the keypoint analog of Detections, with its own connectors:
from_ultralytics, from_inference, from_mediapipe, from_yolo_nas,
from_detectron2, from_transformers, and from_rfdetr.
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8m-pose.pt")
results = model(image)[0]
key_points = sv.KeyPoints.from_ultralytics(results)
edge_annotator = sv.EdgeAnnotator()
vertex_annotator = sv.VertexAnnotator()
annotated = edge_annotator.annotate(image.copy(), key_points=key_points)
annotated = vertex_annotator.annotate(annotated, key_points=key_points)
To track a pose model, convert keypoints to boxes with key_points.as_detections(),
then feed those into ByteTrack as usual.
Classification
sv.Classifications wraps whole-image classifier output, with connectors
from_clip, from_ultralytics, and from_timm, plus get_top_k(k) to retrieve the
highest-scoring classes.
Vision-language models (VLMs)
Parse bounding boxes out of VLM text/JSON output with Detections.from_vlm:
import supervision as sv
paligemma_result = "<loc0256><loc0256><loc0768><loc0768> cat"
detections = sv.Detections.from_vlm(
sv.VLM.PALIGEMMA,
paligemma_result,
resolution_wh=(1000, 1000),
classes=["cat", "dog"],
)
Supported sv.VLM values: PALIGEMMA, FLORENCE_2, QWEN_2_5_VL, QWEN_3_VL,
GOOGLE_GEMINI_2_0, GOOGLE_GEMINI_2_5, MOONDREAM, DEEPSEEK_VL_2.
Note
The older
sv.LMMenum andDetections.from_lmmare deprecated (removal planned forsupervision-0.31.0). Usesv.VLMandDetections.from_vlminstead.
12. Save and Export Detections
Stream detections to CSV or JSON for offline analysis. Both sinks are context managers — open once, append per frame, and the file is finalized on exit.
CSV
with sv.CSVSink("output.csv") as sink:
for frame_index, frame in enumerate(sv.get_video_frames_generator("video.mp4")):
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
sink.append(detections, {"frame_index": frame_index})
JSON
with sv.JSONSink("output.json") as sink:
for frame_index, frame in enumerate(sv.get_video_frames_generator("video.mp4")):
results = model(frame)[0]
detections = sv.Detections.from_ultralytics(results)
sink.append(detections, {"frame_index": frame_index})
The output includes box coordinates, class_id, confidence, tracker_id,
class_name, plus any keys you pass in the second custom_data dictionary. Filter
before appending to export only what you need:
sink.append(detections[detections.confidence > 0.7], {})
13. Working with Images and Video
Supervision bundles the file and frame utilities most CV pipelines need.
| Helper | Purpose |
|---|---|
sv.VideoInfo.from_video_path(path) |
Read width, height, fps, frame count |
sv.get_video_frames_generator(path) |
Iterate frames lazily |
sv.process_video(source_path, target_path, callback) |
Run a callback over a whole video and write the result |
sv.VideoSink(path, video_info) |
Context manager to write frames |
sv.FPSMonitor() |
Measure real-time throughput (.fps) |
sv.crop_image(image, xyxy) |
Crop a region |
sv.resize_image / sv.letterbox_image / sv.scale_image |
Resize helpers |
sv.plot_image(image) |
Show an image in a notebook |
sv.plot_images_grid(images, grid_size=(r, c)) |
Show a grid of images |
Sample assets for tutorials are available without extra installs:
from supervision.assets import download_assets, VideoAssets
download_assets(VideoAssets.PEOPLE_WALKING)
14. Colors
sv.Color and sv.ColorPalette control every annotator's appearance.
red = sv.Color.RED # constants: WHITE, BLACK, GREY, RED, GREEN,
purple = sv.Color.from_hex("#A351FB") # BLUE, YELLOW, ROBOFLOW
purple.as_bgr() # (251, 81, 163) — OpenCV order
palette = sv.ColorPalette.DEFAULT # also ROBOFLOW, LEGACY
palette = sv.ColorPalette.from_hex(["#ff0000", "#00ff00", "#0000ff"])
palette = sv.ColorPalette.from_matplotlib("viridis", 10)
palette.by_idx(3) # the 4th color
Caution
Older code used method-style colors such as
sv.Color.white()andsv.ColorPalette.default(). These were removed insupervision-0.22.0. Use the constants instead:sv.Color.WHITE,sv.ColorPalette.DEFAULT.
15. Troubleshooting
Error: Detections confidence must be provided for tracking.
Cause: You passed a Detections object with confidence=None into
ByteTrack.update_with_detections(). Trackers need confidence scores to associate
objects.
Solution: Use a model output that includes confidence (all detection connectors
do) and do not strip the confidence array before tracking.
Prevention: Track before any filtering step that might drop confidence.
Error: Triggering anchors cannot be empty.
Cause: You constructed PolygonZone(..., triggering_anchors=()) with an empty
iterable.
Solution: Provide at least one sv.Position, e.g.
triggering_anchors=(sv.Position.BOTTOM_CENTER,).
Warning: Smoothing skipped. DetectionsSmoother requires tracker_id.
Cause: DetectionsSmoother.update_with_detections() received detections without
tracker_id.
Solution: Run a tracker first; the smoother averages boxes per track ID.
Prevention: Order your pipeline as detect → track → smooth → annotate.
Warning: detections have coordinates outside the slice bounds
Cause: Inside an InferenceSlicer callback, your model ran on the full image
instead of the image_slice argument it was given.
Solution: Make the callback run inference on its image_slice parameter only, and
return detections in slice-local coordinates — Supervision shifts them back to full-
image space for you.
TypeError/unexpected keyword argument 'frame_resolution_wh'
Cause: Following an old tutorial that calls
PolygonZone(polygon=..., frame_resolution_wh=...). That parameter was removed in
supervision-0.24.0.
Solution: Drop it — sv.PolygonZone(polygon=polygon) is all you need now.
Masks look empty after DetectionDataset.as_coco()
Cause: Known limitation tracked in issue #2285 — segmentation masks may not be written to COCO output in some versions.
Solution: Verify your installed version against the changelog and keep masks in
memory (detections.mask) as a fallback until your version includes the fix.
AttributeError: ... has no attribute 'keypoint' / deprecation warnings
Cause: Importing from the old supervision.keypoint module.
Solution: Use supervision.key_points (with an underscore). The old spelling is
deprecated and slated for removal in supervision-0.30.0.
Tip
To silence Supervision's deprecation warnings in a known-good pipeline, set the environment variable
SUPERVISON_DEPRECATION_WARNING=0before importing the library. (Note the original spelling of the variable.)
16. Frequently Asked Questions
Q: Does Supervision run my model for me?
No. Supervision is model-agnostic and never performs inference. You run any model you
like and pass its output to a Detections.from_* connector.
Q: Which models are supported? Object detection, segmentation, and keypoint outputs from Ultralytics YOLO, Roboflow Inference, Hugging Face Transformers, SAM and SAM 3, Detectron2, MMDetection, YOLO-NAS, YOLOv5, DeepSparse, PaddleDetection, TensorFlow Hub, NCNN, EasyOCR, Azure AI Vision, and VLMs (Florence-2, PaliGemma, Qwen-VL, Gemini, Moondream, DeepSeek-VL2).
Q: How do I filter detections by class?
Use boolean indexing: detections[detections.class_id == 0]. Combine conditions with
& and |.
Q: How do I track objects across frames?
Call tracker.update_with_detections(detections) each frame with sv.ByteTrack (note
it is being superseded by the external trackers package). The tracker fills in
detections.tracker_id.
Q: How do I count objects crossing a line?
Use sv.LineZone with a start and end sv.Point. trigger() returns
(crossed_in, crossed_out). It requires tracker_id, so track first.
Q: How do I count objects inside a region?
Use sv.PolygonZone. trigger() returns a boolean mask, and zone.current_count
gives the live count.
Q: How do I improve detection of tiny objects?
Use sv.InferenceSlicer to run your model on overlapping tiles and merge the results.
Q: What dataset formats can I load and save?
YOLO, COCO JSON, and Pascal VOC via DetectionDataset.from_* and .as_* methods.
Q: How do I compute mAP?
MeanAveragePrecision().update(predictions, targets).compute() from
supervision.metrics. It reports mAP@50:95, @50, @75, per class, and by object size.
Q: Can I use my own colors and fonts?
Yes. Pass sv.Color/sv.ColorPalette to any annotator, and use RichLabelAnnotator
for custom (including non-Latin) fonts.
Q: Is Supervision free? Yes. It is open source under the MIT license.
Q: Why am I seeing a ByteTrack deprecation warning?
The built-in ByteTrack is being phased out in favor of ByteTrackTracker from the
trackers package (pip install trackers); the update method there is update().
Q: Where can I get help? The documentation, the Discord community, and GitHub issues.
17. Quick Reference
The universal pipeline
detections = sv.Detections.from_<model>(result) # 1. connect
detections = detections[detections.confidence > 0.5] # 2. filter
detections = tracker.update_with_detections(detections) # 3. track (video)
mask = zone.trigger(detections) # 4. zone count
annotated = annotator.annotate(scene, detections) # 5. visualize
Position anchors
CENTER, CENTER_LEFT, CENTER_RIGHT, TOP_CENTER, TOP_LEFT, TOP_RIGHT,
BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, CENTER_OF_MASS.
Color lookup strategies
ColorLookup.INDEX (by order), ColorLookup.CLASS (by class — default),
ColorLookup.TRACK (by tracker ID — stable per object across frames).
Key imports
import supervision as sv
from supervision.metrics import MeanAveragePrecision, F1Score, MetricTarget
from supervision.assets import download_assets, VideoAssets