CoolFace
Apppublic

Ajaykumar10/Automatic_Number_Plate_Recognition_ANPR_System

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
app.py368 linesDownload Raw Back to root
1import gradio as gr2import cv23import numpy as np4import easyocr5from ultralytics import YOLO6import time7import re8import pandas as pd9import os10 11# ──────────────────────────────────────────────12#  Configuration13# ──────────────────────────────────────────────14BASE_DIR = os.path.dirname(os.path.abspath(__file__))15YOLO_MODEL_PATH = os.path.join(BASE_DIR, "license_plate_detector.pt")16EASYOCR_MODEL_DIR = os.path.join(BASE_DIR, "models")   # pre-downloaded at build time17 18MIN_ASPECT_RATIO = 2.019MAX_ASPECT_RATIO = 6.020MIN_PLATE_AREA = 150021CONFIDENCE_THRESHOLD = 0.422 23model = None24reader = None25 26 27# ──────────────────────────────────────────────28#  Model Loading29# ──────────────────────────────────────────────30def load_models():31    global model, reader32    print("Loading models...")33 34    # YOLO35    if os.path.exists(YOLO_MODEL_PATH):36        try:37            model = YOLO(YOLO_MODEL_PATH)38            print("✓ YOLO model loaded")39        except Exception as e:40            print(f"⚠ YOLO failed: {e}")41            model = None42    else:43        print(f"⚠ YOLO model not found — YOLO detection disabled")44        model = None45 46    # EasyOCR — uses pre-downloaded models from /app/models/47    try:48        os.makedirs(EASYOCR_MODEL_DIR, exist_ok=True)49        reader = easyocr.Reader(50            ['en'],51            gpu=False,52            verbose=False,53            model_storage_directory=EASYOCR_MODEL_DIR,54            download_enabled=True   # fallback if models folder is missing55        )56        print("✓ EasyOCR loaded")57    except Exception as e:58        print(f"✗ EasyOCR failed: {e}")59        raise60 61    return model, reader62 63 64# ══════════════════════════════════════════════65#  Image Preprocessing66# ══════════════════════════════════════════════67def preprocess_image(image_bgr):68    gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)69    denoised = cv2.fastNlMeansDenoising(gray, h=10)70    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))71    enhanced = clahe.apply(denoised)72    bilateral = cv2.bilateralFilter(enhanced, d=11, sigmaColor=17, sigmaSpace=17)73    blurred = cv2.GaussianBlur(bilateral, (5, 5), 0)74    return {"gray": gray, "enhanced": enhanced, "bilateral": bilateral, "blurred": blurred}75 76 77# ══════════════════════════════════════════════78#  Edge Detection & Contours79# ══════════════════════════════════════════════80def detect_edges(blurred):81    median = np.median(blurred)82    sigma = 0.3383    lower = int(max(0, (1.0 - sigma) * median))84    upper = int(min(255, (1.0 + sigma) * median))85    edges = cv2.Canny(blurred, lower, upper)86    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))87    return cv2.dilate(edges, kernel, iterations=1)88 89 90def extract_contours(edges):91    contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)92    return sorted(contours, key=cv2.contourArea, reverse=True)[:20]93 94 95# ══════════════════════════════════════════════96#  Traditional CV ROI Detection97# ══════════════════════════════════════════════98def find_plate_roi_traditional(image_bgr, contours):99    h_img, w_img = image_bgr.shape[:2]100    candidates = []101    for cnt in contours:102        area = cv2.contourArea(cnt)103        if area < MIN_PLATE_AREA:104            continue105        peri = cv2.arcLength(cnt, True)106        approx = cv2.approxPolyDP(cnt, 0.018 * peri, True)107        if len(approx) == 4:108            x, y, w, h = cv2.boundingRect(approx)109            ar = w / float(h) if h > 0 else 0110            if MIN_ASPECT_RATIO <= ar <= MAX_ASPECT_RATIO and w < 0.9 * w_img and h < 0.9 * h_img:111                candidates.append((x, y, w, h))112    return candidates113 114 115# ══════════════════════════════════════════════116#  OCR117# ══════════════════════════════════════════════118def preprocess_plate_for_ocr(plate_bgr):119    h, w = plate_bgr.shape[:2]120    if w < 200:121        plate_bgr = cv2.resize(plate_bgr, None, fx=200/w, fy=200/w, interpolation=cv2.INTER_CUBIC)122    gray = cv2.cvtColor(plate_bgr, cv2.COLOR_BGR2GRAY)123    sharp = cv2.filter2D(gray, -1, np.array([[-1,-1,-1],[-1,9,-1],[-1,-1,-1]]))124    return cv2.adaptiveThreshold(sharp, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)125 126 127def run_ocr(reader, plate_bgr):128    processed = preprocess_plate_for_ocr(plate_bgr)129    def aggregate(results):130        text, conf, count = "", 0.0, 0131        for (_, txt, c) in results:132            text += txt; conf += c; count += 1133        return text, (conf / count if count else 0.0)134    raw_text, raw_conf = aggregate(reader.readtext(plate_bgr, detail=1, paragraph=False))135    proc_text, proc_conf = aggregate(reader.readtext(processed, detail=1, paragraph=False))136    return (proc_text, proc_conf) if proc_conf >= raw_conf else (raw_text, raw_conf)137 138 139def clean_plate_text(raw):140    return re.sub(r'[^A-Za-z0-9]', '', raw).upper()141 142 143# ══════════════════════════════════════════════144#  YOLO Detection145# ══════════════════════════════════════════════146def detect_with_yolo(model, image_bgr):147    results = model(image_bgr, conf=CONFIDENCE_THRESHOLD, verbose=False)148    return [(int(b.xyxy[0][0]), int(b.xyxy[0][1]), int(b.xyxy[0][2]), int(b.xyxy[0][3]), float(b.conf[0]))149            for b in results[0].boxes]150 151 152# ══════════════════════════════════════════════153#  Accuracy & Drawing154# ══════════════════════════════════════════════155def character_accuracy(pred, gt):156    if not gt:157        return 1.0 if not pred else 0.0158    m, n = len(pred), len(gt)159    dp = [[0]*(n+1) for _ in range(m+1)]160    for i in range(1, m+1):161        for j in range(1, n+1):162            dp[i][j] = dp[i-1][j-1]+1 if pred[i-1]==gt[j-1] else max(dp[i-1][j], dp[i][j-1])163    return dp[m][n] / max(m, n)164 165 166def draw_result(image_bgr, x1, y1, x2, y2, label, color=(0,255,0)):167    out = image_bgr.copy()168    cv2.rectangle(out, (x1,y1), (x2,y2), color, 3)169    (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.9, 2)170    cv2.rectangle(out, (x1, y1-th-10), (x1+tw+6, y1), color, -1)171    cv2.putText(out, label, (x1+3, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,0), 2)172    return out173 174 175# ══════════════════════════════════════════════176#  Main Processing Function177# ══════════════════════════════════════════════178def process_image(image, detection_method, ground_truth):179    ensure_models_loaded()180    global model, reader181 182    if image is None:183        return None,None,None,None,None,None,"⚠ Please upload an image first.",None,None184 185    try:186        image_rgb = np.array(image) if not isinstance(image, np.ndarray) else image187        image_bgr = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2BGR)188 189        # Always compute preprocessing190        prep = preprocess_image(image_bgr)191        prep1 = cv2.cvtColor(prep["gray"],      cv2.COLOR_GRAY2RGB)192        prep2 = cv2.cvtColor(prep["enhanced"],  cv2.COLOR_GRAY2RGB)193        prep3 = cv2.cvtColor(prep["bilateral"], cv2.COLOR_GRAY2RGB)194 195        # Always compute edges196        edges = detect_edges(prep["blurred"])197        contours = extract_contours(edges)198        edges_image = cv2.cvtColor(edges, cv2.COLOR_GRAY2RGB)199        contour_vis = image_bgr.copy()200        cv2.drawContours(contour_vis, contours, -1, (0, 120, 255), 2)201        contour_image = cv2.cvtColor(contour_vis, cv2.COLOR_BGR2RGB)202 203        final_image = image_bgr.copy()204        detections = []205        start_time = time.time()206 207        # YOLO208        if detection_method in ["YOLO (Deep Learning)", "Both"]:209            if model is None and detection_method == "YOLO (Deep Learning)":210                msg = ("⚠ **YOLO model not found.**\n\nUpload `license_plate_detector.pt` "211                       "to your Space, or switch to **Traditional CV** method.")212                return prep1, prep2, prep3, edges_image, contour_image, None, msg, None, None213            elif model is not None:214                for (x1,y1,x2,y2,conf) in detect_with_yolo(model, image_bgr):215                    plate_bgr = image_bgr[y1:y2, x1:x2]216                    if plate_bgr.size == 0: continue217                    raw_text, ocr_conf = run_ocr(reader, plate_bgr)218                    detections.append({"box":(x1,y1,x2,y2),"text":clean_plate_text(raw_text),219                                       "det_conf":conf,"ocr_conf":ocr_conf,"method":"YOLO","plate_img":plate_bgr})220 221        # Traditional CV222        if detection_method in ["Traditional CV (Edge + Contour)", "Both"]:223            for (x,y,w,h) in find_plate_roi_traditional(image_bgr, contours):224                plate_bgr = image_bgr[y:y+h, x:x+w]225                if plate_bgr.size == 0: continue226                raw_text, ocr_conf = run_ocr(reader, plate_bgr)227                detections.append({"box":(x,y,x+w,y+h),"text":clean_plate_text(raw_text),228                                   "det_conf":None,"ocr_conf":ocr_conf,"method":"Traditional CV","plate_img":plate_bgr})229 230        elapsed = time.time() - start_time231 232        if not detections:233            msg = ("❌ **No license plate detected.**\n\nTry:\n"234                   "• A clearer, well-lit image\n• A different detection method\n"235                   "• Ensure the plate is fully visible")236            return prep1, prep2, prep3, edges_image, contour_image, None, msg, None, None237 238        for det in detections:239            x1,y1,x2,y2 = det["box"]240            color = (0,200,0) if det["method"]=="YOLO" else (200,150,0)241            final_image = draw_result(final_image, x1,y1,x2,y2, det["text"] or "???", color)242 243        final_rgb = cv2.cvtColor(final_image, cv2.COLOR_BGR2RGB)244        plate_images = [cv2.cvtColor(d["plate_img"], cv2.COLOR_BGR2RGB) for d in detections]245 246        avg_conf = np.mean([d["ocr_conf"] for d in detections]) * 100247        result_text = (f"⏱ **Processing Time:** {elapsed:.2f}s\n"248                       f"📦 **Plates Detected:** {len(detections)}\n"249                       f"🔤 **Avg OCR Confidence:** {avg_conf:.1f}%\n\n")250        for i, det in enumerate(detections):251            result_text += f"**Plate #{i+1}:**\n- Text: `{det['text'] or 'N/A'}`\n- Method: {det['method']}\n"252            if det["det_conf"]: result_text += f"- Detection Conf: {det['det_conf']*100:.1f}%\n"253            result_text += f"- OCR Conf: {det['ocr_conf']*100:.1f}%\n\n"254 255        if ground_truth and ground_truth.strip():256            gt_clean = clean_plate_text(ground_truth)257            result_text += f"\n✅ **Ground Truth:** `{gt_clean}`\n"258            for i, det in enumerate(detections):259                acc = character_accuracy(det["text"], gt_clean)260                match = "✅ Exact Match" if det["text"]==gt_clean else "❌ Mismatch"261                result_text += f"- Plate #{i+1}: {acc*100:.1f}% | {match}\n"262 263        df = pd.DataFrame([{264            "Plate #": i+1, "Method": d["method"],265            "Recognized Text": d["text"] or "N/A",266            "OCR Conf (%)": f"{d['ocr_conf']*100:.1f}",267            "Det Conf (%)": f"{d['det_conf']*100:.1f}" if d["det_conf"] else "N/A",268        } for i,d in enumerate(detections)])269 270        return prep1, prep2, prep3, edges_image, contour_image, final_rgb, result_text, df, plate_images271 272    except Exception as e:273        return None,None,None,None,None,None,f"❌ Error: {str(e)}",None,None274 275 276# ══════════════════════════════════════════════277#  Gradio Interface278# ══════════════════════════════════════════════279def create_interface():280    with gr.Blocks(title="ANPR System") as demo:281        gr.Markdown("""282        # 🚗 Automatic Number Plate Recognition (ANPR)283        ### Detect and recognize license plates using AI and Computer Vision284        Upload a vehicle image and click **Detect License Plate** to begin.285        """)286 287        with gr.Row():288            with gr.Column(scale=1):289                gr.Markdown("### ⚙️ Configuration")290                image_input = gr.Image(label="📷 Upload Vehicle Image", type="numpy", height=300)291                detection_method = gr.Radio(292                    choices=["YOLO (Deep Learning)", "Traditional CV (Edge + Contour)", "Both"],293                    value="Traditional CV (Edge + Contour)",294                    label="Detection Method",295                    info="YOLO requires license_plate_detector.pt in the Space"296                )297                with gr.Accordion("Advanced Options", open=False):298                    ground_truth = gr.Textbox(299                        label="Ground Truth Plate (Optional)",300                        placeholder="e.g., SN66XMZ",301                        info="Enter correct plate text to evaluate accuracy"302                    )303                process_btn = gr.Button("🔍 Detect License Plate", variant="primary", size="lg")304                gr.Markdown("""305                ---306                ### 📝 Tips:307                - Use clear, well-lit images308                - **Traditional CV** works without any model309                - **YOLO** needs `license_plate_detector.pt` in Space310                """)311 312            with gr.Column(scale=2):313                with gr.Tabs():314                    with gr.Tab("🎯 Results"):315                        final_output = gr.Image(label="Detected License Plates")316                        result_text = gr.Markdown(value="📌 Upload an image and click **Detect License Plate** to begin.")317                        summary_table = gr.Dataframe(318                            headers=["Plate #","Method","Recognized Text","OCR Conf (%)","Det Conf (%)"],319                            label="Detection Summary"320                        )321                    with gr.Tab("🔍 Detected Plates"):322                        plate_gallery = gr.Gallery(label="Cropped License Plates", columns=3, height="auto", object_fit="contain")323                    with gr.Tab("🔧 Preprocessing"):324                        gr.Markdown("**Always shown after clicking Detect.**")325                        with gr.Row():326                            prep_col1 = gr.Image(label="Grayscale")327                            prep_col2 = gr.Image(label="CLAHE Enhanced")328                            prep_col3 = gr.Image(label="Bilateral Filtered")329                    with gr.Tab("📐 Edge Detection"):330                        gr.Markdown("**Always shown after clicking Detect.**")331                        with gr.Row():332                            edges_output = gr.Image(label="Canny Edges")333                            contour_output = gr.Image(label="Contour Overlay")334 335        process_btn.click(336            fn=process_image,337            inputs=[image_input, detection_method, ground_truth],338            outputs=[prep_col1, prep_col2, prep_col3, edges_output, contour_output,339                     final_output, result_text, summary_table, plate_gallery]340        )341 342        gr.Markdown("""343        ---344        <div style="text-align:center;color:#666;">345        <p><strong>ANPR System</strong> | Gradio · OpenCV · EasyOCR · YOLOv8</p>346        <p>Mini Project – Language & Libraries for Technology (LLT)</p>347        </div>348        """)349    return demo350 351 352# ══════════════════════════════════════════════353#  Entry Point354# ══════════════════════════════════════════════355def ensure_models_loaded():356    global model, reader357    if model is None or reader is None:358        load_models()359demo = create_interface()360import os361 362port = int(os.environ.get("PORT", 7860))363 364if __name__ == "__main__":365    demo.launch(366    server_name="0.0.0.0",367    server_port=port368)