diff --git a/frigate/config/camera/onvif.py b/frigate/config/camera/onvif.py index ff34e2a10..d4955799b 100644 --- a/frigate/config/camera/onvif.py +++ b/frigate/config/camera/onvif.py @@ -63,9 +63,9 @@ class PtzAutotrackConfig(FrigateBaseModel): else: raise ValueError("Invalid type for movement_weights") - if len(weights) != 5: + if len(weights) != 6: raise ValueError( - "movement_weights must have exactly 5 floats, remove this line from your config and run autotracking calibration" + "movement_weights must have exactly 6 floats, remove this line from your config and run autotracking calibration" ) return weights diff --git a/frigate/embeddings/__init__.py b/frigate/embeddings/__init__.py index 5e713b3c1..3650303b5 100644 --- a/frigate/embeddings/__init__.py +++ b/frigate/embeddings/__init__.py @@ -243,7 +243,7 @@ class EmbeddingsContext: ) def rename_face(self, old_name: str, new_name: str) -> None: - valid_name_pattern = r"^[a-zA-Z0-9_-]{1,50}$" + valid_name_pattern = r"^[a-zA-Z0-9\s_-]{1,50}$" try: sanitized_old_name = sanitize_filename(old_name, replacement_text="_") diff --git a/frigate/ptz/autotrack.py b/frigate/ptz/autotrack.py index 81e54c6d7..c6db85a6c 100644 --- a/frigate/ptz/autotrack.py +++ b/frigate/ptz/autotrack.py @@ -206,6 +206,7 @@ class PtzAutoTracker: self.calibrating: dict[str, object] = {} self.intercept: dict[str, object] = {} self.move_coefficients: dict[str, object] = {} + self.zoom_time: dict[str, float] = {} self.zoom_factor: dict[str, object] = {} # if cam is set to autotrack, onvif should be set up @@ -272,7 +273,12 @@ class PtzAutoTracker: move_status_supported = self.onvif.get_service_capabilities(camera) - if move_status_supported is None or move_status_supported.lower() != "true": + if not ( + isinstance(move_status_supported, bool) and move_status_supported + ) and not ( + isinstance(move_status_supported, str) + and move_status_supported.lower() == "true" + ): logger.warning( f"Disabling autotracking for {camera}: ONVIF MoveStatus not supported" ) @@ -292,7 +298,7 @@ class PtzAutoTracker: self.move_threads[camera].start() if camera_config.onvif.autotracking.movement_weights: - if len(camera_config.onvif.autotracking.movement_weights) == 5: + if len(camera_config.onvif.autotracking.movement_weights) == 6: camera_config.onvif.autotracking.movement_weights = [ float(val) for val in camera_config.onvif.autotracking.movement_weights @@ -311,7 +317,10 @@ class PtzAutoTracker: camera_config.onvif.autotracking.movement_weights[2] ) self.move_coefficients[camera] = ( - camera_config.onvif.autotracking.movement_weights[3:] + camera_config.onvif.autotracking.movement_weights[3:5] + ) + self.zoom_time[camera] = ( + camera_config.onvif.autotracking.movement_weights[5] ) else: camera_config.onvif.autotracking.enabled = False @@ -360,6 +369,7 @@ class PtzAutoTracker: != ZoomingModeEnum.disabled ): logger.info(f"Calibration for {camera} in progress: 0% complete") + self.zoom_time[camera] = 0 for i in range(2): # absolute move to 0 - fully zoomed out @@ -403,6 +413,7 @@ class PtzAutoTracker: zoom_out_values.append(self.ptz_metrics[camera].zoom_level.value) + zoom_start_time = time.time() # relative move to 0.01 self.onvif._move_relative( camera, @@ -415,13 +426,45 @@ class PtzAutoTracker: while not self.ptz_metrics[camera].motor_stopped.is_set(): self.onvif.get_camera_status(camera) + zoom_stop_time = time.time() + + full_relative_start_time = time.time() + + self.onvif._move_relative( + camera, + -1, + -1, + -1e-2, + 1, + ) + + while not self.ptz_metrics[camera].motor_stopped.is_set(): + self.onvif.get_camera_status(camera) + + full_relative_stop_time = time.time() + + self.onvif._move_relative( + camera, + 1, + 1, + 1e-2, + 1, + ) + + while not self.ptz_metrics[camera].motor_stopped.is_set(): + self.onvif.get_camera_status(camera) + + self.zoom_time[camera] = ( + full_relative_stop_time - full_relative_start_time + ) - (zoom_stop_time - zoom_start_time) + zoom_in_values.append(self.ptz_metrics[camera].zoom_level.value) self.ptz_metrics[camera].max_zoom.value = max(zoom_in_values) self.ptz_metrics[camera].min_zoom.value = min(zoom_out_values) logger.debug( - f"{camera}: Calibration values: max zoom: {self.ptz_metrics[camera].max_zoom.value}, min zoom: {self.ptz_metrics[camera].min_zoom.value}" + f"{camera}: Calibration values: max zoom: {self.ptz_metrics[camera].max_zoom.value}, min zoom: {self.ptz_metrics[camera].min_zoom.value}, zoom time: {self.zoom_time[camera]}" ) else: @@ -537,6 +580,7 @@ class PtzAutoTracker: self.ptz_metrics[camera].max_zoom.value, self.intercept[camera], *self.move_coefficients[camera], + self.zoom_time[camera], ] ) @@ -1061,6 +1105,7 @@ class PtzAutoTracker: average_velocity = np.zeros((4,)) predicted_box = obj.obj_data["box"] + zoom_predicted_box = obj.obj_data["box"] centroid_x = obj.obj_data["centroid"][0] centroid_y = obj.obj_data["centroid"][1] @@ -1069,20 +1114,20 @@ class PtzAutoTracker: pan = ((centroid_x / camera_width) - 0.5) * 2 tilt = (0.5 - (centroid_y / camera_height)) * 2 + _, average_velocity = ( + self._get_valid_velocity(camera, obj) + if "velocity" not in self.tracked_object_metrics[camera] + else ( + self.tracked_object_metrics[camera]["valid_velocity"], + self.tracked_object_metrics[camera]["velocity"], + ) + ) + if ( camera_config.onvif.autotracking.movement_weights ): # use estimates if we have available coefficients predicted_movement_time = self._predict_movement_time(camera, pan, tilt) - _, average_velocity = ( - self._get_valid_velocity(camera, obj) - if "velocity" not in self.tracked_object_metrics[camera] - else ( - self.tracked_object_metrics[camera]["valid_velocity"], - self.tracked_object_metrics[camera]["velocity"], - ) - ) - if np.any(average_velocity): # this box could exceed the frame boundaries if velocity is high # but we'll handle that in _enqueue_move() as two separate moves @@ -1111,6 +1156,34 @@ class PtzAutoTracker: camera, obj, predicted_box, predicted_movement_time, debug_zoom=True ) + if ( + camera_config.onvif.autotracking.movement_weights + and camera_config.onvif.autotracking.zooming == ZoomingModeEnum.relative + and zoom != 0 + ): + zoom_predicted_movement_time = 0 + + if np.any(average_velocity): + zoom_predicted_movement_time = abs(zoom) * self.zoom_time[camera] + + zoom_predicted_box = ( + predicted_box + + camera_fps * zoom_predicted_movement_time * average_velocity + ) + + zoom_predicted_box = np.round(zoom_predicted_box).astype(int) + + centroid_x = round((zoom_predicted_box[0] + zoom_predicted_box[2]) / 2) + centroid_y = round((zoom_predicted_box[1] + zoom_predicted_box[3]) / 2) + + # recalculate pan and tilt with new centroid + pan = ((centroid_x / camera_width) - 0.5) * 2 + tilt = (0.5 - (centroid_y / camera_height)) * 2 + + logger.debug( + f"{camera}: Zoom amount: {zoom}, zoom predicted time: {zoom_predicted_movement_time}, zoom predicted box: {tuple(zoom_predicted_box)}" + ) + self._enqueue_move(camera, obj.obj_data["frame_time"], pan, tilt, zoom) def _autotrack_move_zoom_only(self, camera, obj): @@ -1242,7 +1315,7 @@ class PtzAutoTracker: return # this is a brand new object that's on our camera, has our label, entered the zone, - # is not a false positive, and is not initially motionless + # is not a false positive, and is active if ( # new object self.tracked_object[camera] is None @@ -1252,7 +1325,7 @@ class PtzAutoTracker: and not obj.previous["false_positive"] and not obj.false_positive and not self.tracked_object_history[camera] - and obj.obj_data["motionless_count"] == 0 + and obj.active ): logger.debug( f"{camera}: New object: {obj.obj_data['id']} {obj.obj_data['box']} {obj.obj_data['frame_time']}" diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 5a3deefda..02f5d5e74 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -1491,7 +1491,9 @@ class TestConfig(unittest.TestCase): "fps": 5, }, "onvif": { - "autotracking": {"movement_weights": "0, 1, 1.23, 2.34, 0.50"} + "autotracking": { + "movement_weights": "0, 1, 1.23, 2.34, 0.50, 1" + } }, } }, @@ -1504,6 +1506,7 @@ class TestConfig(unittest.TestCase): "1.23", "2.34", "0.5", + "1.0", ] def test_fails_invalid_movement_weights(self): diff --git a/frigate/util/config.py b/frigate/util/config.py index 7bdc0c3d6..59d704b9e 100644 --- a/frigate/util/config.py +++ b/frigate/util/config.py @@ -319,6 +319,21 @@ def migrate_016_0(config: dict[str, dict[str, any]]) -> dict[str, dict[str, any] camera_config["live"] = live_config + # add another value to movement_weights for autotracking cams + onvif_config = camera_config.get("onvif", {}) + if "autotracking" in onvif_config: + movement_weights = ( + camera_config.get("onvif", {}) + .get("autotracking") + .get("movement_weights", {}) + ) + + if movement_weights and len(movement_weights.split(",")) == 5: + onvif_config["autotracking"]["movement_weights"] = ( + movement_weights + ", 0" + ) + camera_config["onvif"] = onvif_config + new_config["cameras"][name] = camera_config new_config["version"] = "0.16-0"