← Work

motcpp

10 SOTA multi-object trackers in modern C++17 — 10–100× faster than Python, unified API, ONNX ReID backend

Creator & Maintainer · 2024 · active · GitHub ↗

Problem

Multi-object tracking in Python works for research but fails in production: a ByteTrack running at 45 FPS in Python is the bottleneck, not the detector. Robotics and autonomous systems need tracking that runs well under 5ms per frame, leaves headroom for the full perception pipeline, and doesn’t pull in a Python runtime on edge hardware.

The alternative — writing a C++ tracker from scratch — means re-implementing Kalman state machines, IoU matching, track lifecycle management, and ReID embedding pipelines. Every team that needs tracking ends up solving the same problem.

Approach

motcpp implements 10 state-of-the-art trackers — from SORT to BoostTrack — behind a single unified API. Switching between trackers is a one-line change; the update(detections, frame) call signature is identical across all of them.

Inspired by BoxMOT’s algorithmic breadth, the library brings the same coverage to C++ with zero-copy Eigen matrices for hot paths, CMake find_package / FetchContent integration, and an ONNX Runtime ReID backend that accepts any appearance model as an .onnx file. Camera motion compensation (ORB, ECC, SoF) is built in for moving-camera scenarios.

Architecture

Detection matrix [x1, y1, x2, y2, conf, class_id] → tracker update() → track matrix [x1, y1, x2, y2, track_id, conf, class_id, det_idx].

Motion-only trackers (SORT, ByteTrack, OC-SORT, UCMCTrack, OracleTrack) use Kalman predict + IoU assignment without appearance. ReID trackers (StrongSORT, BoT-SORT, DeepOC-SORT, HybridSORT, BoostTrack) additionally run the ONNX appearance model on each crop and update an EMA embedding store per track.

Track state machine: Tentative → Confirmed → Lost → Deleted. All trackers share the same base lifecycle; each overrides the association step.

MOT benchmark tooling is built in: motcpp_eval CLI evaluates on MOT17/MOT20 sequences with pre-generated YOLOX detections and FastReID embeddings available in the GitHub releases.

Results

MOT17 ablation split — Intel i9-13900K, single thread, YOLOX detections + FastReID embeddings:

TrackerHOTAMOTAIDF1FPS
SORT62.475.269.21250
ByteTrack66.576.477.61100
OC-SORT64.673.974.4850
UCMCTrack64.075.673.9980
StrongSORT66.275.877.195
BoT-SORT66.876.278.385
BoostTrack67.577.179.275

C++ vs Python speedup — same tracker, same hardware:

TrackerC++PythonSpeedup
ByteTrack1100 FPS45 FPS24×
OC-SORT850 FPS32 FPS27×
StrongSORT95 FPS8 FPS12×
  • 90% GoogleTest coverage across all components

  • CI-verified on Linux, macOS, and Windows

Lessons

The biggest design decision was the unified update() API. Early versions had separate call signatures for motion-only vs. ReID trackers. Collapsing them to one interface — even when the frame argument is unused by motion-only trackers — made benchmark harnesses and downstream integrations far simpler to write and maintain.

Kalman state space choices (XYSR vs. XYAH vs. ground plane) are not interchangeable between tracker families. Mixing them silently produces wrong predictions; the test suite that validates each tracker against a reference Python implementation was the only reliable guard against this class of bug.

Stack

  • C++17
  • ONNX Runtime 1.16+
  • OpenCV 4.x
  • Eigen3
  • yaml-cpp
  • GoogleTest
  • CMake 3.20+

Technologies

  • C++17
  • ONNX Runtime
  • OpenCV
  • Eigen3
  • ByteTrack
  • BoostTrack
  • GoogleTest