Compare commits

...

2 Commits

2 changed files with 77 additions and 74 deletions

View File

@ -1,14 +1,19 @@
import json import json
import logging
import os import os
import sys import sys
import threading import threading
import time import time
from time import sleep
import ultimateAlprSdk import ultimateAlprSdk
from PIL import Image from PIL import Image
from flask import Flask, request, jsonify, render_template from flask import Flask, request, jsonify, render_template
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
counter_lock = threading.Lock()
counter = 0 counter = 0
bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__))) bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
boot_time = time.time() boot_time = time.time()
@ -22,48 +27,46 @@ information. The server is created using Flask and the ultimateALPR SDK is used
See the README.md file for more information on how to run this script. See the README.md file for more information on how to run this script.
""" """
# Defines the default JSON configuration. More information at # Load configuration from a JSON file or environment variables
# https://www.doubango.org/SDKs/anpr/docs/Configuration_options.html CONFIG_PATH = os.path.join(bundle_dir,
JSON_CONFIG = { 'config.json') # TODO: store config file outside of bundle (to avoid compilation by users)
"assets_folder": os.path.join(bundle_dir, "assets"), # don't change this if os.path.exists(CONFIG_PATH):
with open(CONFIG_PATH, 'r') as config_file:
JSON_CONFIG = json.load(config_file)
else:
JSON_CONFIG = {
"assets_folder": os.path.join(bundle_dir, "assets"),
"charset": "latin",
"car_noplate_detect_enabled": False,
"ienv_enabled": False,
"openvino_enabled": True,
"openvino_device": "CPU",
"npu_enabled": False,
"klass_lpci_enabled": False,
"klass_vcr_enabled": False,
"klass_vmmr_enabled": False,
"klass_vbsr_enabled": False,
"license_token_file": "",
"license_token_data": "",
"charset": "latin", "debug_level": "fatal",
"car_noplate_detect_enabled": False, "debug_write_input_image_enabled": False,
"ienv_enabled": False, "debug_internal_data_path": ".",
"openvino_enabled": True, "num_threads": -1,
"openvino_device": "CPU", "gpgpu_enabled": True,
"npu_enabled": False, "max_latency": -1,
"klass_lpci_enabled": False, "klass_vcr_gamma": 1.5,
"klass_vcr_enabled": False, "detect_roi": [0, 0, 0, 0],
"klass_vmmr_enabled": False, "detect_minscore": 0.35,
"klass_vbsr_enabled": False, "car_noplate_detect_min_score": 0.8,
"license_token_file": "", "pyramidal_search_enabled": False,
"license_token_data": "", "pyramidal_search_sensitivity": 0.38,
"pyramidal_search_minscore": 0.8,
"debug_level": "info", "pyramidal_search_min_image_size_inpixels": 800,
"debug_write_input_image_enabled": False, "recogn_rectify_enabled": True,
"debug_internal_data_path": ".", "recogn_minscore": 0.4,
"recogn_score_type": "min"
"num_threads": -1, }
"gpgpu_enabled": True,
"max_latency": -1,
"klass_vcr_gamma": 1.5,
"detect_roi": [0, 0, 0, 0],
"detect_minscore": 0.35,
"car_noplate_detect_min_score": 0.8,
"pyramidal_search_enabled": False,
"pyramidal_search_sensitivity": 0.38, # default 0.28
"pyramidal_search_minscore": 0.8,
"pyramidal_search_min_image_size_inpixels": 800,
"recogn_rectify_enabled": True, # heavy on cpu
"recogn_minscore": 0.4,
"recogn_score_type": "min"
}
IMAGE_TYPES_MAPPING = { IMAGE_TYPES_MAPPING = {
'RGB': ultimateAlprSdk.ULTALPR_SDK_IMAGE_TYPE_RGB24, 'RGB': ultimateAlprSdk.ULTALPR_SDK_IMAGE_TYPE_RGB24,
@ -75,21 +78,26 @@ config = json.dumps(JSON_CONFIG)
def start_backend_loop(): def start_backend_loop():
global counter global boot_time, counter
load_engine()
# loop for about an hour or 3000 requests then reload the engine (fix for trial license) while True:
while counter < 3000 and time.time() - boot_time < 60 * 60: load_engine()
# every 120 sec
if int(time.time()) % 120 == 0:
if not is_engine_loaded():
unload_engine() # just in case
load_engine()
sleep(1) # loop for about an hour or 3000 requests then reload the engine (fix for trial license)
while counter < 3000 and time.time() - boot_time < 60 * 60:
# every 120 sec
if int(time.time()) % 120 == 0:
if not is_engine_loaded():
unload_engine() # just in case
load_engine()
time.sleep(1)
unload_engine() unload_engine()
start_backend_loop()
# Reset counter and boot_time to restart the loop
with counter_lock:
counter = 0
boot_time = time.time()
def is_engine_loaded(): def is_engine_loaded():
@ -111,7 +119,8 @@ def unload_engine():
def process_image(image: Image) -> str: def process_image(image: Image) -> str:
global counter global counter
counter += 1 with counter_lock:
counter += 1
width, height = image.size width, height = image.size
@ -120,7 +129,6 @@ def process_image(image: Image) -> str:
else: else:
raise ValueError("Invalid mode: %s" % image.mode) raise ValueError("Invalid mode: %s" % image.mode)
# TODO: add check for if engine still loaded
result = ultimateAlprSdk.UltAlprSdkEngine_process( result = ultimateAlprSdk.UltAlprSdkEngine_process(
image_type, image_type,
image.tobytes(), image.tobytes(),
@ -152,28 +160,25 @@ def create_rest_server_flask():
interference = time.time() interference = time.time()
if 'upload' not in request.files: if 'upload' not in request.files:
return jsonify({'error': 'No image found'}) return jsonify({'error': 'No image found'}), 400
if 'grid_size' in request.form and request.form['grid_size'].isdigit():
grid_size = int(request.form['grid_size']) grid_size = int(request.form.get('grid_size', 3))
wanted_cells = request.form.get('wanted_cells')
if wanted_cells:
wanted_cells = [int(cell) for cell in wanted_cells.split(',')]
else: else:
grid_size = None wanted_cells = list(range(1, grid_size * grid_size + 1))
if 'wanted_cells' in request.form and request.form['wanted_cells']:
wanted_cells = request.form['wanted_cells'].split(',')
wanted_cells = [int(cell) for cell in wanted_cells]
else:
wanted_cells = None
image = request.files['upload'] image = request.files['upload']
if image.filename == '': if image.filename == '':
return jsonify({'error': 'No selected file'}) return jsonify({'error': 'No selected file'}), 400
image = Image.open(image) image = Image.open(image)
result = process_image(image) result = process_image(image)
result = convert_to_cpai_compatible(result) result = convert_to_cpai_compatible(result)
if not result['predictions']: if not result['predictions']:
print("No plate found in the image, attempting to split the image") logger.debug("No plate found in the image, attempting to split the image")
predictions_found = find_best_plate_with_split(image, grid_size, wanted_cells) predictions_found = find_best_plate_with_split(image, grid_size, wanted_cells)
if predictions_found: if predictions_found:
@ -234,9 +239,7 @@ def convert_to_cpai_compatible(result):
return response return response
def find_best_plate_with_split(image: Image, grid_size: int = None, wanted_cells: str = None): def find_best_plate_with_split(image: Image, grid_size: int = 3, wanted_cells: list = None):
if grid_size is None:
grid_size = 3
if wanted_cells is None: if wanted_cells is None:
wanted_cells = list(range(1, grid_size * grid_size + 1)) wanted_cells = list(range(1, grid_size * grid_size + 1))
@ -282,8 +285,8 @@ def find_best_plate_with_split(image: Image, grid_size: int = None, wanted_cells
if __name__ == '__main__': if __name__ == '__main__':
engine = threading.Thread(target=start_backend_loop, daemon=True) engine_thread = threading.Thread(target=start_backend_loop, daemon=True)
engine.start() engine_thread.start()
app = create_rest_server_flask() app = create_rest_server_flask()
app.run(host='0.0.0.0', port=5000) app.run(host='0.0.0.0', port=5000)

View File

@ -1,2 +1,2 @@
pyinstaller --noconfirm --console --add-data libs:. --add-data assets:assets --add-data static:static --add-data templates:templates --name easy-local-alpr-1.1.0-openvinocpu_linux_x86_64 "alpr_api.py" pyinstaller --noconfirm --console --add-data libs:. --add-data assets:assets --add-data static:static --add-data templates:templates --name easy-local-alpr-1.4.0-openvinocpu_linux_x86_64 "alpr_api.py"
# optional: --onefile # optional: --onefile