scirs2_vision/tracking/types.rs
1//! Core types for multi-object tracking (SORT / ByteTrack).
2
3/// An axis-aligned bounding box with detection score and optional class ID.
4#[non_exhaustive]
5#[derive(Debug, Clone)]
6pub struct BoundingBox {
7 /// Left edge (x-coordinate of top-left corner).
8 pub x1: f32,
9 /// Top edge (y-coordinate of top-left corner).
10 pub y1: f32,
11 /// Right edge (x-coordinate of bottom-right corner).
12 pub x2: f32,
13 /// Bottom edge (y-coordinate of bottom-right corner).
14 pub y2: f32,
15 /// Detection confidence score in `[0, 1]`.
16 pub score: f32,
17 /// Optional class identifier.
18 pub class_id: Option<usize>,
19}
20
21impl BoundingBox {
22 /// Create a new bounding box.
23 pub fn new(x1: f32, y1: f32, x2: f32, y2: f32, score: f32, class_id: Option<usize>) -> Self {
24 Self {
25 x1,
26 y1,
27 x2,
28 y2,
29 score,
30 class_id,
31 }
32 }
33
34 /// Area of the bounding box in pixels².
35 pub fn area(&self) -> f32 {
36 let w = (self.x2 - self.x1).max(0.0);
37 let h = (self.y2 - self.y1).max(0.0);
38 w * h
39 }
40
41 /// Intersection-over-Union with another bounding box.
42 ///
43 /// Returns 0 for non-overlapping boxes and 1 for identical boxes.
44 pub fn iou(&self, other: &BoundingBox) -> f32 {
45 let inter_x1 = self.x1.max(other.x1);
46 let inter_y1 = self.y1.max(other.y1);
47 let inter_x2 = self.x2.min(other.x2);
48 let inter_y2 = self.y2.min(other.y2);
49
50 if inter_x2 <= inter_x1 || inter_y2 <= inter_y1 {
51 return 0.0;
52 }
53
54 let inter_area = (inter_x2 - inter_x1) * (inter_y2 - inter_y1);
55 let union_area = self.area() + other.area() - inter_area;
56
57 if union_area <= 0.0 {
58 0.0
59 } else {
60 inter_area / union_area
61 }
62 }
63
64 /// Convert to centre-x, centre-y, width, height representation.
65 pub fn to_xywh(&self) -> (f32, f32, f32, f32) {
66 let cx = (self.x1 + self.x2) * 0.5;
67 let cy = (self.y1 + self.y2) * 0.5;
68 let w = self.x2 - self.x1;
69 let h = self.y2 - self.y1;
70 (cx, cy, w, h)
71 }
72}
73
74// ---------------------------------------------------------------------------
75// TrackState
76// ---------------------------------------------------------------------------
77
78/// Life-cycle state of a tracked object.
79#[non_exhaustive]
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum TrackState {
82 /// Newly created track; not yet confirmed by enough consecutive matches.
83 Tentative,
84 /// Track has received enough consecutive matches to be considered reliable.
85 Confirmed,
86 /// Track was not matched in the most recent frame but has not yet aged out.
87 Lost,
88 /// Track has exceeded `max_age` without a match and will be removed.
89 Deleted,
90}
91
92// ---------------------------------------------------------------------------
93// Track
94// ---------------------------------------------------------------------------
95
96/// A single tracked object with its current state and bounding box.
97#[non_exhaustive]
98#[derive(Debug, Clone)]
99pub struct Track {
100 /// Globally unique identifier assigned at creation.
101 pub track_id: u64,
102 /// Current life-cycle state.
103 pub state: TrackState,
104 /// Most recent (predicted or updated) bounding box.
105 pub bbox: BoundingBox,
106 /// Total number of frames since this track was created.
107 pub age: usize,
108 /// Number of consecutive frames in which this track was matched.
109 pub hits: usize,
110 /// Number of frames since the last successful match.
111 pub time_since_update: usize,
112}
113
114// ---------------------------------------------------------------------------
115// SortConfig
116// ---------------------------------------------------------------------------
117
118/// Configuration for the SORT tracker.
119#[non_exhaustive]
120#[derive(Debug, Clone)]
121pub struct SortConfig {
122 /// Maximum number of frames a track can go un-matched before deletion.
123 pub max_age: usize,
124 /// Minimum number of consecutive matches required to confirm a track.
125 pub min_hits: usize,
126 /// IoU threshold used during Hungarian assignment (lower = more permissive).
127 pub iou_threshold: f32,
128}
129
130impl Default for SortConfig {
131 fn default() -> Self {
132 Self {
133 max_age: 3,
134 min_hits: 3,
135 iou_threshold: 0.3,
136 }
137 }
138}
139
140impl SortConfig {
141 /// Create a fully-specified `SortConfig`.
142 pub fn new(max_age: usize, min_hits: usize, iou_threshold: f32) -> Self {
143 Self {
144 max_age,
145 min_hits,
146 iou_threshold,
147 }
148 }
149}
150
151// ---------------------------------------------------------------------------
152// ByteTrackConfig
153// ---------------------------------------------------------------------------
154
155/// Configuration for the ByteTrack tracker (Zhang et al. 2022).
156#[non_exhaustive]
157#[derive(Debug, Clone)]
158pub struct ByteTrackConfig {
159 /// Score threshold separating *high-confidence* from *low-confidence* detections.
160 pub high_thresh: f32,
161 /// Minimum score; detections below this are discarded entirely.
162 pub low_thresh: f32,
163 /// IoU threshold for both association stages.
164 pub match_thresh: f32,
165 /// Maximum frames a lost track survives without a match.
166 pub max_age: usize,
167 /// Minimum consecutive matches required to confirm a new track.
168 pub min_hits: usize,
169}
170
171impl Default for ByteTrackConfig {
172 fn default() -> Self {
173 Self {
174 high_thresh: 0.6,
175 low_thresh: 0.1,
176 match_thresh: 0.8,
177 max_age: 30,
178 min_hits: 3,
179 }
180 }
181}
182
183impl ByteTrackConfig {
184 /// Create a fully-specified `ByteTrackConfig`.
185 pub fn new(
186 high_thresh: f32,
187 low_thresh: f32,
188 match_thresh: f32,
189 max_age: usize,
190 min_hits: usize,
191 ) -> Self {
192 Self {
193 high_thresh,
194 low_thresh,
195 match_thresh,
196 max_age,
197 min_hits,
198 }
199 }
200}
201
202// ---------------------------------------------------------------------------
203// TrackerResult
204// ---------------------------------------------------------------------------
205
206/// Output produced by a tracker after processing a single frame.
207pub struct TrackerResult {
208 /// All currently active (non-deleted) tracks, filtered by confirmation policy.
209 pub tracks: Vec<Track>,
210 /// Index of the frame that produced this result.
211 pub frame_id: usize,
212}