From b267cfb520119bc3c7b52ef0e6470815f8fb74fa Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 3 Jul 2023 09:22:21 -0500 Subject: [PATCH] use mp event instead of value for ptz status --- frigate/app.py | 3 +- frigate/ptz.py | 16 +++--- frigate/ptz_autotrack.py | 89 ++++++++++++++++++-------------- frigate/track/norfair_tracker.py | 6 +-- frigate/types.py | 2 +- frigate/video.py | 10 ++-- 6 files changed, 69 insertions(+), 57 deletions(-) diff --git a/frigate/app.py b/frigate/app.py index 99b2cd3e9..bc1ba7f6f 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -122,7 +122,7 @@ class FrigateApp: "i", self.config.cameras[camera_name].onvif.autotracking.enabled, ), - "ptz_moving": mp.Value("i", 0), + "ptz_stopped": mp.Event(), "motion_threshold": mp.Value( "i", self.config.cameras[camera_name].motion.threshold ), @@ -137,6 +137,7 @@ class FrigateApp: "capture_process": None, "process": None, } + self.camera_metrics[camera_name]["ptz_stopped"].set() self.record_metrics[camera_name] = { "record_enabled": mp.Value( "i", self.config.cameras[camera_name].record.enabled diff --git a/frigate/ptz.py b/frigate/ptz.py index f7971a455..253d76ef6 100644 --- a/frigate/ptz.py +++ b/frigate/ptz.py @@ -216,7 +216,7 @@ class OnvifController: return self.cams[camera_name]["active"] = True - self.camera_metrics[camera_name]["ptz_moving"].value = True + self.camera_metrics[camera_name]["ptz_stopped"].clear() onvif: ONVIFCamera = self.cams[camera_name]["onvif"] move_request = self.cams[camera_name]["relative_move_request"] @@ -268,7 +268,7 @@ class OnvifController: return self.cams[camera_name]["active"] = True - self.camera_metrics[camera_name]["ptz_moving"].value = True + self.camera_metrics[camera_name]["ptz_stopped"].clear() move_request = self.cams[camera_name]["move_request"] onvif: ONVIFCamera = self.cams[camera_name]["onvif"] preset_token = self.cams[camera_name]["presets"][preset] @@ -278,7 +278,7 @@ class OnvifController: "PresetToken": preset_token, } ) - self.camera_metrics[camera_name]["ptz_moving"].value = False + self.camera_metrics[camera_name]["ptz_stopped"].set() self.cams[camera_name]["active"] = False def _zoom(self, camera_name: str, command: OnvifCommandEnum) -> None: @@ -350,10 +350,12 @@ class OnvifController: status_request = self.cams[camera_name]["status_request"] status = onvif.get_service("ptz").GetStatus(status_request) - self.cams[camera_name]["active"] = status.MoveStatus.PanTilt != "IDLE" - self.camera_metrics[camera_name]["ptz_moving"].value = ( - status.MoveStatus.PanTilt != "IDLE" - ) + if status.MoveStatus.PanTilt == "IDLE" or status.MoveStatus.Zoom == "IDLE": + self.cams[camera_name]["active"] = False + self.camera_metrics[camera_name]["ptz_stopped"].set() + else: + self.cams[camera_name]["active"] = True + self.camera_metrics[camera_name]["ptz_stopped"].clear() return { "pan": status.Position.PanTilt.x, diff --git a/frigate/ptz_autotrack.py b/frigate/ptz_autotrack.py index f1fffc59a..9feef0ed6 100644 --- a/frigate/ptz_autotrack.py +++ b/frigate/ptz_autotrack.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) class PtzMotionEstimator: - def __init__(self, config: CameraConfig, ptz_moving) -> None: + def __init__(self, config: CameraConfig, ptz_stopped) -> None: self.frame_manager = SharedMemoryFrameManager() # homography is nice (zooming) but slow, translation is pan/tilt only but fast. self.norfair_motion_estimator = MotionEstimator( @@ -31,11 +31,14 @@ class PtzMotionEstimator: ) self.camera_config = config self.coord_transformations = None - self.ptz_moving = ptz_moving + self.ptz_stopped = ptz_stopped logger.debug(f"Motion estimator init for cam: {config.name}") def motion_estimator(self, detections, frame_time, camera_name): - if self.camera_config.onvif.autotracking.enabled and self.ptz_moving.value: + if ( + self.camera_config.onvif.autotracking.enabled + and not self.ptz_stopped.is_set() + ): # logger.debug( # f"Motion estimator running for {camera_name} - frame time: {frame_time}" # ) @@ -96,6 +99,11 @@ class PtzAutoTrackerThread(threading.Thread): if cam.onvif.autotracking.enabled: self.ptz_autotracker.camera_maintenance(camera_name) time.sleep(1) + else: + # disabled dynamically by mqtt + if self.ptz_autotracker.tracked_object.get(camera_name): + self.ptz_autotracker.tracked_object[camera_name] = None + self.ptz_autotracker.tracked_object_previous[camera_name] = None time.sleep(0.1) logger.info("Exiting autotracker...") @@ -169,6 +177,13 @@ class PtzAutoTracker: tilt = 0 while not self.move_queues[camera].empty(): + queued_pan, queued_tilt = self.move_queues[camera].queue[0] + + # If exceeding the movement range, keep it in the queue and move now + if abs(pan + queued_pan) > 1.0 or abs(tilt + queued_tilt) > 1.0: + logger.debug("Pan or tilt value exceeds 1.0") + break + queued_pan, queued_tilt = self.move_queues[camera].get() logger.debug( f"queue pan: {queued_pan}, queue tilt: {queued_tilt}" @@ -182,16 +197,15 @@ class PtzAutoTracker: logger.debug(f"final pan: {pan}, final tilt: {tilt}") - self.onvif._move_relative(camera, pan, tilt, 0.1) + self.onvif._move_relative(camera, pan, tilt, 1) # Wait until the camera finishes moving - while self.camera_metrics[camera]["ptz_moving"].value: - pass + self.camera_metrics[camera]["ptz_stopped"].wait() except queue.Empty: - pass + time.sleep(0.1) - def enqueue_move(self, camera, pan, tilt): + def _enqueue_move(self, camera, pan, tilt): move_data = (pan, tilt) logger.debug(f"enqueue pan: {pan}, enqueue tilt: {tilt}") self.move_queues[camera].put(move_data) @@ -208,7 +222,7 @@ class PtzAutoTracker: tilt = 0.5 - (obj.obj_data["centroid"][1] / camera_height) # ideas: check object velocity for camera speed? - self.enqueue_move(camera, -pan, tilt) + self._enqueue_move(camera, -pan, tilt) def autotrack_object(self, camera, obj): camera_config = self.config.cameras[camera] @@ -317,35 +331,30 @@ class PtzAutoTracker: # returns camera to preset after timeout when tracking is over autotracker_config = self.config.cameras[camera].onvif.autotracking - if autotracker_config.enabled: - if not self.autotracker_init[camera]: - self._autotracker_setup(self.config.cameras[camera], camera) - # regularly update camera status - if self.camera_metrics[camera]["ptz_moving"].value: - self.onvif.get_camera_status(camera) + if not self.autotracker_init[camera]: + self._autotracker_setup(self.config.cameras[camera], camera) + # regularly update camera status + if not self.camera_metrics[camera]["ptz_stopped"].is_set(): + self.onvif.get_camera_status(camera) - # return to preset if tracking is over - if ( - self.tracked_object[camera] is None - and self.tracked_object_previous[camera] is not None - and ( - # might want to use a different timestamp here? - time.time() - - self.tracked_object_previous[camera].obj_data["frame_time"] - > autotracker_config.timeout - ) - and autotracker_config.return_preset - and not self.camera_metrics[camera]["ptz_moving"].value - ): - logger.debug( - f"Autotrack: Time is {time.time()}, returning to preset: {autotracker_config.return_preset}" - ) - self.onvif._move_to_preset( - camera, - autotracker_config.return_preset.lower(), - ) - self.tracked_object_previous[camera] = None - - def disable_autotracking(self, camera): - # need to call this if autotracking is disabled by mqtt?? - self.tracked_object[camera] = None + # return to preset if tracking is over + if ( + self.tracked_object[camera] is None + and self.tracked_object_previous[camera] is not None + and ( + # might want to use a different timestamp here? + time.time() + - self.tracked_object_previous[camera].obj_data["frame_time"] + > autotracker_config.timeout + ) + and autotracker_config.return_preset + ): + self.camera_metrics[camera]["ptz_stopped"].wait() + logger.debug( + f"Autotrack: Time is {time.time()}, returning to preset: {autotracker_config.return_preset}" + ) + self.onvif._move_to_preset( + camera, + autotracker_config.return_preset.lower(), + ) + self.tracked_object_previous[camera] = None diff --git a/frigate/track/norfair_tracker.py b/frigate/track/norfair_tracker.py index c051bf13a..36bb2532f 100644 --- a/frigate/track/norfair_tracker.py +++ b/frigate/track/norfair_tracker.py @@ -55,7 +55,7 @@ def frigate_distance(detection: Detection, tracked_object) -> float: class NorfairTracker(ObjectTracker): - def __init__(self, config: CameraConfig, ptz_autotracker_enabled, ptz_moving): + def __init__(self, config: CameraConfig, ptz_autotracker_enabled, ptz_stopped): self.tracked_objects = {} self.disappeared = {} self.positions = {} @@ -63,7 +63,7 @@ class NorfairTracker(ObjectTracker): self.camera_config = config self.detect_config = config.detect self.ptz_autotracker_enabled = ptz_autotracker_enabled.value - self.ptz_moving = ptz_moving + self.ptz_stopped = ptz_stopped self.camera_name = config.name self.track_id_map = {} # TODO: could also initialize a tracker per object class if there @@ -75,7 +75,7 @@ class NorfairTracker(ObjectTracker): hit_counter_max=self.max_disappeared, ) if self.ptz_autotracker_enabled: - self.ptz_motion_estimator = PtzMotionEstimator(config, self.ptz_moving) + self.ptz_motion_estimator = PtzMotionEstimator(config, self.ptz_stopped) def register(self, track_id, obj): rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) diff --git a/frigate/types.py b/frigate/types.py index 29991552f..7914d0b90 100644 --- a/frigate/types.py +++ b/frigate/types.py @@ -17,7 +17,7 @@ class CameraMetricsTypes(TypedDict): motion_enabled: Synchronized improve_contrast_enabled: Synchronized ptz_autotracker_enabled: Synchronized - ptz_moving: Synchronized + ptz_stopped: Synchronized motion_threshold: Synchronized motion_contour_area: Synchronized process: Optional[Process] diff --git a/frigate/video.py b/frigate/video.py index 597e90da9..0e30d3b4b 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -458,7 +458,7 @@ def track_camera( motion_enabled = process_info["motion_enabled"] improve_contrast_enabled = process_info["improve_contrast_enabled"] ptz_autotracker_enabled = process_info["ptz_autotracker_enabled"] - ptz_moving = process_info["ptz_moving"] + ptz_stopped = process_info["ptz_stopped"] motion_threshold = process_info["motion_threshold"] motion_contour_area = process_info["motion_contour_area"] @@ -478,7 +478,7 @@ def track_camera( name, labelmap, detection_queue, result_connection, model_config, stop_event ) - object_tracker = NorfairTracker(config, ptz_autotracker_enabled, ptz_moving) + object_tracker = NorfairTracker(config, ptz_autotracker_enabled, ptz_stopped) frame_manager = SharedMemoryFrameManager() @@ -499,7 +499,7 @@ def track_camera( detection_enabled, motion_enabled, stop_event, - ptz_moving, + ptz_stopped, ) logger.info(f"{name}: exiting subprocess") @@ -724,7 +724,7 @@ def process_frames( detection_enabled: mp.Value, motion_enabled: mp.Value, stop_event, - ptz_moving: mp.Value, + ptz_stopped: mp.Event, exit_on_empty: bool = False, ): # attribute labels are not tracked and are not assigned regions @@ -769,7 +769,7 @@ def process_frames( # look for motion if enabled motion_boxes = ( motion_detector.detect(frame) - if motion_enabled.value and not ptz_moving.value + if motion_enabled.value and ptz_stopped.is_set() else [] )