How to Save and Load Tracker States in BoxMOT for Resuming Tracking Sessions

You can save and load tracker states in BoxMOT by serializing the internal tracker object, active tracks, and ReID model weights using pickle, then restoring them to resume interrupted tracking sessions without losing track identities or Kalman filter history.

BoxMOT is a popular multi-object tracking library that integrates detection and Re-identification (ReID) models. While the repository does not ship with a built-in checkpointing mechanism for the tracking pipeline, its modular architecture makes it straightforward to implement persistence for long-running video sequences. By capturing the internal state of trackers like StrongSort or ByteTrack, you can save progress at arbitrary frames and resume later exactly where you left off.

Understanding Tracker State Architecture in BoxMOT

Before implementing save and load functionality, you need to understand which attributes constitute the tracker's state. Concrete trackers in BoxMOT inherit from BaseTracker and maintain several critical data structures during inference.

Key Components to Persist

The essential state components that must be serialized include:

  • self.tracker – The underlying SORT-style tracker instance that holds Kalman filter states and the list of active Track objects.
  • self.active_tracks – A list maintaining currently active track IDs for general tracking scenarios.
  • self.per_class_active_tracks – Per-class bookkeeping structures used when tracking multiple object categories simultaneously.
  • self.frame_count – The current frame index to maintain temporal consistency.
  • ReID model weights – If the tracker uses appearance features, the learned weights in self.model must be captured via state_dict().

In boxmot/trackers/strongsort/strongsort.py, these attributes are initialized in the __init__ method and updated during the update() cycle (lines 41-63).

Where State Lives in the Code

The state resides in specific files across the repository:

Implementing Save and Load Methods in BaseTracker

To enable save and load functionality across all tracker types (StrongSort, OCSort, ByteTrack, etc.), add helper methods to the BaseTracker class in boxmot/trackers/basetracker.py.

Adding State Serialization Methods

Insert the following methods into the BaseTracker class definition:

import pickle
from pathlib import Path
from typing import Any, Dict


class BaseTracker(VisualizationMixin):
    # … existing __init__ and methods …

    def _state_dict(self) -> Dict[str, Any]:
        """
        Return a dictionary that fully describes the current tracker state.
        Sub-classes can extend this method if they keep extra attributes.
        """
        return {
            "tracker": self.tracker,
            "active_tracks": self.active_tracks,
            "per_class_active_tracks": self.per_class_active_tracks,
            "frame_count": self.frame_count,
            "reid_state": getattr(self, "model", None).state_dict()
            if hasattr(self, "model") else None,
        }

    def save_state(self, path: str | Path) -> None:
        """
        Serialise the tracker state to *path* so that a later session can resume.
        """
        path = Path(path)
        path.parent.mkdir(parents=True, exist_ok=True)
        with open(path, "wb") as f:
            pickle.dump(self._state_dict(), f, protocol=pickle.HIGHEST_PROTOCOL)

    def load_state(self, path: str | Path) -> None:
        """
        Load a previously saved state from *path* and inject it back into the tracker.
        The tracker must have been instantiated with the same constructor arguments
        as when it was saved.
        """
        path = Path(path)
        if not path.is_file():
            raise FileNotFoundError(f"Tracker state file not found: {path}")

        with open(path, "rb") as f:
            state = pickle.load(f)

        self.tracker = state["tracker"]
        self.active_tracks = state["active_tracks"]
        self.per_class_active_tracks = state["per_class_active_tracks"]
        self.frame_count = state["frame_count"]

        if hasattr(self, "model") and state["reid_state"] is not None:
            self.model.load_state_dict(state["reid_state"])

These methods use Python's pickle module to serialize the entire tracker state, including Kalman filter instances and NumPy arrays, to a binary file.

Practical Usage for Resuming Tracking Sessions

Once the helper methods are implemented, you can integrate them into your tracking workflow to enable session resumption.

Saving State Periodically

To create checkpoints during a long video sequence, call save_state() at regular intervals within your tracking loop:


# Inside your main tracking loop, after each frame:

if args.save_state_every > 0 and frame_idx % args.save_state_every == 0:
    tracker = predictor.trackers[0]  # assuming batch-size = 1

    tracker.save_state("checkpoints/tracker_state.pkl")

This preserves track IDs, Kalman filter means and covariances, and the ReID model's current weights.

Loading State on Startup

To resume from a saved checkpoint, load the state after tracker instantiation but before processing frames. Modify your entry point to accept a --state argument:

from boxmot.engine.tracker import on_predict_start
from functools import partial
import argparse
import pathlib

parser = argparse.ArgumentParser()
parser.add_argument("--tracking-method", default="strongsort")
parser.add_argument("--reid-model", type=pathlib.Path, required=True)
parser.add_argument("--source", required=True)
parser.add_argument("--state", type=pathlib.Path,
                    help="Path to a previously saved tracker state")
args = parser.parse_args()

def inject_state(predictor, _):
    """Callback to restore tracker state after creation."""
    if args.state and args.state.exists() and predictor.trackers:
        predictor.trackers[0].load_state(args.state)

# Register the injection callback to run after tracker creation

# This hooks into the BoxMOT pipeline in boxmot/engine/tracker.py

original_callback = on_predict_start

def wrapped_on_predict_start(predictor, *args, **kwargs):
    original_callback(predictor, *args, **kwargs)
    inject_state(predictor, None)

# Replace the callback in your pipeline setup

on_predict_start = wrapped_on_predict_start

Integration with the BoxMOT Pipeline

The trackers are instantiated via create_tracker() in boxmot/trackers/tracker_zoo.py and attached to the predictor in boxmot/engine/tracker.py. When resuming, you must instantiate the tracker with identical constructor arguments (tracking method, ReID model, device, etc.) as used during the original session.

For multi-GPU or batch processing scenarios, iterate over predictor.trackers and call save_state() or load_state() for each tracker instance. The pattern remains consistent: serialize each tracker's internal state individually.

Summary

  • BoxMOT does not include built-in checkpointing, but you can add save and load functionality by extending BaseTracker in boxmot/trackers/basetracker.py.
  • Serialize the core state including self.tracker, active track lists, frame count, and ReID model weights using pickle.
  • Restore by instantiating a tracker with identical arguments, then call load_state() to inject the saved attributes.
  • Resume tracking seamlessly by hooking into the on_predict_start callback in boxmot/engine/tracker.py to load state before processing begins.

Frequently Asked Questions

Can I use JSON instead of pickle to save tracker states?

No, you should use pickle or torch.save instead of JSON. The tracker state contains complex Python objects like Kalman filter instances, NumPy arrays, and PyTorch tensors that cannot be serialized to JSON without significant custom encoding. Pickle handles these object types natively while preserving the exact state of the tracking algorithms.

Will resuming from a checkpoint produce identical tracking results?

Yes, if you restore the state correctly using the methods described above, the tracker will resume with identical track IDs, Kalman filter covariances, and appearance features. However, you must ensure the tracker is instantiated with the same configuration parameters (detection threshold, ReID model, etc.) as the original session, and the input video must resume from the exact frame where the checkpoint was created.

How do I handle multiple video streams or batch tracking?

For batch processing with multiple trackers, iterate over the predictor.trackers list and call save_state() or load_state() for each tracker individually. Store each state file with a unique identifier corresponding to the video stream or batch index. When resuming, match each state file to its respective tracker instance in the list.

Is it safe to load tracker states from untrusted sources?

No, loading pickle files from untrusted sources poses security risks because pickle can execute arbitrary code during deserialization. Only load tracker state files that you created yourself or received from trusted sources. For production environments requiring untrusted checkpoints, implement additional validation or use a safer serialization format restricted to specific numeric tensor values.

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 →