diff --git a/demos/multi_camera/src/demo.py b/demos/multi_camera/src/demo.py index b947a323..3766130e 100644 --- a/demos/multi_camera/src/demo.py +++ b/demos/multi_camera/src/demo.py @@ -89,21 +89,24 @@ def draw_feet( for cluster in clusters: color = Palette.choose_color(cluster.id) cluster_center = 0 + cluster_is_alive = False for tracked_object in cluster.tracked_objects.values(): - point = get_absolute_feet(tracked_object) - if transformation_in_reference is not None: - point = transformation_in_reference.abs_to_rel(np.array([point]))[0] - - cluster_center += point - frame = Drawer.circle( - frame, - tuple(point.astype(int)), - radius=radius, - color=color, - thickness=thickness, - ) + if tracked_object.live_points.any(): + cluster_is_alive = True + point = get_absolute_feet(tracked_object) + if transformation_in_reference is not None: + point = transformation_in_reference.abs_to_rel(np.array([point]))[0] + + cluster_center += point + frame = Drawer.circle( + frame, + tuple(point.astype(int)), + radius=radius, + color=color, + thickness=thickness, + ) - if draw_cluster_ids: + if draw_cluster_ids and cluster_is_alive: cluster_center /= len(cluster.tracked_objects) frame = Drawer.text( frame, @@ -127,41 +130,42 @@ def draw_cluster_bboxes( for cluster in clusters: color = Palette.choose_color(cluster.id) for path, tracked_object in cluster.tracked_objects.items(): - frame = images[path] - - if thickness is None: - current_thickness = max(int(max(frame.shape) / 500), 1) - else: - current_thickness = thickness - - # draw the bbox - points = tracked_object.estimate.astype(int) - frame = Drawer.rectangle( - frame, - tuple(points), - color=color, - thickness=current_thickness, - ) - - if draw_cluster_ids: - text = f"{cluster.id}" + if tracked_object.live_points.any(): + frame = images[path] - # the anchor will become the bottom-left of the text, - # we select-top left of the bbox compensating for the thickness of the box - text_anchor = ( - points[0, 0] - current_thickness // 2, - points[0, 1] - current_thickness // 2 - 1, - ) + if thickness is None: + current_thickness = max(int(max(frame.shape) / 500), 1) + else: + current_thickness = thickness - frame = Drawer.text( + # draw the bbox + points = tracked_object.estimate.astype(int) + frame = Drawer.rectangle( frame, - text, - position=text_anchor, - size=text_size, + tuple(points), color=color, - thickness=text_thickness, + thickness=current_thickness, ) - images[path] = frame + + if draw_cluster_ids: + text = f"{cluster.id}" + + # the anchor will become the bottom-left of the text, + # we select-top left of the bbox compensating for the thickness of the box + text_anchor = ( + points[0, 0] - current_thickness // 2, + points[0, 1] - current_thickness // 2 - 1, + ) + + frame = Drawer.text( + frame, + text, + position=text_anchor, + size=text_size, + color=color, + thickness=text_thickness, + ) + images[path] = frame return images @@ -272,22 +276,22 @@ def run(): default=0.2, ) parser.add_argument( - "--distance-threshold", + "--foot-distance-threshold", type=float, - default=1.5, - help="Maximum distance to consider when matching detections and tracked objects", + default=0.2, + help="Maximum spatial distance that two tracked objects of different videos can have in order to match", ) parser.add_argument( - "--foot-distance-threshold", + "--reid-embedding-correlation-threshold", type=float, - default=0.1, - help="Maximum spatial distance that two tracked objects of different videos can have in order to match", + default=0.5, + help="Threshold for embedding match during a reid phase after object has been lost. (The 1-correlation distance we use is bounded in [0, 2])", ) parser.add_argument( "--embedding-correlation-threshold", type=float, - default=0.9, - help="Threshold for embedding match.", + default=1, + help="Threshold for embedding match. (The 1-correlation distance we use is bounded in [0, 2]", ) parser.add_argument( "--max-votes-grow", @@ -340,8 +344,8 @@ def run(): parser.add_argument( "--reid-hit-counter-max", type=int, - default=150, - help="Maximum amount of frames trying to reidentify the object", + default=300, + help="Maximum amount of frames trying to reidentify the object. (Use a value >=0)", ) parser.add_argument( "--nms-threshold", type=float, help="Iou threshold for detector", default=0.15 @@ -509,14 +513,15 @@ def conditional_embedding_to_spatial(detection, tracked_object): trackers[path] = Tracker( distance_function=distance_functions[path], detection_threshold=args.confidence_threshold, - distance_threshold=args.distance_threshold, + distance_threshold=args.embedding_correlation_threshold, initialization_delay=args.initialization_delay, hit_counter_max=args.hit_counter_max, camera_name=path, past_detections_length=10, reid_distance_function=embedding_distance, - reid_distance_threshold=0.5, + reid_distance_threshold=args.reid_embedding_correlation_threshold, reid_hit_counter_max=args.reid_hit_counter_max, + pointwise_hit_counter_max=2, ) tracked_objects[path] = [] @@ -557,6 +562,7 @@ def clusterizer_distance(tracker1, tracker2): memory=args.memory, initialization_delay=args.clusterizer_initialization_delay, reid_hit_counter_max=args.reid_hit_counter_max, + use_only_living_trackers=False, ) while True: diff --git a/norfair/multi_camera.py b/norfair/multi_camera.py index eb24d7a4..e18186ba 100644 --- a/norfair/multi_camera.py +++ b/norfair/multi_camera.py @@ -132,7 +132,11 @@ def cluster_intersection_matrix(current_clusters, clusters): def generate_current_clusters( - trackers_by_camera, distance_function, distance_threshold, join_distance_by="mean" + trackers_by_camera, + distance_function, + distance_threshold, + join_distance_by="mean", + use_only_living_trackers=False, ): # In case number of camera is variable, I will redefine the distance function @@ -141,6 +145,10 @@ def generate_current_clusters( current_clusters = flatten_list(trackers_by_camera) + # use only alive trackers: + if use_only_living_trackers: + current_clusters = [obj for obj in current_clusters if obj.live_points.any()] + if len(current_clusters) > 0: distance_matrix = ( np.zeros((len(current_clusters), len(current_clusters))) @@ -399,24 +407,26 @@ def remove_current_cluster_from_clusters( def swap_cluster_ids(clusters, cluster_number, cluster_number_with_oldest_tracker): old_cluster = clusters[cluster_number_with_oldest_tracker] - old_cluster_id = old_cluster.id old_cluster_fake_id = old_cluster.fake_id - old_cluster_age = old_cluster.age cluster = clusters[cluster_number] - cluster_id = cluster.id cluster_fake_id = cluster.fake_id - cluster_age = cluster.age - cluster.id = old_cluster_id - cluster.fake_id = old_cluster_fake_id - cluster.age = old_cluster_age - old_cluster.id = cluster_id - old_cluster.fake_id = cluster_fake_id - old_cluster.age = cluster_age + if old_cluster_fake_id < cluster_fake_id: + old_cluster_age = old_cluster.age + old_cluster_id = old_cluster.id + + cluster_age = cluster.age + cluster_id = cluster.id + cluster.id = old_cluster_id + cluster.fake_id = old_cluster_fake_id + cluster.age = old_cluster_age + old_cluster.id = cluster_id + old_cluster.fake_id = cluster_fake_id + old_cluster.age = cluster_age - clusters[cluster_number_with_oldest_tracker] = old_cluster - clusters[cluster_number] = cluster + clusters[cluster_number_with_oldest_tracker] = old_cluster + clusters[cluster_number] = cluster return clusters @@ -431,6 +441,7 @@ def __init__( memory: int = 3, initialization_delay: int = 4, reid_hit_counter_max: int = 0, + use_only_living_trackers: bool = False, ): """ Associate trackers from different cameras/videos. @@ -467,6 +478,10 @@ def __init__( - reid_hit_counter_max: int. If doing reid in the tracking, then provide the reid_hit_counter_max so that the MultiCameraClusterizer instance knows for how long to keep storing clusters of tracked objects that have dissapeared. + + - use_only_living_trackers: bool. + Filter tracked objects that have no alive points. This can be useful since tracked objects that are not alive might have + position that will not match well with their position in a different camera. """ if max_votes_grow < 1: raise ValueError("max_votes_grow parameter needs to be >= 1") @@ -500,6 +515,7 @@ def __init__( self.initialization_delay = initialization_delay + max_votes_grow self.reid_hit_counter_max = reid_hit_counter_max + 1 + self.use_only_living_trackers = use_only_living_trackers def update(self, trackers_by_camera): @@ -521,6 +537,7 @@ def update(self, trackers_by_camera): self.distance_function, self.distance_threshold, self.join_distance_by, + self.use_only_living_trackers, ) self.past_clusters.insert(0, deepcopy(current_clusters)) @@ -591,13 +608,12 @@ def update(self, trackers_by_camera): self.clusters[cluster_number] = cluster - # keep the id of the cluster with the oldest object - if cluster_number not in cluster_numbers_with_oldest_tracker: - self.clusters = swap_cluster_ids( - self.clusters, - cluster_number, - cluster_numbers_with_oldest_tracker[0], - ) + # keep the smallest id with the oldest object + self.clusters = swap_cluster_ids( + self.clusters, + cluster_number, + np.array(cluster_numbers_with_oldest_tracker).min(), + ) # update the matrix of intersections so that the current cluster is now contained in self.clusters[cluster_number] intersection_matrix_ids[cluster_number][