Skip to content

Commit bfb8903

Browse files
Bordaclaude
andcommitted
experiment(optimize/i5-rework): fix age-discount guard regression
- Apply age discount only to cost matrix (not threshold check): raw IoU used for min-threshold gate, discount only biases solver assignment toward active tracks - Tighten Optuna search range [0.0, 0.2] -> [0.0, 0.1] - Fix pre-existing bug: optimize_tracking.py final re-eval now applies _apply_kalman_patch --- Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 47da1d3 commit bfb8903

3 files changed

Lines changed: 44 additions & 21 deletions

File tree

autotrack/optimize_tracking.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ def main(
857857
)
858858

859859
best_params = study.best_params
860+
_apply_kalman_patch(best_params, tracker)
860861
best_metrics = _run_eval(
861862
params=best_params,
862863
sequences=sequences,

autotrack/search_space.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"iou_age_weight": {
3939
"type": "float",
4040
"low": 0.0,
41-
"high": 0.5
41+
"high": 0.1
4242
},
4343
"high_conf_det_threshold": {
4444
"type": "float",

trackers/core/bytetrack/tracker.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,17 @@ class ByteTrackTracker(BaseTracker):
5555
low-confidence association. Lower values are more permissive when
5656
reviving tracks from low-confidence detections.
5757
iou_age_weight: `float` specifying how much to discount IoU
58-
similarity for lost tracks in stage-1 association. Each track's
59-
IoU row is scaled by ``1 / (1 + iou_age_weight * lost_frames)``
60-
where ``lost_frames = max(0, time_since_update - 1)``. This
61-
makes the assignment prefer active tracks over stale predictions,
62-
reducing identity switches. ``0`` disables the discount.
63-
Only applied in stage 1; stage 2 is unaffected so that lost
64-
tracks can still recover via low-confidence detections.
58+
similarity for lost tracks in stage-1 assignment ranking.
59+
Each lost track's IoU row is scaled by
60+
``1 / (1 + iou_age_weight * lost_frames)`` where
61+
``lost_frames = max(0, time_since_update - 1)``. The discount
62+
biases the solver to prefer active tracks over stale
63+
predictions (reducing identity switches) but only affects
64+
ranking — the minimum-IoU threshold is checked against the
65+
*raw* IoU so that valid matches are never rejected by the
66+
discount alone. ``0`` disables the discount. Only applied in
67+
stage 1; stage 2 is unaffected so that lost tracks can still
68+
recover via low-confidence detections.
6569
high_conf_det_threshold: `float` specifying threshold for separating
6670
high and low confidence detections in the two-stage association.
6771
"""
@@ -141,16 +145,25 @@ def update(
141145
# algorithm prefers active tracks over stale predictions. This
142146
# reduces identity switches from a drifted prediction "stealing"
143147
# a detection that should go to the correct active track.
148+
#
149+
# The discount is applied only to the cost matrix used by the
150+
# solver for ranking; the threshold check uses the *raw* IoU so
151+
# that valid matches are never rejected by the discount alone.
152+
# Only tracks that have been lost for at least 1 frame are
153+
# discounted — freshly-seen tracks (time_since_update == 1 after
154+
# predict, i.e. lost_frames == 0) are never penalised.
144155
if self.iou_age_weight > 0 and iou_matrix.size > 0:
145156
lost_frames = np.array(
146157
[max(0, t.time_since_update - 1) for t in self.tracks],
147158
dtype=np.float32,
148159
)
149160
discount = 1.0 / (1.0 + self.iou_age_weight * lost_frames)
150-
iou_matrix = iou_matrix * discount[:, np.newaxis]
161+
solver_iou = iou_matrix * discount[:, np.newaxis]
162+
else:
163+
solver_iou = iou_matrix
151164

152165
matched, unmatched_tracks, unmatched_high = self._get_associated_indices(
153-
iou_matrix, self.minimum_iou_threshold
166+
solver_iou, self.minimum_iou_threshold, raw_similarity=iou_matrix
154167
)
155168

156169
for row, col in matched:
@@ -219,32 +232,41 @@ def _get_associated_indices(
219232
self,
220233
similarity_matrix: np.ndarray,
221234
min_similarity_thresh: float,
235+
raw_similarity: np.ndarray | None = None,
222236
) -> tuple[list[tuple[int, int]], set[int], set[int]]:
223-
"""
224-
Associate detections to tracks based on Similarity (IoU) using the
225-
Jonker-Volgenant algorithm approach with no initialization instead of the
226-
Hungarian algorithm as mentioned in the SORT paper, but it solves the
227-
assignment problem in an optimal way.
237+
"""Associate detections to tracks based on similarity (IoU).
238+
239+
Uses the Jonker-Volgenant algorithm (via ``linear_sum_assignment``)
240+
to solve the assignment optimally.
228241
229242
Args:
230-
similarity_matrix: Similarity matrix between tracks (rows) and detections (columns).
231-
min_similarity_thresh: Minimum similarity threshold for a valid match.
243+
similarity_matrix: Similarity matrix between tracks (rows) and
244+
detections (columns). Used by the solver for ranking.
245+
min_similarity_thresh: Minimum similarity threshold for a valid
246+
match.
247+
raw_similarity: Optional unmodified similarity matrix. When
248+
provided, the threshold check uses this matrix instead of
249+
``similarity_matrix`` so that solver-side discounts (e.g.
250+
the age discount) cannot reject otherwise valid matches.
232251
233252
Returns:
234-
Matched indices (list of (tracker_idx, detection_idx)), indices of
235-
unmatched tracks, indices of unmatched detections.
236-
""" # noqa: E501
253+
Matched indices (list of (tracker_idx, detection_idx)), indices
254+
of unmatched tracks, indices of unmatched detections.
255+
"""
237256
matched_indices = []
238257
n_tracks, n_detections = similarity_matrix.shape
239258
unmatched_tracks = set(range(n_tracks))
240259
unmatched_detections = set(range(n_detections))
241260

261+
# Use raw similarity for threshold gating when available
262+
thresh_matrix = raw_similarity if raw_similarity is not None else similarity_matrix
263+
242264
if n_tracks > 0 and n_detections > 0:
243265
row_indices, col_indices = linear_sum_assignment(
244266
similarity_matrix, maximize=True
245267
)
246268
for row, col in zip(row_indices, col_indices):
247-
if similarity_matrix[row, col] >= min_similarity_thresh:
269+
if thresh_matrix[row, col] >= min_similarity_thresh:
248270
matched_indices.append((row, col))
249271
unmatched_tracks.remove(row)
250272
unmatched_detections.remove(col)

0 commit comments

Comments
 (0)