Skip to content

Commit 905bbfd

Browse files
committed
misc(feat): eulpr
Signed-off-by: 0xnu <f@finbarrs.eu>
1 parent e4108cc commit 905bbfd

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

eulpr_image.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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

Comments
 (0)