Refactor reviews to remove motion and only create review segments in expected circumstances

This commit is contained in:
Nicolas Mowen 2024-04-12 10:01:02 -06:00
parent d6d6cbe5fe
commit 76d7e29640

View File

@ -32,13 +32,11 @@ THUMB_WIDTH = 320
THRESHOLD_ALERT_ACTIVITY = 120 THRESHOLD_ALERT_ACTIVITY = 120
THRESHOLD_DETECTION_ACTIVITY = 30 THRESHOLD_DETECTION_ACTIVITY = 30
THRESHOLD_MOTION_ACTIVITY = 30
class SeverityEnum(str, Enum): class SeverityEnum(str, Enum):
alert = "alert" alert = "alert"
detection = "detection" detection = "detection"
signification_motion = "significant_motion"
class PendingReviewSegment: class PendingReviewSegment:
@ -50,7 +48,6 @@ class PendingReviewSegment:
detections: dict[str, str], detections: dict[str, str],
zones: set[str] = set(), zones: set[str] = set(),
audio: set[str] = set(), audio: set[str] = set(),
motion: list[int] = [],
): ):
rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
self.id = f"{frame_time}-{rand_id}" self.id = f"{frame_time}-{rand_id}"
@ -60,7 +57,6 @@ class PendingReviewSegment:
self.detections = detections self.detections = detections
self.zones = zones self.zones = zones
self.audio = audio self.audio = audio
self.sig_motion_areas = motion
self.last_update = frame_time self.last_update = frame_time
# thumbnail # thumbnail
@ -117,7 +113,6 @@ class PendingReviewSegment:
"objects": list(set(self.detections.values())), "objects": list(set(self.detections.values())),
"zones": list(self.zones), "zones": list(self.zones),
"audio": list(self.audio), "audio": list(self.audio),
"significant_motion_areas": self.sig_motion_areas,
}, },
} }
@ -170,7 +165,6 @@ class ReviewSegmentMaintainer(threading.Thread):
segment: PendingReviewSegment, segment: PendingReviewSegment,
frame_time: float, frame_time: float,
objects: list[TrackedObject], objects: list[TrackedObject],
motion: list,
) -> None: ) -> None:
"""Validate if existing review segment should continue.""" """Validate if existing review segment should continue."""
camera_config = self.config.cameras[segment.camera] camera_config = self.config.cameras[segment.camera]
@ -180,10 +174,6 @@ class ReviewSegmentMaintainer(threading.Thread):
if frame_time > segment.last_update: if frame_time > segment.last_update:
segment.last_update = frame_time segment.last_update = frame_time
# update type for this segment now that active objects are detected
if segment.severity == SeverityEnum.signification_motion:
segment.severity = SeverityEnum.detection
if len(active_objects) > segment.frame_active_count: if len(active_objects) > segment.frame_active_count:
frame_id = f"{camera_config.name}{frame_time}" frame_id = f"{camera_config.name}{frame_time}"
yuv_frame = self.frame_manager.get( yuv_frame = self.frame_manager.get(
@ -201,24 +191,26 @@ class ReviewSegmentMaintainer(threading.Thread):
else: else:
segment.detections[object["id"]] = f'{object["label"]}-verified' segment.detections[object["id"]] = f'{object["label"]}-verified'
# if object is alert label and has qualified for recording # if object is alert label
# and has entered required zones or required zones is not set
# mark this review as alert # mark this review as alert
if ( if (
segment.severity == SeverityEnum.detection segment.severity != SeverityEnum.alert
and object["has_clip"] and object["label"] in camera_config.review.alerts.labels
and object["label"] in camera_config.objects.alert and (
not camera_config.review.alerts.required_zones
or (
len(object["current_zones"]) > 0
and set(object["current_zones"])
& set(camera_config.review.alerts.required_zones)
)
)
): ):
segment.severity = SeverityEnum.alert segment.severity = SeverityEnum.alert
# keep zones up to date # keep zones up to date
if len(object["current_zones"]) > 0: if len(object["current_zones"]) > 0:
segment.zones.update(object["current_zones"]) segment.zones.update(object["current_zones"])
elif (
segment.severity == SeverityEnum.signification_motion
and len(motion) >= THRESHOLD_MOTION_ACTIVITY
):
if frame_time > segment.last_update:
segment.last_update = frame_time
else: else:
if segment.severity == SeverityEnum.alert and frame_time > ( if segment.severity == SeverityEnum.alert and frame_time > (
segment.last_update + THRESHOLD_ALERT_ACTIVITY segment.last_update + THRESHOLD_ALERT_ACTIVITY
@ -232,7 +224,6 @@ class ReviewSegmentMaintainer(threading.Thread):
camera: str, camera: str,
frame_time: float, frame_time: float,
objects: list[TrackedObject], objects: list[TrackedObject],
motion: list,
) -> None: ) -> None:
"""Check if a new review segment should be created.""" """Check if a new review segment should be created."""
camera_config = self.config.cameras[camera] camera_config = self.config.cameras[camera]
@ -242,15 +233,9 @@ class ReviewSegmentMaintainer(threading.Thread):
has_sig_object = False has_sig_object = False
detections: dict[str, str] = {} detections: dict[str, str] = {}
zones: set = set() zones: set = set()
severity = None
for object in active_objects: for object in active_objects:
if (
not has_sig_object
and object["has_clip"]
and object["label"] in camera_config.objects.alert
):
has_sig_object = True
if not object["sub_label"]: if not object["sub_label"]:
detections[object["id"]] = object["label"] detections[object["id"]] = object["label"]
elif object["sub_label"][0] in ALL_ATTRIBUTE_LABELS: elif object["sub_label"][0] in ALL_ATTRIBUTE_LABELS:
@ -258,8 +243,47 @@ class ReviewSegmentMaintainer(threading.Thread):
else: else:
detections[object["id"]] = f'{object["label"]}-verified' detections[object["id"]] = f'{object["label"]}-verified'
# if object is alert label
# and has entered required zones or required zones is not set
# mark this review as alert
if (
severity != SeverityEnum.alert
and object["label"] in camera_config.review.alerts.labels
and (
not camera_config.review.alerts.required_zones
or (
len(object["current_zones"]) > 0
and set(object["current_zones"])
& set(camera_config.review.alerts.required_zones)
)
)
):
severity = SeverityEnum.alert
# if object is detection label
# and review is not already a detection or alert
# and has entered required zones or required zones is not set
# mark this review as alert
if (
not severity
and (
not camera_config.review.detections.labels
or object["label"] in (camera_config.review.detections.labels)
)
and (
not camera_config.review.detections.required_zones
or (
len(object["current_zones"]) > 0
and set(object["current_zones"])
& set(camera_config.review.detections.required_zones)
)
)
):
severity = SeverityEnum.detection
zones.update(object["current_zones"]) zones.update(object["current_zones"])
if severity:
self.active_review_segments[camera] = PendingReviewSegment( self.active_review_segments[camera] = PendingReviewSegment(
camera, camera,
frame_time, frame_time,
@ -267,26 +291,17 @@ class ReviewSegmentMaintainer(threading.Thread):
detections, detections,
audio=set(), audio=set(),
zones=zones, zones=zones,
motion=[],
) )
frame_id = f"{camera_config.name}{frame_time}" frame_id = f"{camera_config.name}{frame_time}"
yuv_frame = self.frame_manager.get(frame_id, camera_config.frame_shape_yuv) yuv_frame = self.frame_manager.get(
frame_id, camera_config.frame_shape_yuv
)
self.active_review_segments[camera].update_frame( self.active_review_segments[camera].update_frame(
camera_config, yuv_frame, active_objects camera_config, yuv_frame, active_objects
) )
self.frame_manager.close(frame_id) self.frame_manager.close(frame_id)
self.update_segment(self.active_review_segments[camera]) self.update_segment(self.active_review_segments[camera])
elif len(motion) >= 20:
self.active_review_segments[camera] = PendingReviewSegment(
camera,
frame_time,
SeverityEnum.signification_motion,
detections={},
audio=set(),
motion=motion,
zones=set(),
)
def run(self) -> None: def run(self) -> None:
while not self.stop_event.is_set(): while not self.stop_event.is_set():
@ -344,13 +359,22 @@ class ReviewSegmentMaintainer(threading.Thread):
current_segment, current_segment,
frame_time, frame_time,
current_tracked_objects, current_tracked_objects,
motion_boxes,
) )
elif topic == DetectionTypeEnum.audio and len(audio_detections) > 0: elif topic == DetectionTypeEnum.audio and len(audio_detections) > 0:
camera_config = self.config.cameras[camera]
if frame_time > current_segment.last_update: if frame_time > current_segment.last_update:
current_segment.last_update = frame_time current_segment.last_update = frame_time
current_segment.audio.update(audio_detections) for audio in audio_detections:
if audio in camera_config.review.alerts.labels:
current_segment.audio.add(audio)
current_segment.severity = SeverityEnum.alert
elif (
not camera_config.review.detections.labels
or audio in camera_config.review.detections.labels
):
current_segment.audio.add(audio)
elif topic == DetectionTypeEnum.api: elif topic == DetectionTypeEnum.api:
if manual_info["state"] == ManualEventState.complete: if manual_info["state"] == ManualEventState.complete:
current_segment.detections[manual_info["event_id"]] = ( current_segment.detections[manual_info["event_id"]] = (
@ -378,16 +402,34 @@ class ReviewSegmentMaintainer(threading.Thread):
camera, camera,
frame_time, frame_time,
current_tracked_objects, current_tracked_objects,
motion_boxes,
) )
elif topic == DetectionTypeEnum.audio and len(audio_detections) > 0: elif topic == DetectionTypeEnum.audio and len(audio_detections) > 0:
severity = None
camera_config = self.config.cameras[camera]
detections = []
for audio in audio_detections:
if audio in camera_config.review.alerts.labels:
detections.append(audio)
severity = SeverityEnum.alert
elif (
not camera_config.review.detections.labels
or audio in camera_config.review.detections.labels
):
detections.append(audio_detections)
if not severity:
severity = SeverityEnum.detection
if severity:
self.active_review_segments[camera] = PendingReviewSegment( self.active_review_segments[camera] = PendingReviewSegment(
camera, camera,
frame_time, frame_time,
SeverityEnum.detection, severity,
{}, {},
set(), set(),
set(audio_detections), set(detections),
[], [],
) )
elif topic == DetectionTypeEnum.api: elif topic == DetectionTypeEnum.api:
@ -425,8 +467,16 @@ def get_active_objects(
return [ return [
o o
for o in all_objects for o in all_objects
if o["motionless_count"] < camera_config.detect.stationary.threshold if o["motionless_count"]
and o["position_changes"] > 0 < camera_config.detect.stationary.threshold # no stationary objects
and o["frame_time"] == frame_time and o["position_changes"] > 0 # object must have moved at least once
and not o["false_positive"] and o["frame_time"] == frame_time # object must be detected in this frame
and not o["false_positive"] # object must not be a false positive
and (
o["label"] in camera_config.review.alerts.labels
or (
not camera_config.review.detections.labels
or o["label"] in camera_config.review.detections.labels
)
) # object must be in the alerts or detections label list
] ]