How to do computer vision in Python with Roboflow Supervision (with or without OpenCV)

Supervision is the artificial vision library in Python by Roboflow: it takes detections from any model (YOLO, RF-DETR, Transformers, or Gemini) and gives you a single API to draw them, count them by zones, track them between frames, and convert datasets between YOLO, COCO, and Pascal VOC. It’s free, MIT-licensed, and as of version 0.30.0 it installs without OpenCV. We measured how much that costs, and if you work with video, the answer is: a lot.

Verified on September 17, 2026 with supervision 0.30.3 (PyPI, September 14, 2026) and the develop branch. At the time of publishing this note, the repository had 50.8 thousand stars on GitHub.

What is Supervision by Roboflow?

Supervision is the layer that sits between your model and your computer vision application. Each object detection framework returns its own result type. Supervision converts them all into a single object, sv.Detections. This way, the code that annotates, filters, counts, and exports doesn’t change when you switch models.

Its documentation lists converters for Ultralytics, Roboflow Inference, Transformers, SAM, Detectron2, and MMDetection, among others. It also includes parsers for vision and language models like Florence-2, PaliGemma, Qwen VL, and Gemini. RF-DETR, Roboflow’s own detector, skips the conversion step: its predict method already returns an sv.Detections.

It’s not a new project:

  • The first commit is from November 2022.
  • The repository has more than 5,000 commits from over 200 authors.
  • It recorded 97 commits in the 30 days prior to this note.

Roboflow’s documentation states that the library exceeds one million monthly downloads on PyPI; this figure is from the provider itself.

How do you install Supervision and why does it still need OpenCV?

pip install supervision

Requires Python 3.10 or higher; support for 3.9 was removed in version 0.30.0.

That same version added an alternative backend built on NumPy, Pillow, and PyAV. The project’s migration guide indicates that Supervision no longer installs OpenCV or offers an extra for OpenCV. The guide includes a line to check which backend your process chose:

python -c "from supervision import _cv2; print(_cv2.BACKEND_NAME)"

In a clean environment it prints fallback. When importing the library, a UserWarning also appears recommending that you install opencv-python for full performance and compatibility. We wanted to know how much performance is lost.

Our measurement. We used two new virtual environments:

  • Machine: Linux x86_64, one 2.1 GHz Xeon vCPU, Python 3.12.3, and supervision 0.30.3.
  • Environments: one with the alternative backend and one with opencv-python-headless 5.0.0.93.
  • Test: synthetic frames of 1920×1080. We only measured drawing, with no inference from any model. Each value is the average of 30 runs.
Detections per frame BoxAnnotator without OpenCV BoxAnnotator with OpenCV
1 8.1 ms 0.6 ms
5 30.2 ms 0.6 ms
50 268.9 ms 1.0 ms

The other operations showed the same pattern:

  • LabelAnnotator: 284.6 ms versus 1.3 ms with 50 detections.
  • sv.resize_image to 640×360: 6.9 ms versus 0.6 ms.

The cost of drawing without OpenCV grows about 5 ms for each box. With 50 objects, that leaves fewer than four frames per second just for drawing, before the model even runs.

What you do save is disk space: the site-packages of the environment without OpenCV weighed 434 MB, versus 586 MB with OpenCV, about 150 MB less. PyAV, which the alternative backend needs for video, takes up 103 MB by itself in both.

The practical rule:

  • Datasets, metrics, conversions, and servers that don’t draw: the backend without OpenCV works fine and the image stays lighter.
  • Anything that annotates video: install OpenCV in Python. The migration guide asks you to choose exactly one package family and never both:
    • opencv-python-headless for servers and containers.
    • opencv-python for desktop applications.

One detail: the annotation example in the project’s own README starts with import cv2. With just a pip install supervision and nothing else, that line fails with ModuleNotFoundError: No module named 'cv2'. We reproduced it.

How do you detect objects with YOLO or RF-DETR in Python?

The project’s guide shows the same three steps for any framework: run the model, load the result into sv.Detections, and annotate. This is the version with RF-DETR, copied from the documentation. It uses cv2.imread, so it assumes you installed OpenCV as explained above:

import cv2
import supervision as sv
from rfdetr import RFDETRMedium

model = RFDETRMedium()
image = cv2.imread("dog.jpeg")
detections = model.predict(image[:, :, ::-1])

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)

With YOLO from Ultralytics, only the model call and one conversion line change: detections = sv.Detections.from_ultralytics(results). With Transformers, the line is sv.Detections.from_transformers(...). Everything that comes after stays identical.

That portability has a licensing consequence worth reviewing before choosing. According to PyPI metadata as of September 17, 2026:

  • rfdetr (1.10.1) uses Apache-2.0.
  • ultralytics (8.4.155) uses AGPL-3.0.

Supervision is MIT, but the model you connect brings its own terms.

How do you make a people counter with Python?

A people counter with Supervision has three parts:

  1. A polygon that defines the zone.
  2. An sv.PolygonZone created from that polygon.
  3. A call to trigger on the detections from each frame, which returns a boolean mask with those that fall inside the zone.

From the project’s counting guide:

zones = [sv.PolygonZone(polygon=polygon) for polygon in polygons]
mask = zone.trigger(detections=detections)
detections_filtered = detections[mask]
```To get the polygon coordinates, Roboflow offers a web tool, [PolygonZone](https://roboflow.github.io/polygonzone/): you upload a frame, mark the corners, and it returns NumPy arrays.

To count only people, filter by class before passing the detections to the zone. The `count_people_in_zone` example from the repository does it this way with RF-DETR:

```python
filter_by_class = detections.class_id == PERSON_CLASS_ID
filter_by_confidence = detections.confidence > confidence_threshold
return detections[filter_by_class & filter_by_confidence]

Here’s the trap of a “not tied to a model” taken literally: class IDs are not portable. A comment in the same example clarifies that in RF-DETR, COCO class IDs start at 1, so person is 1. In Ultralytics and Inference’s mapping, person is 0, and the example version for Ultralytics filters with detections.class_id == 0. If you switch models and don’t change that number, your people counter starts counting something else without raising any error.

There are two other details, from the documentation and the changelog:

  • To count what crosses a line instead of what’s inside an area, use sv.LineZone. It requires detections.tracker_id, so you need a tracker first (next section).
  • Invalid polygons: since 0.30.3, a PolygonZone with fewer than three vertices raises a ValueError. Before, it created a zone that never activated and said nothing. We confirmed the new error in 0.30.3.

How do I track objects between frames?

Here the documentation and the published package don’t match yet, so it’s worth paying attention to the date of everything you read.

The develop changelog lists sv.ByteTrack as removed in the upcoming 0.31.0. It’s replaced by ByteTrackTracker, from Roboflow’s standalone trackers package. As of September 17, 2026, 0.31.0 hasn’t been released: 0.30.3 still includes sv.ByteTrack, and some of the published documentation still describes it. Write new code against trackers so it survives the next version. From the tracking guide:

pip install trackers
import numpy as np
import supervision as sv
from rfdetr import RFDETRMedium
from trackers import ByteTrackTracker

model = RFDETRMedium()
tracker = ByteTrackTracker(track_activation_threshold=0.25, minimum_consecutive_frames=1)
box_annotator = sv.BoxAnnotator()

def callback(frame: np.ndarray, _: int) -> np.ndarray:
    detections = model.predict(frame[:, :, ::-1])
    detections = tracker.update(detections)
    return box_annotator.annotate(frame.copy(), detections=detections)

sv.process_video(
    source_path="people-walking.mp4",
    target_path="result.mp4",
    callback=callback
)

If you’re migrating code, keep two things in mind:

  • The method name changes: now it’s update(), not update_with_detections().
  • Unconfirmed tracks come back with tracker_id equal to -1. In our test, even with minimum_consecutive_frames=1, a new object got -1 in its first frame and a real ID from the second frame onward. The guide filters them with detections = detections[detections.tracker_id != -1].

The trap: trackers 2.6.0 (Apache-2.0) declares opencv-python>=4.8.0 as a dependency. Installing it brings OpenCV back, and specifically the desktop package, not the headless one. If you followed the advice to use opencv-python-headless on servers, adding tracking leaves both package families in your environment. That’s exactly what the migration guide asks you to avoid. We confirmed it on a clean install.

How do I convert a YOLO dataset to COCO?

You load the dataset, split it if you want, and export it. We ran it on a synthetic dataset of 10 images and reloaded the COCO output without problems:

dataset = sv.DetectionDataset.from_yolo(
    images_directory_path=...,
    annotations_directory_path=...,
    data_yaml_path=...,
)
train_dataset, test_dataset = dataset.split(split_ratio=0.7)
dataset.as_coco(
    images_directory_path=...,
    annotations_path=...,
)

from_coco, from_pascal_voc, as_yolo, and as_pascal_voc follow the same pattern, so any conversion between the three formats is just two function calls.

A bug you might encounter today. If your YOLO label files write class IDs with decimals (1.0 instead of 1), version 0.30.3 aborts the entire load with ValueError: invalid literal for int() with base 10: '1.0'. This is what np.savetxt produces by default, and we reproduced it.

The fix (#2580) is already merged into develop, but as of September 17, 2026 it wasn’t in any published release. That same pending batch fixes class names with non-ASCII characters (like café), which fail on Windows (#2585). Until the next release, write class IDs as integers.

Is Supervision free and does it tie you to Roboflow?

Supervision is free, MIT-licensed, and doesn’t require a Roboflow account. You only need a Roboflow API key if you run models with their inference package or download datasets from Roboflow.

That package has some friction today. We did a test resolution with pip install --dry-run on September 17, 2026 (Linux x86_64, Python 3.12):

  • pip install inference resolved inference 1.6.0 with supervision 0.29.1, not 0.30.x.
  • The same resolution brought three OpenCV families: opencv-python, opencv-contrib-python, and opencv-python-headless.
  • Forcing supervision==0.30.3, pip downgraded to inference 1.3.8.

If you want the current version of Supervision, the converters that go straight to RF-DETR, Ultralytics, or Transformers avoid that conflict.

What’s coming in the next version of Supervision?

This is in develop and as of September 17, 2026 hadn’t been released:

  • Deprecations planned for 0.31.0, including:
    • sv.ByteTrack.
    • The supervision.keypoint module; use supervision.key_points instead.
    • sv.LMM and Detections.from_lmm; use sv.VLM and Detections.from_vlm instead.
    • The old import path for MeanAveragePrecision.
  • New vision and language model parsers:
    • Structured detection output from Gemini 3.6 and 3.7. Gemini 3.5 already landed in 0.30.0.
    • Kosmos-2.
  • Metrics: aggregate_metric_results() and plot_aggregate_metric_results(), for comparing multiple models in a single table or chart.
  • Dataset fixes:
    • Class IDs with decimals and Windows encoding mentioned above.
    • Handling EXIF orientation in photos taken with a phone.

If you pin supervision==0.30.3 today, read the changelog before upgrading.