Track Objects¶
Track detected objects across video frames to assign persistent IDs and analyze
motion. This guide ports the Supervision object detection and segmentation
workflow to ml-pipes; keypoint tracking is not yet covered by this package.
Download the source video used throughout the tutorial:
from supervision.assets import VideoAssets, download_assets
download_assets(VideoAssets.PEOPLE_WALKING)
Object Detection & Segmentation¶
Run Inference¶
Run detection on each video frame, then draw boxes on the resulting scene.
import supervision as sv
from ml_pipes.core import Pipeline
from ml_pipes.standard import Recall, Select, Store
from ml_pipes.supervision import BoxAnnotator, Detections
from ml_pipes.supervision.inference import RoboflowInference
pipeline = Pipeline(
[
Store("source_frame"),
RoboflowInference(model_id="yolov8n-640"),
Select(0),
Detections.FromInference(),
Recall("source_frame", prepend=True),
BoxAnnotator(),
]
)
def callback(frame, _: int):
annotated_frame, _ = pipeline(frame)
return annotated_frame
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
import numpy as np
import supervision as sv
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="yolov8n-640", api_key="<ROBOFLOW_API_KEY>")
box_annotator = sv.BoxAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model.infer(frame)[0]
detections = sv.Detections.from_inference(results)
return box_annotator.annotate(frame.copy(), detections=detections)
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
Tracking¶
After inference, update a stateful tracker with each frame's detections. The
ml-pipes ByteTrack wrapper uses the current external trackers package.
import supervision as sv
from ml_pipes.core import Pipeline
from ml_pipes.standard import Recall, Select, Store
from ml_pipes.supervision import BoxAnnotator, Detections
from ml_pipes.supervision.inference import RoboflowInference
from ml_pipes.supervision.trackers import ByteTrack
pipeline = Pipeline(
[
Store("source_frame"),
RoboflowInference(model_id="yolov8n-640"),
Select(0),
Detections.FromInference(),
ByteTrack(),
Recall("source_frame", prepend=True),
BoxAnnotator(),
]
)
def callback(frame, _: int):
annotated_frame, _ = pipeline(frame)
return annotated_frame
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
import numpy as np
import supervision as sv
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="yolov8n-640", api_key="<ROBOFLOW_API_KEY>")
tracker = sv.ByteTrack()
box_annotator = sv.BoxAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model.infer(frame)[0]
detections = sv.Detections.from_inference(results)
detections = tracker.update_with_detections(detections)
return box_annotator.annotate(frame.copy(), detections=detections)
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
Annotate Video with Tracking IDs¶
Add persistent IDs and class names with LabelAnnotator after the tracker
updates the detections.
import supervision as sv
from ml_pipes.core import Pipeline
from ml_pipes.standard import Recall, Select, Store
from ml_pipes.supervision import BoxAnnotator, Detections, LabelAnnotator
from ml_pipes.supervision.inference import RoboflowInference
from ml_pipes.supervision.trackers import ByteTrack
pipeline = Pipeline(
[
Store("source_frame"),
RoboflowInference(model_id="yolov8n-640"),
Select(0),
Detections.FromInference(),
ByteTrack(),
Recall("source_frame", prepend=True),
BoxAnnotator(),
LabelAnnotator(show_tracker_id=True, show_class=True),
]
)
def callback(frame, _: int):
annotated_frame, _ = pipeline(frame)
return annotated_frame
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
import numpy as np
import supervision as sv
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="yolov8n-640", api_key="<ROBOFLOW_API_KEY>")
tracker = sv.ByteTrack()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model.infer(frame)[0]
detections = sv.Detections.from_inference(results)
detections = tracker.update_with_detections(detections)
labels = [
f"#{tracker_id} {class_name}"
for class_name, tracker_id
in zip(detections.data["class_name"], detections.tracker_id)
]
annotated_frame = box_annotator.annotate(
frame.copy(), detections=detections)
return label_annotator.annotate(
annotated_frame, detections=detections, labels=labels)
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
Annotate Video with Traces¶
Draw each track's historical path with TraceAnnotator after the box and
label layers.
import supervision as sv
from ml_pipes.core import Pipeline
from ml_pipes.standard import Recall, Select, Store
from ml_pipes.supervision import BoxAnnotator, Detections, LabelAnnotator, TraceAnnotator
from ml_pipes.supervision.inference import RoboflowInference
from ml_pipes.supervision.trackers import ByteTrack
pipeline = Pipeline(
[
Store("source_frame"),
RoboflowInference(model_id="yolov8n-640"),
Select(0),
Detections.FromInference(),
ByteTrack(),
Recall("source_frame", prepend=True),
BoxAnnotator(),
LabelAnnotator(show_tracker_id=True, show_class=True),
TraceAnnotator(),
]
)
def callback(frame, _: int):
annotated_frame, _ = pipeline(frame)
return annotated_frame
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
import numpy as np
import supervision as sv
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="yolov8n-640", api_key="<ROBOFLOW_API_KEY>")
tracker = sv.ByteTrack()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
trace_annotator = sv.TraceAnnotator()
def callback(frame: np.ndarray, _: int) -> np.ndarray:
results = model.infer(frame)[0]
detections = sv.Detections.from_inference(results)
detections = tracker.update_with_detections(detections)
labels = [
f"#{tracker_id} {class_name}"
for class_name, tracker_id
in zip(detections.data["class_name"], detections.tracker_id)
]
annotated_frame = box_annotator.annotate(
frame.copy(), detections=detections)
annotated_frame = label_annotator.annotate(
annotated_frame, detections=detections, labels=labels)
return trace_annotator.annotate(
annotated_frame, detections=detections)
sv.process_video(
source_path="people-walking.mp4",
target_path="result.mp4",
callback=callback,
)
Frequently Asked Questions¶
How do I track objects across video frames with supervision?¶
Pass Detections to sv.ByteTrack.update_with_detections() on each frame. The tracker assigns persistent IDs. Combine with sv.TraceAnnotator to visualize trajectories. sv.ByteTrack is deprecated in favor of ByteTrackTracker from the trackers package, where the update method is named update().
What should I know about ByteTrack?¶
ByteTrack uses low-confidence detections during association, which can improve continuity during missed or weak detections. Supervision's built-in ByteTrack wrapper is deprecated in favor of the external trackers package.
Can I track instances instead of bounding boxes?¶
Yes. ByteTrack tracks bounding boxes. For instance masks, use sv.MaskAnnotator with the tracker IDs to color-code each tracked object consistently.
Does ByteTrack work with any detection model?¶
Yes. ByteTrack is model-agnostic - it accepts any Detections object with bounding boxes, regardless of the supported converter or model output that produced it.
Authors¶
- Piotr Skalski - Computer Vision Engineer, Roboflow
- Soumik Mandal - ML Engineer, Roboflow