From 121ea3782553003e98b8f7cf8dd92e1a9138e9e0 Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Thu, 4 Feb 2021 21:44:44 -0600 Subject: [PATCH] allow defining required zones for snapshots/clips/mqtt --- docs/docs/configuration/cameras.md | 16 ++++++++++------ frigate/config.py | 29 +++++++++++++++++++++++++---- frigate/events.py | 14 +++++++++++++- frigate/object_processing.py | 22 ++++++++++++++++++++-- 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index cb2c5df16..e301316a1 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -139,6 +139,8 @@ clips: # Optional: Objects to save clips for. (default: all tracked objects) objects: - person + # Optional: Restrict clips to objects that entered any of the listed zones (default: no required zones) + required_zones: [] # Optional: Camera override for retention settings (default: global values) retain: # Required: Default retention days (default: shown below) @@ -166,6 +168,8 @@ snapshots: crop: False # Optional: height to resize the snapshot to (default: original size) height: 175 + # Optional: Restrict snapshots to objects that entered any of the listed zones (default: no required zones) + required_zones: [] # Optional: Camera override for retention settings (default: global values) retain: # Required: Default retention days (default: shown below) @@ -226,12 +230,6 @@ cameras: # Optional: stream specific input args (default: inherit) input_args: - # Optional: camera specific global args (default: inherit) - global_args: - # Optional: camera specific hwaccel args (default: inherit) - hwaccel_args: - # Optional: camera specific input args (default: inherit) - input_args: # Optional: camera specific output args (default: inherit) output_args: @@ -291,6 +289,8 @@ cameras: # Optional: Objects to save clips for. (default: all tracked objects) objects: - person + # Optional: Restrict clips to objects that entered any of the listed zones (default: no required zones) + required_zones: [] # Optional: Camera override for retention settings (default: global values) retain: # Required: Default retention days (default: shown below) @@ -324,6 +324,8 @@ cameras: crop: False # Optional: height to resize the snapshot to (default: original size) height: 175 + # Optional: Restrict snapshots to objects that entered any of the listed zones (default: no required zones) + required_zones: [] # Optional: Camera override for retention settings (default: global values) retain: # Required: Default retention days (default: shown below) @@ -346,6 +348,8 @@ cameras: crop: True # Optional: height to resize the snapshot to (default: shown below) height: 270 + # Optional: Restrict mqtt messages to objects that entered any of the listed zones (default: no required zones) + required_zones: [] # Optional: Camera level object filters config. objects: diff --git a/frigate/config.py b/frigate/config.py index e84db706f..7035659ac 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -198,6 +198,7 @@ CAMERAS_SCHEMA = vol.Schema(vol.All( vol.Optional('enabled', default=False): bool, vol.Optional('pre_capture', default=5): int, vol.Optional('post_capture', default=5): int, + vol.Optional('required_zones', default=[]): [str], 'objects': [str], vol.Optional('retain', default={}): RETAIN_SCHEMA, }, @@ -213,6 +214,7 @@ CAMERAS_SCHEMA = vol.Schema(vol.All( vol.Optional('timestamp', default=False): bool, vol.Optional('bounding_box', default=False): bool, vol.Optional('crop', default=False): bool, + vol.Optional('required_zones', default=[]): [str], 'height': int, vol.Optional('retain', default={}): RETAIN_SCHEMA, }, @@ -221,7 +223,8 @@ CAMERAS_SCHEMA = vol.Schema(vol.All( vol.Optional('timestamp', default=True): bool, vol.Optional('bounding_box', default=True): bool, vol.Optional('crop', default=True): bool, - vol.Optional('height', default=270): int + vol.Optional('height', default=270): int, + vol.Optional('required_zones', default=[]): [str], }, vol.Optional('objects', default={}): OBJECTS_SCHEMA, vol.Optional('motion', default={}): MOTION_SCHEMA, @@ -570,6 +573,7 @@ class CameraSnapshotsConfig(): self._crop = config['crop'] self._height = config.get('height') self._retain = RetainConfig(global_config['snapshots']['retain'], config['retain']) + self._required_zones = config['required_zones'] @property def enabled(self): @@ -594,6 +598,10 @@ class CameraSnapshotsConfig(): @property def retain(self): return self._retain + + @property + def required_zones(self): + return self._required_zones def to_dict(self): return { @@ -602,7 +610,8 @@ class CameraSnapshotsConfig(): 'bounding_box': self.bounding_box, 'crop': self.crop, 'height': self.height, - 'retain': self.retain.to_dict() + 'retain': self.retain.to_dict(), + 'required_zones': self.required_zones } class CameraMqttConfig(): @@ -612,6 +621,7 @@ class CameraMqttConfig(): self._bounding_box = config['bounding_box'] self._crop = config['crop'] self._height = config.get('height') + self._required_zones = config['required_zones'] @property def enabled(self): @@ -633,13 +643,18 @@ class CameraMqttConfig(): def height(self): return self._height + @property + def required_zones(self): + return self._required_zones + def to_dict(self): return { 'enabled': self.enabled, 'timestamp': self.timestamp, 'bounding_box': self.bounding_box, 'crop': self.crop, - 'height': self.height + 'height': self.height, + 'required_zones': self.required_zones } class CameraClipsConfig(): @@ -649,6 +664,7 @@ class CameraClipsConfig(): self._post_capture = config['post_capture'] self._objects = config.get('objects') self._retain = RetainConfig(global_config['clips']['retain'], config['retain']) + self._required_zones = config['required_zones'] @property def enabled(self): @@ -670,13 +686,18 @@ class CameraClipsConfig(): def retain(self): return self._retain + @property + def required_zones(self): + return self._required_zones + def to_dict(self): return { 'enabled': self.enabled, 'pre_capture': self.pre_capture, 'post_capture': self.post_capture, 'objects': self.objects, - 'retain': self.retain.to_dict() + 'retain': self.retain.to_dict(), + 'required_zones': self.required_zones } class CameraRtmpConfig(): diff --git a/frigate/events.py b/frigate/events.py index ea43dc099..335927cb3 100644 --- a/frigate/events.py +++ b/frigate/events.py @@ -31,6 +31,18 @@ class EventProcessor(threading.Thread): self.event_processed_queue = event_processed_queue self.events_in_process = {} self.stop_event = stop_event + + def should_create_clip(self, camera, event_data): + if event_data['false_positive']: + return False + + # if there are required zones and there is no overlap + required_zones = self.config.cameras[camera].clips.required_zones + if len(required_zones) > 0 and not set(event_data['entered_zones']) & set(required_zones): + logger.debug(f"Not creating clip for {event_data['id']} because it did not enter required zones") + return False + + return True def refresh_cache(self): cached_files = os.listdir(CACHE_DIR) @@ -193,7 +205,7 @@ class EventProcessor(threading.Thread): if event_type == 'end': clips_config = self.config.cameras[camera].clips - if not event_data['false_positive']: + if self.should_create_clip(camera, event_data): clip_created = False if clips_config.enabled and (clips_config.objects is None or event_data['label'] in clips_config.objects): clip_created = self.create_clip(camera, event_data, clips_config.pre_capture, clips_config.post_capture) diff --git a/frigate/object_processing.py b/frigate/object_processing.py index 2f8f03dd4..af36665c7 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -454,7 +454,7 @@ class TrackedObjectProcessor(threading.Thread): message = { 'before': obj.previous, 'after': obj.to_dict(), 'type': 'end' } self.client.publish(f"{self.topic_prefix}/events", json.dumps(message), retain=False) # write snapshot to disk if enabled - if snapshot_config.enabled: + if snapshot_config.enabled and self.should_save_snapshot(camera, obj): jpg_bytes = obj.get_jpg_bytes( timestamp=snapshot_config.timestamp, bounding_box=snapshot_config.bounding_box, @@ -468,7 +468,7 @@ class TrackedObjectProcessor(threading.Thread): def snapshot(camera, obj: TrackedObject, current_frame_time): mqtt_config = self.config.cameras[camera].mqtt - if mqtt_config.enabled: + if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj): jpg_bytes = obj.get_jpg_bytes( timestamp=mqtt_config.timestamp, bounding_box=mqtt_config.bounding_box, @@ -499,6 +499,24 @@ class TrackedObjectProcessor(threading.Thread): # } self.zone_data = defaultdict(lambda: defaultdict(lambda: {})) + def should_save_snapshot(self, camera, obj: TrackedObject): + # if there are required zones and there is no overlap + required_zones = self.config.cameras[camera].snapshots.required_zones + if len(required_zones) > 0 and not obj.entered_zones & set(required_zones): + logger.debug(f"Not creating snapshot for {obj.obj_data['id']} because it did not enter required zones") + return False + + return True + + def should_mqtt_snapshot(self, camera, obj: TrackedObject): + # if there are required zones and there is no overlap + required_zones = self.config.cameras[camera].mqtt.required_zones + if len(required_zones) > 0 and not obj.entered_zones & set(required_zones): + logger.debug(f"Not sending mqtt for {obj.obj_data['id']} because it did not enter required zones") + return False + + return True + def get_best(self, camera, label): # TODO: need a lock here camera_state = self.camera_states[camera]