allow defining required zones for snapshots/clips/mqtt

This commit is contained in:
Blake Blackshear 2021-02-04 21:44:44 -06:00
parent 9592d95599
commit 121ea37825
4 changed files with 68 additions and 13 deletions

View File

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

View File

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

View File

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

View File

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