mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-16 12:02:09 +03:00
Keep segment going when detection is newer than end of alert
This commit is contained in:
parent
c5f83b947a
commit
39c26af2cb
@ -64,8 +64,12 @@ class PendingReviewSegment:
|
|||||||
self.sub_labels = sub_labels
|
self.sub_labels = sub_labels
|
||||||
self.zones = zones
|
self.zones = zones
|
||||||
self.audio = audio
|
self.audio = audio
|
||||||
self.last_update = frame_time
|
|
||||||
self.thumb_time: float | None = None
|
self.thumb_time: float | None = None
|
||||||
|
self.last_alert_time: float | None = None
|
||||||
|
self.last_detection_time: float = frame_time
|
||||||
|
|
||||||
|
if severity == SeverityEnum.alert:
|
||||||
|
self.last_alert_time = frame_time
|
||||||
|
|
||||||
# thumbnail
|
# thumbnail
|
||||||
self._frame = np.zeros((THUMB_HEIGHT * 3 // 2, THUMB_WIDTH), np.uint8)
|
self._frame = np.zeros((THUMB_HEIGHT * 3 // 2, THUMB_WIDTH), np.uint8)
|
||||||
@ -126,13 +130,27 @@ class PendingReviewSegment:
|
|||||||
self.frame_path, self._frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]
|
self.frame_path, self._frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_time(self, frame_time: float, update_severity: SeverityEnum) -> None:
|
||||||
|
if update_severity == SeverityEnum.alert:
|
||||||
|
self.last_alert_time = frame_time
|
||||||
|
elif update_severity == SeverityEnum.detection:
|
||||||
|
self.last_detection_time = frame_time
|
||||||
|
|
||||||
def get_data(self, ended: bool) -> dict:
|
def get_data(self, ended: bool) -> dict:
|
||||||
|
end_time = None
|
||||||
|
|
||||||
|
if ended:
|
||||||
|
if self.severity == SeverityEnum.alert:
|
||||||
|
end_time = self.last_alert_time
|
||||||
|
else:
|
||||||
|
end_time = self.last_detection_time
|
||||||
|
|
||||||
return copy.deepcopy(
|
return copy.deepcopy(
|
||||||
{
|
{
|
||||||
ReviewSegment.id.name: self.id,
|
ReviewSegment.id.name: self.id,
|
||||||
ReviewSegment.camera.name: self.camera,
|
ReviewSegment.camera.name: self.camera,
|
||||||
ReviewSegment.start_time.name: self.start_time,
|
ReviewSegment.start_time.name: self.start_time,
|
||||||
ReviewSegment.end_time.name: self.last_update if ended else None,
|
ReviewSegment.end_time.name: end_time,
|
||||||
ReviewSegment.severity.name: self.severity.value,
|
ReviewSegment.severity.name: self.severity.value,
|
||||||
ReviewSegment.thumb_path.name: self.frame_path,
|
ReviewSegment.thumb_path.name: self.frame_path,
|
||||||
ReviewSegment.data.name: {
|
ReviewSegment.data.name: {
|
||||||
@ -155,6 +173,8 @@ class ActiveObjects:
|
|||||||
camera_config: CameraConfig,
|
camera_config: CameraConfig,
|
||||||
all_objects: list[TrackedObject],
|
all_objects: list[TrackedObject],
|
||||||
):
|
):
|
||||||
|
self.camera_config = camera_config
|
||||||
|
|
||||||
# get current categorization of objects to know if
|
# get current categorization of objects to know if
|
||||||
# these objects are currently being categorized
|
# these objects are currently being categorized
|
||||||
self.categorized_objects = {
|
self.categorized_objects = {
|
||||||
@ -189,12 +209,13 @@ class ActiveObjects:
|
|||||||
or (
|
or (
|
||||||
len(o["current_zones"]) > 0
|
len(o["current_zones"]) > 0
|
||||||
and set(o["current_zones"])
|
and set(o["current_zones"])
|
||||||
& o(camera_config.review.alerts.required_zones)
|
& set(camera_config.review.alerts.required_zones)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
and camera_config.review.alerts.enabled
|
and camera_config.review.alerts.enabled
|
||||||
):
|
):
|
||||||
self.categorized_objects["alerts"].append(o)
|
self.categorized_objects["alerts"].append(o)
|
||||||
|
continue
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
@ -212,6 +233,7 @@ class ActiveObjects:
|
|||||||
and camera_config.review.detections.enabled
|
and camera_config.review.detections.enabled
|
||||||
):
|
):
|
||||||
self.categorized_objects["detections"].append(o)
|
self.categorized_objects["detections"].append(o)
|
||||||
|
continue
|
||||||
|
|
||||||
def has_active_objects(self) -> bool:
|
def has_active_objects(self) -> bool:
|
||||||
return (
|
return (
|
||||||
@ -219,14 +241,20 @@ class ActiveObjects:
|
|||||||
or len(self.categorized_objects["detections"]) > 0
|
or len(self.categorized_objects["detections"]) > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_activity_category(self) -> SeverityEnum | None:
|
def has_activity_category(self, severity: SeverityEnum) -> bool:
|
||||||
if len(self.categorized_objects["alerts"]) > 0:
|
if (
|
||||||
return SeverityEnum.alert
|
severity == SeverityEnum.alert
|
||||||
|
and len(self.categorized_objects["alerts"]) > 0
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
if len(self.categorized_objects["detections"]) > 0:
|
if (
|
||||||
return SeverityEnum.detection
|
severity == SeverityEnum.detection
|
||||||
|
and len(self.categorized_objects["detections"]) > 0
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
return None
|
return False
|
||||||
|
|
||||||
def get_all_objects(self) -> list[TrackedObject]:
|
def get_all_objects(self) -> list[TrackedObject]:
|
||||||
return (
|
return (
|
||||||
@ -368,8 +396,18 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
should_update_image = False
|
should_update_image = False
|
||||||
should_update_state = False
|
should_update_state = False
|
||||||
|
|
||||||
if frame_time > segment.last_update:
|
# if segment is not alert category but current activity is
|
||||||
segment.last_update = frame_time
|
if (
|
||||||
|
segment.severity != SeverityEnum.alert
|
||||||
|
and activity.has_activity_category(SeverityEnum.alert)
|
||||||
|
):
|
||||||
|
segment.update_time(frame_time, SeverityEnum.alert)
|
||||||
|
segment.severity = SeverityEnum.alert
|
||||||
|
should_update_state = True
|
||||||
|
should_update_image = True
|
||||||
|
|
||||||
|
if activity.has_activity_category(SeverityEnum.detection):
|
||||||
|
segment.update_time(frame_time, SeverityEnum.detection)
|
||||||
|
|
||||||
for object in activity.get_all_objects():
|
for object in activity.get_all_objects():
|
||||||
if not object["sub_label"]:
|
if not object["sub_label"]:
|
||||||
@ -380,17 +418,6 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
segment.detections[object["id"]] = f"{object['label']}-verified"
|
segment.detections[object["id"]] = f"{object['label']}-verified"
|
||||||
segment.sub_labels[object["id"]] = object["sub_label"][0]
|
segment.sub_labels[object["id"]] = object["sub_label"][0]
|
||||||
|
|
||||||
# if object is alert label
|
|
||||||
# and has entered required zones or required zones is not set
|
|
||||||
# mark this review as alert
|
|
||||||
if (
|
|
||||||
segment.severity != SeverityEnum.alert
|
|
||||||
and activity.get_activity_category() == SeverityEnum.alert
|
|
||||||
):
|
|
||||||
segment.severity = SeverityEnum.alert
|
|
||||||
should_update_state = True
|
|
||||||
should_update_image = True
|
|
||||||
|
|
||||||
# keep zones up to date
|
# keep zones up to date
|
||||||
if len(object["current_zones"]) > 0:
|
if len(object["current_zones"]) > 0:
|
||||||
for zone in object["current_zones"]:
|
for zone in object["current_zones"]:
|
||||||
@ -448,10 +475,41 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if segment.severity == SeverityEnum.alert and frame_time > (
|
if segment.severity == SeverityEnum.alert and frame_time > (
|
||||||
segment.last_update + THRESHOLD_ALERT_ACTIVITY
|
segment.last_alert_time + THRESHOLD_ALERT_ACTIVITY
|
||||||
):
|
):
|
||||||
|
needs_new_detection = (
|
||||||
|
segment.last_detection_time > segment.last_alert_time
|
||||||
|
and (segment.last_detection_time + THRESHOLD_DETECTION_ACTIVITY)
|
||||||
|
> frame_time
|
||||||
|
)
|
||||||
|
|
||||||
self._publish_segment_end(segment, prev_data)
|
self._publish_segment_end(segment, prev_data)
|
||||||
elif frame_time > (segment.last_update + THRESHOLD_DETECTION_ACTIVITY):
|
|
||||||
|
if needs_new_detection:
|
||||||
|
new_detections = {}
|
||||||
|
new_zones = set()
|
||||||
|
|
||||||
|
for o in activity.categorized_objects["detections"]:
|
||||||
|
new_detections[o["id"]] = o["label"]
|
||||||
|
new_zones.update(o["current_zones"])
|
||||||
|
|
||||||
|
self.active_review_segments[activity.camera_config.name] = (
|
||||||
|
PendingReviewSegment(
|
||||||
|
activity.camera_config.name,
|
||||||
|
frame_time,
|
||||||
|
SeverityEnum.detection,
|
||||||
|
new_detections,
|
||||||
|
sub_labels=[],
|
||||||
|
audio=set(),
|
||||||
|
zones=list(new_zones),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self._publish_segment_start(
|
||||||
|
self.active_review_segments[activity.camera_config.name]
|
||||||
|
)
|
||||||
|
elif frame_time > (
|
||||||
|
segment.last_detection_time + THRESHOLD_DETECTION_ACTIVITY
|
||||||
|
):
|
||||||
self._publish_segment_end(segment, prev_data)
|
self._publish_segment_end(segment, prev_data)
|
||||||
|
|
||||||
def check_if_new_segment(
|
def check_if_new_segment(
|
||||||
@ -472,18 +530,14 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
severity = None
|
severity = None
|
||||||
|
|
||||||
# if activity is alert category mark this review as alert
|
# if activity is alert category mark this review as alert
|
||||||
if (
|
if severity != SeverityEnum.alert and activity.has_activity_category(
|
||||||
severity != SeverityEnum.alert
|
SeverityEnum.alert
|
||||||
and activity.get_activity_category() == SeverityEnum.alert
|
|
||||||
):
|
):
|
||||||
severity = SeverityEnum.alert
|
severity = SeverityEnum.alert
|
||||||
|
|
||||||
# if object is detection label and not already higher severity
|
# if object is detection label and not already higher severity
|
||||||
# mark this review as detection
|
# mark this review as detection
|
||||||
if (
|
if not severity and activity.has_activity_category(SeverityEnum.detection):
|
||||||
not severity
|
|
||||||
and activity.get_activity_category() == SeverityEnum.detection
|
|
||||||
):
|
|
||||||
severity = SeverityEnum.detection
|
severity = SeverityEnum.detection
|
||||||
|
|
||||||
for object in activity.get_all_objects():
|
for object in activity.get_all_objects():
|
||||||
@ -603,9 +657,6 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
elif topic == DetectionTypeEnum.audio and len(audio_detections) > 0:
|
elif topic == DetectionTypeEnum.audio and len(audio_detections) > 0:
|
||||||
camera_config = self.config.cameras[camera]
|
camera_config = self.config.cameras[camera]
|
||||||
|
|
||||||
if frame_time > current_segment.last_update:
|
|
||||||
current_segment.last_update = frame_time
|
|
||||||
|
|
||||||
for audio in audio_detections:
|
for audio in audio_detections:
|
||||||
if (
|
if (
|
||||||
audio in camera_config.review.alerts.labels
|
audio in camera_config.review.alerts.labels
|
||||||
@ -613,11 +664,15 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
):
|
):
|
||||||
current_segment.audio.add(audio)
|
current_segment.audio.add(audio)
|
||||||
current_segment.severity = SeverityEnum.alert
|
current_segment.severity = SeverityEnum.alert
|
||||||
|
current_segment.update_time(frame_time, SeverityEnum.alert)
|
||||||
elif (
|
elif (
|
||||||
camera_config.review.detections.labels is None
|
camera_config.review.detections.labels is None
|
||||||
or audio in camera_config.review.detections.labels
|
or audio in camera_config.review.detections.labels
|
||||||
) and camera_config.review.detections.enabled:
|
) and camera_config.review.detections.enabled:
|
||||||
current_segment.audio.add(audio)
|
current_segment.audio.add(audio)
|
||||||
|
current_segment.update_time(
|
||||||
|
frame_time, SeverityEnum.detection
|
||||||
|
)
|
||||||
elif topic == DetectionTypeEnum.api or topic == DetectionTypeEnum.lpr:
|
elif topic == DetectionTypeEnum.api or topic == DetectionTypeEnum.lpr:
|
||||||
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"]] = (
|
||||||
@ -633,7 +688,7 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
and self.config.cameras[camera].review.detections.enabled
|
and self.config.cameras[camera].review.detections.enabled
|
||||||
):
|
):
|
||||||
current_segment.severity = SeverityEnum.detection
|
current_segment.severity = SeverityEnum.detection
|
||||||
current_segment.last_update = manual_info["end_time"]
|
current_segment.last_alert_time = manual_info["end_time"]
|
||||||
elif manual_info["state"] == ManualEventState.start:
|
elif manual_info["state"] == ManualEventState.start:
|
||||||
self.indefinite_events[camera][manual_info["event_id"]] = (
|
self.indefinite_events[camera][manual_info["event_id"]] = (
|
||||||
manual_info["label"]
|
manual_info["label"]
|
||||||
@ -653,7 +708,8 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
current_segment.severity = SeverityEnum.detection
|
current_segment.severity = SeverityEnum.detection
|
||||||
|
|
||||||
# temporarily make it so this event can not end
|
# temporarily make it so this event can not end
|
||||||
current_segment.last_update = sys.maxsize
|
current_segment.last_alert_time = sys.maxsize
|
||||||
|
current_segment.last_detection_time = sys.maxsize
|
||||||
elif manual_info["state"] == ManualEventState.end:
|
elif manual_info["state"] == ManualEventState.end:
|
||||||
event_id = manual_info["event_id"]
|
event_id = manual_info["event_id"]
|
||||||
|
|
||||||
@ -661,7 +717,12 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
self.indefinite_events[camera].pop(event_id)
|
self.indefinite_events[camera].pop(event_id)
|
||||||
|
|
||||||
if len(self.indefinite_events[camera]) == 0:
|
if len(self.indefinite_events[camera]) == 0:
|
||||||
current_segment.last_update = manual_info["end_time"]
|
current_segment.last_alert_time = manual_info[
|
||||||
|
"end_time"
|
||||||
|
]
|
||||||
|
current_segment.last_detection_time = manual_info[
|
||||||
|
"end_time"
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Event with ID {event_id} has a set duration and can not be ended manually."
|
f"Event with ID {event_id} has a set duration and can not be ended manually."
|
||||||
@ -729,11 +790,17 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
# temporarily make it so this event can not end
|
# temporarily make it so this event can not end
|
||||||
self.active_review_segments[
|
self.active_review_segments[
|
||||||
camera
|
camera
|
||||||
].last_update = sys.maxsize
|
].last_alert_time = sys.maxsize
|
||||||
|
self.active_review_segments[
|
||||||
|
camera
|
||||||
|
].last_detection_time = sys.maxsize
|
||||||
elif manual_info["state"] == ManualEventState.complete:
|
elif manual_info["state"] == ManualEventState.complete:
|
||||||
self.active_review_segments[
|
self.active_review_segments[
|
||||||
camera
|
camera
|
||||||
].last_update = manual_info["end_time"]
|
].last_alert_time = manual_info["end_time"]
|
||||||
|
self.active_review_segments[
|
||||||
|
camera
|
||||||
|
].last_detection_time = manual_info["end_time"]
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Manual event API has been called for {camera}, but alerts are disabled. This manual event will not appear as an alert."
|
f"Manual event API has been called for {camera}, but alerts are disabled. This manual event will not appear as an alert."
|
||||||
@ -757,11 +824,17 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
# temporarily make it so this event can not end
|
# temporarily make it so this event can not end
|
||||||
self.active_review_segments[
|
self.active_review_segments[
|
||||||
camera
|
camera
|
||||||
].last_update = sys.maxsize
|
].last_alert_time = sys.maxsize
|
||||||
|
self.active_review_segments[
|
||||||
|
camera
|
||||||
|
].last_detection_time = sys.maxsize
|
||||||
elif manual_info["state"] == ManualEventState.complete:
|
elif manual_info["state"] == ManualEventState.complete:
|
||||||
self.active_review_segments[
|
self.active_review_segments[
|
||||||
camera
|
camera
|
||||||
].last_update = manual_info["end_time"]
|
].last_alert_time = manual_info["end_time"]
|
||||||
|
self.active_review_segments[
|
||||||
|
camera
|
||||||
|
].last_detection_time = manual_info["end_time"]
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Dedicated LPR camera API has been called for {camera}, but detections are disabled. LPR events will not appear as a detection."
|
f"Dedicated LPR camera API has been called for {camera}, but detections are disabled. LPR events will not appear as a detection."
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user