APIMONSTER commited on
Commit
84a96e9
Β·
verified Β·
1 Parent(s): 52b0e0c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -69
app.py CHANGED
@@ -1,80 +1,92 @@
1
  # app.py
2
- import re
3
- import json
4
  import cv2
5
- import tempfile
6
  import numpy as np
7
  import gradio as gr
8
  from ultralytics import YOLO
9
  from paddleocr import PaddleOCR
10
 
11
- # ─── 0) Patch for old np.int usage ─────────────────────────────────
12
  np.int = int
13
 
14
- # ─── 1) Character map (exactly the one used for training) ──────────
15
- # Digits + uppercase letters + space
16
- MY_CHAR_LIST = list("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ")
17
 
18
- # ─── 2) Load models ────────────────────────────────────────────────
19
- yolo = YOLO("models/best.pt") # your YOLOv8 detector
20
 
 
21
  ocr = PaddleOCR(
22
- det=False, # disable PaddleOCR detector on crops
23
- rec=True, # enable recognition
24
  rec_model_dir="models/ocr_model",
25
- use_angle_cls=True, # angle classifier
26
- cls=True, # v3.x flag
27
- use_space_char=True # allow space in output
 
28
  )
29
- # Override the internal character list so it matches your training:
30
- ocr.text_recognizer.character = MY_CHAR_LIST
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- # ─── 3) Plate formatting ───────────────────────────────────────────
33
  def format_plate(s: str) -> str:
34
- """Normalize raw OCR-> 'DD AAA DDDD' or 'Unknown'."""
35
  s = re.sub(r'[^A-Z0-9]', '', s.upper())
36
  m = re.match(r'^(\d{2})([A-Z]{1,3})(\d{2,4})$', s)
37
  return f"{m.group(1)} {m.group(2)} {m.group(3)}" if m else "Unknown"
38
 
39
- # ─── 4) OCR helper ────────────────────────────────────────────────
40
- def recognize_plate(crop):
41
- """
42
- Run OCR on a 128Γ—32 crop; return (text, confidence).
43
- """
44
- recs = ocr.ocr(crop, det=False, cls=True)
45
- if not recs or len(recs[0]) < 2:
46
- return "", 0.0
47
- text, score = recs[0][1]
48
- return text, float(score)
49
-
50
- # ─── 5) Single-image inference ────────────────────────────────────
51
  def run_image(img, conf=0.25):
52
- # YOLO expects BGR
53
  bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
54
- results = yolo(bgr, conf=conf)[0]
55
  out = bgr.copy()
56
 
57
- for box in results.boxes.xyxy.cpu().numpy().astype(int):
58
  x1,y1,x2,y2 = box
59
  crop = out[y1:y2, x1:x2]
60
  if crop.size == 0:
61
  continue
62
 
63
- # resize to match your OCR training size
64
- plate_img = cv2.resize(crop, (128, 32))
65
- text, score = recognize_plate(plate_img)
66
- plate = format_plate(text)
 
 
 
 
 
67
  label = f"{plate} ({score:.2f})"
68
 
69
  # draw
70
- cv2.rectangle(out, (x1,y1), (x2,y2), (0,255,0), 2)
71
  cv2.putText(out, label, (x1, y1-8),
72
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
73
 
74
- # back to RGB
75
- return cv2.cvtColor(out, cv2.COLOR_BGR2RGB), f"{len(results.boxes)} plate(s) detected"
76
 
77
- # ─── 6) Video inference ────────────────────────────────────────────
78
  def run_video(video_file, conf=0.25):
79
  cap = cv2.VideoCapture(video_file)
80
  fps = cap.get(cv2.CAP_PROP_FPS) or 30
@@ -82,52 +94,43 @@ def run_video(video_file, conf=0.25):
82
  h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
83
 
84
  out_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
85
- writer = cv2.VideoWriter(
86
- out_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w,h)
87
- )
88
- records = []
89
- frame_idx = 0
90
 
91
  while True:
92
  ret, frame = cap.read()
93
- if not ret:
94
- break
95
- frame_idx += 1
96
- t = frame_idx / fps
97
 
98
- results = yolo(frame, conf=conf)[0]
99
- for box in results.boxes.xyxy.cpu().numpy().astype(int):
100
  x1,y1,x2,y2 = box
101
  crop = frame[y1:y2, x1:x2]
102
- if crop.size == 0:
103
- continue
104
 
105
- plate_img = cv2.resize(crop, (128, 32))
106
- text, score = recognize_plate(plate_img)
107
- plate = format_plate(text)
 
 
 
 
108
 
109
  if plate != "Unknown":
110
- records.append({
111
- "time_s": round(t, 2),
112
- "plate": plate,
113
- "conf": round(score, 3)
114
- })
115
 
116
- cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2)
117
  cv2.putText(frame, plate, (x1, y1-8),
118
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
119
 
120
  writer.write(frame)
121
 
122
- cap.release()
123
- writer.release()
124
-
125
  with open("output.json","w") as f:
126
  json.dump(records, f, indent=2)
127
-
128
  return out_path, "Done"
129
 
130
- # ─── 7) Gradio UI ─────────────────────────────────────────────────
131
  with gr.Blocks() as demo:
132
  gr.Markdown("## πŸš— License Plate Detection + Recognition")
133
 
@@ -135,7 +138,7 @@ with gr.Blocks() as demo:
135
  with gr.Column():
136
  img_in = gr.Image(type="numpy", label="Upload Image")
137
  vid_in = gr.File(label="Upload Video (.mp4)")
138
- conf = gr.Slider(0.0, 1.0, 0.25, 0.01, label="YOLO Confidence")
139
  btn_i = gr.Button("Run Image")
140
  btn_v = gr.Button("Run Video")
141
  with gr.Column():
 
1
  # app.py
2
+ import re, json, tempfile
 
3
  import cv2
 
4
  import numpy as np
5
  import gradio as gr
6
  from ultralytics import YOLO
7
  from paddleocr import PaddleOCR
8
 
9
+ # ─── 0) np.int patch for older PaddleOCR calls
10
  np.int = int
11
 
12
+ # ─── 1) Plate character set (digits + uppercase + space)
13
+ CHAR_LIST = list("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ")
 
14
 
15
+ # ─── 2) Load YOLOv8 detector
16
+ yolo = YOLO("models/best.pt")
17
 
18
+ # ─── 3) Init PaddleOCR recognition-only, override ALL params in-code
19
  ocr = PaddleOCR(
20
+ det=False, # disable det on plate crops
21
+ rec=True, # recognition-only
22
  rec_model_dir="models/ocr_model",
23
+ rec_image_shape="3,32,128", # must match your training
24
+ cls=True, # angle classifier
25
+ use_angle_cls=True,
26
+ use_space_char=True
27
  )
28
+ # Force our exact char map (no dict file needed)
29
+ ocr.text_recognizer.character = CHAR_LIST
30
+
31
+ # ─── 4) Normalize & format OCR output
32
+ def normalize_ocr(recs):
33
+ """
34
+ recs might be:
35
+ - [] β†’ no read
36
+ - [["ABC123", 0.82]] β†’ default det=False
37
+ - [["ABC123", 0.82], ...] β†’ (unlikely here)
38
+ - [[box,…], ("ABC123",0.82)] β†’ old det=True style
39
+ return text:str, score:float
40
+ """
41
+ if not recs:
42
+ return "", 0.0
43
+ first = recs[0]
44
+ # case: ["TXT",score]
45
+ if isinstance(first, (list,tuple)) and len(first)==2 and isinstance(first[0], str):
46
+ return first[0], float(first[1])
47
+ # case: [<box>, (<txt>,score)] or [<box>, [txt,score]]
48
+ if isinstance(first, (list,tuple)) and len(first)==2 and isinstance(first[1], (list,tuple)):
49
+ return first[1][0], float(first[1][1])
50
+ return "", 0.0
51
 
 
52
  def format_plate(s: str) -> str:
53
+ """β€˜DD AAA DDDD’ veya Unknown"""
54
  s = re.sub(r'[^A-Z0-9]', '', s.upper())
55
  m = re.match(r'^(\d{2})([A-Z]{1,3})(\d{2,4})$', s)
56
  return f"{m.group(1)} {m.group(2)} {m.group(3)}" if m else "Unknown"
57
 
58
+ # ─── 5) Single-image inference
 
 
 
 
 
 
 
 
 
 
 
59
  def run_image(img, conf=0.25):
60
+ # YOLO wants BGR
61
  bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
62
+ res = yolo(bgr, conf=conf)[0]
63
  out = bgr.copy()
64
 
65
+ for box in res.boxes.xyxy.cpu().numpy().astype(int):
66
  x1,y1,x2,y2 = box
67
  crop = out[y1:y2, x1:x2]
68
  if crop.size == 0:
69
  continue
70
 
71
+ # resize to OCR input
72
+ plate_img = cv2.resize(crop, (128,32))
73
+ # safe OCR
74
+ try:
75
+ recs = ocr.ocr(plate_img, det=False, cls=True)
76
+ except Exception:
77
+ recs = []
78
+ txt, score = normalize_ocr(recs)
79
+ plate = format_plate(txt)
80
  label = f"{plate} ({score:.2f})"
81
 
82
  # draw
83
+ cv2.rectangle(out, (x1,y1),(x2,y2), (0,255,0), 2)
84
  cv2.putText(out, label, (x1, y1-8),
85
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
86
 
87
+ return cv2.cvtColor(out, cv2.COLOR_BGR2RGB), f"{len(res.boxes)} plate(s) detected"
 
88
 
89
+ # ─── 6) Video inference
90
  def run_video(video_file, conf=0.25):
91
  cap = cv2.VideoCapture(video_file)
92
  fps = cap.get(cv2.CAP_PROP_FPS) or 30
 
94
  h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
95
 
96
  out_path = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
97
+ writer = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w,h))
98
+ records, idx = [], 0
 
 
 
99
 
100
  while True:
101
  ret, frame = cap.read()
102
+ if not ret: break
103
+ idx += 1; t = idx/fps
 
 
104
 
105
+ res = yolo(frame, conf=conf)[0]
106
+ for box in res.boxes.xyxy.cpu().numpy().astype(int):
107
  x1,y1,x2,y2 = box
108
  crop = frame[y1:y2, x1:x2]
109
+ if crop.size == 0: continue
 
110
 
111
+ plate_img = cv2.resize(crop, (128,32))
112
+ try:
113
+ recs = ocr.ocr(plate_img, det=False, cls=True)
114
+ except:
115
+ recs = []
116
+ txt, score = normalize_ocr(recs)
117
+ plate = format_plate(txt)
118
 
119
  if plate != "Unknown":
120
+ records.append({"time_s":round(t,2),"plate":plate,"conf":round(score,3)})
 
 
 
 
121
 
122
+ cv2.rectangle(frame, (x1,y1),(x2,y2), (0,255,0), 2)
123
  cv2.putText(frame, plate, (x1, y1-8),
124
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2)
125
 
126
  writer.write(frame)
127
 
128
+ cap.release(); writer.release()
 
 
129
  with open("output.json","w") as f:
130
  json.dump(records, f, indent=2)
 
131
  return out_path, "Done"
132
 
133
+ # ─── 7) Gradio UI
134
  with gr.Blocks() as demo:
135
  gr.Markdown("## πŸš— License Plate Detection + Recognition")
136
 
 
138
  with gr.Column():
139
  img_in = gr.Image(type="numpy", label="Upload Image")
140
  vid_in = gr.File(label="Upload Video (.mp4)")
141
+ conf = gr.Slider(0.0,1.0,0.25,0.01, label="YOLO Confidence")
142
  btn_i = gr.Button("Run Image")
143
  btn_v = gr.Button("Run Video")
144
  with gr.Column():