From 493d72608142e011cae482f7a1225a7d3fa69710 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Fri, 16 Feb 2024 08:25:00 -0700 Subject: [PATCH] remove motion and detection camera metrics --- frigate/app.py | 30 ------------- frigate/comms/config_updater.py | 4 +- frigate/comms/dispatcher.py | 72 ++++++++++++++----------------- frigate/comms/inter_process.py | 2 +- frigate/config.py | 16 ++++++- frigate/events/audio.py | 7 +-- frigate/motion/improved_motion.py | 31 +++++++------ frigate/output/birdseye.py | 12 +++--- frigate/record/maintainer.py | 7 +-- frigate/video.py | 24 +++++------ 10 files changed, 91 insertions(+), 114 deletions(-) diff --git a/frigate/app.py b/frigate/app.py index 63672b014..94b680a93 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -129,35 +129,6 @@ class FrigateApp: # issue https://github.com/python/typeshed/issues/8799 # from mypy 0.981 onwards "process_fps": mp.Value("d", 0.0), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "detection_enabled": mp.Value( # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "i", - self.config.cameras[camera_name].detect.enabled, - ), - "motion_enabled": mp.Value("i", True), # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "improve_contrast_enabled": mp.Value( # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "i", - self.config.cameras[camera_name].motion.improve_contrast, - ), - "motion_threshold": mp.Value( # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "i", - self.config.cameras[camera_name].motion.threshold, - ), - "motion_contour_area": mp.Value( # type: ignore[typeddict-item] - # issue https://github.com/python/typeshed/issues/8799 - # from mypy 0.981 onwards - "i", - self.config.cameras[camera_name].motion.contour_area, - ), "detection_fps": mp.Value("d", 0.0), # type: ignore[typeddict-item] # issue https://github.com/python/typeshed/issues/8799 # from mypy 0.981 onwards @@ -387,7 +358,6 @@ class FrigateApp: self.config, self.inter_config_updater, self.onvif_controller, - self.camera_metrics, self.ptz_metrics, comms, ) diff --git a/frigate/comms/config_updater.py b/frigate/comms/config_updater.py index b87a7f30e..e5ea2e5ce 100644 --- a/frigate/comms/config_updater.py +++ b/frigate/comms/config_updater.py @@ -46,9 +46,7 @@ class ConfigSubscriber: def check_for_update(self) -> Optional[tuple[str, any]]: """Returns updated config or None if no update.""" try: - topic = self.socket.recv_string( - flags=zmq.NOBLOCK - ) + topic = self.socket.recv_string(flags=zmq.NOBLOCK) return (topic, self.socket.recv_pyobj()) except zmq.ZMQError: return (None, None) diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index 81ce80d1a..84b84eb3c 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -9,7 +9,7 @@ from frigate.config import BirdseyeModeEnum, FrigateConfig from frigate.const import INSERT_MANY_RECORDINGS, INSERT_PREVIEW, REQUEST_REGION_GRID from frigate.models import Previews, Recordings from frigate.ptz.onvif import OnvifCommandEnum, OnvifController -from frigate.types import CameraMetricsTypes, PTZMetricsTypes +from frigate.types import PTZMetricsTypes from frigate.util.object import get_camera_regions_grid from frigate.util.services import restart_frigate @@ -43,14 +43,12 @@ class Dispatcher: config: FrigateConfig, config_updater: ConfigPublisher, onvif: OnvifController, - camera_metrics: dict[str, CameraMetricsTypes], ptz_metrics: dict[str, PTZMetricsTypes], communicators: list[Communicator], ) -> None: self.config = config self.config_updater = config_updater self.onvif = onvif - self.camera_metrics = camera_metrics self.ptz_metrics = ptz_metrics self.comms = communicators @@ -119,44 +117,51 @@ class Dispatcher: def _on_detect_command(self, camera_name: str, payload: str) -> None: """Callback for detect topic.""" detect_settings = self.config.cameras[camera_name].detect + motion_settings = self.config.cameras[camera_name].motion if payload == "ON": - if not self.camera_metrics[camera_name]["detection_enabled"].value: + if not detect_settings.enabled: logger.info(f"Turning on detection for {camera_name}") - self.camera_metrics[camera_name]["detection_enabled"].value = True detect_settings.enabled = True - if not self.camera_metrics[camera_name]["motion_enabled"].value: + if not motion_settings.enabled: logger.info( f"Turning on motion for {camera_name} due to detection being enabled." ) - self.camera_metrics[camera_name]["motion_enabled"].value = True + motion_settings.enabled = True + self.config_updater.publish( + f"config/motion/{camera_name}", motion_settings + ) self.publish(f"{camera_name}/motion/state", payload, retain=True) elif payload == "OFF": - if self.camera_metrics[camera_name]["detection_enabled"].value: + if detect_settings.enabled: logger.info(f"Turning off detection for {camera_name}") - self.camera_metrics[camera_name]["detection_enabled"].value = False detect_settings.enabled = False + self.config_updater.publish(f"config/detect/{camera_name}", detect_settings) self.publish(f"{camera_name}/detect/state", payload, retain=True) def _on_motion_command(self, camera_name: str, payload: str) -> None: """Callback for motion topic.""" + detect_settings = self.config.cameras[camera_name].detect + motion_settings = self.config.cameras[camera_name].motion + if payload == "ON": - if not self.camera_metrics[camera_name]["motion_enabled"].value: + if not motion_settings.enabled: logger.info(f"Turning on motion for {camera_name}") - self.camera_metrics[camera_name]["motion_enabled"].value = True + motion_settings.enabled = True elif payload == "OFF": - if self.camera_metrics[camera_name]["detection_enabled"].value: + if detect_settings.enabled: logger.error( "Turning off motion is not allowed when detection is enabled." ) return - if self.camera_metrics[camera_name]["motion_enabled"].value: + if motion_settings.enabled: logger.info(f"Turning off motion for {camera_name}") - self.camera_metrics[camera_name]["motion_enabled"].value = False + motion_settings.enabled = False + self.config_updater.publish(f"config/motion/{camera_name}", motion_settings) self.publish(f"{camera_name}/motion/state", payload, retain=True) def _on_motion_improve_contrast_command( @@ -166,20 +171,15 @@ class Dispatcher: motion_settings = self.config.cameras[camera_name].motion if payload == "ON": - if not self.camera_metrics[camera_name]["improve_contrast_enabled"].value: + if not motion_settings.improve_contrast: logger.info(f"Turning on improve contrast for {camera_name}") - self.camera_metrics[camera_name][ - "improve_contrast_enabled" - ].value = True motion_settings.improve_contrast = True # type: ignore[union-attr] elif payload == "OFF": - if self.camera_metrics[camera_name]["improve_contrast_enabled"].value: + if motion_settings.improve_contrast: logger.info(f"Turning off improve contrast for {camera_name}") - self.camera_metrics[camera_name][ - "improve_contrast_enabled" - ].value = False motion_settings.improve_contrast = False # type: ignore[union-attr] + self.config_updater.publish(f"config/motion/{camera_name}", motion_settings) self.publish(f"{camera_name}/improve_contrast/state", payload, retain=True) def _on_ptz_autotracker_command(self, camera_name: str, payload: str) -> None: @@ -218,8 +218,8 @@ class Dispatcher: motion_settings = self.config.cameras[camera_name].motion logger.info(f"Setting motion contour area for {camera_name}: {payload}") - self.camera_metrics[camera_name]["motion_contour_area"].value = payload motion_settings.contour_area = payload # type: ignore[union-attr] + self.config_updater.publish(f"config/motion/{camera_name}", motion_settings) self.publish(f"{camera_name}/motion_contour_area/state", payload, retain=True) def _on_motion_threshold_command(self, camera_name: str, payload: int) -> None: @@ -232,8 +232,8 @@ class Dispatcher: motion_settings = self.config.cameras[camera_name].motion logger.info(f"Setting motion threshold for {camera_name}: {payload}") - self.camera_metrics[camera_name]["motion_threshold"].value = payload motion_settings.threshold = payload # type: ignore[union-attr] + self.config_updater.publish(f"config/motion/{camera_name}", motion_settings) self.publish(f"{camera_name}/motion_threshold/state", payload, retain=True) def _on_audio_command(self, camera_name: str, payload: str) -> None: @@ -255,9 +255,7 @@ class Dispatcher: logger.info(f"Turning off audio detection for {camera_name}") audio_settings.enabled = False - self.config_updater.publish( - f"config/audio/{camera_name}", self.config.cameras[camera_name].audio - ) + self.config_updater.publish(f"config/audio/{camera_name}", audio_settings) self.publish(f"{camera_name}/audio/state", payload, retain=True) def _on_recordings_command(self, camera_name: str, payload: str) -> None: @@ -279,9 +277,7 @@ class Dispatcher: logger.info(f"Turning off recordings for {camera_name}") record_settings.enabled = False - self.config_updater.publish( - f"config/record/{camera_name}", self.config.cameras[camera_name].record - ) + self.config_updater.publish(f"config/record/{camera_name}", record_settings) self.publish(f"{camera_name}/recordings/state", payload, retain=True) def _on_snapshots_command(self, camera_name: str, payload: str) -> None: @@ -328,9 +324,7 @@ class Dispatcher: logger.info(f"Turning off birdseye for {camera_name}") birdseye_settings.enabled = False - self.config_updater.publish( - f"config/birdseye/{camera_name}", self.config.cameras[camera_name].birdseye - ) + self.config_updater.publish(f"config/birdseye/{camera_name}", birdseye_settings) self.publish(f"{camera_name}/birdseye/state", payload, retain=True) def _on_birdseye_mode_command(self, camera_name: str, payload: str) -> None: @@ -340,18 +334,16 @@ class Dispatcher: logger.info(f"Invalid birdseye_mode command: {payload}") return - birdseye_config = self.config.cameras[camera_name].birdseye + birdseye_settings = self.config.cameras[camera_name].birdseye - if not birdseye_config.enabled: + if not birdseye_settings.enabled: logger.info(f"Birdseye mode not enabled for {camera_name}") return - birdseye_config.mode = BirdseyeModeEnum(payload.lower()) + birdseye_settings.mode = BirdseyeModeEnum(payload.lower()) logger.info( - f"Setting birdseye mode for {camera_name} to {birdseye_config.mode}" + f"Setting birdseye mode for {camera_name} to {birdseye_settings.mode}" ) - self.config_updater.publish( - f"config/birdseye/{camera_name}", self.config.cameras[camera_name].birdseye - ) + self.config_updater.publish(f"config/birdseye/{camera_name}", birdseye_settings) self.publish(f"{camera_name}/birdseye_mode/state", payload, retain=True) diff --git a/frigate/comms/inter_process.py b/frigate/comms/inter_process.py index c312bf869..bcf99738f 100644 --- a/frigate/comms/inter_process.py +++ b/frigate/comms/inter_process.py @@ -32,7 +32,7 @@ class InterProcessCommunicator(Communicator): self.reader_thread.start() def read(self) -> None: - while not self.stop_event.wait(0.5): + while not self.stop_event.wait(0.1): while True: # load all messages that are queued try: (topic, value) = self.socket.recv_pyobj(flags=zmq.NOBLOCK) diff --git a/frigate/config.py b/frigate/config.py index 5ab51b026..be5c469b3 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -300,6 +300,7 @@ class RecordConfig(FrigateBaseModel): class MotionConfig(FrigateBaseModel): + enabled: bool = Field(default=True, title="Enable motion on all cameras.") threshold: int = Field( default=30, title="Motion detection threshold (1-255).", @@ -321,6 +322,9 @@ class MotionConfig(FrigateBaseModel): default=30, title="Delay for updating MQTT with no motion detected.", ) + enabled_in_config: Optional[bool] = Field( + title="Keep track of original state of motion detection." + ) class RuntimeMotionConfig(MotionConfig): @@ -1041,6 +1045,14 @@ def verify_autotrack_zones(camera_config: CameraConfig) -> ValueError | None: ) +def verify_motion_and_detect(camera_config: CameraConfig) -> ValueError | None: + """Verify that required_zones are specified when autotracking is enabled.""" + if camera_config.detect.enabled and not camera_config.motion.enabled: + raise ValueError( + f"Camera {camera_config.name} has motion detection disabled and object detection enabled but object detection requires motion detection ." + ) + + class FrigateConfig(FrigateBaseModel): mqtt: MqttConfig = Field(title="MQTT Configuration.") database: DatabaseConfig = Field( @@ -1202,8 +1214,8 @@ class FrigateConfig(FrigateBaseModel): **FRIGATE_ENV_VARS ) # set config pre-value - camera_config.record.enabled_in_config = camera_config.record.enabled camera_config.audio.enabled_in_config = camera_config.audio.enabled + camera_config.record.enabled_in_config = camera_config.record.enabled camera_config.onvif.autotracking.enabled_in_config = ( camera_config.onvif.autotracking.enabled ) @@ -1250,6 +1262,7 @@ class FrigateConfig(FrigateBaseModel): raw_mask=camera_config.motion.mask, **camera_config.motion.dict(exclude_unset=True), ) + camera_config.motion.enabled_in_config = camera_config.motion.enabled # Set live view stream if none is set if not camera_config.live.stream_name: @@ -1261,6 +1274,7 @@ class FrigateConfig(FrigateBaseModel): verify_recording_segments_setup_with_reasonable_time(camera_config) verify_zone_objects_are_tracked(camera_config) verify_autotrack_zones(camera_config) + verify_motion_and_detect(camera_config) # generate the ffmpeg commands camera_config.create_ffmpeg_cmds() diff --git a/frigate/events/audio.py b/frigate/events/audio.py index 27498e252..c56bfa1a8 100644 --- a/frigate/events/audio.py +++ b/frigate/events/audio.py @@ -338,9 +338,10 @@ class AudioEventMaintainer(threading.Thread): while not self.stop_event.is_set(): # check if there is an updated config - updated_topic, updated_audio_config = ( - self.config_subscriber.check_for_update() - ) + ( + updated_topic, + updated_audio_config, + ) = self.config_subscriber.check_for_update() if updated_topic: self.config.audio = updated_audio_config diff --git a/frigate/motion/improved_motion.py b/frigate/motion/improved_motion.py index 603d8fda4..a9677a468 100644 --- a/frigate/motion/improved_motion.py +++ b/frigate/motion/improved_motion.py @@ -5,6 +5,7 @@ import imutils import numpy as np from scipy.ndimage import gaussian_filter +from frigate.comms.config_updater import ConfigSubscriber from frigate.config import MotionConfig from frigate.motion import MotionDetector @@ -17,9 +18,6 @@ class ImprovedMotionDetector(MotionDetector): frame_shape, config: MotionConfig, fps: int, - improve_contrast, - threshold, - contour_area, name="improved", blur_radius=1, interpolation=cv2.INTER_NEAREST, @@ -44,14 +42,12 @@ class ImprovedMotionDetector(MotionDetector): self.mask = np.where(resized_mask == [0]) self.save_images = False self.calibrating = True - self.improve_contrast = improve_contrast - self.threshold = threshold - self.contour_area = contour_area self.blur_radius = blur_radius self.interpolation = interpolation self.contrast_values = np.zeros((contrast_frame_history, 2), np.uint8) self.contrast_values[:, 1:2] = 255 self.contrast_values_index = 0 + self.config_subscriber = ConfigSubscriber(f"config/motion/{name}") def is_calibrating(self): return self.calibrating @@ -59,6 +55,15 @@ class ImprovedMotionDetector(MotionDetector): def detect(self, frame): motion_boxes = [] + # check for updated motion config + _, updated_motion_config = self.config_subscriber.check_for_update() + + if updated_motion_config: + self.config = updated_motion_config + + if not self.config.enabled: + return motion_boxes + gray = frame[0 : self.frame_shape[0], 0 : self.frame_shape[1]] # resize frame @@ -72,7 +77,7 @@ class ImprovedMotionDetector(MotionDetector): resized_saved = resized_frame.copy() # Improve contrast - if self.improve_contrast.value: + if self.config.improve_contrast: # TODO tracking moving average of min/max to avoid sudden contrast changes minval = np.percentile(resized_frame, 4).astype(np.uint8) maxval = np.percentile(resized_frame, 96).astype(np.uint8) @@ -110,7 +115,7 @@ class ImprovedMotionDetector(MotionDetector): # compute the threshold image for the current frame thresh = cv2.threshold( - frameDelta, self.threshold.value, 255, cv2.THRESH_BINARY + frameDelta, self.config.threshold, 255, cv2.THRESH_BINARY )[1] # dilate the thresholded image to fill in holes, then find contours @@ -127,7 +132,7 @@ class ImprovedMotionDetector(MotionDetector): # if the contour is big enough, count it as motion contour_area = cv2.contourArea(c) total_contour_area += contour_area - if contour_area > self.contour_area.value: + if contour_area > self.config.contour_area: x, y, w, h = cv2.boundingRect(c) motion_boxes.append( ( @@ -170,9 +175,11 @@ class ImprovedMotionDetector(MotionDetector): ] cv2.imwrite( f"debug/frames/{self.name}-{self.frame_counter}.jpg", - cv2.hconcat(frames) - if self.frame_shape[0] > self.frame_shape[1] - else cv2.vconcat(frames), + ( + cv2.hconcat(frames) + if self.frame_shape[0] > self.frame_shape[1] + else cv2.vconcat(frames) + ), ) if len(motion_boxes) > 0: diff --git a/frigate/output/birdseye.py b/frigate/output/birdseye.py index 72a7f2dfe..b4ac2198f 100644 --- a/frigate/output/birdseye.py +++ b/frigate/output/birdseye.py @@ -433,10 +433,7 @@ class BirdsEyeFrameManager: # check if we need to reset the layout because there is a different number of cameras if len(self.active_cameras) - len(active_cameras) == 0: - if ( - len(self.active_cameras) == 1 - and self.active_cameras[0] == active_cameras[0] - ): + if len(self.active_cameras) == 1 and self.active_cameras != active_cameras: reset_layout = True elif max_camera_refresh: reset_layout = True @@ -758,9 +755,10 @@ class Birdseye: ) -> None: # check if there is an updated config while True: - updated_topic, updated_birdseye_config = ( - self.config_subscriber.check_for_update() - ) + ( + updated_topic, + updated_birdseye_config, + ) = self.config_subscriber.check_for_update() if not updated_topic: break diff --git a/frigate/record/maintainer.py b/frigate/record/maintainer.py index 6a70ee298..64c2143eb 100644 --- a/frigate/record/maintainer.py +++ b/frigate/record/maintainer.py @@ -444,9 +444,10 @@ class RecordingMaintainer(threading.Thread): # check if there is an updated config while True: - updated_topic, updated_record_config = ( - self.config_subscriber.check_for_update() - ) + ( + updated_topic, + updated_record_config, + ) = self.config_subscriber.check_for_update() if not updated_topic: break diff --git a/frigate/video.py b/frigate/video.py index 774da4c99..d954f78fc 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -11,6 +11,7 @@ import time import cv2 from setproctitle import setproctitle +from frigate.comms.config_updater import ConfigSubscriber from frigate.comms.inter_process import InterProcessRequestor from frigate.config import CameraConfig, DetectConfig, ModelConfig from frigate.const import ( @@ -406,11 +407,6 @@ def track_camera( listen() frame_queue = process_info["frame_queue"] - detection_enabled = process_info["detection_enabled"] - motion_enabled = process_info["motion_enabled"] - improve_contrast_enabled = process_info["improve_contrast_enabled"] - motion_threshold = process_info["motion_threshold"] - motion_contour_area = process_info["motion_contour_area"] frame_shape = config.frame_shape objects_to_track = config.objects.track @@ -420,9 +416,6 @@ def track_camera( frame_shape, config.motion, config.detect.fps, - improve_contrast_enabled, - motion_threshold, - motion_contour_area, ) object_detector = RemoteObjectDetector( name, labelmap, detection_queue, result_connection, model_config, stop_event @@ -450,8 +443,6 @@ def track_camera( process_info, objects_to_track, object_filters, - detection_enabled, - motion_enabled, stop_event, ptz_metrics, region_grid, @@ -519,8 +510,6 @@ def process_frames( process_info: dict, objects_to_track: list[str], object_filters, - detection_enabled: mp.Value, - motion_enabled: mp.Value, stop_event, ptz_metrics: PTZMetricsTypes, region_grid, @@ -530,6 +519,7 @@ def process_frames( detection_fps = process_info["detection_fps"] current_frame_time = process_info["detection_frame"] next_region_update = get_tomorrow_at_time(2) + config_subscriber = ConfigSubscriber(f"config/detect/{camera_name}") fps_tracker = EventsPerSecond() fps_tracker.start() @@ -540,6 +530,12 @@ def process_frames( region_min_size = get_min_region_size(model_config) while not stop_event.is_set(): + # check for updated detect config + _, updated_detect_config = config_subscriber.check_for_update() + + if updated_detect_config: + detect_config = updated_detect_config + if ( datetime.datetime.now().astimezone(datetime.timezone.utc) > next_region_update @@ -570,13 +566,13 @@ def process_frames( continue # look for motion if enabled - motion_boxes = motion_detector.detect(frame) if motion_enabled.value else [] + motion_boxes = motion_detector.detect(frame) regions = [] consolidated_detections = [] # if detection is disabled - if not detection_enabled.value: + if not detect_config.enabled: object_tracker.match_and_update(frame_time, []) else: # get stationary object ids