Update metrics inference speed to start with 0 ms

This commit is contained in:
Nicolas Mowen 2025-05-08 08:39:11 -06:00
parent 325a150cc4
commit b8f6a5d6b6
5 changed files with 46 additions and 50 deletions

View File

@ -25,7 +25,7 @@ from frigate.comms.event_metadata_updater import (
from frigate.const import CLIPS_DIR from frigate.const import CLIPS_DIR
from frigate.embeddings.onnx.lpr_embedding import LPR_EMBEDDING_SIZE from frigate.embeddings.onnx.lpr_embedding import LPR_EMBEDDING_SIZE
from frigate.types import TrackedObjectUpdateTypesEnum 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 frigate.util.image import area
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -36,8 +36,10 @@ WRITE_DEBUG_IMAGES = False
class LicensePlateProcessingMixin: class LicensePlateProcessingMixin:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.plate_rec_speed = InferenceSpeed(self.metrics.alpr_speed)
self.plates_rec_second = EventsPerSecond() self.plates_rec_second = EventsPerSecond()
self.plates_rec_second.start() self.plates_rec_second.start()
self.plate_det_speed = InferenceSpeed(self.metrics.yolov9_lpr_speed)
self.plates_det_second = EventsPerSecond() self.plates_det_second = EventsPerSecond()
self.plates_det_second.start() self.plates_det_second.start()
self.event_metadata_publisher = EventMetadataPublisher() self.event_metadata_publisher = EventMetadataPublisher()
@ -1157,22 +1159,6 @@ class LicensePlateProcessingMixin:
# 5. Return True if previous plate scores higher # 5. Return True if previous plate scores higher
return prev_score > curr_score 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: 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.""" """Generate a unique ID for a plate event based on camera and text."""
now = datetime.datetime.now().timestamp() 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" f"{camera}: YOLOv9 LPD inference time: {(datetime.datetime.now().timestamp() - yolov9_start) * 1000:.2f} ms"
) )
self.plates_det_second.update() self.plates_det_second.update()
self.__update_yolov9_metrics( self.plate_det_speed.update(
datetime.datetime.now().timestamp() - yolov9_start 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" f"{camera}: YOLOv9 LPD inference time: {(datetime.datetime.now().timestamp() - yolov9_start) * 1000:.2f} ms"
) )
self.plates_det_second.update() self.plates_det_second.update()
self.__update_yolov9_metrics( self.plate_det_speed.update(
datetime.datetime.now().timestamp() - yolov9_start datetime.datetime.now().timestamp() - yolov9_start
) )
@ -1433,7 +1419,7 @@ class LicensePlateProcessingMixin:
camera, id, license_plate_frame camera, id, license_plate_frame
) )
self.plates_rec_second.update() 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: if license_plates:
for plate, confidence, text_area in zip(license_plates, confidences, areas): for plate, confidence, text_area in zip(license_plates, confidences, areas):

View File

@ -25,7 +25,7 @@ from frigate.data_processing.common.face.model import (
FaceRecognizer, FaceRecognizer,
) )
from frigate.types import TrackedObjectUpdateTypesEnum 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 frigate.util.image import area
from ..types import DataProcessorMetrics from ..types import DataProcessorMetrics
@ -56,6 +56,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
self.person_face_history: dict[str, list[tuple[str, float, int]]] = {} self.person_face_history: dict[str, list[tuple[str, float, int]]] = {}
self.recognizer: FaceRecognizer | None = None self.recognizer: FaceRecognizer | None = None
self.faces_per_second = EventsPerSecond() self.faces_per_second = EventsPerSecond()
self.inference_speed = InferenceSpeed(self.metrics.face_rec_speed)
download_path = os.path.join(MODEL_CACHE_DIR, "facedet") download_path = os.path.join(MODEL_CACHE_DIR, "facedet")
self.model_files = { self.model_files = {
@ -153,9 +154,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
def __update_metrics(self, duration: float) -> None: def __update_metrics(self, duration: float) -> None:
self.faces_per_second.update() self.faces_per_second.update()
self.metrics.face_rec_speed.value = ( self.inference_speed.update(duration)
self.metrics.face_rec_speed.value * 9 + duration
) / 10
def process_frame(self, obj_data: dict[str, any], frame: np.ndarray): def process_frame(self, obj_data: dict[str, any], frame: np.ndarray):
"""Look for faces in image.""" """Look for faces in image."""

View File

@ -7,7 +7,9 @@ from multiprocessing.sharedctypes import Synchronized
class DataProcessorMetrics: class DataProcessorMetrics:
image_embeddings_speed: Synchronized image_embeddings_speed: Synchronized
image_embeddings_eps: Synchronized
text_embeddings_speed: Synchronized text_embeddings_speed: Synchronized
text_embeddings_eps: Synchronized
face_rec_speed: Synchronized face_rec_speed: Synchronized
face_rec_fps: Synchronized face_rec_fps: Synchronized
alpr_speed: Synchronized alpr_speed: Synchronized
@ -16,15 +18,15 @@ class DataProcessorMetrics:
yolov9_lpr_pps: Synchronized yolov9_lpr_pps: Synchronized
def __init__(self): 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.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.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.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.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) self.yolov9_lpr_pps = mp.Value("d", 0.0)

View File

@ -21,7 +21,7 @@ from frigate.data_processing.types import DataProcessorMetrics
from frigate.db.sqlitevecq import SqliteVecQueueDatabase from frigate.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.models import Event from frigate.models import Event
from frigate.types import ModelStatusTypesEnum 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 frigate.util.path import get_event_thumbnail_bytes
from .onnx.jina_v1_embedding import JinaV1ImageEmbedding, JinaV1TextEmbedding from .onnx.jina_v1_embedding import JinaV1ImageEmbedding, JinaV1TextEmbedding
@ -75,8 +75,10 @@ class Embeddings:
self.metrics = metrics self.metrics = metrics
self.requestor = InterProcessRequestor() self.requestor = InterProcessRequestor()
self.image_inference_speed = InferenceSpeed(self.metrics.image_embeddings_speed)
self.image_eps = EventsPerSecond() self.image_eps = EventsPerSecond()
self.image_eps.start() self.image_eps.start()
self.text_inference_speed = InferenceSpeed(self.metrics.text_embeddings_speed)
self.text_eps = EventsPerSecond() self.text_eps = EventsPerSecond()
self.text_eps.start() self.text_eps.start()
@ -183,10 +185,7 @@ class Embeddings:
(event_id, serialize(embedding)), (event_id, serialize(embedding)),
) )
duration = datetime.datetime.now().timestamp() - start self.image_inference_speed.update(datetime.datetime.now().timestamp() - start)
self.metrics.image_embeddings_speed.value = (
self.metrics.image_embeddings_speed.value * 9 + duration
) / 10
self.image_eps.update() self.image_eps.update()
return embedding return embedding
@ -220,9 +219,7 @@ class Embeddings:
) )
duration = datetime.datetime.now().timestamp() - start duration = datetime.datetime.now().timestamp() - start
self.metrics.text_embeddings_speed.value = ( self.text_inference_speed.update(duration / len(ids))
self.metrics.text_embeddings_speed.value * 9 + (duration / len(ids))
) / 10
return embeddings return embeddings
@ -241,10 +238,7 @@ class Embeddings:
(event_id, serialize(embedding)), (event_id, serialize(embedding)),
) )
duration = datetime.datetime.now().timestamp() - start self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
self.metrics.text_embeddings_speed.value = (
self.metrics.text_embeddings_speed.value * 9 + duration
) / 10
self.text_eps.update() self.text_eps.update()
return embedding return embedding
@ -276,10 +270,7 @@ class Embeddings:
items, items,
) )
duration = datetime.datetime.now().timestamp() - start self.text_inference_speed.update(datetime.datetime.now().timestamp() - start)
self.metrics.text_embeddings_speed.value = (
self.metrics.text_embeddings_speed.value * 9 + (duration / len(ids))
) / 10
return embeddings return embeddings

View File

@ -11,6 +11,7 @@ import shlex
import struct import struct
import urllib.parse import urllib.parse
from collections.abc import Mapping from collections.abc import Mapping
from multiprocessing.sharedctypes import Synchronized
from pathlib import Path from pathlib import Path
from typing import Any, Optional, Tuple, Union from typing import Any, Optional, Tuple, Union
from zoneinfo import ZoneInfoNotFoundError from zoneinfo import ZoneInfoNotFoundError
@ -26,16 +27,16 @@ logger = logging.getLogger(__name__)
class EventsPerSecond: 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._start = None
self._max_events = max_events self._max_events = max_events
self._last_n_seconds = last_n_seconds self._last_n_seconds = last_n_seconds
self._timestamps = [] self._timestamps = []
def start(self): def start(self) -> None:
self._start = datetime.datetime.now().timestamp() self._start = datetime.datetime.now().timestamp()
def update(self): def update(self) -> None:
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()
if self._start is None: if self._start is None:
self._start = now self._start = now
@ -45,7 +46,7 @@ class EventsPerSecond:
self._timestamps = self._timestamps[(1 - self._max_events) :] self._timestamps = self._timestamps[(1 - self._max_events) :]
self.expire_timestamps(now) self.expire_timestamps(now)
def eps(self): def eps(self) -> float:
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()
if self._start is None: if self._start is None:
self._start = now self._start = now
@ -58,12 +59,29 @@ class EventsPerSecond:
return len(self._timestamps) / seconds return len(self._timestamps) / seconds
# remove aged out timestamps # remove aged out timestamps
def expire_timestamps(self, now): def expire_timestamps(self, now: float) -> None:
threshold = now - self._last_n_seconds threshold = now - self._last_n_seconds
while self._timestamps and self._timestamps[0] < threshold: while self._timestamps and self._timestamps[0] < threshold:
del self._timestamps[0] 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: def deep_merge(dct1: dict, dct2: dict, override=False, merge_lists=False) -> dict:
""" """
:param dct1: First dict to merge :param dct1: First dict to merge