import cv2
import pandas as pd
import easyocr
from ultralytics import YOLO
from datetime import datetime
import os
import re
import numpy as np
# Use the 'alpr' library
try:
from openalpr import Alpr
print("[INFO] OpenALPR library loaded successfully.")
except ImportError:
Alpr = None
print("[WARN] OpenALPR library not found. Falling back to EasyOCR only. Install
OpenALPR for better accuracy.")
# ================== CONFIG ==================
MODEL_PATH = "yolo_model/[Link]" # your YOLO path (if trained for plates,
great)
REGISTERED_FILE = "[Link]"
CONF_THRESHOLD = 0.35
GUIDE_COLOR = (0, 255, 255) # Yellow
MIN_PLATE_AREA = 2000 # contour fallback minimum area
# OpenALPR specific config
ALPR_COUNTRY = "eu" # Use 'us' for North America, 'eu' for Europe, etc.
ALPR_TOP_CANDIDATES = 3
# =============================================
# load model if exists
model = None
try:
if [Link](MODEL_PATH):
model = YOLO(MODEL_PATH)
print("[INFO] YOLO model loaded:", MODEL_PATH)
else:
print("[WARN] YOLO model not found at", MODEL_PATH, "– using contour
fallback.")
model = None
except Exception as e:
print("[WARN] Failed loading YOLO model:", e)
model = None
# Load registered details
if [Link](REGISTERED_FILE):
registered_df = pd.read_csv(REGISTERED_FILE)
else:
registered_df = [Link](columns=["number_plate", "name", "department"])
registered_df.to_csv(REGISTERED_FILE, index=False)
# Create attendance file for today
date_str = [Link]().strftime("%Y-%m-%d")
attendance_file = f"attendance_{date_str}.csv"
if not [Link](attendance_file):
[Link](columns=["number_plate", "name", "department",
"time"]).to_csv(attendance_file, index=False)
attendance_df = pd.read_csv(attendance_file)
# OCR reader (easyocr)
try:
reader = [Link](['en'], gpu=False)
except Exception as e:
raise RuntimeError("EasyOCR init failed. Install easyocr and dependencies.")
from e
# OpenALPR reader
alpr_instance = None
if Alpr:
try:
alpr_instance = Alpr(ALPR_COUNTRY, "/etc/openalpr/[Link]",
"/usr/share/openalpr/runtime_data")
if not alpr_instance.is_loaded():
raise RuntimeError("Error loading OpenALPR.")
except Exception as e:
print(f"[WARN] OpenALPR init failed: {e}. Falling back to EasyOCR.")
alpr_instance = None
# Track today's attendance in-memory
today_attendance = set(attendance_df['number_plate'].astype(str).tolist())
# ROI selection vars
roi_coords = None
drawing = False
ix, iy = -1, -1
def draw_rectangle(event, x, y, flags, param):
"""Mouse callback for drawing ROI on the preview frame."""
global ix, iy, drawing, roi_coords
frame_copy = [Link]()
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix, iy = x, y
elif event == cv2.EVENT_MOUSEMOVE and drawing:
[Link](frame_copy, (ix, iy), (x, y), GUIDE_COLOR, 2)
[Link]("Select ROI", frame_copy)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
roi_coords = (min(ix, x), min(iy, y), max(ix, x), max(iy, y))
print(f"[INFO] ROI Selected: {roi_coords}")
[Link]("Select ROI")
# ---------------- image preprocessing helpers ----------------
def enhance_variants(crop):
"""Return list of RGB images (uint8) with several preprocessing variants for
OCR."""
variants = []
# Original (RGB)
[Link]([Link](crop, cv2.COLOR_BGR2RGB))
# CLAHE on grayscale
gray = [Link](crop, cv2.COLOR_BGR2GRAY)
clahe = [Link](clipLimit=3.0, tileGridSize=(8, 8))
cl = [Link](gray)
[Link]([Link](cl, cv2.COLOR_GRAY2RGB))
# Adaptive Threshold
try:
at = [Link](gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
[Link]([Link](at, cv2.COLOR_GRAY2RGB))
except Exception:
pass
# Morphological close of adaptive
try:
kernel = [Link](cv2.MORPH_RECT, (3, 3))
morph = [Link](at, cv2.MORPH_CLOSE, kernel)
[Link]([Link](morph, cv2.COLOR_GRAY2RGB))
except Exception:
pass
# Unsharp mask (sharpen)
try:
blur = [Link](crop, (0, 0), 3)
unsharp = [Link](crop, 1.5, blur, -0.5, 0)
[Link]([Link](unsharp, cv2.COLOR_BGR2RGB))
except Exception:
pass
# resized large (improve OCR for small plates)
h, w = [Link][:2]
scale = 2 if max(h, w) < 200 else 1
if scale > 1:
big = [Link](crop, (w * scale, h * scale),
interpolation=cv2.INTER_CUBIC)
[Link]([Link](big, cv2.COLOR_BGR2RGB))
# ensure unique by size to avoid duplicates
uniq = []
seen_shapes = set()
for v in variants:
if [Link] not in seen_shapes:
seen_shapes.add([Link])
[Link](v)
return uniq
alnum_re = [Link](r'[^A-Z0-9]')
def clean_plate_text(s):
if not s: return ""
s = [Link]()
s = alnum_re.sub('', s)
# small heuristics
s = [Link]('O', '0').replace('I', '1').replace('L', '1')
return [Link]()
def ocr_best_easyocr(crop):
"""Try multiple enhancements and return best (text, conf) using EasyOCR."""
variants = enhance_variants(crop)
best_text = ""
best_conf = 0.0
for v in variants:
try:
res = [Link](v, detail=1, paragraph=False)
except Exception:
res = []
for (bbox, text, conf) in res:
if not text:
continue
cleaned = clean_plate_text(text)
if not cleaned:
continue
# prefer longer plausible plate (min length 4)
score = conf * (len(cleaned) / 10.0)
if conf > best_conf or (conf == best_conf and len(cleaned) >
len(best_text)):
best_conf = conf
best_text = cleaned
return best_text, best_conf
def ocr_best_alpr(crop):
"""Use OpenALPR to get the best result."""
if not alpr_instance:
return "", 0.0
best_text = ""
best_conf = 0.0
try:
# Convert BGR image to byte string for alpr
img_bytes = [Link]('.jpg', crop)[1].tobytes()
results = alpr_instance.recognize(img_bytes)
if not results['results']:
return "", 0.0
for plate_result in results['results']:
for candidate in plate_result['candidates']:
text = clean_plate_text(candidate['plate'])
conf = candidate['confidence'] / 100.0 # ALPR confidence is 0-100
if text and conf > best_conf:
best_conf = conf
best_text = text
except Exception as e:
print(f"[WARN] OpenALPR recognition failed: {e}")
return "", 0.0
return best_text, best_conf
# --------------- contour fallback plate detection ---------------
def plate_candidates_by_contours(roi):
"""Return list of bboxes (x1,y1,x2,y2) in ROI that look like plates."""
gray = [Link](roi, cv2.COLOR_BGR2GRAY)
gray = [Link](gray, 9, 75, 75)
edged = [Link](gray, 50, 200)
cnts, _ = [Link]([Link](), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
candidates = []
cnts = sorted(cnts, key=[Link], reverse=True)[:20]
H, W = [Link][:2]
for c in cnts:
peri = [Link](c, True)
approx = [Link](c, 0.03 * peri, True)
x, y, w, h = [Link](approx)
area = w * h
if area < MIN_PLATE_AREA:
continue
aspect = w / float(h) if h > 0 else 0
if 2.0 < aspect < 7.5 and area > MIN_PLATE_AREA:
# clamp inside ROI
x1, y1, x2, y2 = max(0, x), max(0, y), min(W - 1, x + w), min(H - 1, y
+ h)
[Link]((x1, y1, x2, y2))
return candidates
# ----------------------- process once -------------------------
def process_once(frame):
global registered_df, attendance_df, today_attendance, roi_coords
if not roi_coords:
print("Error: No ROI selected.")
return
x1, y1, x2, y2 = roi_coords
roi = frame[y1:y2, x1:x2]
if [Link] == 0:
print("ROI is empty.")
return
candidates = []
# First try YOLO if available AND model likely plate-aware
used_yolo = False
if model is not None:
try:
# get detections on ROI
results = [Link](roi, conf=CONF_THRESHOLD, imgsz=640,
verbose=False)
res = results[0]
if hasattr(res, "boxes") and len([Link]) > 0:
xy = [Link]().numpy()
for (bx1, by1, bx2, by2) in xy:
[Link]((int(bx1), int(by1), int(bx2), int(by2)))
used_yolo = True
except Exception as e:
print("[WARN] YOLO detect failed:", e)
used_yolo = False
# If YOLO produced none, use contour fallback
if not candidates:
candidates = plate_candidates_by_contours(roi)
print(f"[INFO] Contour fallback produced {len(candidates)} candidates.")
detected_any = False
for (cx1, cy1, cx2, cy2) in candidates:
plate_crop = roi[cy1:cy2, cx1:cx2]
if plate_crop.size == 0:
continue
# Use OpenALPR first if available
plate_text_alpr, conf_alpr = "", 0.0
if alpr_instance:
plate_text_alpr, conf_alpr = ocr_best_alpr(plate_crop)
# Then use EasyOCR as a fallback or for comparison
plate_text_easyocr, conf_easyocr = ocr_best_easyocr(plate_crop)
# Decide which result is better
final_plate = ""
final_conf = 0.0
if conf_alpr > conf_easyocr and plate_text_alpr:
final_plate = plate_text_alpr
final_conf = conf_alpr
print(f"[DEBUG] ALPR candidate: '{final_plate}' (conf
{final_conf:.2f})")
elif plate_text_easyocr:
final_plate = plate_text_easyocr
final_conf = conf_easyocr
print(f"[DEBUG] EasyOCR candidate: '{final_plate}' (conf
{final_conf:.2f})")
if not final_plate:
continue
detected_any = True
# check registry
match = registered_df[registered_df['number_plate'] == final_plate]
time_now = [Link]().strftime("%H:%M:%S")
if not [Link]:
if final_plate not in today_attendance:
today_attendance.add(final_plate)
new_entry = {
"number_plate": final_plate,
"name": [Link][0]['name'],
"department": [Link][0].get('department', ''),
"time": time_now
}
attendance_df = [Link]([attendance_df,
[Link]([new_entry])], ignore_index=True)
attendance_df.to_csv(attendance_file, index=False)
print(f"[ATTENDANCE] Recorded {final_plate} at {time_now}")
else:
print(f"[INFO] {final_plate} already marked today.")
else:
# new plate -> prompt register
print(f"[NEW] Plate '{final_plate}' not registered.")
name = input("Enter Name (blank to skip): ").strip()
if name:
dept = input("Department (optional): ").strip()
new_reg = {"number_plate": final_plate, "name": name, "department":
dept}
registered_df = [Link]([registered_df, [Link]([new_reg])],
ignore_index=True)
registered_df.to_csv(REGISTERED_FILE, index=False)
today_attendance.add(final_plate)
new_entry = {"number_plate": final_plate, "name": name,
"department": dept, "time": time_now}
attendance_df = [Link]([attendance_df,
[Link]([new_entry])], ignore_index=True)
attendance_df.to_csv(attendance_file, index=False)
print(f"[REGISTERED & ATTENDANCE] {final_plate} -> {name} at
{time_now}")
else:
print("[INFO] Registration skipped by user.")
# draw detection on original frame (map ROI coords -> frame coords)
fx1, fy1 = x1 + cx1, y1 + cy1
fx2, fy2 = x1 + cx2, y1 + cy2
[Link](frame, (fx1, fy1), (fx2, fy2), (0, 255, 0), 2)
[Link](frame, final_plate, (fx1, max(12, fy1 - 6)),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
if not detected_any:
print("[RESULT] No readable plate detected. Try reselecting ROI or
improving lighting/angle.")
else:
# show final frame with overlay for 3 seconds so user can see result
[Link]("Result", frame)
[Link](3000)
[Link]("Result")
# ================== RUN ==================
cap = [Link](0)
ret, first_frame = [Link]()
if not ret:
print("Error: Cannot access webcam")
[Link]()
exit()
# Show first frame and let user draw ROI
[Link]("Select ROI", first_frame)
[Link]("Select ROI", draw_rectangle, first_frame)
print("[INSTR] Draw a rectangle with mouse to select ROI, then press any key in
that window.")
[Link](0) # wait until ROI selected
# Capture a fresh frame, then process once
ret, final_frame = [Link]()
if not ret:
print("Error: failed to capture final frame")
else:
process_once(final_frame)
[Link]()
[Link]()
if alpr_instance:
alpr_instance.unload()