From ad0e6c3b51498beb25046b74704c86ac87128503 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Fri, 29 Mar 2024 16:59:29 -0400 Subject: [PATCH 01/11] Add minimum consecutive frames to ByteTrack --- supervision/tracker/byte_tracker/core.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index c77878cf9..6475bf508 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -13,7 +13,7 @@ class STrack(BaseTrack): shared_kalman = KalmanFilter() - def __init__(self, tlwh, score, class_ids): + def __init__(self, tlwh, score, class_ids, minimum_consecutive_frames): # wait activate self._tlwh = np.asarray(tlwh, dtype=np.float32) self.kalman_filter = None @@ -24,6 +24,8 @@ def __init__(self, tlwh, score, class_ids): self.class_ids = class_ids self.tracklet_len = 0 + self.minimum_consecutive_frames = minimum_consecutive_frames + def predict(self): mean_state = self.mean.copy() if self.state != TrackState.Tracked: @@ -60,7 +62,7 @@ def activate(self, kalman_filter, frame_id): self.tracklet_len = 0 self.state = TrackState.Tracked - if frame_id == 1: + if frame_id == 1 and self.minimum_consecutive_frames == 1: self.is_activated = True self.frame_id = frame_id self.start_frame = frame_id @@ -71,7 +73,7 @@ def re_activate(self, new_track, frame_id, new_id=False): ) self.tracklet_len = 0 self.state = TrackState.Tracked - self.is_activated = True + self.frame_id = frame_id if new_id: self.track_id = self.next_id() @@ -93,7 +95,8 @@ def update(self, new_track, frame_id): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - self.is_activated = True + if self.tracklet_len >= self.minimum_consecutive_frames: + self.is_activated = True self.score = new_track.score @@ -186,6 +189,10 @@ class ByteTrack: Increasing minimum_matching_threshold improves accuracy but risks fragmentation. Decreasing it improves completeness but risks false positives and drift. frame_rate (int, optional): The frame rate of the video. + minimum_consecutive_frames (int, optional): Number of consecutive frames that an object must + be tracked before it is considered a 'valid' track. + Increasing minimum_consecutive_frames prevents the creation of accidental tracks from + false detection or double detection, but risks missing shorter tracks. """ # noqa: E501 // docs @deprecated_parameter( @@ -218,6 +225,7 @@ def __init__( lost_track_buffer: int = 30, minimum_matching_threshold: float = 0.8, frame_rate: int = 30, + minimum_consecutive_frames: int = 1, ): self.track_activation_threshold = track_activation_threshold self.minimum_matching_threshold = minimum_matching_threshold @@ -225,6 +233,7 @@ def __init__( self.frame_id = 0 self.det_thresh = self.track_activation_threshold + 0.1 self.max_time_lost = int(frame_rate / 30.0 * lost_track_buffer) + self.minimum_consecutive_frames = minimum_consecutive_frames self.kalman_filter = KalmanFilter() self.tracked_tracks: List[STrack] = [] @@ -290,6 +299,7 @@ def callback(frame: np.ndarray, index: int) -> np.ndarray: return detections[detections.tracker_id != -1] else: + detections = Detections.empty() detections.tracker_id = np.array([], dtype=int) return detections @@ -345,7 +355,7 @@ def update_with_tensors(self, tensors: np.ndarray) -> List[STrack]: if len(dets) > 0: """Detections""" detections = [ - STrack(STrack.tlbr_to_tlwh(tlbr), s, c) + STrack(STrack.tlbr_to_tlwh(tlbr), s, c, self.minimum_consecutive_frames) for (tlbr, s, c) in zip(dets, scores_keep, class_ids_keep) ] else: @@ -387,7 +397,7 @@ def update_with_tensors(self, tensors: np.ndarray) -> List[STrack]: if len(dets_second) > 0: """Detections""" detections_second = [ - STrack(STrack.tlbr_to_tlwh(tlbr), s, c) + STrack(STrack.tlbr_to_tlwh(tlbr), s, c, self.minimum_consecutive_frames) for (tlbr, s, c) in zip(dets_second, scores_second, class_ids_second) ] else: From eaff97f9027fc9b1b67c92156d29d36407b80128 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Wed, 10 Apr 2024 17:33:16 -0400 Subject: [PATCH 02/11] add external and internal track_ids to keep valid track ids sequential --- supervision/tracker/byte_tracker/core.py | 46 +++++++++++++++--------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 6475bf508..bf82044a6 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -12,6 +12,7 @@ class STrack(BaseTrack): shared_kalman = KalmanFilter() + _external_count = 0 def __init__(self, tlwh, score, class_ids, minimum_consecutive_frames): # wait activate @@ -24,6 +25,8 @@ def __init__(self, tlwh, score, class_ids, minimum_consecutive_frames): self.class_ids = class_ids self.tracklet_len = 0 + self.external_track_id = -1 + self.minimum_consecutive_frames = minimum_consecutive_frames def predict(self): @@ -55,7 +58,7 @@ def multi_predict(stracks): def activate(self, kalman_filter, frame_id): """Start a new tracklet""" self.kalman_filter = kalman_filter - self.track_id = self.next_id() + self.internal_track_id = self.next_id() self.mean, self.covariance = self.kalman_filter.initiate( self.tlwh_to_xyah(self._tlwh) ) @@ -76,7 +79,7 @@ def re_activate(self, new_track, frame_id, new_id=False): self.frame_id = frame_id if new_id: - self.track_id = self.next_id() + self.internal_track_id = self.next_id() self.score = new_track.score def update(self, new_track, frame_id): @@ -95,8 +98,9 @@ def update(self, new_track, frame_id): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if self.tracklet_len >= self.minimum_consecutive_frames: + if self.tracklet_len == self.minimum_consecutive_frames: self.is_activated = True + self.external_track_id = self.next_external_id() self.score = new_track.score @@ -133,6 +137,15 @@ def tlwh_to_xyah(tlwh): def to_xyah(self): return self.tlwh_to_xyah(self.tlwh) + + @staticmethod + def next_external_id(): + STrack._external_count += 1 + return STrack._external_count + + @staticmethod + def reset_external_counter(): + STrack._external_count = 0 @staticmethod def tlbr_to_tlwh(tlbr): @@ -147,7 +160,7 @@ def tlwh_to_tlbr(tlwh): return ret def __repr__(self): - return "OT_{}_({}-{})".format(self.track_id, self.start_frame, self.end_frame) + return "OT_{}_({}-{})".format(self.internal_track_id, self.start_frame, self.end_frame) def detections2boxes(detections: Detections) -> np.ndarray: @@ -294,7 +307,7 @@ def callback(frame: np.ndarray, index: int) -> np.ndarray: matches, _, _ = matching.linear_assignment(iou_costs, 0.5) detections.tracker_id = np.full(len(detections), -1, dtype=int) for i_detection, i_track in matches: - detections.tracker_id[i_detection] = int(tracks[i_track].track_id) + detections.tracker_id[i_detection] = int(tracks[i_track].external_track_id) return detections[detections.tracker_id != -1] @@ -318,6 +331,7 @@ def reset(self): self.lost_tracks: List[STrack] = [] self.removed_tracks: List[STrack] = [] BaseTrack.reset_counter() + STrack.reset_external_counter() def update_with_tensors(self, tensors: np.ndarray) -> List[STrack]: """ @@ -478,22 +492,22 @@ def joint_tracks( ) -> List[STrack]: """ Joins two lists of tracks, ensuring that the resulting list does not - contain tracks with duplicate track_id values. + contain tracks with duplicate internal_track_id values. Parameters: - track_list_a: First list of tracks (with track_id attribute). - track_list_b: Second list of tracks (with track_id attribute). + track_list_a: First list of tracks (with internal_track_id attribute). + track_list_b: Second list of tracks (with internal_track_id attribute). Returns: Combined list of tracks from track_list_a and track_list_b - without duplicate track_id values. + without duplicate internal_track_id values. """ seen_track_ids = set() result = [] for track in track_list_a + track_list_b: - if track.track_id not in seen_track_ids: - seen_track_ids.add(track.track_id) + if track.internal_track_id not in seen_track_ids: + seen_track_ids.add(track.internal_track_id) result.append(track) return result @@ -502,17 +516,17 @@ def joint_tracks( def sub_tracks(track_list_a: List, track_list_b: List) -> List[int]: """ Returns a list of tracks from track_list_a after removing any tracks - that share the same track_id with tracks in track_list_b. + that share the same internal_track_id with tracks in track_list_b. Parameters: - track_list_a: List of tracks (with track_id attribute). - track_list_b: List of tracks (with track_id attribute) to + track_list_a: List of tracks (with internal_track_id attribute). + track_list_b: List of tracks (with internal_track_id attribute) to be subtracted from track_list_a. Returns: List of remaining tracks from track_list_a after subtraction. """ - tracks = {track.track_id: track for track in track_list_a} - track_ids_b = {track.track_id for track in track_list_b} + tracks = {track.internal_track_id: track for track in track_list_a} + track_ids_b = {track.internal_track_id for track in track_list_b} for track_id in track_ids_b: tracks.pop(track_id, None) From a2ab113f214d92e785bc5ddba5d0f64e669fe39f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:37:41 +0000 Subject: [PATCH 03/11] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index bf82044a6..8e8104f4c 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -137,7 +137,7 @@ def tlwh_to_xyah(tlwh): def to_xyah(self): return self.tlwh_to_xyah(self.tlwh) - + @staticmethod def next_external_id(): STrack._external_count += 1 @@ -160,7 +160,9 @@ def tlwh_to_tlbr(tlwh): return ret def __repr__(self): - return "OT_{}_({}-{})".format(self.internal_track_id, self.start_frame, self.end_frame) + return "OT_{}_({}-{})".format( + self.internal_track_id, self.start_frame, self.end_frame + ) def detections2boxes(detections: Detections) -> np.ndarray: @@ -307,7 +309,9 @@ def callback(frame: np.ndarray, index: int) -> np.ndarray: matches, _, _ = matching.linear_assignment(iou_costs, 0.5) detections.tracker_id = np.full(len(detections), -1, dtype=int) for i_detection, i_track in matches: - detections.tracker_id[i_detection] = int(tracks[i_track].external_track_id) + detections.tracker_id[i_detection] = int( + tracks[i_track].external_track_id + ) return detections[detections.tracker_id != -1] From 2c9a1f69c5ad1f460c2c5a413d802b2cef967d64 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:54:23 +0000 Subject: [PATCH 04/11] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 8e8104f4c..1fa988649 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -142,7 +142,7 @@ def to_xyah(self): def next_external_id(): STrack._external_count += 1 return STrack._external_count - + @staticmethod def reset_external_counter(): STrack._external_count = 0 From 6166d924588b8ca3fe6cf821f02a098a5fc34f67 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 08:21:26 -0400 Subject: [PATCH 05/11] fix reassigning track_id after a track is re-activated. --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 1fa988649..f35e62df5 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -98,7 +98,7 @@ def update(self, new_track, frame_id): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if self.tracklet_len == self.minimum_consecutive_frames: + if self.tracklet_len == self.minimum_consecutive_frames and not self.is_activated: self.is_activated = True self.external_track_id = self.next_external_id() From 755f292ed1ef87f593cf2f057b9a95e7e84a2b59 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:21:44 +0000 Subject: [PATCH 06/11] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index f35e62df5..10b7f0fe3 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -98,7 +98,10 @@ def update(self, new_track, frame_id): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if self.tracklet_len == self.minimum_consecutive_frames and not self.is_activated: + if ( + self.tracklet_len == self.minimum_consecutive_frames + and not self.is_activated + ): self.is_activated = True self.external_track_id = self.next_external_id() From 7924a13dc59dd26f7cd2cc9e83c098fd4f453da5 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 08:28:08 -0400 Subject: [PATCH 07/11] only assign external track_id once --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 10b7f0fe3..6c320073f 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -100,7 +100,7 @@ def update(self, new_track, frame_id): self.state = TrackState.Tracked if ( self.tracklet_len == self.minimum_consecutive_frames - and not self.is_activated + and self.external_track_id == -1 ): self.is_activated = True self.external_track_id = self.next_external_id() From b1d5d4faf07f28d3f99fcb36dc2e65eb65167776 Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 09:21:32 -0400 Subject: [PATCH 08/11] fix to exactly match existing release version when minimum_consecutive_frames = 1 --- supervision/tracker/byte_tracker/core.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index 6c320073f..d7dc2461b 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -65,8 +65,12 @@ def activate(self, kalman_filter, frame_id): self.tracklet_len = 0 self.state = TrackState.Tracked - if frame_id == 1 and self.minimum_consecutive_frames == 1: + if frame_id == 1: self.is_activated = True + + if self.minimum_consecutive_frames == 1: + self.external_track_id = self.next_external_id() + self.frame_id = frame_id self.start_frame = frame_id From bcb31f880958af32e2b7aa788f1afe37389458bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:21:48 +0000 Subject: [PATCH 09/11] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index d7dc2461b..410c1050c 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -70,7 +70,7 @@ def activate(self, kalman_filter, frame_id): if self.minimum_consecutive_frames == 1: self.external_track_id = self.next_external_id() - + self.frame_id = frame_id self.start_frame = frame_id From f98ded49a226557bc474cbc46253689023f9664b Mon Sep 17 00:00:00 2001 From: Raif Olson Date: Thu, 11 Apr 2024 09:27:24 -0400 Subject: [PATCH 10/11] fix --- supervision/tracker/byte_tracker/core.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index d7dc2461b..8b072fa23 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -102,12 +102,10 @@ def update(self, new_track, frame_id): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if ( - self.tracklet_len == self.minimum_consecutive_frames - and self.external_track_id == -1 - ): + if (self.tracklet_len == self.minimum_consecutive_frames): self.is_activated = True - self.external_track_id = self.next_external_id() + if (self.external_track_id == -1): + self.external_track_id = self.next_external_id() self.score = new_track.score From 361e444f53f2281e2b0297f5b267f66136292d5f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 13:27:40 +0000 Subject: [PATCH 11/11] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/tracker/byte_tracker/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervision/tracker/byte_tracker/core.py b/supervision/tracker/byte_tracker/core.py index cfaae1305..55db6293d 100644 --- a/supervision/tracker/byte_tracker/core.py +++ b/supervision/tracker/byte_tracker/core.py @@ -102,9 +102,9 @@ def update(self, new_track, frame_id): self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh) ) self.state = TrackState.Tracked - if (self.tracklet_len == self.minimum_consecutive_frames): + if self.tracklet_len == self.minimum_consecutive_frames: self.is_activated = True - if (self.external_track_id == -1): + if self.external_track_id == -1: self.external_track_id = self.next_external_id() self.score = new_track.score