Dedicated LPR improvements (#17453)

* remove license plate from attributes for dedicated lpr cameras

* ensure we always have a color

* use frigate+ models with dedicated lpr cameras

* docs

* docs clarity

* docs enrichments

* use license_plate as object type
This commit is contained in:
Josh Hawkins
2025-03-30 07:43:24 -06:00
committed by GitHub
parent 2c1ded37a1
commit 2920127ada
6 changed files with 183 additions and 59 deletions
+6 -2
View File
@@ -88,7 +88,9 @@ class CameraState:
thickness = 1
else:
thickness = 2
color = self.config.model.colormap[obj["label"]]
color = self.config.model.colormap.get(
obj["label"], (255, 255, 255)
)
else:
thickness = 1
color = (255, 0, 0)
@@ -110,7 +112,9 @@ class CameraState:
and obj["frame_time"] == frame_time
):
thickness = 5
color = self.config.model.colormap[obj["label"]]
color = self.config.model.colormap.get(
obj["label"], (255, 255, 255)
)
# debug autotracking zooming - show the zoom factor box
if (
@@ -21,7 +21,6 @@ from frigate.comms.event_metadata_updater import (
EventMetadataPublisher,
EventMetadataTypeEnum,
)
from frigate.config.camera.camera import CameraTypeEnum
from frigate.const import CLIPS_DIR
from frigate.embeddings.onnx.lpr_embedding import LPR_EMBEDDING_SIZE
from frigate.util.builtin import EventsPerSecond
@@ -972,7 +971,7 @@ class LicensePlateProcessingMixin:
(
now,
camera,
"car",
"license_plate",
event_id,
True,
plate_score,
@@ -994,9 +993,7 @@ class LicensePlateProcessingMixin:
if not self.config.cameras[camera].lpr.enabled:
return
if not dedicated_lpr and self.config.cameras[camera].type == CameraTypeEnum.lpr:
return
# dedicated LPR cam without frigate+
if dedicated_lpr:
id = "dedicated-lpr"
@@ -1050,8 +1047,11 @@ class LicensePlateProcessingMixin:
else:
id = obj_data["id"]
# don't run for non car objects
if obj_data.get("label") != "car":
# don't run for non car or non license plate (dedicated lpr with frigate+) objects
if (
obj_data.get("label") != "car"
and obj_data.get("label") != "license_plate"
):
logger.debug(
f"{camera}: Not a processing license plate for non car object."
)
@@ -1131,26 +1131,34 @@ class LicensePlateProcessingMixin:
license_plate[0] : license_plate[2],
]
else:
# don't run for object without attributes
if not obj_data.get("current_attributes"):
# don't run for object without attributes if this isn't dedicated lpr with frigate+
if (
not obj_data.get("current_attributes")
and obj_data.get("label") != "license_plate"
):
logger.debug(f"{camera}: No attributes to parse.")
return
attributes: list[dict[str, any]] = obj_data.get(
"current_attributes", []
)
for attr in attributes:
if attr.get("label") != "license_plate":
continue
if obj_data.get("label") == "car":
attributes: list[dict[str, any]] = obj_data.get(
"current_attributes", []
)
for attr in attributes:
if attr.get("label") != "license_plate":
continue
if license_plate is None or attr.get(
"score", 0.0
) > license_plate.get("score", 0.0):
license_plate = attr
if license_plate is None or attr.get(
"score", 0.0
) > license_plate.get("score", 0.0):
license_plate = attr
# no license plates detected in this frame
if not license_plate:
return
# no license plates detected in this frame
if not license_plate:
return
# we are using dedicated lpr with frigate+
if obj_data.get("label") == "license_plate":
license_plate = obj_data
license_plate_box = license_plate.get("box")
@@ -1160,7 +1168,9 @@ class LicensePlateProcessingMixin:
or area(license_plate_box)
< self.config.cameras[obj_data["camera"]].lpr.min_area
):
logger.debug(f"{camera}: Invalid license plate box {license_plate}")
logger.debug(
f"{camera}: Area for license plate box {area(license_plate_box)} is less than min_area {self.config.cameras[obj_data['camera']].lpr.min_area}"
)
return
license_plate_frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420)
@@ -1239,8 +1249,11 @@ class LicensePlateProcessingMixin:
)
return
# For LPR cameras, match or assign plate ID using Jaro-Winkler distance
if dedicated_lpr:
# For dedicated LPR cameras, match or assign plate ID using Jaro-Winkler distance
if (
dedicated_lpr
and "license_plate" not in self.config.cameras[camera].objects.track
):
plate_id = None
for existing_id, data in self.detected_license_plates.items():
@@ -1306,8 +1319,11 @@ class LicensePlateProcessingMixin:
(id, top_plate, avg_confidence),
)
if dedicated_lpr:
# save the best snapshot
# save the best snapshot for dedicated lpr cams not using frigate+
if (
dedicated_lpr
and "license_plate" not in self.config.cameras[camera].objects.track
):
logger.debug(
f"{camera}: Writing snapshot for {id}, {top_plate}, {current_time}"
)
+5 -1
View File
@@ -457,7 +457,11 @@ class EmbeddingMaintainer(threading.Thread):
camera_config = self.config.cameras[camera]
if not camera_config.type == CameraTypeEnum.lpr:
if (
camera_config.type != CameraTypeEnum.lpr
or "license_plate" in camera_config.objects.track
):
# we're not a dedicated lpr camera or we are one but we're using frigate+
return
try:
+1 -1
View File
@@ -442,7 +442,7 @@ class TrackedObject:
if bounding_box:
thickness = 2
color = self.colormap[self.obj_data["label"]]
color = self.colormap.get(self.obj_data["label"], (255, 255, 255))
# draw the bounding boxes on the frame
box = self.thumbnail_data["box"]
+30 -7
View File
@@ -15,6 +15,7 @@ from frigate.camera import CameraMetrics, PTZMetrics
from frigate.comms.config_updater import ConfigSubscriber
from frigate.comms.inter_process import InterProcessRequestor
from frigate.config import CameraConfig, DetectConfig, ModelConfig
from frigate.config.camera.camera import CameraTypeEnum
from frigate.const import (
CACHE_DIR,
CACHE_SEGMENT_FORMAT,
@@ -519,6 +520,7 @@ def track_camera(
frame_queue,
frame_shape,
model_config,
config,
config.detect,
frame_manager,
motion_detector,
@@ -585,6 +587,7 @@ def process_frames(
frame_queue: mp.Queue,
frame_shape,
model_config: ModelConfig,
camera_config: CameraConfig,
detect_config: DetectConfig,
frame_manager: FrameManager,
motion_detector: MotionDetector,
@@ -612,6 +615,29 @@ def process_frames(
region_min_size = get_min_region_size(model_config)
attributes_map = model_config.attributes_map
all_attributes = model_config.all_attributes
# remove license_plate from attributes if this camera is a dedicated LPR cam
if camera_config.type == CameraTypeEnum.lpr:
modified_attributes_map = model_config.attributes_map.copy()
if (
"car" in modified_attributes_map
and "license_plate" in modified_attributes_map["car"]
):
modified_attributes_map["car"] = [
attr
for attr in modified_attributes_map["car"]
if attr != "license_plate"
]
attributes_map = modified_attributes_map
all_attributes = [
attr for attr in model_config.all_attributes if attr != "license_plate"
]
while not stop_event.is_set():
_, updated_enabled_config = enabled_config_subscriber.check_for_update()
@@ -805,9 +831,7 @@ def process_frames(
# if detection was run on this frame, consolidate
if len(regions) > 0:
tracked_detections = [
d
for d in consolidated_detections
if d[0] not in model_config.all_attributes
d for d in consolidated_detections if d[0] not in all_attributes
]
# now that we have refined our detections, we need to track objects
object_tracker.match_and_update(
@@ -819,7 +843,7 @@ def process_frames(
# group the attribute detections based on what label they apply to
attribute_detections: dict[str, list[TrackedObjectAttribute]] = {}
for label, attribute_labels in model_config.attributes_map.items():
for label, attribute_labels in attributes_map.items():
attribute_detections[label] = [
TrackedObjectAttribute(d)
for d in consolidated_detections
@@ -836,8 +860,7 @@ def process_frames(
for attributes in attribute_detections.values():
for attribute in attributes:
filtered_objects = filter(
lambda o: attribute.label
in model_config.attributes_map.get(o["label"], []),
lambda o: attribute.label in attributes_map.get(o["label"], []),
all_objects,
)
selected_object_id = attribute.find_best_object(filtered_objects)
@@ -885,7 +908,7 @@ def process_frames(
for obj in object_tracker.tracked_objects.values():
if obj["frame_time"] == frame_time:
thickness = 2
color = model_config.colormap[obj["label"]]
color = model_config.colormap.get(obj["label"], (255, 255, 255))
else:
thickness = 1
color = (255, 0, 0)