# Simple centroid tracker (frame-to-frame assignment by nearest centroid with distance gate) import numpy as np class SimpleTracker: def __init__(self, max_lost=30, dist_thresh=80): self.next_id = 1 self.tracks = {} # id -> {'bbox':(x1,y1,x2,y2), 'centroid':(x,y), 'lost':0, 'history':[]} self.max_lost = max_lost self.dist_thresh = dist_thresh def _centroid(self, bbox): x1,y1,x2,y2 = bbox return ((x1+x2)/2.0, (y1+y2)/2.0) def update(self, person_bboxes): # person_bboxes: list of (x1,y1,x2,y2) centroids = [self._centroid(b) for b in person_bboxes] # Build cost matrix track_ids = list(self.tracks.keys()) T = len(track_ids); D = len(centroids) cost = np.full((T, D), fill_value=1e9, dtype=float) for i, tid in enumerate(track_ids): tx, ty = self.tracks[tid]['centroid'] for j, c in enumerate(centroids): cost[i,j] = np.linalg.norm(np.array([tx,ty]) - np.array(c)) # Greedy assignment by nearest assigned_tracks = set() assigned_dets = set() while True: i,j = np.unravel_index(cost.argmin(), cost.shape) if cost.size else (None,None) if cost.size == 0: break if cost[i,j] > self.dist_thresh: break # assign tid = track_ids[i] self.tracks[tid]['bbox'] = person_bboxes[j] self.tracks[tid]['centroid'] = centroids[j] self.tracks[tid]['lost'] = 0 self.tracks[tid]['history'].append(centroids[j]) assigned_tracks.add(i); assigned_dets.add(j) cost[i,:] = 1e9 cost[:,j] = 1e9 # Increase lost for unassigned tracks for idx, tid in enumerate(track_ids): if idx not in assigned_tracks: self.tracks[tid]['lost'] += 1 # Remove lost tracks for tid in list(self.tracks.keys()): if self.tracks[tid]['lost'] > self.max_lost: del self.tracks[tid] # Add new tracks for unassigned detections for j, b in enumerate(person_bboxes): if j not in assigned_dets: c = self._centroid(b) self.tracks[self.next_id] = {'bbox': b, 'centroid': c, 'lost':0, 'history':[c]} self.next_id += 1 return self.tracks