Supervision: Computer Vision Toolkit by Roboflow
In computer vision projects, after a model detects objects, there is still much to do: drawing bounding boxes, filtering detections, counting objects, and tracking movement. All of this requires substantial and repetitive code if written from scratch.
Supervision by Roboflow is a Python toolkit that simplifies all post-detection tasks. With a consistent API, Supervision works with various detection models like YOLO, Detectron2, and SAM, allowing you to focus on application logic rather than boilerplate code.What Is Supervision?
Supervision is an open-source Python library by Roboflow that simplifies computer vision workflows. Its key features include:
- Detections Object: Universal representation for detection results from various models
- Annotators: Various visual annotator types (bounding box, mask, label, trace, halo)
- Filtering: Filter detections by confidence, class, zone, and other criteria
- Polygon Zones: Define polygon zones for counting and area analysis
- Line Counters: Count objects crossing virtual lines
- Object Tracking: ByteTrack integration for cross-frame object tracking
- Video Processing: Utilities for frame-by-frame video processing
- FPS Monitor: Real-time performance monitoring
Installation
Basic Installation
pip install supervision
Installation with Model Dependencies
# With YOLO (Ultralytics)
pip install supervision ultralytics
With Detectron2
pip install supervision detectron2
With SAM (Segment Anything)
pip install supervision segment-anything
Verify Installation
import supervision as sv
print(f"Supervision version: {sv.version}")
Detections Object
sv.Detections is the core object in Supervision that universally represents detection results.
Creating Detections from Various Models
import supervision as sv
import numpy as np
From Ultralytics YOLO
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model("image.jpg")
detections = sv.Detections.fromultralytics(results[0])
print(f"Number of detections: {len(detections)}")
print(f"Bounding boxes: {detections.xyxy}")
print(f"Confidence: {detections.confidence}")
print(f"Class IDs: {detections.classid}")
From Detectron2
from detectron2.engine import DefaultPredictor
from detectron2.config import getcfg
cfg = getcfg()
cfg.mergefromfile("config.yaml")
predictor = DefaultPredictor(cfg)
outputs = predictor(image)
detections = sv.Detections.fromdetectron2(outputs)
From SAM (Segment Anything)
from segmentanything import sammodelregistry, SamAutomaticMaskGenerator
sam = sammodelregistryvith.pth"">"vith"
maskgenerator = SamAutomaticMaskGenerator(sam)
masks = maskgenerator.generate(image)
detections = sv.Detections.fromsam(masks)
Creating Detections Manually
# Create detections manually
detections = sv.Detections(
xyxy=np.array([
[100, 200, 300, 400],
[150, 250, 350, 450]
]),
confidence=np.array([0.95, 0.87]),
classid=np.array([0, 1])
)
Annotators: Detection Visualization
Supervision provides various annotators for visualizing detection results.
Bounding Box Annotator
import supervision as sv
import cv2
image = cv2.imread("image.jpg")
Bounding box annotator
bboxannotator = sv.BoxAnnotator(
thickness=2,
color=sv.ColorPalette.fromhex(["#FF0000", "#00FF00", "#0000FF"])
)
annotatedimage = bboxannotator.annotate(
scene=image.copy(),
detections=detections
)
cv2.imwrite("annotatedbbox.jpg", annotatedimage)
Label Annotator
# Label annotator for displaying text
labelannotator = sv.LabelAnnotator(
textscale=0.5,
textthickness=1,
textpadding=5
)
Create labels with class names and confidence
labels = [
f"{classnames[classid]} {confidence:.2f}"
for classid, confidence
in zip(detections.classid, detections.confidence)
]
annotatedimage = labelannotator.annotate(
scene=annotatedimage,
detections=detections,
labels=labels
)
Mask Annotator
# For models that produce segmentation masks
maskannotator = sv.MaskAnnotator(
opacity=0.4
)
annotatedimage = maskannotator.annotate(
scene=image.copy(),
detections=detections
)
Trace Annotator (for Tracking)
# Trace annotator displays object movement trails
traceannotator = sv.TraceAnnotator(
thickness=2,
tracelength=50 # Trail length in frames
)
annotatedimage = traceannotator.annotate(
scene=image.copy(),
detections=detections
)
Halo Annotator
# Halo annotator creates a halo effect around objects
haloannotator = sv.HaloAnnotator(
opacity=0.5,
kernelsize=40
)
annotatedimage = haloannotator.annotate(
scene=image.copy(),
detections=detections
)
Combining Multiple Annotators
# Combine multiple annotators
bboxannotator = sv.BoxAnnotator()
labelannotator = sv.LabelAnnotator()
maskannotator = sv.MaskAnnotator(opacity=0.3)
Apply sequentially
annotated = image.copy()
annotated = maskannotator.annotate(scene=annotated, detections=detections)
annotated = bboxannotator.annotate(scene=annotated, detections=detections)
annotated = labelannotator.annotate(
scene=annotated,
detections=detections,
labels=labels
)
cv2.imwrite("fullyannotated.jpg", annotated)
Filtering Detections
Filter by Confidence
# Filter detections with confidence > 0.5
highconfidence = detections[detections.confidence > 0.5]
print(f"High confidence detections: {len(highconfidence)}")
Filter with confidence range
medium = detections[
(detections.confidence > 0.3) & (detections.confidence < 0.7)
]
Filter by Class
# Filter specific classes only (e.g.: person = 0, car = 2)
persons = detections[detections.classid == 0]
cars = detections[detections.classid == 2]
Filter multiple classes at once
vehicles = detections[np.isin(detections.classid, [2, 3, 5, 7])]
Filter by Area
# Filter by bounding box area
areas = detections.boxarea
largeobjects = detections[areas > 10000]
smallobjects = detections[areas < 1000]
Filter by Zone
# Define a polygon zone
zonepolygon = np.array([
[100, 100],
[500, 100],
[500, 400],
[100, 400]
])
zone = sv.PolygonZone(polygon=zonepolygon)
Filter detections within the zone
inzone = detections[zone.trigger(detections=detections)]
print(f"Objects in zone: {len(inzone)}")
Polygon Zones
Polygon Zones allow you to define custom areas for monitoring and counting.
import supervision as sv
import numpy as np
import cv2
Define zone as polygon
parkingzone = np.array([
[200, 300], [600, 300], [650, 500], [150, 500]
])
Create polygon zone
zone = sv.PolygonZone(polygon=parkingzone)
zoneannotator = sv.PolygonZoneAnnotator(
zone=zone,
color=sv.Color.GREEN,
thickness=2,
textthickness=2,
textscale=1
)
Process frame
isinzone = zone.trigger(detections=detections)
countinzone = np.sum(isinzone)
Annotate
annotated = zoneannotator.annotate(scene=image.copy())
print(f"Objects in parking: {countinzone}")
Multiple Zones
# Define multiple zones
zonespolygons = [
np.array([[100, 100], [400, 100], [400, 300], [100, 300]]), # Zone A
np.array([[500, 100], [800, 100], [800, 300], [500, 300]]), # Zone B
np.array([[100, 400], [800, 400], [800, 600], [100, 600]]), # Zone C
]
colors = [sv.Color.RED, sv.Color.GREEN, sv.Color.BLUE]
zones = []
zoneannotators = []
for polygon, color in zip(zonespolygons, colors):
zone = sv.PolygonZone(polygon=polygon)
zones.append(zone)
zoneannotators.append(
sv.PolygonZoneAnnotator(zone=zone, color=color)
)
Process each zone
for i, zone in enumerate(zones):
zone.trigger(detections=detections)
annotated = zoneannotators[i].annotate(scene=annotated)
Line Counters
Line counters count objects crossing a virtual line.
import supervision as sv
import numpy as np
Define virtual line
linestart = sv.Point(x=0, y=400)
lineend = sv.Point(x=800, y=400)
linezone = sv.LineZone(
start=linestart,
end=lineend
)
linezoneannotator = sv.LineZoneAnnotator(
thickness=2,
textthickness=2,
textscale=1
)
Process frame (in video loop)
linezone.trigger(detections=detections)
Get counts
print(f"In: {linezone.incount}")
print(f"Out: {linezone.outcount}")
Annotate
annotated = linezoneannotator.annotate(
frame=image.copy(),
linecounter=linezone
)
ByteTrack Object Tracking
ByteTrack enables consistent object tracking across video frames.
import supervision as sv
Initialize tracker
tracker = sv.ByteTrack(
trackactivationthreshold=0.25,
losttrackbuffer=30,
minimummatchingthreshold=0.8,
framerate=30
)
In video processing loop
def processframe(frame, model):
results = model(frame)
detections = sv.Detections.fromultralytics(results[0])
# Update tracker
detections = tracker.updatewithdetections(detections)
# Now detections have trackerid
print(f"Tracker IDs: {detections.trackerid}")
return detections
Tracking with Trace
tracker = sv.ByteTrack()
traceannotator = sv.TraceAnnotator(
thickness=2,
tracelength=50
)
bboxannotator = sv.BoxAnnotator()
labelannotator = sv.LabelAnnotator()
def processframe(frame, model):
results = model(frame)
detections = sv.Detections.fromultralytics(results[0])
detections = tracker.updatewithdetections(detections)
# Create labels with tracker ID
labels = [
f"#{trackerid} {classnames[classid]}"
for trackerid, classid
in zip(detections.trackerid, detections.classid)
]
# Annotate
annotated = frame.copy()
annotated = traceannotator.annotate(scene=annotated, detections=detections)
annotated = bboxannotator.annotate(scene=annotated, detections=detections)
annotated = labelannotator.annotate(
scene=annotated, detections=detections, labels=labels
)
return annotated
Integration with YOLO
import supervision as sv
from ultralytics import YOLO
import cv2
Load model
model = YOLO("yolov8n.pt")
Setup annotators
bboxannotator = sv.BoxAnnotator()
labelannotator = sv.LabelAnnotator()
Process image
image = cv2.imread("street.jpg")
results = model(image)
detections = sv.Detections.fromultralytics(results[0])
Filter vehicles only
vehicleclasses = [2, 3, 5, 7] # car, motorcycle, bus, truck
vehicles = detections[np.isin(detections.classid, vehicleclasses)]
Create labels
labels = [
f"{model.names[classid]} {conf:.2f}"
for classid, conf in zip(vehicles.classid, vehicles.confidence)
]
Annotate
annotated = bboxannotator.annotate(scene=image.copy(), detections=vehicles)
annotated = labelannotator.annotate(
scene=annotated, detections=vehicles, labels=labels
)
cv2.imwrite("vehiclesdetected.jpg", annotated)
print(f"Total vehicles detected: {len(vehicles)}")
Video Processing
Processing Video with VideoSink
import supervision as sv
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
tracker = sv.ByteTrack()
bboxannotator = sv.BoxAnnotator()
labelannotator = sv.LabelAnnotator()
Video information
videoinfo = sv.VideoInfo.fromvideopath("inputvideo.mp4")
print(f"Resolution: {videoinfo.width}x{videoinfo.height}")
print(f"FPS: {videoinfo.fps}")
print(f"Total frames: {videoinfo.totalframes}")
Process video
with sv.VideoSink(
targetpath="outputvideo.mp4",
videoinfo=videoinfo
) as sink:
for frame in sv.getvideoframesgenerator(
sourcepath="inputvideo.mp4"
):
# Detection
results = model(frame)
detections = sv.Detections.fromultralytics(results[0])
detections = tracker.updatewithdetections(detections)
# Annotate
labels = [
f"#{tid} {model.names[cid]}"
for tid, cid in zip(
detections.trackerid, detections.classid
)
]
annotated = bboxannotator.annotate(
scene=frame.copy(), detections=detections
)
annotated = labelannotator.annotate(
scene=annotated, detections=detections, labels=labels
)
sink.writeframe(annotated)
Callback-Based Video Processing
def callback(frame: np.ndarray, index: int) -> np.ndarray:
results = model(frame)
detections = sv.Detections.fromultralytics(results[0])
detections = tracker.updatewithdetections(detections)
annotated = bboxannotator.annotate(
scene=frame.copy(), detections=detections
)
return annotated
sv.processvideo(
sourcepath="input.mp4",
targetpath="output.mp4",
callback=callback
)
FPS Monitor
import supervision as sv
import time
fpsmonitor = sv.FPSMonitor(samplesize=30)
for frame in sv.getvideoframesgenerator("video.mp4"):
fpsmonitor.tick()
# Process frame...
results = model(frame)
detections = sv.Detections.fromultralytics(results[0])
# Display FPS
fps = fpsmonitor.fps
print(f"FPS: {fps:.1f}")
# Add FPS to frame
cv2.putText(
frame,
f"FPS: {fps:.1f}",
(10, 30),
cv2.FONTHERSHEYSIMPLEX,
1,
(0, 255, 0),
2
)
Practical Example: Vehicle Counting System with Zone-Based Tracking
Here is a complete example of building a vehicle counting system that uses polygon zones and object tracking.
import supervision as sv
from ultralytics import YOLO
import numpy as np
import cv2
Configuration
MODELPATH = "yolov8n.pt"
VIDEOPATH = "trafficvideo.mp4"
OUTPUTPATH = "trafficcounted.mp4"
Vehicle classes in COCO dataset
VEHICLECLASSES = {
2: "car",
3: "motorcycle",
5: "bus",
7: "truck"
}
class VehicleCounter:
def init(self, videopath: str, modelpath: str):
self.model = YOLO(modelpath)
self.videoinfo = sv.VideoInfo.fromvideopath(videopath)
# Initialize tracker
self.tracker = sv.ByteTrack(
trackactivationthreshold=0.25,
losttrackbuffer=30,
minimummatchingthreshold=0.8,
framerate=self.videoinfo.fps
)
# Define counting zones (adjust to your video)
# Entry zone (top)
self.entryzonepolygon = np.array([
[100, 200], [700, 200], [700, 300], [100, 300]
])
# Exit zone (bottom)
self.exitzonepolygon = np.array([
[100, 400], [700, 400], [700, 500], [100, 500]
])
# Create zones
self.entryzone = sv.PolygonZone(
polygon=self.entryzonepolygon
)
self.exitzone = sv.PolygonZone(
polygon=self.exitzonepolygon
)
# Line counter (horizontal line in the middle)
self.linezone = sv.LineZone(
start=sv.Point(x=50, y=350),
end=sv.Point(x=750, y=350)
)
# Annotators
self.bboxannotator = sv.BoxAnnotator(thickness=2)
self.labelannotator = sv.LabelAnnotator(
textscale=0.5,
textthickness=1
)
self.traceannotator = sv.TraceAnnotator(
thickness=2,
tracelength=60
)
self.entryzoneannotator = sv.PolygonZoneAnnotator(
zone=self.entryzone,
color=sv.Color.GREEN,
thickness=2
)
self.exitzoneannotator = sv.PolygonZoneAnnotator(
zone=self.exitzone,
color=sv.Color.RED,
thickness=2
)
self.lineannotator = sv.LineZoneAnnotator(
thickness=2,
textthickness=2,
textscale=0.8
)
# Statistics
self.vehiclecounts = {name: 0 for name in VEHICLECLASSES.values()}
self.trackedids = set()
def processframe(self, frame: np.ndarray, frameindex: int) -> np.ndarray:
# Object detection
results = self.model(frame, verbose=False)
detections = sv.Detections.fromultralytics(results[0])
# Filter vehicles only
vehiclemask = np.isin(
detections.classid,
list(VEHICLECLASSES.keys())
)
detections = detections[vehiclemask]
# Filter low confidence
detections = detections[detections.confidence > 0.3]
# Update tracker
detections = self.tracker.updatewithdetections(detections)
# Update zones and line counter
self.entryzone.trigger(detections=detections)
self.exitzone.trigger(detections=detections)
self.linezone.trigger(detections=detections)
# Count unique vehicles
if detections.trackerid is not None:
for tid, cid in zip(detections.trackerid, detections.classid):
if tid not in self.trackedids:
self.trackedids.add(tid)
vehicletype = VEHICLECLASSES.get(cid, "unknown")
self.vehiclecounts[vehicletype] += 1
# Create labels
labels = []
if detections.trackerid is not None:
labels = [
f"#{tid} {VEHICLECLASSES.get(cid, '?')} {conf:.2f}"
for tid, cid, conf in zip(
detections.trackerid,
detections.classid,
detections.confidence
)
]
# Annotate frame
annotated = frame.copy()
annotated = self.traceannotator.annotate(
scene=annotated, detections=detections
)
annotated = self.bboxannotator.annotate(
scene=annotated, detections=detections
)
annotated = self.labelannotator.annotate(
scene=annotated, detections=detections, labels=labels
)
annotated = self.entryzoneannotator.annotate(scene=annotated)
annotated = self.exitzoneannotator.annotate(scene=annotated)
annotated = self.lineannotator.annotate(
frame=annotated, linecounter=self.linezone
)
# Overlay statistics
self.drawstats(annotated, frameindex)
return annotated
def drawstats(self, frame: np.ndarray, frameindex: int):
"""Draw statistics overlay on frame."""
# Semi-transparent background
overlay = frame.copy()
cv2.rectangle(overlay, (10, 10), (300, 180), (0, 0, 0), -1)
cv2.addWeighted(overlay, 0.6, frame, 0.4, 0, frame)
# Title
cv2.putText(
frame, "Vehicle Counter",
(20, 35), cv2.FONTHERSHEYSIMPLEX, 0.7, (255, 255, 255), 2
)
# Per-vehicle details
yoffset = 60
total = 0
for vehicletype, count in self.vehiclecounts.items():
cv2.putText(
frame,
f"{vehicletype}: {count}",
(20, yoffset),
cv2.FONTHERSHEYSIMPLEX,
0.5,
(200, 200, 200),
1
)
yoffset += 25
total += count
# Total
cv2.putText(
frame,
f"Total: {total}",
(20, yoffset + 5),
cv2.FONTHERSHEYSIMPLEX,
0.6,
(0, 255, 0),
2
)
# Line counter
cv2.putText(
frame,
f"In: {self.linezone.incount} | Out: {self.linezone.outcount}",
(20, yoffset + 30),
cv2.FONTHERSHEYSIMPLEX,
0.5,
(255, 255, 0),
1
)
def run(self, outputpath: str):
"""Run the counting process on video."""
print(f"Video: {self.videoinfo.width}x{self.videoinfo.height}")
print(f"FPS: {self.videoinfo.fps}")
print(f"Total frames: {self.videoinfo.totalframes}")
print("Processing video...")
fpsmonitor = sv.FPSMonitor(samplesize=30)
with sv.VideoSink(
targetpath=outputpath,
videoinfo=self.videoinfo
) as sink:
for index, frame in enumerate(
sv.getvideoframesgenerator(VIDEOPATH)
):
fpsmonitor.tick()
annotated = self.processframe(frame, index)
sink.writeframe(annotated)
# Progress report every 100 frames
if index % 100 == 0:
fps = fpsmonitor.fps
progress = (index / self.videoinfo.totalframes) * 100
print(
f"Frame {index}/{self.videoinfo.totalframes} "
f"({progress:.1f}%) - FPS: {fps:.1f}"
)
# Final report
print("\n=== Counting Results ===")
total = 0
for vehicletype, count in self.vehiclecounts.items():
print(f" {vehicletype}: {count}")
total += count
print(f" Total vehicles: {total}")
print(f" Unique tracked IDs: {len(self.trackedids)}")
print(f" Line In: {self.linezone.incount}")
print(f" Line Out: {self.linezone.outcount}")
Run
if name == "main":
counter = VehicleCounter(
videopath=VIDEOPATH,
modelpath=MODELPATH
)
counter.run(outputpath=OUTPUTPATH)
Running the Vehicle Counter
# Install dependencies
pip install supervision ultralytics opencv-python numpy
Download YOLO model (automatic on first run)
Prepare your traffic video
Run
python vehiclecounter.py
Configuring Zones for Your Video
To define zones that match your video, use Supervision utilities:
import supervision as sv
import cv2
Get the first frame from video
framegenerator = sv.getvideoframesgenerator("trafficvideo.mp4")
firstframe = next(framegenerator)
Display and note zone coordinates
cv2.imshow("First Frame", firstframe)
cv2.waitKey(0)
cv2.destroyAllWindows()
Use the noted coordinates for polygon zones
Tips and Best Practices
trackactivationthreshold and losttrack_buffer based on your scenarioConclusion
Supervision by Roboflow is an extremely useful toolkit for simplifying computer vision workflows. With consistent abstractions for various detection models, rich visual annotators, and powerful tracking features, Supervision enables you to build production-ready computer vision applications with clean and maintainable code. Seamless integration with YOLO, Detectron2, and SAM makes it the go-to choice for modern computer vision projects.