diff --git a/frigate/data_processing/common/license_plate/mixin.py b/frigate/data_processing/common/license_plate/mixin.py index f4ff08644..b62239491 100644 --- a/frigate/data_processing/common/license_plate/mixin.py +++ b/frigate/data_processing/common/license_plate/mixin.py @@ -25,7 +25,7 @@ from frigate.comms.event_metadata_updater import ( from frigate.const import CLIPS_DIR from frigate.embeddings.onnx.lpr_embedding import LPR_EMBEDDING_SIZE from frigate.types import TrackedObjectUpdateTypesEnum -from frigate.util.builtin import EventsPerSecond +from frigate.util.builtin import EventsPerSecond, InferenceSpeed from frigate.util.image import area logger = logging.getLogger(__name__) @@ -36,8 +36,10 @@ WRITE_DEBUG_IMAGES = False class LicensePlateProcessingMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.plate_rec_speed = InferenceSpeed(self.metrics.alpr_speed) self.plates_rec_second = EventsPerSecond() self.plates_rec_second.start() + self.plate_det_speed = InferenceSpeed(self.metrics.yolov9_lpr_speed) self.plates_det_second = EventsPerSecond() self.plates_det_second.start() self.event_metadata_publisher = EventMetadataPublisher() @@ -1157,22 +1159,6 @@ class LicensePlateProcessingMixin: # 5. Return True if previous plate scores higher return prev_score > curr_score - def __update_yolov9_metrics(self, duration: float) -> None: - """ - Update inference metrics. - """ - self.metrics.yolov9_lpr_speed.value = ( - self.metrics.yolov9_lpr_speed.value * 9 + duration - ) / 10 - - def __update_lpr_metrics(self, duration: float) -> None: - """ - Update inference metrics. - """ - self.metrics.alpr_speed.value = ( - self.metrics.alpr_speed.value * 9 + duration - ) / 10 - def _generate_plate_event(self, camera: str, plate: str, plate_score: float) -> str: """Generate a unique ID for a plate event based on camera and text.""" now = datetime.datetime.now().timestamp() @@ -1228,7 +1214,7 @@ class LicensePlateProcessingMixin: f"{camera}: YOLOv9 LPD inference time: {(datetime.datetime.now().timestamp() - yolov9_start) * 1000:.2f} ms" ) self.plates_det_second.update() - self.__update_yolov9_metrics( + self.plate_det_speed.update( datetime.datetime.now().timestamp() - yolov9_start ) @@ -1319,7 +1305,7 @@ class LicensePlateProcessingMixin: f"{camera}: YOLOv9 LPD inference time: {(datetime.datetime.now().timestamp() - yolov9_start) * 1000:.2f} ms" ) self.plates_det_second.update() - self.__update_yolov9_metrics( + self.plate_det_speed.update( datetime.datetime.now().timestamp() - yolov9_start ) @@ -1433,7 +1419,7 @@ class LicensePlateProcessingMixin: camera, id, license_plate_frame ) self.plates_rec_second.update() - self.__update_lpr_metrics(datetime.datetime.now().timestamp() - start) + self.plate_rec_speed.update(datetime.datetime.now().timestamp() - start) if license_plates: for plate, confidence, text_area in zip(license_plates, confidences, areas): diff --git a/frigate/data_processing/real_time/face.py b/frigate/data_processing/real_time/face.py index 8fe47b23a..a7e1a63ba 100644 --- a/frigate/data_processing/real_time/face.py +++ b/frigate/data_processing/real_time/face.py @@ -25,7 +25,7 @@ from frigate.data_processing.common.face.model import ( FaceRecognizer, ) from frigate.types import TrackedObjectUpdateTypesEnum -from frigate.util.builtin import EventsPerSecond +from frigate.util.builtin import EventsPerSecond, InferenceSpeed from frigate.util.image import area from ..types import DataProcessorMetrics @@ -56,6 +56,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi): self.person_face_history: dict[str, list[tuple[str, float, int]]] = {} self.recognizer: FaceRecognizer | None = None self.faces_per_second = EventsPerSecond() + self.inference_speed = InferenceSpeed(self.metrics.face_rec_speed) download_path = os.path.join(MODEL_CACHE_DIR, "facedet") self.model_files = { @@ -153,9 +154,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi): def __update_metrics(self, duration: float) -> None: self.faces_per_second.update() - self.metrics.face_rec_speed.value = ( - self.metrics.face_rec_speed.value * 9 + duration - ) / 10 + self.inference_speed.update(duration) def process_frame(self, obj_data: dict[str, any], frame: np.ndarray): """Look for faces in image.""" diff --git a/frigate/data_processing/types.py b/frigate/data_processing/types.py index 8ec7b9617..a19a856bf 100644 --- a/frigate/data_processing/types.py +++ b/frigate/data_processing/types.py @@ -7,7 +7,9 @@ from multiprocessing.sharedctypes import Synchronized class DataProcessorMetrics: image_embeddings_speed: Synchronized + image_embeddings_eps: Synchronized text_embeddings_speed: Synchronized + text_embeddings_eps: Synchronized face_rec_speed: Synchronized face_rec_fps: Synchronized alpr_speed: Synchronized @@ -16,15 +18,15 @@ class DataProcessorMetrics: yolov9_lpr_pps: Synchronized def __init__(self): - self.image_embeddings_speed = mp.Value("d", 0.01) + self.image_embeddings_speed = mp.Value("d", 0.0) self.image_embeddings_eps = mp.Value("d", 0.0) - self.text_embeddings_speed = mp.Value("d", 0.01) + self.text_embeddings_speed = mp.Value("d", 0.0) self.text_embeddings_eps = mp.Value("d", 0.0) - self.face_rec_speed = mp.Value("d", 0.01) + self.face_rec_speed = mp.Value("d", 0.0) self.face_rec_fps = mp.Value("d", 0.0) - self.alpr_speed = mp.Value("d", 0.01) + self.alpr_speed = mp.Value("d", 0.0) self.alpr_pps = mp.Value("d", 0.0) - self.yolov9_lpr_speed = mp.Value("d", 0.01) + self.yolov9_lpr_speed = mp.Value("d", 0.0) self.yolov9_lpr_pps = mp.Value("d", 0.0) diff --git a/frigate/embeddings/embeddings.py b/frigate/embeddings/embeddings.py index 2fda584d3..096077916 100644 --- a/frigate/embeddings/embeddings.py +++ b/frigate/embeddings/embeddings.py @@ -21,7 +21,7 @@ from frigate.data_processing.types import DataProcessorMetrics from frigate.db.sqlitevecq import SqliteVecQueueDatabase from frigate.models import Event from frigate.types import ModelStatusTypesEnum -from frigate.util.builtin import EventsPerSecond, serialize +from frigate.util.builtin import EventsPerSecond, InferenceSpeed, serialize from frigate.util.path import get_event_thumbnail_bytes from .onnx.jina_v1_embedding import JinaV1ImageEmbedding, JinaV1TextEmbedding @@ -75,8 +75,10 @@ class Embeddings: self.metrics = metrics self.requestor = InterProcessRequestor() + self.image_inference_speed = InferenceSpeed(self.metrics.image_embeddings_speed) self.image_eps = EventsPerSecond() self.image_eps.start() + self.text_inference_speed = InferenceSpeed(self.metrics.text_embeddings_speed) self.text_eps = EventsPerSecond() self.text_eps.start() @@ -183,10 +185,7 @@ class Embeddings: (event_id, serialize(embedding)), ) - duration = datetime.datetime.now().timestamp() - start - self.metrics.image_embeddings_speed.value = ( - self.metrics.image_embeddings_speed.value * 9 + duration - ) / 10 + self.image_inference_speed.update(datetime.datetime.now().timestamp() - start) self.image_eps.update() return embedding @@ -220,9 +219,7 @@ class Embeddings: ) duration = datetime.datetime.now().timestamp() - start - self.metrics.text_embeddings_speed.value = ( - self.metrics.text_embeddings_speed.value * 9 + (duration / len(ids)) - ) / 10 + self.text_inference_speed.update(duration / len(ids)) return embeddings @@ -241,10 +238,7 @@ class Embeddings: (event_id, serialize(embedding)), ) - duration = datetime.datetime.now().timestamp() - start - self.metrics.text_embeddings_speed.value = ( - self.metrics.text_embeddings_speed.value * 9 + duration - ) / 10 + self.text_inference_speed.update(datetime.datetime.now().timestamp() - start) self.text_eps.update() return embedding @@ -276,10 +270,7 @@ class Embeddings: items, ) - duration = datetime.datetime.now().timestamp() - start - self.metrics.text_embeddings_speed.value = ( - self.metrics.text_embeddings_speed.value * 9 + (duration / len(ids)) - ) / 10 + self.text_inference_speed.update(datetime.datetime.now().timestamp() - start) return embeddings diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 5f573ef78..0f245107a 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -11,6 +11,7 @@ import shlex import struct import urllib.parse from collections.abc import Mapping +from multiprocessing.sharedctypes import Synchronized from pathlib import Path from typing import Any, Optional, Tuple, Union from zoneinfo import ZoneInfoNotFoundError @@ -26,16 +27,16 @@ logger = logging.getLogger(__name__) class EventsPerSecond: - def __init__(self, max_events=1000, last_n_seconds=10): + def __init__(self, max_events=1000, last_n_seconds=10) -> None: self._start = None self._max_events = max_events self._last_n_seconds = last_n_seconds self._timestamps = [] - def start(self): + def start(self) -> None: self._start = datetime.datetime.now().timestamp() - def update(self): + def update(self) -> None: now = datetime.datetime.now().timestamp() if self._start is None: self._start = now @@ -45,7 +46,7 @@ class EventsPerSecond: self._timestamps = self._timestamps[(1 - self._max_events) :] self.expire_timestamps(now) - def eps(self): + def eps(self) -> float: now = datetime.datetime.now().timestamp() if self._start is None: self._start = now @@ -58,12 +59,29 @@ class EventsPerSecond: return len(self._timestamps) / seconds # remove aged out timestamps - def expire_timestamps(self, now): + def expire_timestamps(self, now: float) -> None: threshold = now - self._last_n_seconds while self._timestamps and self._timestamps[0] < threshold: del self._timestamps[0] +class InferenceSpeed: + def __init__(self, metric: Synchronized) -> None: + self.__metric = metric + self.__initialized = False + + def update(self, inference_time: float) -> None: + if not self.__initialized: + self.__metric.value = inference_time + self.__initialized = True + return + + self.__metric.value = (self.__metric.value * 9 + inference_time) / 10 + + def current(self) -> float: + return self.__metric.value + + def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict: """ :param dct1: First dict to merge