mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-11 05:35:25 +03:00
Refactor reviews to remove motion and only create review segments in expected circumstances
This commit is contained in:
parent
d6d6cbe5fe
commit
76d7e29640
@ -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,35 +243,65 @@ 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"])
|
||||||
|
|
||||||
self.active_review_segments[camera] = PendingReviewSegment(
|
if severity:
|
||||||
camera,
|
self.active_review_segments[camera] = PendingReviewSegment(
|
||||||
frame_time,
|
camera,
|
||||||
SeverityEnum.alert if has_sig_object else SeverityEnum.detection,
|
frame_time,
|
||||||
detections,
|
SeverityEnum.alert if has_sig_object else SeverityEnum.detection,
|
||||||
audio=set(),
|
detections,
|
||||||
zones=zones,
|
audio=set(),
|
||||||
motion=[],
|
zones=zones,
|
||||||
)
|
)
|
||||||
|
|
||||||
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(
|
||||||
self.active_review_segments[camera].update_frame(
|
frame_id, camera_config.frame_shape_yuv
|
||||||
camera_config, yuv_frame, active_objects
|
)
|
||||||
)
|
self.active_review_segments[camera].update_frame(
|
||||||
self.frame_manager.close(frame_id)
|
camera_config, yuv_frame, active_objects
|
||||||
self.update_segment(self.active_review_segments[camera])
|
)
|
||||||
elif len(motion) >= 20:
|
self.frame_manager.close(frame_id)
|
||||||
self.active_review_segments[camera] = PendingReviewSegment(
|
self.update_segment(self.active_review_segments[camera])
|
||||||
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,18 +402,36 @@ 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:
|
||||||
self.active_review_segments[camera] = PendingReviewSegment(
|
severity = None
|
||||||
camera,
|
|
||||||
frame_time,
|
camera_config = self.config.cameras[camera]
|
||||||
SeverityEnum.detection,
|
detections = []
|
||||||
{},
|
|
||||||
set(),
|
for audio in audio_detections:
|
||||||
set(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(
|
||||||
|
camera,
|
||||||
|
frame_time,
|
||||||
|
severity,
|
||||||
|
{},
|
||||||
|
set(),
|
||||||
|
set(detections),
|
||||||
|
[],
|
||||||
|
)
|
||||||
elif topic == DetectionTypeEnum.api:
|
elif topic == DetectionTypeEnum.api:
|
||||||
self.active_review_segments[camera] = PendingReviewSegment(
|
self.active_review_segments[camera] = PendingReviewSegment(
|
||||||
camera,
|
camera,
|
||||||
@ -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
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user