1+ import cv2
2+ import numpy as np
3+ from ultralytics import YOLO
4+ import easyocr
5+ from PIL import Image
6+ from huggingface_hub import hf_hub_download
7+ import os
8+ import warnings
9+
10+ # Suppress warnings
11+ warnings .filterwarnings ('ignore' )
12+
13+ # Download EULPR model from HuggingFace
14+ print ("Downloading EULPR model from HuggingFace..." )
15+ model_path = hf_hub_download (repo_id = "0xnu/european-license-plate-recognition" , filename = "model.onnx" )
16+ config_path = hf_hub_download (repo_id = "0xnu/european-license-plate-recognition" , filename = "config.json" )
17+
18+ # Load EULPR model with explicit task specification
19+ yolo_model = YOLO (model_path , task = 'detect' )
20+ ocr_reader = easyocr .Reader (['en' , 'de' , 'fr' , 'es' , 'it' , 'nl' ], gpu = False , verbose = False )
21+
22+ def recognize_license_plate (image_path ):
23+ """
24+ Recognise European licence plates using EULPR detection and EasyOCR text extraction.
25+
26+ Args:
27+ image_path (str): Path to the input image
28+
29+ Returns:
30+ list: List of dictionaries containing detected plate text and confidence scores
31+ """
32+ # Validate file exists
33+ if not os .path .exists (image_path ):
34+ raise FileNotFoundError (f"Image file not found: { image_path } " )
35+
36+ # Load and validate image
37+ image = cv2 .imread (image_path )
38+ if image is None :
39+ raise ValueError (f"Cannot read image file: { image_path } " )
40+
41+ # Convert colour space
42+ image_rgb = cv2 .cvtColor (image , cv2 .COLOR_BGR2RGB )
43+
44+ # Detect licence plates using EULPR
45+ results = yolo_model (image_rgb , conf = 0.5 , iou = 0.4 , verbose = False )
46+
47+ plates = []
48+
49+ for result in results :
50+ boxes = result .boxes
51+ if boxes is not None :
52+ for box in boxes :
53+ # Get coordinates
54+ x1 , y1 , x2 , y2 = box .xyxy [0 ].cpu ().numpy ()
55+
56+ # Crop plate with bounds checking
57+ h , w = image_rgb .shape [:2 ]
58+ x1 , y1 , x2 , y2 = max (0 , int (x1 )), max (0 , int (y1 )), min (w , int (x2 )), min (h , int (y2 ))
59+
60+ if x2 > x1 and y2 > y1 : # Valid crop dimensions
61+ plate_crop = image_rgb [y1 :y2 , x1 :x2 ]
62+
63+ # Extract text only if crop is valid
64+ if plate_crop .size > 0 :
65+ # Enhance image quality for better OCR results
66+ plate_crop_enhanced = enhance_plate_image (plate_crop )
67+
68+ ocr_results = ocr_reader .readtext (plate_crop_enhanced )
69+ if ocr_results :
70+ text = ocr_results [0 ][1 ]
71+ confidence = float (ocr_results [0 ][2 ])
72+ detection_confidence = float (box .conf [0 ])
73+
74+ plates .append ({
75+ 'text' : text ,
76+ 'ocr_confidence' : confidence ,
77+ 'detection_confidence' : detection_confidence ,
78+ 'bbox' : [x1 , y1 , x2 , y2 ]
79+ })
80+
81+ return plates
82+
83+ def enhance_plate_image (plate_crop ):
84+ """
85+ Enhance plate image quality for improved OCR accuracy.
86+
87+ Args:
88+ plate_crop (np.ndarray): Cropped plate image
89+
90+ Returns:
91+ np.ndarray: Enhanced plate image
92+ """
93+ # Convert to grayscale
94+ gray = cv2 .cvtColor (plate_crop , cv2 .COLOR_RGB2GRAY )
95+
96+ # Apply Gaussian blur to reduce noise
97+ blurred = cv2 .GaussianBlur (gray , (3 , 3 ), 0 )
98+
99+ # Apply adaptive thresholding
100+ enhanced = cv2 .adaptiveThreshold (blurred , 255 , cv2 .ADAPTIVE_THRESH_GAUSSIAN_C , cv2 .THRESH_BINARY , 11 , 2 )
101+
102+ # Convert back to RGB
103+ enhanced_rgb = cv2 .cvtColor (enhanced , cv2 .COLOR_GRAY2RGB )
104+
105+ return enhanced_rgb
106+
107+ def process_multiple_images (image_directory ):
108+ """
109+ Process multiple images in a directory for licence plate recognition.
110+
111+ Args:
112+ image_directory (str): Path to directory containing images
113+
114+ Returns:
115+ dict: Results for each processed image
116+ """
117+ supported_formats = ('.jpg' , '.jpeg' , '.png' , '.bmp' , '.tiff' )
118+ results_dict = {}
119+
120+ if not os .path .exists (image_directory ):
121+ print (f"Directory not found: { image_directory } " )
122+ return results_dict
123+
124+ image_files = [f for f in os .listdir (image_directory ) if f .lower ().endswith (supported_formats )]
125+
126+ for image_file in image_files :
127+ image_path = os .path .join (image_directory , image_file )
128+ try :
129+ results = recognize_license_plate (image_path )
130+ results_dict [image_file ] = results
131+ print (f"Processed { image_file } : { len (results )} plates detected" )
132+ except Exception as e :
133+ print (f"Error processing { image_file } : { e } " )
134+ results_dict [image_file ] = []
135+
136+ return results_dict
137+
138+ # Create examples directory if it doesn't exist
139+ os .makedirs ('./examples' , exist_ok = True )
140+
141+ # Process single image
142+ image_path = './examples/poland_car.jpeg'
143+ if os .path .exists (image_path ):
144+ try :
145+ results = recognize_license_plate (image_path )
146+ print ("Detection Results:" )
147+ for i , plate in enumerate (results ):
148+ print (f"Plate { i + 1 } : { plate ['text' ]} (OCR: { plate ['ocr_confidence' ]:.2f} , Detection: { plate ['detection_confidence' ]:.2f} )" )
149+ except Exception as e :
150+ print (f"Error processing image: { e } " )
151+ else :
152+ print (f"Please ensure the image file exists at: { image_path } " )
153+ print ("Current working directory:" , os .getcwd ())
154+ print ("Contents of examples directory:" , os .listdir ('./examples' ) if os .path .exists ('./examples' ) else "Directory doesn't exist" )
155+
156+ # Optional: Process all images in examples directory
157+ # batch_results = process_multiple_images('./examples')
158+ # print("\nBatch Processing Results:")
159+ # for filename, plates in batch_results.items():
160+ # print(f"{filename}: {len(plates)} plates detected")
0 commit comments