use crate::tracking::{
hungarian::hungarian_assign,
kalman_box::KalmanBoxTracker,
types::{BoundingBox, SortConfig, Track, TrackState, TrackerResult},
};
struct SortTrack {
kalman: KalmanBoxTracker,
track_id: u64,
state: TrackState,
age: usize,
hits: usize,
time_since_update: usize,
class_id: Option<usize>,
}
impl SortTrack {
fn new(id: u64, bbox: &BoundingBox) -> Self {
Self {
kalman: KalmanBoxTracker::new(bbox),
track_id: id,
state: TrackState::Tentative,
age: 1,
hits: 1,
time_since_update: 0,
class_id: bbox.class_id,
}
}
fn to_public(&self) -> Track {
let mut bbox = self.kalman.get_state();
bbox.class_id = self.class_id;
Track {
track_id: self.track_id,
state: self.state.clone(),
bbox,
age: self.age,
hits: self.hits,
time_since_update: self.time_since_update,
}
}
}
pub struct SortTracker {
config: SortConfig,
tracks: Vec<SortTrack>,
next_id: u64,
}
impl SortTracker {
pub fn new(config: SortConfig) -> Self {
Self {
config,
tracks: Vec::new(),
next_id: 1,
}
}
pub fn update(&mut self, detections: &[BoundingBox], frame_id: usize) -> TrackerResult {
let predicted: Vec<BoundingBox> =
self.tracks.iter_mut().map(|t| t.kalman.predict()).collect();
let n_dets = detections.len();
let n_trks = self.tracks.len();
let cost: Vec<Vec<f32>> = (0..n_dets)
.map(|di| {
(0..n_trks)
.map(|ti| {
let iou = detections[di].iou(&predicted[ti]);
1.0 - iou })
.collect()
})
.collect();
let assignment = if n_dets > 0 && n_trks > 0 {
hungarian_assign(&cost)
} else {
vec![None; n_dets]
};
let mut matched_trk: Vec<bool> = vec![false; n_trks];
let mut unmatched_dets: Vec<usize> = Vec::new();
for (di, opt_ti) in assignment.iter().enumerate() {
match opt_ti {
Some(ti) if *ti < n_trks => {
let ti = *ti;
let iou_val = 1.0 - cost[di][ti];
if iou_val >= self.config.iou_threshold {
self.tracks[ti].kalman.update(&detections[di]);
self.tracks[ti].hits += 1;
self.tracks[ti].time_since_update = 0;
self.tracks[ti].class_id = detections[di].class_id;
matched_trk[ti] = true;
} else {
unmatched_dets.push(di);
}
}
_ => {
unmatched_dets.push(di);
}
}
}
for (ti, matched) in matched_trk.iter().enumerate() {
if !matched {
self.tracks[ti].time_since_update += 1;
}
}
for &di in &unmatched_dets {
let id = self.next_id;
self.next_id += 1;
self.tracks.push(SortTrack::new(id, &detections[di]));
}
for t in self.tracks.iter_mut() {
t.age += 1;
if t.time_since_update == 0 {
if t.hits >= self.config.min_hits || frame_id < self.config.min_hits {
t.state = TrackState::Confirmed;
} else {
t.state = TrackState::Tentative;
}
} else if t.time_since_update > self.config.max_age {
t.state = TrackState::Deleted;
} else {
t.state = TrackState::Lost;
}
}
self.tracks.retain(|t| t.state != TrackState::Deleted);
let tracks = self
.tracks
.iter()
.filter(|t| {
t.state == TrackState::Confirmed
|| (t.state == TrackState::Tentative && t.hits >= self.config.min_hits)
})
.map(|t| t.to_public())
.collect();
TrackerResult { tracks, frame_id }
}
pub fn num_tracks(&self) -> usize {
self.tracks.len()
}
}