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:

  1. __init__ to accept configuration parameters and call the parent constructor with super().__init__(**init_args, _tracker_name='YourTracker', **kwargs).
  2. update decorated with @BaseTracker.setup_decorator and optionally @BaseTracker.per_class_decorator to process detections and return tracked objects.
  3. 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 BaseTracker in boxmot/trackers/basetracker.py to ensure API compatibility with the BoxMOT ecosystem.
  • Implement __init__ to capture parameters and call super().__init__ with init_args and _tracker_name for proper logging.
  • Use decorators @BaseTracker.setup_decorator and @BaseTracker.per_class_decorator on the update method 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.py and 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:

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 →