EALPR_OCR_V2 / app.py
Pant0x's picture
Update app.py
bb513b5 verified
import os
import cv2
import numpy as np
import gradio as gr
from ultralytics import YOLO
# 1. Load Models - Fixed paths to match Docker structure
plate_model = YOLO("car_plate_best.pt")
ocr_model = YOLO("model_ocr.pt")
# Explicit mapping to ARABIC-INDIC digits (٠١٢٣٤٥٦٧٨٩)
arabic_mapping = {
"aain": "ع", "alef": "أ", "a": "أ", "alf": "أ", "baa": "ب",
"daal": "د", "dal": "د", "e": "ع", "faa": "ف", "geem": "ج",
"haa": "هـ", "kaaf": "ك", "laam": "ل", "meem": "م", "noon": "ن",
"qaf": "ق", "raa": "ر", "sad": "ص", "seen": "س", "taa": "ط",
"waaw": "و", "waw": "و", "yaa": "ى", "zay": "ز", "dad": "ض",
"0": "٠", "1": "١", "2": "٢", "3": "٣", "4": "٤",
"5": "٥", "6": "٦", "7": "٧", "8": "٨", "9": "٩",
}
def predict_plate(img):
if img is None:
return None, "", ""
try:
# Gradio sends RGB, YOLO likes BGR
img_cv = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
display_img = img_cv.copy()
# 1. Plate Detection
plate_results = plate_model.predict(source=img_cv, conf=0.30, verbose=False)
best_plate_box = None
max_conf = -1
for box in plate_results[0].boxes:
x1, y1, x2, y2 = map(int, box.xyxy[0])
conf = float(box.conf)
cv2.rectangle(display_img, (x1, y1), (x2, y2), (0, 255, 0), 3)
if conf > max_conf:
max_conf = conf
best_plate_box = (x1, y1, x2, y2)
letters_str, numbers_str = "", ""
if best_plate_box:
x1, y1, x2, y2 = best_plate_box
h, w = img_cv.shape[:2]
margin = 20 # Increased margin for better OCR on HF
cx1, cy1, cx2, cy2 = max(0, x1-margin), max(0, y1-margin), min(w, x2+margin), min(h, y2+margin)
plate_crop = img_cv[cy1:cy2, cx1:cx2]
if plate_crop.size > 0:
# Optimized OCR params for Hugging Face CPU/GPU
ocr_results = ocr_model.predict(source=plate_crop, conf=0.25, iou=0.45, agnostic_nms=True, verbose=False)
detections = []
for obox in ocr_results[0].boxes:
cid = int(obox.cls)
ename = ocr_results[0].names[cid]
# Ignore 'License Plate' label
if ename.lower() == "license plate":
continue
achr = arabic_mapping.get(ename, ename)
ox1, _, ox2, _ = obox.xyxy[0]
detections.append(((ox1 + ox2) / 2, achr))
# Spatial sort Left-to-Right
detections.sort(key=lambda d: d[0])
arabic_digits_set = set("٠١٢٣٤٥٦٧٨٩")
det_letters = [d[1] for d in detections if d[1] not in arabic_digits_set]
det_numbers = [d[1] for d in detections if d[1] in arabic_digits_set]
# Reverse for RTL display
det_numbers.reverse()
det_letters.reverse()
letters_str = " ".join(det_letters)
numbers_str = " ".join(det_numbers)
return cv2.cvtColor(display_img, cv2.COLOR_BGR2RGB), numbers_str, letters_str
except Exception:
return None, "Error", "Error"
# UI Setup
with gr.Blocks(theme=gr.themes.Default()) as demo:
gr.Markdown("# 🚗 Arabic License Plate OCR")
with gr.Row():
with gr.Column():
input_img = gr.Image(label="Upload or Camera", type="pil")
btn = gr.Button("Extract Plate Info", variant="primary")
with gr.Column():
output_img = gr.Image(label="Detection Result")
with gr.Row():
# Numbers (Left) | Letters (Right)
out_numbers = gr.Textbox(label="Numbers (Arabic Script)", interactive=False)
out_letters = gr.Textbox(label="Arabic Letters", interactive=False)
btn.click(fn=predict_plate, inputs=input_img, outputs=[output_img, out_numbers, out_letters], api_name="predict_plate")
if __name__ == "__main__":
# CRITICAL: Added server_name and server_port for Hugging Face
demo.launch(server_name="0.0.0.0", server_port=7860)