Add stationary scan duration for LPR (#20444)

This commit is contained in:
Josh Hawkins 2025-10-12 07:20:14 -05:00 committed by GitHub
parent 78d487045b
commit a2ad77c36e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 13 deletions

View File

@ -5,7 +5,7 @@ title: License Plate Recognition (LPR)
Frigate can recognize license plates on vehicles and automatically add the detected characters to the `recognized_license_plate` field or a known name as a `sub_label` to tracked objects of type `car` or `motorcycle`. A common use case may be to read the license plates of cars pulling into a driveway or cars passing by on a street. Frigate can recognize license plates on vehicles and automatically add the detected characters to the `recognized_license_plate` field or a known name as a `sub_label` to tracked objects of type `car` or `motorcycle`. A common use case may be to read the license plates of cars pulling into a driveway or cars passing by on a street.
LPR works best when the license plate is clearly visible to the camera. For moving vehicles, Frigate continuously refines the recognition process, keeping the most confident result. However, LPR does not run on stationary vehicles. LPR works best when the license plate is clearly visible to the camera. For moving vehicles, Frigate continuously refines the recognition process, keeping the most confident result. When a vehicle becomes stationary, LPR continues to run for a short time after to attempt recognition.
When a plate is recognized, the details are: When a plate is recognized, the details are:

View File

@ -48,6 +48,9 @@ class LicensePlateProcessingMixin:
MODEL_CACHE_DIR, "paddleocr-onnx", "ppocr_keys_v1.txt" MODEL_CACHE_DIR, "paddleocr-onnx", "ppocr_keys_v1.txt"
) )
) )
# process plates that are stationary and have no position changes for 5 seconds
self.stationary_scan_duration = 5
self.batch_size = 6 self.batch_size = 6
# Object config # Object config
@ -1269,20 +1272,38 @@ class LicensePlateProcessingMixin:
) )
return return
# don't run for stationary car objects # don't run for non-stationary objects with no position changes to avoid processing uncertain moving objects
if obj_data.get("stationary") == True: # zero position_changes is the initial state after registering a new tracked object
# LPR will run 2 frames after detect.min_initialized is reached
if obj_data.get("position_changes", 0) == 0 and not obj_data.get(
"stationary", False
):
logger.debug( logger.debug(
f"{camera}: Not a processing license plate for a stationary car/motorcycle object." f"{camera}: Skipping LPR for non-stationary {obj_data['label']} object {id} with no position changes. (Detected in {self.config.cameras[camera].detect.min_initialized + 1} concurrent frames, threshold to run is {self.config.cameras[camera].detect.min_initialized + 2} frames)"
) )
return return
# don't run for objects with no position changes # run for stationary objects for a limited time after they become stationary
# this is the initial state after registering a new tracked object if obj_data.get("stationary") == True:
# LPR will run 2 frames after detect.min_initialized is reached threshold = self.config.cameras[camera].detect.stationary.threshold
if obj_data.get("position_changes", 0) == 0: if obj_data.get("motionless_count", 0) >= threshold:
logger.debug( frames_since_stationary = (
f"{camera}: Plate detected in {self.config.cameras[camera].detect.min_initialized + 1} concurrent frames, LPR frame threshold ({self.config.cameras[camera].detect.min_initialized + 2})" obj_data.get("motionless_count", 0) - threshold
) )
fps = self.config.cameras[camera].detect.fps
time_since_stationary = frames_since_stationary / fps
# only print this log for a short time to avoid log spam
if (
self.stationary_scan_duration
< time_since_stationary
<= self.stationary_scan_duration + 1
):
logger.debug(
f"{camera}: {obj_data.get('label', 'An')} object {id} has been stationary for > {self.stationary_scan_duration} seconds, skipping LPR."
)
if time_since_stationary > self.stationary_scan_duration:
return return
license_plate: Optional[dict[str, Any]] = None license_plate: Optional[dict[str, Any]] = None
@ -1424,7 +1445,7 @@ class LicensePlateProcessingMixin:
license_plate_frame, license_plate_frame,
) )
logger.debug(f"{camera}: Running plate recognition.") logger.debug(f"{camera}: Running plate recognition for id: {id}.")
# run detection, returns results sorted by confidence, best first # run detection, returns results sorted by confidence, best first
start = datetime.datetime.now().timestamp() start = datetime.datetime.now().timestamp()