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>
This commit is contained in:
Raif Olsonandjirka authored and GitHub committed 2026-02-20 00:58:55 +01:00
1 parent 6576a846d9
commit b5ccf6882d
3 files changed
+30 -6

No files matched your search

+3 -3
View File
@@ -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):
@@ -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
+25
View File
@@ -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]))