APIMONSTER's picture
Update app.py
3a43469 verified
# app.py
import re, json, tempfile
import cv2
import numpy as np
import gradio as gr
import gc
import psutil
from ultralytics import YOLO
from paddleocr import PaddleOCR
np.int = int # For backward compatibility
CHAR_LIST = list("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ")
yolo = YOLO("models/best.pt")
def print_mem_usage(tag=""):
mem = psutil.virtual_memory()
print(f"[{tag}] RAM usage: {mem.used / 1024**2:.2f} MB / {mem.total / 1024**2:.2f} MB ({mem.percent}%)")
def normalize_ocr(recs):
if not recs:
return "", 0.0
first = recs[0]
if isinstance(first, (list, tuple)) and len(first) == 2 and isinstance(first[0], str):
return first[0], float(first[1])
if isinstance(first, (list, tuple)) and len(first) == 2 and isinstance(first[1], (list, tuple)):
return first[1][0], float(first[1][1])
return "", 0.0
def format_plate(s: str) -> str:
s = re.sub(r'[^A-Z0-9]', '', s.upper())
m = re.match(r'^(\d{2})([A-Z]{1,3})(\d{2,4})$', s)
return f"{m.group(1)} {m.group(2)} {m.group(3)}" if m else f"RAW: {s}" if s else "Unknown"
def correct_perspective(image, box):
x1, y1, x2, y2 = box
h, w = image.shape[:2]
margin = 5
x1 = max(0, x1 - margin)
y1 = max(0, y1 - margin)
x2 = min(w, x2 + margin)
y2 = min(h, y2 + margin)
crop = image[y1:y2, x1:x2]
if crop.size == 0:
return None
src_pts = np.float32([[0, 0], [crop.shape[1], 0], [crop.shape[1], crop.shape[0]], [0, crop.shape[0]]])
dst_pts = np.float32([[0, 0], [128, 0], [128, 32], [0, 32]])
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
warped = cv2.warpPerspective(crop, M, (128, 32))
return warped
def create_ocr():
ocr = PaddleOCR(
det=False,
rec=True,
rec_model_dir="models/ocr_model",
rec_image_shape="3,32,128",
cls=True,
use_angle_cls=True,
use_space_char=True
)
ocr.text_recognizer.character = CHAR_LIST
return ocr
def run_image(img, conf=0.25):
ocr = create_ocr()
bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
res = yolo(bgr, conf=conf)[0]
out = bgr.copy()
for box in res.boxes.xyxy.cpu().numpy().astype(int):
warped = correct_perspective(out, box)
if warped is None:
continue
try:
recs = ocr.ocr(warped, det=False, cls=True)
except:
recs = []
gc.collect()
print_mem_usage("After OCR")
txt, score = normalize_ocr(recs)
plate = format_plate(txt)
label = f"{plate} ({score:.2f})"
x1, y1, x2, y2 = box
cv2.rectangle(out, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(out, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
return cv2.cvtColor(out, cv2.COLOR_BGR2RGB), f"{len(res.boxes)} plate(s) detected"
def run_video(video_file, conf=0.25):
ocr = create_ocr()
cap = cv2.VideoCapture(video_file)
fps = cap.get(cv2.CAP_PROP_FPS) or 30
w, h = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
out_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
writer = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
records, idx = [], 0
while True:
ret, frame = cap.read()
if not ret:
break
idx += 1
t = idx / fps
res = yolo(frame, conf=conf)[0]
for box in res.boxes.xyxy.cpu().numpy().astype(int):
warped = correct_perspective(frame, box)
if warped is None:
continue
try:
recs = ocr.ocr(warped, det=False, cls=True)
except:
recs = []
gc.collect()
print_mem_usage("After OCR")
txt, score = normalize_ocr(recs)
plate = format_plate(txt)
raw_txt = plate[5:] if plate.startswith("RAW:") else plate
if raw_txt != "Unknown":
records.append({"time_s": round(t, 2), "plate": raw_txt, "conf": round(score, 3)})
x1, y1, x2, y2 = box
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, plate, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
writer.write(frame)
cap.release()
writer.release()
with open("output.json", "w") as f:
json.dump(records, f, indent=2)
return out_path, "Done"
with gr.Blocks() as demo:
gr.Markdown("## 🚗 License Plate Detection + Recognition")
with gr.Row():
with gr.Column():
img_in = gr.Image(type="numpy", label="Upload Image")
vid_in = gr.File(label="Upload Video (.mp4)")
conf = gr.Slider(0.0, 1.0, value=0.25, step=0.01, label="YOLO Confidence")
btn_i = gr.Button("Run Image")
btn_v = gr.Button("Run Video")
with gr.Column():
img_out = gr.Image(type="numpy", label="Annotated Image")
vid_out = gr.Video(label="Annotated Video")
status = gr.Textbox(label="Status / JSON Path")
btn_i.click(run_image, [img_in, conf], [img_out, status])
btn_v.click(run_video, [vid_in, conf], [vid_out, status])
if __name__ == "__main__":
demo.launch()