Detection confidence threshold
for track activation. Increasing track_activation_threshold improves accuracy
and stability but might miss true detections. Decreasing it increases
completeness but risks introducing noise and instability.
Number of frames to buffer when a track is lost.
Increasing lost_track_buffer enhances occlusion handling, significantly
reducing the likelihood of track fragmentation or disappearance caused
by brief detection gaps.
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.
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.
1
Source code in supervision/tracker/byte_tracker/core.py
classByteTrack:""" Initialize the ByteTrack object. <video controls> <source src="https://media.roboflow.com/supervision/video-examples/how-to/track-objects/annotate-video-with-traces.mp4" type="video/mp4"> </video> Parameters: track_activation_threshold (float): Detection confidence threshold for track activation. Increasing track_activation_threshold improves accuracy and stability but might miss true detections. Decreasing it increases completeness but risks introducing noise and instability. lost_track_buffer (int): Number of frames to buffer when a track is lost. Increasing lost_track_buffer enhances occlusion handling, significantly reducing the likelihood of track fragmentation or disappearance caused by brief detection gaps. minimum_matching_threshold (float): 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. frame_rate (int): The frame rate of the video. minimum_consecutive_frames (int): 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 // docsdef__init__(self,track_activation_threshold:float=0.25,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_thresholdself.minimum_matching_threshold=minimum_matching_thresholdself.frame_id=0self.det_thresh=self.track_activation_threshold+0.1self.max_time_lost=int(frame_rate/30.0*lost_track_buffer)self.minimum_consecutive_frames=minimum_consecutive_framesself.kalman_filter=KalmanFilter()self.shared_kalman=KalmanFilter()self.tracked_tracks:List[STrack]=[]self.lost_tracks:List[STrack]=[]self.removed_tracks:List[STrack]=[]# Warning, possible bug: If you also set internal_id to start at 1,# all traces will be connected across objects.self.internal_id_counter=IdCounter()self.external_id_counter=IdCounter(start_id=1)defupdate_with_detections(self,detections:Detections)->Detections:""" Updates the tracker with the provided detections and returns the updated detection results. Args: detections (Detections): The detections to pass through the tracker. Example: ```python import supervision as sv from ultralytics import YOLO model = YOLO(<MODEL_PATH>) tracker = sv.ByteTrack() box_annotator = sv.BoxAnnotator() label_annotator = sv.LabelAnnotator() def callback(frame: np.ndarray, index: int) -> np.ndarray: results = model(frame)[0] detections = sv.Detections.from_ultralytics(results) detections = tracker.update_with_detections(detections) labels = [f"#{tracker_id}" for tracker_id in detections.tracker_id] annotated_frame = box_annotator.annotate( scene=frame.copy(), detections=detections) annotated_frame = label_annotator.annotate( scene=annotated_frame, detections=detections, labels=labels) return annotated_frame sv.process_video( source_path=<SOURCE_VIDEO_PATH>, target_path=<TARGET_VIDEO_PATH>, callback=callback ) ``` """tensors=np.hstack((detections.xyxy,detections.confidence[:,np.newaxis],))tracks=self.update_with_tensors(tensors=tensors)iflen(tracks)>0:detection_bounding_boxes=np.asarray([det[:4]fordetintensors])track_bounding_boxes=np.asarray([track.tlbrfortrackintracks])ious=box_iou_batch(detection_bounding_boxes,track_bounding_boxes)iou_costs=1-iousmatches,_,_=matching.linear_assignment(iou_costs,0.5)detections.tracker_id=np.full(len(detections),-1,dtype=int)fori_detection,i_trackinmatches:detections.tracker_id[i_detection]=int(tracks[i_track].external_track_id)returndetections[detections.tracker_id!=-1]else:detections=Detections.empty()detections.tracker_id=np.array([],dtype=int)returndetectionsdefreset(self)->None:""" Resets the internal state of the ByteTrack tracker. This method clears the tracking data, including tracked, lost, and removed tracks, as well as resetting the frame counter. It's particularly useful when processing multiple videos sequentially, ensuring the tracker starts with a clean state for each new video. """self.frame_id=0self.internal_id_counter.reset()self.external_id_counter.reset()self.tracked_tracks=[]self.lost_tracks=[]self.removed_tracks=[]defupdate_with_tensors(self,tensors:np.ndarray)->List[STrack]:""" Updates the tracker with the provided tensors and returns the updated tracks. Parameters: tensors: The new tensors to update with. Returns: List[STrack]: Updated tracks. """self.frame_id+=1activated_starcks=[]refind_stracks=[]lost_stracks=[]removed_stracks=[]scores=tensors[:,4]bboxes=tensors[:,:4]remain_inds=scores>self.track_activation_thresholdinds_low=scores>0.1inds_high=scores<self.track_activation_thresholdinds_second=np.logical_and(inds_low,inds_high)dets_second=bboxes[inds_second]dets=bboxes[remain_inds]scores_keep=scores[remain_inds]scores_second=scores[inds_second]iflen(dets)>0:"""Detections"""detections=[STrack(STrack.tlbr_to_tlwh(tlbr),score_keep,self.minimum_consecutive_frames,self.shared_kalman,self.internal_id_counter,self.external_id_counter,)for(tlbr,score_keep)inzip(dets,scores_keep)]else:detections=[]""" Add newly detected tracklets to tracked_stracks"""unconfirmed=[]tracked_stracks=[]# type: list[STrack]fortrackinself.tracked_tracks:ifnottrack.is_activated:unconfirmed.append(track)else:tracked_stracks.append(track)""" Step 2: First association, with high score detection boxes"""strack_pool=joint_tracks(tracked_stracks,self.lost_tracks)# Predict the current location with KFSTrack.multi_predict(strack_pool,self.shared_kalman)dists=matching.iou_distance(strack_pool,detections)dists=matching.fuse_score(dists,detections)matches,u_track,u_detection=matching.linear_assignment(dists,thresh=self.minimum_matching_threshold)foritracked,idetinmatches:track=strack_pool[itracked]det=detections[idet]iftrack.state==TrackState.Tracked:track.update(detections[idet],self.frame_id)activated_starcks.append(track)else:track.re_activate(det,self.frame_id)refind_stracks.append(track)""" Step 3: Second association, with low score detection boxes"""# association the untrack to the low score detectionsiflen(dets_second)>0:"""Detections"""detections_second=[STrack(STrack.tlbr_to_tlwh(tlbr),score_second,self.minimum_consecutive_frames,self.shared_kalman,self.internal_id_counter,self.external_id_counter,)for(tlbr,score_second)inzip(dets_second,scores_second)]else:detections_second=[]r_tracked_stracks=[strack_pool[i]foriinu_trackifstrack_pool[i].state==TrackState.Tracked]dists=matching.iou_distance(r_tracked_stracks,detections_second)matches,u_track,u_detection_second=matching.linear_assignment(dists,thresh=0.5)foritracked,idetinmatches:track=r_tracked_stracks[itracked]det=detections_second[idet]iftrack.state==TrackState.Tracked:track.update(det,self.frame_id)activated_starcks.append(track)else:track.re_activate(det,self.frame_id)refind_stracks.append(track)foritinu_track:track=r_tracked_stracks[it]ifnottrack.state==TrackState.Lost:track.state=TrackState.Lostlost_stracks.append(track)"""Deal with unconfirmed tracks, usually tracks with only one beginning frame"""detections=[detections[i]foriinu_detection]dists=matching.iou_distance(unconfirmed,detections)dists=matching.fuse_score(dists,detections)matches,u_unconfirmed,u_detection=matching.linear_assignment(dists,thresh=0.7)foritracked,idetinmatches:unconfirmed[itracked].update(detections[idet],self.frame_id)activated_starcks.append(unconfirmed[itracked])foritinu_unconfirmed:track=unconfirmed[it]track.state=TrackState.Removedremoved_stracks.append(track)""" Step 4: Init new stracks"""forinewinu_detection:track=detections[inew]iftrack.score<self.det_thresh:continuetrack.activate(self.kalman_filter,self.frame_id)activated_starcks.append(track)""" Step 5: Update state"""fortrackinself.lost_tracks:ifself.frame_id-track.frame_id>self.max_time_lost:track.state=TrackState.Removedremoved_stracks.append(track)self.tracked_tracks=[tfortinself.tracked_tracksift.state==TrackState.Tracked]self.tracked_tracks=joint_tracks(self.tracked_tracks,activated_starcks)self.tracked_tracks=joint_tracks(self.tracked_tracks,refind_stracks)self.lost_tracks=sub_tracks(self.lost_tracks,self.tracked_tracks)self.lost_tracks.extend(lost_stracks)self.lost_tracks=sub_tracks(self.lost_tracks,self.removed_tracks)self.removed_tracks=removed_stracksself.tracked_tracks,self.lost_tracks=remove_duplicate_tracks(self.tracked_tracks,self.lost_tracks)output_stracks=[trackfortrackinself.tracked_tracksiftrack.is_activated]returnoutput_stracks
Resets the internal state of the ByteTrack tracker.
This method clears the tracking data, including tracked, lost,
and removed tracks, as well as resetting the frame counter. It's
particularly useful when processing multiple videos sequentially,
ensuring the tracker starts with a clean state for each new video.
Source code in supervision/tracker/byte_tracker/core.py
defreset(self)->None:""" Resets the internal state of the ByteTrack tracker. This method clears the tracking data, including tracked, lost, and removed tracks, as well as resetting the frame counter. It's particularly useful when processing multiple videos sequentially, ensuring the tracker starts with a clean state for each new video. """self.frame_id=0self.internal_id_counter.reset()self.external_id_counter.reset()self.tracked_tracks=[]self.lost_tracks=[]self.removed_tracks=[]
defupdate_with_tensors(self,tensors:np.ndarray)->List[STrack]:""" Updates the tracker with the provided tensors and returns the updated tracks. Parameters: tensors: The new tensors to update with. Returns: List[STrack]: Updated tracks. """self.frame_id+=1activated_starcks=[]refind_stracks=[]lost_stracks=[]removed_stracks=[]scores=tensors[:,4]bboxes=tensors[:,:4]remain_inds=scores>self.track_activation_thresholdinds_low=scores>0.1inds_high=scores<self.track_activation_thresholdinds_second=np.logical_and(inds_low,inds_high)dets_second=bboxes[inds_second]dets=bboxes[remain_inds]scores_keep=scores[remain_inds]scores_second=scores[inds_second]iflen(dets)>0:"""Detections"""detections=[STrack(STrack.tlbr_to_tlwh(tlbr),score_keep,self.minimum_consecutive_frames,self.shared_kalman,self.internal_id_counter,self.external_id_counter,)for(tlbr,score_keep)inzip(dets,scores_keep)]else:detections=[]""" Add newly detected tracklets to tracked_stracks"""unconfirmed=[]tracked_stracks=[]# type: list[STrack]fortrackinself.tracked_tracks:ifnottrack.is_activated:unconfirmed.append(track)else:tracked_stracks.append(track)""" Step 2: First association, with high score detection boxes"""strack_pool=joint_tracks(tracked_stracks,self.lost_tracks)# Predict the current location with KFSTrack.multi_predict(strack_pool,self.shared_kalman)dists=matching.iou_distance(strack_pool,detections)dists=matching.fuse_score(dists,detections)matches,u_track,u_detection=matching.linear_assignment(dists,thresh=self.minimum_matching_threshold)foritracked,idetinmatches:track=strack_pool[itracked]det=detections[idet]iftrack.state==TrackState.Tracked:track.update(detections[idet],self.frame_id)activated_starcks.append(track)else:track.re_activate(det,self.frame_id)refind_stracks.append(track)""" Step 3: Second association, with low score detection boxes"""# association the untrack to the low score detectionsiflen(dets_second)>0:"""Detections"""detections_second=[STrack(STrack.tlbr_to_tlwh(tlbr),score_second,self.minimum_consecutive_frames,self.shared_kalman,self.internal_id_counter,self.external_id_counter,)for(tlbr,score_second)inzip(dets_second,scores_second)]else:detections_second=[]r_tracked_stracks=[strack_pool[i]foriinu_trackifstrack_pool[i].state==TrackState.Tracked]dists=matching.iou_distance(r_tracked_stracks,detections_second)matches,u_track,u_detection_second=matching.linear_assignment(dists,thresh=0.5)foritracked,idetinmatches:track=r_tracked_stracks[itracked]det=detections_second[idet]iftrack.state==TrackState.Tracked:track.update(det,self.frame_id)activated_starcks.append(track)else:track.re_activate(det,self.frame_id)refind_stracks.append(track)foritinu_track:track=r_tracked_stracks[it]ifnottrack.state==TrackState.Lost:track.state=TrackState.Lostlost_stracks.append(track)"""Deal with unconfirmed tracks, usually tracks with only one beginning frame"""detections=[detections[i]foriinu_detection]dists=matching.iou_distance(unconfirmed,detections)dists=matching.fuse_score(dists,detections)matches,u_unconfirmed,u_detection=matching.linear_assignment(dists,thresh=0.7)foritracked,idetinmatches:unconfirmed[itracked].update(detections[idet],self.frame_id)activated_starcks.append(unconfirmed[itracked])foritinu_unconfirmed:track=unconfirmed[it]track.state=TrackState.Removedremoved_stracks.append(track)""" Step 4: Init new stracks"""forinewinu_detection:track=detections[inew]iftrack.score<self.det_thresh:continuetrack.activate(self.kalman_filter,self.frame_id)activated_starcks.append(track)""" Step 5: Update state"""fortrackinself.lost_tracks:ifself.frame_id-track.frame_id>self.max_time_lost:track.state=TrackState.Removedremoved_stracks.append(track)self.tracked_tracks=[tfortinself.tracked_tracksift.state==TrackState.Tracked]self.tracked_tracks=joint_tracks(self.tracked_tracks,activated_starcks)self.tracked_tracks=joint_tracks(self.tracked_tracks,refind_stracks)self.lost_tracks=sub_tracks(self.lost_tracks,self.tracked_tracks)self.lost_tracks.extend(lost_stracks)self.lost_tracks=sub_tracks(self.lost_tracks,self.removed_tracks)self.removed_tracks=removed_stracksself.tracked_tracks,self.lost_tracks=remove_duplicate_tracks(self.tracked_tracks,self.lost_tracks)output_stracks=[trackfortrackinself.tracked_tracksiftrack.is_activated]returnoutput_stracks