From b5ccf6882de9b71d8dfbaba6d050213c8f3c92d4 Mon Sep 17 00:00:00 2001 From: Raif Olson <99894460+rolson24@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:58:55 -0600 Subject: [PATCH] Fix external track ids (#1364) * fix exteranl track ids getting assigned too early * Fixed docstring to correct minimum_matching_threshold explaination * Change overlap value for duplicate tracks to be 0.05 * Add test to ensure ByteTracker handles short-lived tracks without consuming external IDs --------- Co-authored-by: jirka <6035284+Borda@users.noreply.github.com> --- src/supervision/tracker/byte_tracker/core.py | 6 ++--- .../byte_tracker/single_object_track.py | 5 ++-- tests/tracker/test_byte_tracker.py | 25 +++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/supervision/tracker/byte_tracker/core.py b/src/supervision/tracker/byte_tracker/core.py index 7c024887..21871b9f 100644 --- a/src/supervision/tracker/byte_tracker/core.py +++ b/src/supervision/tracker/byte_tracker/core.py @@ -31,8 +31,8 @@ class ByteTrack: reducing the likelihood of track fragmentation or disappearance caused by brief detection gaps. minimum_matching_threshold: Threshold for matching tracks with detections. - Increasing minimum_matching_threshold improves accuracy but risks fragmentation. - Decreasing it improves completeness but risks false positives and drift. + Decreasing minimum_matching_threshold improves accuracy but risks fragmentation. + Increasing it improves completeness but risks false positives and drift. frame_rate: The frame rate of the video. minimum_consecutive_frames: Number of consecutive frames that an object must be tracked before it is considered a 'valid' track. @@ -371,7 +371,7 @@ def remove_duplicate_tracks( tracks_a: list[STrack], tracks_b: list[STrack] ) -> tuple[list[STrack], list[STrack]]: pairwise_distance = matching.iou_distance(tracks_a, tracks_b) - matching_pairs = np.where(pairwise_distance < 0.15) + matching_pairs = np.where(pairwise_distance < 0.05) duplicates_a, duplicates_b = set(), set() for track_index_a, track_index_b in zip(*matching_pairs): diff --git a/src/supervision/tracker/byte_tracker/single_object_track.py b/src/supervision/tracker/byte_tracker/single_object_track.py index ce460390..26f6bb68 100644 --- a/src/supervision/tracker/byte_tracker/single_object_track.py +++ b/src/supervision/tracker/byte_tracker/single_object_track.py @@ -91,9 +91,8 @@ class STrack: self.state = TrackState.Tracked if frame_id == 1: self.is_activated = True - - if self.minimum_consecutive_frames == 1: - self.external_track_id = self.external_id_counter.new_id() + if self.minimum_consecutive_frames == 1: + self.external_track_id = self.external_id_counter.new_id() self.frame_id = frame_id self.start_frame = frame_id diff --git a/tests/tracker/test_byte_tracker.py b/tests/tracker/test_byte_tracker.py index c653fd06..83e25b06 100644 --- a/tests/tracker/test_byte_tracker.py +++ b/tests/tracker/test_byte_tracker.py @@ -36,3 +36,28 @@ def test_byte_tracker( byte_tracker = sv.ByteTrack() tracked_detections = [byte_tracker.update_with_detections(d) for d in detections] assert tracked_detections[-1] == expected_results + + +def test_byte_tracker_does_not_skip_external_ids_for_short_lived_tracks() -> None: + def detections_from_boxes(boxes: list[list[float]]) -> sv.Detections: + return sv.Detections( + xyxy=np.array(boxes, dtype=np.float32), + class_id=np.zeros(len(boxes), dtype=int), + confidence=np.ones(len(boxes), dtype=np.float32), + ) + + # A transient false-positive appears and disappears before becoming confirmed. + # It should not consume an external tracker id. + frames = [ + detections_from_boxes([[0, 0, 10, 10]]), + detections_from_boxes([[0, 0, 10, 10], [100, 100, 110, 110]]), + detections_from_boxes([[0, 0, 10, 10]]), + detections_from_boxes([[0, 0, 10, 10], [200, 200, 210, 210]]), + detections_from_boxes([[0, 0, 10, 10], [200, 200, 210, 210]]), + ] + + byte_tracker = sv.ByteTrack(minimum_consecutive_frames=1) + + tracked = [byte_tracker.update_with_detections(frame) for frame in frames] + assert tracked[-1].tracker_id is not None + assert np.array_equal(np.sort(tracked[-1].tracker_id), np.array([1, 2]))