mirror of
https://github.com/alexhopeoconnor/frigate.git
synced 2026-10-04 03:18:11 +10:00
filter dedicated LPR plates before creating the event (#23977)
This commit is contained in:
@@ -1172,6 +1172,28 @@ class LicensePlateProcessingMixin:
|
|||||||
|
|
||||||
return rep["plate"], rep["conf"], rep["char_confidences"], rep["area"]
|
return rep["plate"], rep["conf"], rep["char_confidences"], rep["area"]
|
||||||
|
|
||||||
|
def _passes_plate_filters(self, camera: str, plate: str) -> bool:
|
||||||
|
"""Check a plate against the configured length and format filters."""
|
||||||
|
if len(plate) < self.lpr_config.min_plate_length:
|
||||||
|
logger.debug(
|
||||||
|
f"{camera}: Filtered out plate '{plate}' due to length ({len(plate)} < {self.lpr_config.min_plate_length})"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.lpr_config.format:
|
||||||
|
try:
|
||||||
|
if not re.fullmatch(self.lpr_config.format, plate):
|
||||||
|
logger.debug(
|
||||||
|
f"{camera}: Filtered out plate '{plate}' due to format mismatch"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
except re.error:
|
||||||
|
logger.error(
|
||||||
|
f"{camera}: Invalid regex in LPR format configuration: {self.lpr_config.format}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
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()
|
||||||
@@ -1511,10 +1533,14 @@ class LicensePlateProcessingMixin:
|
|||||||
plate_id = None
|
plate_id = None
|
||||||
|
|
||||||
for existing_id, data in self.detected_license_plates.items():
|
for existing_id, data in self.detected_license_plates.items():
|
||||||
|
# entries from the object pipeline on this camera have no
|
||||||
|
# last_seen until they pass the filters below
|
||||||
|
last_seen = data.get("last_seen")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
data["camera"] == camera
|
data["camera"] == camera
|
||||||
and data["last_seen"] is not None
|
and last_seen is not None
|
||||||
and current_time - data["last_seen"]
|
and current_time - last_seen
|
||||||
<= self.config.cameras[camera].lpr.expire_time
|
<= self.config.cameras[camera].lpr.expire_time
|
||||||
):
|
):
|
||||||
similarity = JaroWinkler.similarity(data["plate"], top_plate)
|
similarity = JaroWinkler.similarity(data["plate"], top_plate)
|
||||||
@@ -1525,6 +1551,11 @@ class LicensePlateProcessingMixin:
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
if plate_id is None:
|
if plate_id is None:
|
||||||
|
# the event id doubles as the cluster key, so a plate rejected
|
||||||
|
# after this point would leave an entry that never expires
|
||||||
|
if not self._passes_plate_filters(camera, top_plate):
|
||||||
|
return
|
||||||
|
|
||||||
plate_id = self._generate_plate_event(camera, top_plate, avg_confidence)
|
plate_id = self._generate_plate_event(camera, top_plate, avg_confidence)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: New plate event for dedicated LPR camera {plate_id}: {top_plate}"
|
f"{camera}: New plate event for dedicated LPR camera {plate_id}: {top_plate}"
|
||||||
@@ -1569,27 +1600,12 @@ class LicensePlateProcessingMixin:
|
|||||||
f"{camera}: Clustering changed top plate '{top_plate}' (conf: {avg_confidence:.3f}) to rep '{rep_plate}' (conf: {rep_conf:.3f})"
|
f"{camera}: Clustering changed top plate '{top_plate}' (conf: {avg_confidence:.3f}) to rep '{rep_plate}' (conf: {rep_conf:.3f})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply length and format filters to the clustered representative
|
# filter the clustered representative rather than individual OCR
|
||||||
# rather than individual OCR readings, so noisy variants still
|
# readings, so noisy variants still contribute to clustering even
|
||||||
# contribute to clustering even when they don't pass on their own.
|
# when they don't pass on their own
|
||||||
if len(rep_plate) < self.lpr_config.min_plate_length:
|
if not self._passes_plate_filters(camera, rep_plate):
|
||||||
logger.debug(
|
|
||||||
f"{camera}: Filtered out clustered plate '{rep_plate}' due to length ({len(rep_plate)} < {self.lpr_config.min_plate_length})"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.lpr_config.format:
|
|
||||||
try:
|
|
||||||
if not re.fullmatch(self.lpr_config.format, rep_plate):
|
|
||||||
logger.debug(
|
|
||||||
f"{camera}: Filtered out clustered plate '{rep_plate}' due to format mismatch"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except re.error:
|
|
||||||
logger.error(
|
|
||||||
f"{camera}: Invalid regex in LPR format configuration: {self.lpr_config.format}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update stored rep
|
# Update stored rep
|
||||||
self.detected_license_plates[id].update(
|
self.detected_license_plates[id].update(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user