How to Implement a Custom Tracker in BoxMOT by Extending BaseTracker
Extend BaseTracker from the BoxMOT library to create a custom multi-object tracker that integrates seamlessly with the existing CLI, visualization tools, and evaluation pipeline.
BoxMOT (by mikel-brostrom/boxmot) standardizes multi-object tracking through an abstract BaseTracker class that defines the interface for all tracking algorithms. Whether you are prototyping a novel association method or adapting research code for production, inheriting from this base class ensures compatibility with the library's model zoo, configuration system, and inference engine. This guide walks through the architectural requirements and provides a complete, runnable implementation.
Understanding the BaseTracker Abstraction
The BaseTracker class in boxmot/trackers/basetracker.py defines the contract that every tracker must fulfill. It provides common functionality such as argument logging, input validation, and decorator utilities for setup and per-class processing.
Any concrete tracker—such as StrongSort (boxmot/trackers/strongsort/strongsort.py) or ByteTrack (boxmot/trackers/bytetrack/bytetrack.py)—must implement:
__init__to accept configuration parameters and call the parent constructor withsuper().__init__(**init_args, _tracker_name='YourTracker', **kwargs).updatedecorated with@BaseTracker.setup_decoratorand optionally@BaseTracker.per_class_decoratorto process detections and return tracked objects.reset(optional) to clear internal state when the tracker instance is reused across sequences.
The update method must accept detections as a NumPy array and return an (N, 8) array where each row contains [x1, y1, x2, y2, track_id, confidence, class_id, detection_index].
Step-by-Step Implementation Guide
Create the Tracker Module
Create a new Python module inside the boxmot/trackers/ directory. For a tracker named MyTracker, create boxmot/trackers/mytracker/mytracker.py. Import the base class and required utilities:
import numpy as np
from boxmot.trackers.basetracker import BaseTracker
Implement the Class Structure
Define a class that subclasses BaseTracker using CamelCase naming. Implement __init__ to capture tracker-specific hyperparameters:
def __init__(self, max_age=30, min_hits=3, **kwargs):
# Build init_args dict excluding 'self' and 'kwargs' for the base logger
init_args = {k: v for k, v in locals().items() if k not in ("self", "kwargs")}
super().__init__(**init_args, _tracker_name="MyTracker", **kwargs)
# Initialize tracker-specific attributes
self.max_age = max_age
self.min_hits = min_hits
The super().__init__ call is critical: it registers the tracker name and logs all initialization arguments for reproducibility, as seen in StrongSort's constructor at lines 66-69.
Implement the Update Method
The update method is the core logic. It must be decorated with @BaseTracker.setup_decorator to run one-time initialization (e.g., setting the association function) and optionally @BaseTracker.per_class_decorator to enable per-class tracking. The signature must match:
@BaseTracker.setup_decorator
@BaseTracker.per_class_decorator
def update(self, dets: np.ndarray, img: np.ndarray, embs: np.ndarray = None) -> np.ndarray:
# Validate inputs
self.check_inputs(dets, img, embs)
# Process detections...
ByteTrack's implementation at lines 84-86 demonstrates this exact decorator pattern.
Process Detections and Generate Outputs
Inside update, append a detection index column to maintain correspondence between input detections and output tracks:
dets = np.hstack([dets, np.arange(len(dets)).reshape(-1, 1)])
For each detection, assign a unique track ID and build the output array. The return value must be a NumPy array of shape (N, 8) with columns [x1, y1, x2, y2, id, conf, cls, det_ind]. If no tracks exist, return an empty array with shape (0, 8).
Register in tracker_zoo.py
To make the tracker accessible via the CLI, register it in boxmot/trackers/tracker_zoo.py by adding an entry to the TRACKER_MAPPING dictionary:
TRACKER_MAPPING = {
# ... existing trackers ...
"mytracker": "boxmot.trackers.mytracker.mytracker.MyTracker",
}
This registry pattern allows the factory function in tracker_zoo.py (lines 10-18) to instantiate your tracker dynamically from a string identifier.
Add Configuration YAML
Create a default configuration file at boxmot/configs/trackers/mytracker.yaml following the schema used by existing trackers:
max_age:
type: int
default: 30
min_hits:
type: int
default: 3
det_thresh:
type: float
default: 0.3
This enables instantiation via the BoxMOT configuration system when users specify --tracker mytracker in the CLI.
Complete Working Example: EchoTracker
Below is a minimal yet complete custom tracker that forwards each detection as a track with a monotonically increasing ID. This demonstrates proper inheritance, decorator usage, argument handling, and output formatting.
# boxmot/trackers/echotracker/echotracker.py
import numpy as np
from boxmot.trackers.basetracker import BaseTracker
class EchoTracker(BaseTracker):
"""
EchoTracker – a pedagogical example that echoes detections as tracks.
Each detection receives a unique, incrementing track ID.
"""
def __init__(self, **kwargs):
# Gather all arguments passed via CLI or tracker_zoo
init_args = {k: v for k, v in locals().items() if k not in ("self", "kwargs")}
# Pass to BaseTracker for logging and initialization
super().__init__(**init_args, _tracker_name="EchoTracker", **kwargs)
self._next_id = 0
@BaseTracker.setup_decorator
@BaseTracker.per_class_decorator
def update(
self, dets: np.ndarray, img: np.ndarray, embs: np.ndarray = None
) -> np.ndarray:
"""
Parameters
----------
dets : np.ndarray, shape (N, 6)
Rows are [x1, y1, x2, y2, conf, cls]
img : np.ndarray
Current frame (unused here)
embs : np.ndarray, optional
Appearance embeddings (ignored)
Returns
-------
np.ndarray, shape (M, 8)
Rows are [x1, y1, x2, y2, id, conf, cls, det_ind]
"""
# Validate input shapes (optional but recommended)
self.check_inputs(dets, img, embs)
# Append detection index column
dets = np.hstack([dets, np.arange(len(dets)).reshape(-1, 1)])
tracks = []
for x1, y1, x2, y2, conf, cls, det_ind in dets:
tracks.append([x1, y1, x2, y2, self._next_id, conf, cls, det_ind])
self._next_id += 1
return np.asarray(tracks) if tracks else np.empty((0, 8))
def reset(self):
"""Clear internal state when tracker is reused."""
self._next_id = 0
Registration (in boxmot/trackers/tracker_zoo.py):
TRACKER_MAPPING = {
# ... other trackers ...
"echotracker": "boxmot.trackers.echotracker.echotracker.EchoTracker",
}
Configuration (boxmot/configs/trackers/echotracker.yaml):
det_thresh:
type: float
default: 0.3
max_age:
type: int
default: 30
min_hits:
type: int
default: 3
You can now run the tracker from the command line:
python -m boxmot.engine.cli track \
--source path/to/video.mp4 \
--tracker echotracker \
--detector yolox
Summary
- Inherit from
BaseTrackerinboxmot/trackers/basetracker.pyto ensure API compatibility with the BoxMOT ecosystem. - Implement
__init__to capture parameters and callsuper().__init__withinit_argsand_tracker_namefor proper logging. - Use decorators
@BaseTracker.setup_decoratorand@BaseTracker.per_class_decoratoron theupdatemethod to enable initialization hooks and per-class tracking. - Return the correct shape: an
(N, 8)NumPy array with columns[x1, y1, x2, y2, id, conf, cls, det_ind]. - Register in
tracker_zoo.pyand create a YAML config to expose the tracker via the CLI and configuration system.
Frequently Asked Questions
What is the minimum number of methods I must implement to create a valid BoxMOT tracker?
You must implement __init__ and update. The __init__ method must call super().__init__(**init_args, _tracker_name='YourName', **kwargs) to properly initialize the base class. The update method must accept dets, img, and optional embs parameters and return a NumPy array of shape (N, 8). Optionally, you can override reset() to clear state between video sequences, though the base class provides an empty placeholder.
How do I access the original detection indices in my tracker's output?
Append a column containing np.arange(len(dets)) to your detections array before processing. The final output must include this as the eighth column (index 7) in the returned array. This allows downstream components in BoxMOT to correlate tracks with their original detections for visualization or evaluation.
Can I use precomputed appearance embeddings (embs) in my custom tracker?
Yes. The update method receives an optional embs parameter containing appearance embeddings for the current detections. Validate the input using self.check_inputs(dets, img, embs) to ensure dimensions match, then access the embeddings to implement appearance-based association logic similar to StrongSort.
Where does BoxMOT look for tracker configurations when using the CLI?
The system loads YAML configuration files from boxmot/configs/trackers/<tracker_name>.yaml. The tracker_zoo.py module uses the TRACKER_MAPPING dictionary to resolve the tracker name to a class path, then instantiates the class using parameters defined in the corresponding YAML file. Ensure your configuration follows the established schema with type and default keys for each parameter.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →