* Catch error and show toast when failing to delete review items

* i18n keys

* add link to speed estimation docs in zone edit pane

* Implement reset of tracked object update for each camera

* Cleanup

* register mqtt callbacks for toggling alerts and detections

* clarify snapshots docs

* clarify semantic search reindexing

* add ukrainian

* adjust date granularity for last recording time

The api endpoint only returns granularity down to the day

* Add amd hardware

* fix crash in face library on initial start after enabling

* Fix recordings view for mobile landscape

The events view incorrectly was displaying two columns on landscape view and it only took up 20% of the screen width. Additionally, in landscape view the timeline was too wide (especially on iPads of various screen sizes) and would overlap the main video

* face rec overfitting instructions

* Clarify

* face docs

* clarify

* clarify

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
Josh Hawkins
2025-05-11 13:03:53 -05:00
committed by GitHub
parent 8094dd4075
commit f39ddbc00d
21 changed files with 130 additions and 25 deletions

View File

@@ -1552,6 +1552,12 @@ class LicensePlateProcessingMixin:
(base64.b64encode(encoded_img).decode("ASCII"), id, camera),
)
if id not in self.detected_license_plates:
if camera not in self.camera_current_cars:
self.camera_current_cars[camera] = []
self.camera_current_cars[camera].append(id)
self.detected_license_plates[id] = {
"plate": top_plate,
"char_confidences": top_char_confidences,
@@ -1564,7 +1570,7 @@ class LicensePlateProcessingMixin:
def handle_request(self, topic, request_data) -> dict[str, any] | None:
return
def expire_object(self, object_id: str):
def expire_object(self, object_id: str, camera: str):
if object_id in self.detected_license_plates:
self.detected_license_plates.pop(object_id)

View File

@@ -50,10 +50,11 @@ class RealTimeProcessorApi(ABC):
pass
@abstractmethod
def expire_object(self, object_id: str) -> None:
def expire_object(self, object_id: str, camera: str) -> None:
"""Handle objects that are no longer detected.
Args:
object_id (str): id of object that is no longer detected.
camera (str): name of camera that object was detected on.
Returns:
None.

View File

@@ -152,6 +152,6 @@ class BirdRealTimeProcessor(RealTimeProcessorApi):
def handle_request(self, topic, request_data):
return None
def expire_object(self, object_id):
def expire_object(self, object_id, camera):
if object_id in self.detected_birds:
self.detected_birds.pop(object_id)

View File

@@ -54,6 +54,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
self.face_detector: cv2.FaceDetectorYN = None
self.requires_face_detection = "face" not in self.config.objects.all_objects
self.person_face_history: dict[str, list[tuple[str, float, int]]] = {}
self.camera_current_people: dict[str, list[str]] = {}
self.recognizer: FaceRecognizer | None = None
self.faces_per_second = EventsPerSecond()
self.inference_speed = InferenceSpeed(self.metrics.face_rec_speed)
@@ -282,9 +283,13 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
if id not in self.person_face_history:
self.person_face_history[id] = []
if camera not in self.camera_current_people:
self.camera_current_people[camera] = []
self.person_face_history[id].append(
(sub_label, score, face_frame.shape[0] * face_frame.shape[1])
)
self.camera_current_people[camera].append(id)
(weighted_sub_label, weighted_score) = self.weighted_average(
self.person_face_history[id]
)
@@ -420,10 +425,25 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
)
shutil.move(current_file, new_file)
def expire_object(self, object_id: str):
def expire_object(self, object_id: str, camera: str):
if object_id in self.person_face_history:
self.person_face_history.pop(object_id)
if object_id in self.camera_current_people.get(camera, []):
self.camera_current_people[camera].remove(object_id)
if len(self.camera_current_people[camera]) == 0:
self.requestor.send_data(
"tracked_object_update",
json.dumps(
{
"type": TrackedObjectUpdateTypesEnum.face,
"name": None,
"camera": camera,
}
),
)
def weighted_average(
self, results_list: list[tuple[str, float, int]], max_weight: int = 4000
):

View File

@@ -1,5 +1,6 @@
"""Handle processing images for face detection and recognition."""
import json
import logging
import numpy as np
@@ -13,6 +14,7 @@ from frigate.data_processing.common.license_plate.mixin import (
from frigate.data_processing.common.license_plate.model import (
LicensePlateModelRunner,
)
from frigate.types import TrackedObjectUpdateTypesEnum
from ..types import DataProcessorMetrics
from .api import RealTimeProcessorApi
@@ -36,6 +38,7 @@ class LicensePlateRealTimeProcessor(LicensePlateProcessingMixin, RealTimeProcess
self.lpr_config = config.lpr
self.config = config
self.sub_label_publisher = sub_label_publisher
self.camera_current_cars: dict[str, list[str]] = {}
super().__init__(config, metrics)
def process_frame(
@@ -50,6 +53,22 @@ class LicensePlateRealTimeProcessor(LicensePlateProcessingMixin, RealTimeProcess
def handle_request(self, topic, request_data) -> dict[str, any] | None:
return
def expire_object(self, object_id: str):
def expire_object(self, object_id: str, camera: str):
if object_id in self.detected_license_plates:
self.detected_license_plates.pop(object_id)
if object_id in self.camera_current_cars.get(camera, []):
self.camera_current_cars[camera].remove(object_id)
if len(self.camera_current_cars[camera]) == 0:
self.requestor.send_data(
"tracked_object_update",
json.dumps(
{
"type": TrackedObjectUpdateTypesEnum.lpr,
"name": None,
"plate": None,
"camera": camera,
}
),
)