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="rfdetr-small"),
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="rfdetr-small", 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 and the direct Supervision examples both use
ByteTrackTracker from the external trackers package.
ByteTrack uses low-confidence detections during association and consumes sv.Detections
rather than model-specific results; see the upstream trackers package
for algorithm and tuning details.
ByteTrack tracks boxes, not mask geometry. For segmentation results,
MaskAnnotator can color each object's mask by tracker ID.
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="rfdetr-small"),
Select(0),
Detections.FromInference(),
ByteTrack(track_activation_threshold=0.25, minimum_consecutive_frames=1),
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
import trackers
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="rfdetr-small", api_key="<ROBOFLOW_API_KEY>")
tracker = trackers.ByteTrackTracker(track_activation_threshold=0.25, minimum_consecutive_frames=1)
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(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.
The tracker returns -1 for detections without a confirmed track. Filter those
out after tracking, before drawing ID labels or traces, as in the
upstream tutorial source.
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="rfdetr-small"),
Select(0),
Detections.FromInference(),
ByteTrack(track_activation_threshold=0.25, minimum_consecutive_frames=1),
Detections.Filter(lambda detections: detections.tracker_id != -1),
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
import trackers
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="rfdetr-small", api_key="<ROBOFLOW_API_KEY>")
tracker = trackers.ByteTrackTracker(track_activation_threshold=0.25, minimum_consecutive_frames=1)
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(detections)
detections = detections[detections.tracker_id != -1]
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="rfdetr-small"),
Select(0),
Detections.FromInference(),
ByteTrack(track_activation_threshold=0.25, minimum_consecutive_frames=1),
Detections.Filter(lambda detections: detections.tracker_id != -1),
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
import trackers
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="rfdetr-small", api_key="<ROBOFLOW_API_KEY>")
tracker = trackers.ByteTrackTracker(track_activation_threshold=0.25, minimum_consecutive_frames=1)
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(detections)
detections = detections[detections.tracker_id != -1]
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,
)
Smooth Tracked Detections¶
For detection-only video, optionally place DetectionsSmoother after tracking
and the confirmed-track filter, before annotation. It requires tracker IDs and
does not support segmentation;
length is the smoothing window in frames. See the upstream smoother reference.
Extend the previous trace-annotation flow with the highlighted smoothing stage.
In ml-pipes, insert it before Recall, while the payload is still detections.
Reuse the same pipeline or stateful objects for the whole video.
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, DetectionsSmoother, LabelAnnotator, TraceAnnotator
from ml_pipes.supervision.inference import RoboflowInference
from ml_pipes.supervision.trackers import ByteTrack
pipeline = Pipeline(
[
Store("source_frame"),
RoboflowInference(model_id="rfdetr-small"),
Select(0),
Detections.FromInference(),
ByteTrack(track_activation_threshold=0.25, minimum_consecutive_frames=1),
Detections.Filter(lambda detections: detections.tracker_id != -1),
DetectionsSmoother(length=5),
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
import trackers
from inference.models.utils import get_roboflow_model
model = get_roboflow_model(model_id="rfdetr-small", api_key="<ROBOFLOW_API_KEY>")
tracker = trackers.ByteTrackTracker(track_activation_threshold=0.25, minimum_consecutive_frames=1)
smoother = sv.DetectionsSmoother(length=5)
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(detections)
detections = detections[detections.tracker_id != -1]
detections = smoother.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,
)
Authors¶
- Piotr Skalski - Computer Vision Engineer, Roboflow
- Soumik Mandal - ML Engineer, Roboflow