Skip to content

trackforge / trackers / botsort


Module botsort

BoT-SORT: Robust associations multi-object tracking

This module implements the BoT-SORT algorithm.

BoT-SORT: Robust Associations Multi-Pedestrian Tracking Nir Aharon, Roy Orfaig, Ben-Zion Bobrovsky arXiv:2206.14651

Algorithm overview

BoT-SORT builds on ByteTrack's two-stage association and adds two pieces:

  • Camera motion compensation (CMC) warps each track's Kalman prediction by a caller-supplied affine transform before association, so tracking survives panning and zooming cameras. The transform is the shared common::cmc infrastructure.
  • Appearance fusion combines a cosine distance to each track's appearance embedding with the IoU distance in the high-confidence stage. Appearance is used only when it is confident (below appearance_thresh) and the pair is spatially close (IoU distance below proximity_thresh); the fused cost is the smaller of the two. Each track keeps an exponential moving average of its embeddings. With no embeddings the association reduces to ByteTrack with camera motion.

The two-stage cascade is unchanged from ByteTrack: high-confidence detections are matched first (on the fused cost), then low-confidence detections recover fragmented tracks on IoU alone.

This is a clean-room implementation. The tracker applies a camera-motion transform but does not estimate it: the caller supplies the affine (for example from image registration), keeping the core free of heavy computer-vision dependencies.

Builds on

  • utils::kalman - the shared 8-dimensional Kalman filter
  • utils::geometry - iou_batch, tlwh_to_xyah
  • utils::assignment - greedy_match, iou_match
  • trackers::common - KalmanTrack and CameraMotion (CMC)
  • trackers::byte_track - the TrackState lifecycle shared with ByteTrack

Parameters

Parameter Default Description
track_thresh 0.5 Confidence split between high- and low-score detections
track_buffer 30 Frames a lost track is kept alive before removal
match_thresh 0.8 Maximum cost for a first-stage (high-confidence) match
det_thresh 0.6 Minimum score to start a new track
proximity_thresh 0.5 IoU-distance gate above which appearance is ignored
appearance_thresh 0.25 Cosine-distance gate above which appearance is ignored

Rust API

```rust,ignore use trackforge::trackers::botsort::BotSort;

let mut tracker = BotSort::new(0.5, 30, 0.8, 0.6, 0.5, 0.25);

let detections = vec![([100.0, 100.0, 50.0, 100.0], 0.9, 0)]; let embeddings = vec![vec![0.1, 0.2, 0.3]]; // one appearance vector per detection let tracks = tracker.update(detections, &embeddings); for t in tracks { println!("ID: {}, Box: {:?}", t.track_id, t.tlwh); }

## Python API

```python
from trackforge import BOTSORT

tracker = BOTSORT(
    track_thresh=0.5,
    track_buffer=30,
    match_thresh=0.8,
    det_thresh=0.6,
    proximity_thresh=0.5,
    appearance_thresh=0.25,
)

detections = [([100.0, 100.0, 50.0, 100.0], 0.9, 0)]
embeddings = [[0.1, 0.2, 0.3]]  # one appearance vector per detection; omit for motion only
tracks = tracker.update(detections, embeddings)

Moving camera: pass a [a, b, tx, c, d, ty] affine mapping the previous frame
to the current one (estimate it however you like, e.g. with OpenCV).
camera_motion = [1.0, 0.0, 12.0, 0.0, 1.0, -4.0]
tracks = tracker.update(detections, embeddings, camera_motion)

for track_id, tlwh, score, class_id, det_ind in tracks:
    print(f"ID: {track_id}, Box: {tlwh}")

Credit

Clean-room Rust implementation of the algorithm described in the paper above. Original reference implementation: NirAharon/BoT-SORT.

Citation

@article{aharon2022botsort,
  title={BoT-SORT: Robust Associations Multi-Pedestrian Tracking},
  author={Aharon, Nir and Orfaig, Roy and Bobrovsky, Ben-Zion},
  journal={arXiv preprint arXiv:2206.14651},
  year={2022}
}

Quick Reference

Item Kind Description
BotSort struct BoT-SORT tracker.
BotSortParams struct Settings for [BotSort].
BotTrack struct A single tracked object managed by BoT-SORT.

Types

BotSort

struct BotSort {
    // [REDACTED: Private Fields]
}

BoT-SORT tracker.

Extends ByteTrack's two-stage cascade with camera motion compensation and an appearance-fused first stage. Camera motion is supplied by the caller as an affine transform (see CameraMotion); appearance embeddings are optional and, when present, fused into the high-confidence association by taking the smaller of the IoU distance and the gated cosine distance.

Example

use trackforge::trackers::botsort::BotSort;

// track_thresh=0.5, track_buffer=30, match_thresh=0.8, det_thresh=0.6,
// proximity_thresh=0.5, appearance_thresh=0.25
let mut tracker = BotSort::new(0.5, 30, 0.8, 0.6, 0.5, 0.25);

let detections = vec![([100.0, 100.0, 50.0, 100.0], 0.9, 0)];
let tracks = tracker.update(detections, &[]);
for t in &tracks {
    println!("ID: {}, Box: {:?}", t.track_id, t.tlwh);
}

Implementations

fn new(track_thresh: f32, track_buffer: usize, match_thresh: f32, det_thresh: f32, proximity_thresh: f32, appearance_thresh: f32) -> Self

Create a new BoT-SORT tracker.

# Arguments

Argument Description
track_thresh Confidence split between high- and low-score detections (default: 0.5).
track_buffer Frames a lost track is kept alive (default: 30).
match_thresh Maximum cost for a first-stage match (default: 0.8).
det_thresh Minimum score to start a new track (default: 0.6).
proximity_thresh IoU-distance gate above which appearance is ignored (default: 0.5).
appearance_thresh Cosine-distance gate above which appearance is ignored (default: 0.25).

fn from_params(params: BotSortParams) -> Self

Create a BoT-SORT tracker from a BotSortParams.

BoT-SORT activates a track on its first high confidence match, so

params.common.min_hits has no effect; only common.max_age is used.

fn update(&mut self, detections: Vec<([f32; 4], f32, i64)>, embeddings: &[Vec<f32>]) -> Vec<BotTrack>

Update the tracker with the current frame's detections and embeddings.

embeddings is parallel to detections; pass an empty slice to track on

motion only. Returns the activated tracks for this frame.

fn update_with_camera_motion(&mut self, detections: Vec<([f32; 4], f32, i64)>, embeddings: &[Vec<f32>], camera_motion: &CameraMotion) -> Vec<BotTrack>

Update the tracker, first warping track predictions by camera_motion.

camera_motion maps the previous frame's coordinates into the current frame

(see CameraMotion); pass [CameraMotion::identity] for a static camera.

Trait Implementations

impl Default for BotSort

fn default() -> Self

fn to_subset(&self) -> Option<SS>

fn is_in_subset(&self) -> bool

fn to_subset_unchecked(&self) -> SS

fn from_subset(element: &SS) -> SP

BotSortParams

struct BotSortParams {
    pub common: crate::trackers::common::CommonParams,
    pub track_thresh: f32,
    pub match_thresh: f32,
    pub det_thresh: f32,
    pub second_match_thresh: f32,
    pub proximity_thresh: f32,
    pub appearance_thresh: f32,
}

Settings for BotSort.

BoT-SORT is ByteTrack plus camera motion and an optional appearance term, so its params look like ByteTrack's plus two Re-ID gates. Shared lifecycle fields live in CommonParams; BoT-SORT maps its track buffer onto common.max_age and, like ByteTrack, activates on the first match so common.min_hits has no effect. Build it with default.

Fields

Name Type Description
common crate::trackers::common::CommonParams Shared lifecycle settings. common.max_age is the track buffer length.
track_thresh f32 Score above which a detection is treated as high confidence and matched first.
match_thresh f32 First stage match cutoff, a maximum IoU distance of one minus IoU. Lower is stricter.
det_thresh f32 Smallest score an unmatched high confidence detection needs to start a new track.
second_match_thresh f32 Second stage match cutoff for recovering objects from low confidence detections, a maximum IoU distance. Reference value 0.5.
proximity_thresh f32 How much boxes must overlap before appearance is allowed to influence the match, as a maximum IoU distance. If a track and a detection are farther apart than this, only motion is used and appearance is ignored.
appearance_thresh f32 How close two appearance embeddings must be for Re-ID to help the match, as a maximum cosine distance. Above this the appearance term is dropped and the match falls back to motion.

Trait Implementations

impl Clone for BotSortParams

fn clone(&self) -> BotSortParams

impl Copy for BotSortParams
impl Debug for BotSortParams

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

impl Default for BotSortParams

fn default() -> Self

impl PartialEq for BotSortParams

fn eq(&self, other: &BotSortParams) -> bool

fn to_subset(&self) -> Option<SS>

fn is_in_subset(&self) -> bool

fn to_subset_unchecked(&self) -> SS

fn from_subset(element: &SS) -> SP

BotTrack

struct BotTrack {
    pub tlwh: [f32; 4],
    pub score: f32,
    pub class_id: i64,
    pub track_id: u64,
    pub state: crate::trackers::byte_track::TrackState,
    pub is_activated: bool,
    pub frame_id: usize,
    pub start_frame: usize,
    pub tracklet_len: usize,
    pub det_ind: Option<usize>,
    // [REDACTED: Private Fields]
}

A single tracked object managed by BoT-SORT.

Carries the shared KalmanTrack state, the ByteTrack-style lifecycle used by the two-stage cascade, and a smoothed appearance embedding (exponential moving average) used for the appearance-fused association.

Fields

Name Type Description
tlwh [f32; 4] Bounding box in TLWH (top-left x, top-left y, width, height) format.
score f32 Detection confidence of the most recent match.
class_id i64 Class label of the most recent match.
track_id u64 Unique track identifier (0 until the track is activated).
state crate::trackers::byte_track::TrackState Lifecycle state (New, Tracked, Lost, Removed).
is_activated bool Whether the track is confirmed and returned to callers.
frame_id usize Frame id of the most recent update.
start_frame usize Frame id at which the track started.
tracklet_len usize Number of consecutive frames the track has been followed.
det_ind Option<usize> Index of the source detection in the frame's input list (when available).

Trait Implementations

impl Clone for BotTrack

fn clone(&self) -> BotTrack

impl Debug for BotTrack

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

fn to_subset(&self) -> Option<SS>

fn is_in_subset(&self) -> bool

fn to_subset_unchecked(&self) -> SS

fn from_subset(element: &SS) -> SP