remove motion and detection camera metrics

This commit is contained in:
Nicolas Mowen 2024-02-16 08:25:00 -07:00
parent e95004c211
commit 154a810592
12 changed files with 94 additions and 124 deletions

View File

@ -22,7 +22,7 @@ from frigate.comms.dispatcher import Communicator, Dispatcher
from frigate.comms.inter_process import InterProcessCommunicator
from frigate.comms.mqtt import MqttClient
from frigate.comms.ws import WebSocketClient
from frigate.config import BirdseyeModeEnum, FrigateConfig
from frigate.config import FrigateConfig
from frigate.const import (
CACHE_DIR,
CLIPS_DIR,
@ -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,
)

View File

@ -3,7 +3,7 @@
import multiprocessing as mp
import os
from multiprocessing.synchronize import Event as MpEvent
from typing import Callable, Optional
from typing import Optional
import zmq
@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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),
else cv2.vconcat(frames)
),
)
if len(motion_boxes) > 0:

View File

@ -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

View File

@ -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

View File

@ -265,7 +265,7 @@ def stats_snapshot(
"process_fps": round(camera_stats["process_fps"].value, 2),
"skipped_fps": round(camera_stats["skipped_fps"].value, 2),
"detection_fps": round(camera_stats["detection_fps"].value, 2),
"detection_enabled": camera_stats["detection_enabled"].value,
"detection_enabled": config.cameras[name].detect.enabled,
"pid": pid,
"capture_pid": cpid,
"ffmpeg_pid": ffmpeg_pid,

View File

@ -10,23 +10,16 @@ from frigate.object_detection import ObjectDetectProcess
class CameraMetricsTypes(TypedDict):
camera_fps: Synchronized
capture_process: Optional[Process]
detection_enabled: Synchronized
detection_fps: Synchronized
detection_frame: Synchronized
ffmpeg_pid: Synchronized
frame_queue: Queue
motion_enabled: Synchronized
improve_contrast_enabled: Synchronized
motion_threshold: Synchronized
motion_contour_area: Synchronized
process: Optional[Process]
process_fps: Synchronized
read_start: Synchronized
skipped_fps: Synchronized
audio_rms: Synchronized
audio_dBFS: Synchronized
birdseye_enabled: Synchronized
birdseye_mode: Synchronized
class PTZMetricsTypes(TypedDict):

View File

@ -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