How to Visualize Tracking Trajectories with BoxMOT Utilities

To visualize tracking trajectories in BoxMOT, inherit from VisualizationMixin and call plot_results() with show_trajectories=True, which renders each track's history_observations as colored circles via plot_trackers_trajectories() in boxmot/utils/visualization.py.

BoxMOT provides a dedicated visualization stack that eliminates the need for manual drawing code when building multi-object trackers. Located in boxmot/utils/visualization.py, the architecture uses a mixin pattern to inject trajectory plotting capabilities into any tracker that maintains observation history. This guide explains the internal mechanics and provides runnable implementations for both mock and production trackers.

Understanding the Visualization Architecture

The BoxMOT visualization system centers on the VisualizationMixin class, which exposes high-level rendering methods to trackers without requiring them to implement drawing logic directly.

The VisualizationMixin Interface

Any tracker inheriting from VisualizationMixin gains access to three primary methods:

  • plot_box_on_img – Renders individual bounding boxes (solid or dashed) with track ID, confidence score, and class label
  • plot_trackers_trajectories – Draws the complete motion history of a track as progressively thickening colored circles, using a deterministic hash (id_to_color) to assign consistent colors per track ID
  • plot_results – Orchestrates the entire visualization pipeline by iterating over active, lost, and removed tracks, applying appropriate styling, and returning the annotated image

Trackers only need to expose specific attributes (active_tracks, optional lost_stracks/removed_stracks, is_obb, target_id, removed_display_frames) for the mixin to function correctly.

Helper Classes for State Management

The mixin delegates state-specific rendering to two concrete helper classes:

  1. ExplicitStateVisualization – Used when trackers explicitly expose lost_stracks and removed_stracks collections
  2. InferredStateVisualization – Used when a tracker provides only active_tracks, with the visualizer inferring state transitions on-the-fly

Core Methods for Trajectory Visualization

plot_trackers_trajectories

The plot_trackers_trajectories method (lines 15-21 in boxmot/utils/visualization.py) handles the actual trajectory drawing. When enabled, it receives a track's history_observations list—a sequence of previous bounding box coordinates—and renders each point as a circle. Older observations receive thicker circles to create a visual trail effect, while the color remains consistent per track ID through deterministic hashing.

plot_results Orchestration

The plot_results method (lines 228-258) serves as the entry point for frame rendering. It:

  1. Categorizes tracks into active, lost, and removed states
  2. Determines line style (solid for active/removed, dashed for lost)
  3. Conditionally invokes plot_trackers_trajectories when show_trajectories=True
  4. Manages the display lifecycle of removed tracks via removed_display_frames

The high-level tracker engine calls this method automatically after each frame update (specifically at line 147 in boxmot/engine/tracker.py), applying the visualization directly to result.orig_img.

Enabling Trajectory Visualization in Your Tracker

To successfully visualize tracking trajectories, your implementation must satisfy three requirements:

  1. Inherit from VisualizationMixin – Add the mixin to your tracker class definition
  2. Populate history_observations – Append the current bounding box to track.history_observations during every tracker update cycle
  3. Configure plot_results parameters – Pass show_trajectories=True when calling the visualization method

Optional configurations include setting is_obb=True for oriented bounding box support, adjusting removed_display_frames to control how long removed tracks remain visible, and enabling show_lost=True to display dashed bounding boxes for tentative tracks.

Code Examples

Minimal Mock Tracker Example

The following implementation demonstrates the minimal structure required to render trajectories without a full detection pipeline:

import numpy as np
import cv2
from boxmot.utils.visualization import VisualizationMixin

class SimpleTracker(VisualizationMixin):
    def __init__(self):
        self.is_obb = False
        self.target_id = None
        self.removed_display_frames = 10
        self._plot_frame_idx = 0
        self._removed_first_seen = {}
        self._removed_expired = set()
        self.active_tracks = []

class Track:
    def __init__(self, tid, history):
        self.id = tid
        self.history_observations = history
        self.conf = 0.9
        self.cls = 0
        self.time_since_update = 0
        self.is_activated = True
        self.hits = 10
        self.xyxy = history[-1]

# Create dummy data

frame = np.zeros((200, 200, 3), dtype=np.uint8)
track = Track(1, [
    [20, 20, 60, 60],
    [30, 30, 70, 70],
    [40, 40, 80, 80],
])

tracker = SimpleTracker()
tracker.active_tracks = [track]

# Render with trajectories enabled

out = tracker.plot_results(
    img=frame,
    show_trajectories=True,
    show_lost=False,
    thickness=2,
    fontscale=0.5,
)

cv2.imwrite("trajectory_demo.png", out)

Real-World ByteTrack Implementation

For production use with the BoxMOT engine, trajectory visualization integrates seamlessly with existing trackers like ByteTrack:

from boxmot.engine.tracker import Tracker
from boxmot.detection import detect
import cv2

# Initialize pipeline

tracker = Tracker(
    detector="yolox",
    tracker="bytetrack",
    device="cpu",
)

# Process frame

frame = cv2.imread("sample.jpg")
detections = detect(frame)
result = tracker.process(frame, detections)

# Visualize results

vis = tracker.plot_results(
    img=result.orig_img,
    show_trajectories=True,
    show_lost=True,
    thickness=2,
    fontscale=0.5,
)

cv2.imwrite("bytetrack_trajectory.png", vis)

In this implementation, the Tracker class automatically populates history_observations for each track, allowing plot_trackers_trajectories to render the motion history without additional user code.

Summary

  • Inherit from VisualizationMixin located in boxmot/utils/visualization.py to add trajectory drawing capabilities to any tracker
  • Enable trajectories by passing show_trajectories=True to plot_results(), which invokes plot_trackers_trajectories() internally
  • Maintain history by appending bounding boxes to each track's history_observations list during the update cycle
  • Configure display options via show_lost, removed_display_frames, and is_obb parameters to control visual presentation
  • Integration point occurs automatically at line 147 of boxmot/engine/tracker.py when using the high-level Tracker API

Frequently Asked Questions

How does BoxMOT assign consistent colors to track trajectories?

BoxMOT uses a deterministic hash function id_to_color that maps each unique track ID to a specific RGB value. This ensures that trajectory circles and bounding boxes for a given track maintain the same color throughout the video sequence, even if the track temporarily disappears or changes state.

What data structure stores the trajectory points for visualization?

Each track object must maintain a history_observations attribute containing a list of bounding box coordinates (typically in [x1, y1, x2, y2] format). The plot_trackers_trajectories method iterates over this list to render progressively thickening circles, with older observations receiving larger radii to create a motion trail effect.

Can I visualize trajectories for trackers that don't expose lost or removed tracks?

Yes. BoxMOT provides InferredStateVisualization for trackers that only expose active_tracks. The visualizer infers state transitions automatically, allowing you to use plot_results() with show_trajectories=True even without explicit lost_stracks or removed_stracks collections.

Where does the actual rendering call happen in the BoxMOT pipeline?

When using the high-level Tracker API from boxmot/engine/tracker.py, the visualization occurs automatically at line 147, where the engine calls tracker.plot_results() on the original image and assigns the result back to result.orig_img. For custom implementations, you must explicitly call this method after each tracking update.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →