Sfoglia il codice sorgente

allow defining required zones for snapshots/clips/mqtt

Blake Blackshear 4 anni fa
parent
commit
121ea37825
4 ha cambiato i file con 68 aggiunte e 13 eliminazioni
  1. 10 6
      docs/docs/configuration/cameras.md
  2. 25 4
      frigate/config.py
  3. 13 1
      frigate/events.py
  4. 20 2
      frigate/object_processing.py

+ 10 - 6
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:

+ 25 - 4
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():

+ 13 - 1
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)

+ 20 - 2
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]