pluto90 commited on
Commit
6ca2490
Β·
verified Β·
1 Parent(s): e3d2462

Update app/services/inference.py

Browse files
Files changed (1) hide show
  1. app/services/inference.py +2 -302
app/services/inference.py CHANGED
@@ -1,216 +1,9 @@
1
- # # inference.py
2
-
3
- # from ultralytics import YOLO
4
- # import os
5
- # import cv2
6
- # import easyocr
7
- # import numpy as np
8
-
9
-
10
-
11
- # # Load model once (global)
12
- # MODEL_PATH = os.path.join("src", "models", "best.pt")
13
- # model = YOLO(MODEL_PATH)
14
-
15
- # reader= easyocr.Reader(
16
- # ['en'],
17
- # gpu=True,
18
- # )
19
-
20
- # # Plate characters only — kills J→] Z→z O→0 confusion
21
- # PLATE_ALLOWLIST = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 '
22
-
23
- # CONF_THRESHOLD= 0.3
24
-
25
-
26
- # def preprocess_plate(crop: np.ndarray) -> np.ndarray:
27
- # """
28
- # Clean up a plate crop before passing to OCR.
29
- # Steps: upscale if small β†’ grayscale β†’ denoise β†’ sharpen β†’ adaptive threshold
30
- # """
31
- # h, w = crop.shape[:2]
32
-
33
- # # 1. Upscale only if the crop is genuinely small
34
- # # Target: at least 100px tall so characters are readable
35
- # if h < 100:
36
- # scale = 100 / h
37
- # crop = cv2.resize(crop, None, fx=scale, fy=scale,
38
- # interpolation=cv2.INTER_CUBIC)
39
- # elif h < 200:
40
- # # Modest 1.5x for medium crops
41
- # crop = cv2.resize(crop, None, fx=1.5, fy=1.5,
42
- # interpolation=cv2.INTER_CUBIC)
43
- # # If already large enough, don't upscale β€” it can blur
44
-
45
- # # 2. Grayscale
46
- # gray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
47
-
48
- # # 3. Denoise (fastNlMeans: removes sensor noise without destroying edges)
49
- # gray = cv2.fastNlMeansDenoising(gray, h=15, templateWindowSize=7, searchWindowSize=21)
50
-
51
- # # 4. Sharpen β€” unsharp mask style
52
- # blurred = cv2.GaussianBlur(gray, (0, 0), 2)
53
- # gray = cv2.addWeighted(gray, 1.8, blurred, -0.8, 0)
54
-
55
- # # 5. Adaptive threshold β†’ clean black-on-white binary image
56
- # # Works much better than global threshold for varying lighting
57
- # binary = cv2.adaptiveThreshold(
58
- # gray, 255,
59
- # cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
60
- # cv2.THRESH_BINARY,
61
- # blockSize=15,
62
- # C=8
63
- # )
64
-
65
- # # 6. Add a small white border β€” prevents edge characters from being clipped
66
- # binary = cv2.copyMakeBorder(binary, 10, 10, 10, 10,
67
- # cv2.BORDER_CONSTANT, value=255)
68
-
69
- # return binary
70
-
71
-
72
-
73
-
74
-
75
- # def read_plate_text(crop: np.ndarray) -> tuple[str, float]:
76
- # """
77
- # Run OCR on a plate crop. Returns (text, ocr_confidence).
78
- # Tries preprocessed binary first; falls back to color crop if no result.
79
- # """
80
- # processed = preprocess_plate(crop)
81
-
82
- # results = reader.readtext(
83
- # processed,
84
- # detail=1,
85
- # paragraph=False, # treat each text region independently
86
- # decoder='beamsearch', # more accurate than greedy
87
- # beamWidth=10,
88
- # batch_size=1,
89
- # allowlist=PLATE_ALLOWLIST,
90
- # # EasyOCR hint: plate text is usually 1-2 lines, wide aspect
91
- # width_ths=0.8, # merge nearby text boxes horizontally
92
- # contrast_ths=0.05,
93
- # adjust_contrast=0.7,
94
- # text_threshold=0.6,
95
- # low_text=0.3,
96
- # )
97
-
98
- # if not results:
99
- # # Fallback: try on the raw color crop
100
- # results = reader.readtext(
101
- # crop,
102
- # detail=1,
103
- # allowlist=PLATE_ALLOWLIST,
104
- # decoder='beamsearch',
105
- # beamWidth=10,
106
- # )
107
-
108
- # if not results:
109
- # return "", 0.0
110
-
111
- # # Sort by confidence descending, take best
112
- # results.sort(key=lambda x: x[2], reverse=True)
113
- # best = results[0]
114
- # text = best[1].upper().strip()
115
- # conf = float(best[2])
116
-
117
- # # If multiple boxes detected, try joining them in left-to-right order
118
- # # (handles split plates like "KV67" + "HUJ" as separate regions)
119
- # if len(results) > 1:
120
- # # Sort all boxes by their x-coordinate (left edge of bbox)
121
- # sorted_by_x = sorted(results, key=lambda x: x[0][0][0])
122
- # joined = " ".join(r[1].upper().strip() for r in sorted_by_x)
123
- # avg_conf = sum(r[2] for r in sorted_by_x) / len(sorted_by_x)
124
- # # Use joined version only if average confidence is decent
125
- # if avg_conf >= 0.5:
126
- # text = joined
127
- # conf = avg_conf
128
-
129
- # return text, round(conf, 3)
130
-
131
-
132
-
133
-
134
- # def detect_license_plate(image_path):
135
- # results= model(image_path)
136
- # image= cv2.imread(image_path)
137
-
138
- # detections= []
139
-
140
- # for result in results:
141
- # for box in result.boxes:
142
- # x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
143
- # conf = float(box.conf[0])
144
-
145
-
146
- # if conf < CONF_THRESHOLD:
147
- # continue
148
-
149
-
150
- # # CROPPING
151
- # # Crop with a small margin to avoid clipping plate edges
152
- # margin = 4
153
- # h_img, w_img = image.shape[:2]
154
- # cx1 = max(0, x1 - margin)
155
- # cy1 = max(0, y1 - margin)
156
- # cx2 = min(w_img, x2 + margin)
157
- # cy2 = min(h_img, y2 + margin)
158
-
159
- # plate_crop = image[cy1:cy2, cx1:cx2]
160
-
161
- # plate_text, ocr_conf = read_plate_text(plate_crop)
162
-
163
- # # Draw bounding box
164
- # cv2.rectangle(image, (x1, y1), (x2, y2), (0, 0, 220), 2)
165
-
166
-
167
- # # Label: text + detection confidence
168
- # label = f"{plate_text} ({round(conf, 2)})" if plate_text else f"({round(conf, 2)})"
169
- # (lw, lh), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.55, 2)
170
-
171
-
172
- # # Background rect for label so it's always readable
173
- # cv2.rectangle(image, (x1, y1 - lh - baseline - 6), (x1 + lw + 4, y1), (0, 0, 220), -1)
174
- # cv2.putText(
175
- # image, label,
176
- # (x1 + 2, y1 - baseline - 2),
177
- # cv2.FONT_HERSHEY_SIMPLEX, 0.55,
178
- # (255, 255, 255), 2
179
- # )
180
-
181
-
182
- # detections.append({
183
- # "bbox": {
184
- # "x1": int(x1),
185
- # "y1": int(y1),
186
- # "x2": int(x2),
187
- # "y2": int(y2)
188
- # },
189
- # "confidence": round(conf, 3),
190
- # "text": plate_text,
191
- # "ocr_confidence": round(ocr_conf, 3) if ocr_conf else None,
192
- # })
193
-
194
- # # output image
195
- # name, ext= os.path.splitext(image_path)
196
- # output_path= f"{name}_output{ext}"
197
- # cv2.imwrite(output_path, image)
198
-
199
-
200
- # return detections, output_path
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
 
209
  from ultralytics import YOLO
210
  import os
211
  import cv2
212
  import numpy as np
213
- import easyocr
214
  import re
215
  from fast_plate_ocr import LicensePlateRecognizer
216
 
@@ -224,90 +17,6 @@ ocr= LicensePlateRecognizer("cct-s-v2-global-model")
224
  CONF_THRESHOLD = 0.255
225
  PLATE_ALLOWLIST = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 '
226
 
227
- # ── Preprocessing ─────────────────────────────────────────────────────────────
228
-
229
- # def preprocess_plate(crop: np.ndarray) -> list[np.ndarray]:
230
- # """
231
- # Returns multiple processed versions of the crop.
232
- # OCR is run on all of them and best result is picked.
233
- # """
234
- # h, w = crop.shape[:2]
235
-
236
- # # Upscale only if genuinely small β€” target 80px height minimum
237
- # scale = max(1.0, 80 / h)
238
- # if scale > 1.0:
239
- # crop = cv2.resize(crop, None, fx=scale, fy=scale,
240
- # interpolation=cv2.INTER_CUBIC)
241
-
242
- # gray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
243
-
244
- # # Version 1: CLAHE β€” improves local contrast without over-brightening
245
- # clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(4, 4))
246
- # v1 = clahe.apply(gray)
247
-
248
- # # Version 2: Otsu threshold β€” works well on clean plates
249
- # _, v2 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
250
-
251
- # # Version 3: Inverted Otsu β€” for dark-on-light plates
252
- # v3 = cv2.bitwise_not(v2)
253
-
254
- # # Version 4: Sharpened grayscale β€” good for slightly blurry crops
255
- # blurred = cv2.GaussianBlur(gray, (0, 0), 1.5)
256
- # v4 = cv2.addWeighted(gray, 2.0, blurred, -1.0, 0)
257
-
258
- # # Add white padding to all versions so edge chars aren't clipped
259
- # pad = lambda img: cv2.copyMakeBorder(img, 12, 12, 12, 12,
260
- # cv2.BORDER_CONSTANT, value=255)
261
- # return [pad(v) for v in [v1, v2, v3, v4]]
262
-
263
-
264
- # def clean_text(text: str) -> str:
265
- # """Strip non-plate characters and normalize spacing."""
266
- # text = text.upper().strip()
267
- # # Remove anything that's not A-Z, 0-9, or space
268
- # text = re.sub(r'[^A-Z0-9 ]', '', text)
269
- # # Collapse multiple spaces
270
- # text = re.sub(r' +', ' ', text).strip()
271
- # return text
272
-
273
-
274
- # def run_ocr_on_versions(versions: list[np.ndarray]) -> tuple[str, float]:
275
- # """
276
- # Run OCR on each preprocessed version, collect all results,
277
- # return the highest-confidence clean result.
278
- # """
279
- # candidates = []
280
-
281
- # for img in versions:
282
- # try:
283
- # results = reader.readtext(
284
- # img,
285
- # detail=1,
286
- # allowlist=PLATE_ALLOWLIST,
287
- # paragraph=True, # merge into one line β€” avoids multi-box noise
288
- # decoder='greedy', # greedy is actually more stable for short strings
289
- # text_threshold=0.5,
290
- # low_text=0.3,
291
- # width_ths=1.0, # aggressive merge: treat plate as single region
292
- # mag_ratio=1.0,
293
- # )
294
-
295
- # for (_, text, conf) in results:
296
- # cleaned = clean_text(text)
297
- # if len(cleaned) >= 4: # ignore single chars / noise
298
- # candidates.append((cleaned, float(conf)))
299
-
300
- # except Exception:
301
- # continue
302
-
303
- # if not candidates:
304
- # return "", 0.0
305
-
306
- # # Pick highest confidence
307
- # candidates.sort(key=lambda x: x[1], reverse=True)
308
- # return candidates[0]
309
-
310
-
311
 
312
 
313
  # ── Main ──────────────────────────────────────���───────────────────────────────
@@ -353,16 +62,7 @@ def detect_license_plate(image_path: str):
353
  label = f"{plate_text} ({round(conf, 2)})" if plate_text else f"({round(conf, 2)})"
354
  (lw, lh), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.55, 2)
355
 
356
- # Solid background behind label for readability
357
- # cv2.rectangle(image,
358
- # (x1, y1 - lh - baseline - 6),
359
- # (x1 + lw + 6, y1),
360
- # (0, 0, 220), -1)
361
- # cv2.putText(image, label,
362
- # (x1 + 3, y1 - baseline - 2),
363
- # cv2.FONT_HERSHEY_SIMPLEX, 0.55,
364
- # (255, 255, 255), 2)
365
-
366
  detections.append({
367
  "bbox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
368
  "confidence": round(conf, 3),
 
1
+ # inference.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  from ultralytics import YOLO
4
  import os
5
  import cv2
6
  import numpy as np
 
7
  import re
8
  from fast_plate_ocr import LicensePlateRecognizer
9
 
 
17
  CONF_THRESHOLD = 0.255
18
  PLATE_ALLOWLIST = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 '
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
 
22
  # ── Main ──────────────────────────────────────���───────────────────────────────
 
62
  label = f"{plate_text} ({round(conf, 2)})" if plate_text else f"({round(conf, 2)})"
63
  (lw, lh), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.55, 2)
64
 
65
+
 
 
 
 
 
 
 
 
 
66
  detections.append({
67
  "bbox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
68
  "confidence": round(conf, 3),