Abs6187 commited on
Commit
b7bf529
·
verified ·
1 Parent(s): 67507e0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -262
app.py CHANGED
@@ -11,6 +11,7 @@ import zipfile
11
  import io
12
  from datetime import datetime
13
 
 
14
  try:
15
  from license_plate_ocr import extract_license_plate_text
16
  OCR_AVAILABLE = True
@@ -20,25 +21,49 @@ except ImportError as e:
20
  OCR_AVAILABLE = False
21
 
22
  try:
23
- from advanced_ocr import extract_license_plate_text_advanced, get_available_models, set_ocr_model
 
 
 
 
24
  ADVANCED_OCR_AVAILABLE = True
25
  print("Advanced OCR module loaded successfully")
26
  except ImportError as e:
27
  print(f"Advanced OCR module not available: {e}")
28
  ADVANCED_OCR_AVAILABLE = False
29
 
30
- torch.hub.download_url_to_file('https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-1.jpg?raw=true', 'sample_1.jpg')
31
- torch.hub.download_url_to_file('https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-2.jpg?raw=true', 'sample_2.jpg')
32
- torch.hub.download_url_to_file('https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-3.jpg?raw=true', 'sample_3.jpg')
33
- torch.hub.download_url_to_file('https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-4.jpg?raw=true', 'sample_4.jpg')
34
- torch.hub.download_url_to_file('https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-5.jpg?raw=true', 'sample_5.jpg')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
- model = YOLO("best.pt")
37
- class_names = {0: 'With Helmet', 1: 'Without Helmet', 2: 'License Plate'}
38
 
39
- def crop_license_plates(image, detections, extract_text=False, selected_model="auto"):
 
 
40
  cropped_plates = []
41
-
42
  try:
43
  if isinstance(image, str):
44
  if not os.path.exists(image):
@@ -50,138 +75,157 @@ def crop_license_plates(image, detections, extract_text=False, selected_model="a
50
  elif not isinstance(image, Image.Image):
51
  print(f"Error: Unsupported image type: {type(image)}")
52
  return cropped_plates
53
-
54
  if image.size[0] == 0 or image.size[1] == 0:
55
  print("Error: Image has zero dimensions")
56
  return cropped_plates
57
-
58
  except Exception as e:
59
  print(f"Error loading image: {e}")
60
  return cropped_plates
61
-
62
  for i, detection in enumerate(detections):
63
  try:
64
- if detection['Object'] != 'License Plate':
65
  continue
66
-
67
- pos_str = detection['Position'].strip('()')
68
- if ',' not in pos_str:
69
- print(f"Error: Invalid position format for detection {i}: {detection['Position']}")
 
 
70
  continue
71
-
72
- x1, y1 = map(int, pos_str.split(', '))
73
-
74
- dims_str = detection['Dimensions']
75
- if 'x' not in dims_str:
76
- print(f"Error: Invalid dimensions format for detection {i}: {detection['Dimensions']}")
 
 
77
  continue
78
-
79
- width, height = map(int, dims_str.split('x'))
80
-
81
  if width <= 0 or height <= 0:
82
  print(f"Error: Invalid dimensions for detection {i}: {width}x{height}")
83
  continue
84
-
85
  x2, y2 = x1 + width, y1 + height
86
-
87
  if x1 < 0 or y1 < 0 or x2 > image.width or y2 > image.height:
88
- print(f"Warning: Bounding box extends beyond image boundaries for detection {i}")
 
 
89
  x1 = max(0, x1)
90
  y1 = max(0, y1)
91
  x2 = min(image.width, x2)
92
  y2 = min(image.height, y2)
93
-
94
  if x2 <= x1 or y2 <= y1:
95
- print(f"Error: Invalid crop coordinates for detection {i}: ({x1},{y1}) to ({x2},{y2})")
 
 
96
  continue
97
-
98
  cropped_plate = image.crop((x1, y1, x2, y2))
99
-
100
  if cropped_plate.size[0] == 0 or cropped_plate.size[1] == 0:
101
- print(f"Error: Cropped image has zero dimensions for detection {i}")
 
 
102
  continue
103
-
104
  plate_data = {
105
- 'image': cropped_plate,
106
- 'confidence': detection['Confidence'],
107
- 'position': detection['Position'],
108
- 'crop_coords': f"({x1},{y1}) to ({x2},{y2})",
109
- 'text': 'Processing...'
110
  }
111
-
112
  if extract_text and (OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE):
113
  try:
114
- print(f"Extracting text from license plate {i+1} using {selected_ocr_model}...")
115
-
 
 
116
  if ADVANCED_OCR_AVAILABLE and selected_ocr_model != "basic":
117
  if selected_ocr_model != "auto":
118
  set_ocr_model(selected_ocr_model)
119
- plate_text = extract_license_plate_text_advanced(cropped_plate,
120
- None if selected_ocr_model == "auto" else selected_ocr_model)
 
 
121
  else:
122
  plate_text = extract_license_plate_text(cropped_plate)
123
-
124
- if plate_text and plate_text.strip() and not plate_text.startswith('Error'):
125
- plate_data['text'] = plate_text.strip()
 
 
 
 
126
  print(f"Extracted text: {plate_text.strip()}")
127
  else:
128
- plate_data['text'] = 'No text detected'
129
  print(f"No text found in plate {i+1}")
130
  except Exception as e:
131
  print(f"OCR extraction failed for plate {i+1}: {e}")
132
- plate_data['text'] = f'OCR Failed: {str(e)}'
133
  elif extract_text and not (OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE):
134
- plate_data['text'] = 'OCR not available'
135
  else:
136
- plate_data['text'] = 'OCR disabled'
137
-
138
  cropped_plates.append(plate_data)
139
-
140
  except ValueError as e:
141
  print(f"Error parsing coordinates for detection {i}: {e}")
142
  continue
143
  except Exception as e:
144
  print(f"Error cropping license plate {i}: {e}")
145
  continue
146
-
147
  return cropped_plates
148
 
 
149
  def create_download_files(annotated_image, cropped_plates, detections):
150
  try:
151
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
152
-
153
  os.makedirs("temp", exist_ok=True)
154
-
155
  annotated_path = f"temp/annotated_image_{timestamp}.jpg"
156
  try:
157
  annotated_image.save(annotated_path, quality=95)
158
  except Exception as e:
159
  print(f"Error saving annotated image: {e}")
160
  return None, None, []
161
-
162
  plate_paths = []
163
  for i, plate_data in enumerate(cropped_plates):
164
  try:
165
  plate_path = f"temp/license_plate_{i+1}_{timestamp}.jpg"
166
- plate_data['image'].save(plate_path, quality=95)
167
  plate_paths.append(plate_path)
168
  except Exception as e:
169
  print(f"Error saving license plate {i+1}: {e}")
170
  continue
171
-
172
  report_data = []
173
  for detection in detections:
174
  report_data.append(detection)
175
-
176
  for i, plate_data in enumerate(cropped_plates):
177
- report_data.append({
178
- 'Object': f'License Plate {i+1} - Text',
179
- 'Confidence': plate_data['confidence'],
180
- 'Position': plate_data['position'],
181
- 'Dimensions': 'Extracted Text',
182
- 'Text': plate_data.get('text', 'N/A')
183
- })
184
-
 
 
185
  report_path = f"temp/detection_report_{timestamp}.csv"
186
  if report_data:
187
  try:
@@ -190,10 +234,10 @@ def create_download_files(annotated_image, cropped_plates, detections):
190
  except Exception as e:
191
  print(f"Error creating detection report: {e}")
192
  report_path = None
193
-
194
  zip_path = f"temp/detection_results_{timestamp}.zip"
195
  try:
196
- with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
197
  if os.path.exists(annotated_path):
198
  zipf.write(annotated_path, f"annotated_image_{timestamp}.jpg")
199
  for plate_path in plate_paths:
@@ -204,99 +248,103 @@ def create_download_files(annotated_image, cropped_plates, detections):
204
  except Exception as e:
205
  print(f"Error creating ZIP file: {e}")
206
  return None, annotated_path, plate_paths
207
-
208
  return zip_path, annotated_path, plate_paths
209
-
210
  except Exception as e:
211
  print(f"Error in create_download_files: {e}")
212
  return None, None, []
213
 
 
 
214
  def yoloV8_func(
215
- image=None,
216
- image_size=640,
217
- conf_threshold=0.4,
218
  iou_threshold=0.5,
219
  show_stats=True,
220
  show_confidence=True,
221
  crop_plates=True,
222
  extract_text=False,
223
  ocr_on_no_helmet=False,
224
- selected_ocr_model="auto"
225
  ):
226
  if image_size is None:
227
  image_size = 640
228
-
229
  if not isinstance(image_size, int):
230
  image_size = int(image_size)
231
-
232
- imgsz = [image_size, image_size]
233
 
 
234
  results = model.predict(image, conf=conf_threshold, iou=iou_threshold, imgsz=imgsz)
235
 
236
  annotated_image = results[0].plot()
237
-
238
  if isinstance(annotated_image, np.ndarray):
239
  annotated_image = Image.fromarray(cv2.cvtColor(annotated_image, cv2.COLOR_BGR2RGB))
240
-
241
  boxes = results[0].boxes
242
  detections = []
243
-
244
  if boxes is not None and len(boxes) > 0:
245
  for i, (box, cls, conf) in enumerate(zip(boxes.xyxy, boxes.cls, boxes.conf)):
246
  x1, y1, x2, y2 = box.tolist()
247
  class_id = int(cls)
248
  confidence = float(conf)
249
  label = class_names.get(class_id, f"Class {class_id}")
250
-
251
- detections.append({
252
- "Object": label,
253
- "Confidence": f"{confidence:.2f}",
254
- "Position": f"({int(x1)}, {int(y1)})",
255
- "Dimensions": f"{int(x2-x1)}x{int(y2-y1)}"
256
- })
257
-
 
258
  cropped_plates = []
259
  license_plate_gallery = []
260
  plate_texts = []
261
  download_files = None
262
-
263
- has_no_helmet = any(detection['Object'] == 'Without Helmet' for detection in detections)
264
  should_extract_text = extract_text or (ocr_on_no_helmet and has_no_helmet)
265
  ocr_available = OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE
266
-
267
  if crop_plates and detections:
268
  try:
269
- license_plate_count = len([d for d in detections if d['Object'] == 'License Plate'])
270
  print(f"Processing {license_plate_count} license plates...")
271
-
272
  if ocr_on_no_helmet and has_no_helmet:
273
  print("⚠️ No helmet detected - OCR will be performed on license plates")
274
-
275
- cropped_plates = crop_license_plates(image, detections, should_extract_text, selected_ocr_model)
 
 
276
  print(f"Successfully cropped {len(cropped_plates)} license plates")
277
-
278
- license_plate_gallery = [plate_data['image'] for plate_data in cropped_plates]
279
-
280
  if should_extract_text and ocr_available:
281
  print("Extracting text from license plates...")
282
  plate_texts = []
283
  for i, plate_data in enumerate(cropped_plates):
284
- text = plate_data.get('text', 'No text detected')
285
  print(f"Plate {i+1} text: {text}")
286
  if ocr_on_no_helmet and has_no_helmet:
287
  plate_texts.append(f"🚨 No Helmet Violation - Plate {i+1}: {text}")
288
  else:
289
  plate_texts.append(f"Plate {i+1}: {text}")
290
  elif should_extract_text and not ocr_available:
291
- plate_texts = ["OCR not available - install requirements: pip install transformers easyocr"]
 
 
292
  elif not should_extract_text:
293
- if ocr_on_no_helmet and not has_no_helmet:
294
- plate_texts = [f"Plate {i+1}: OCR only on no-helmet violations" for i in range(len(cropped_plates))]
295
- else:
296
- plate_texts = [f"Plate {i+1}: Text extraction disabled" for i in range(len(cropped_plates))]
297
-
298
  if cropped_plates or detections:
299
- download_files, _, _ = create_download_files(annotated_image, cropped_plates, detections)
 
 
300
  if download_files is None:
301
  print("Warning: Could not create download files")
302
  except Exception as e:
@@ -305,198 +353,287 @@ def yoloV8_func(
305
  license_plate_gallery = []
306
  plate_texts = ["Error processing license plates"]
307
  download_files = None
308
-
309
  stats_text = ""
310
  if show_stats and detections:
311
  df = pd.DataFrame(detections)
312
- counts = df['Object'].value_counts().to_dict()
313
  stats_text = "Detection Summary:\n"
314
  for obj, count in counts.items():
315
  stats_text += f"- {obj}: {count}\n"
316
-
317
  if cropped_plates:
318
  stats_text += f"\nLicense Plates Cropped: {len(cropped_plates)}\n"
319
-
320
  if has_no_helmet:
321
  stats_text += "⚠️ HELMET VIOLATION DETECTED!\n"
322
-
323
- if should_extract_text and OCR_AVAILABLE:
324
  stats_text += "Extracted Text:\n"
325
  for i, plate_data in enumerate(cropped_plates):
326
- text = plate_data.get('text', 'No text')
327
  if has_no_helmet and ocr_on_no_helmet:
328
  stats_text += f"🚨 Violation - Plate {i+1}: {text}\n"
329
  else:
330
  stats_text += f"- Plate {i+1}: {text}\n"
331
-
332
- if show_stats and stats_text:
333
- draw = ImageDraw.Draw(annotated_image)
334
- try:
335
- font = ImageFont.truetype("arial.ttf", 20)
336
- except:
337
- font = ImageFont.load_default()
338
-
339
- text_bbox = draw.textbbox((0, 0), stats_text, font=font)
340
- text_width = text_bbox[2] - text_bbox[0]
341
- text_height = text_bbox[3] - text_bbox[1]
342
- draw.rectangle([10, 10, 20 + text_width, 20 + text_height], fill=(0, 0, 0, 128))
343
-
344
- draw.text((15, 15), stats_text, font=font, fill=(255, 255, 255))
345
-
346
- detection_table = pd.DataFrame(detections) if detections else pd.DataFrame(columns=["Object", "Confidence", "Position", "Dimensions"])
347
-
348
- plate_text_output = "\n".join(plate_texts) if plate_texts else "No license plates detected or OCR disabled"
349
-
350
- return annotated_image, detection_table, stats_text, license_plate_gallery, download_files, plate_text_output
351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  custom_css = """
353
  #title { text-align: center; }
354
  #description { text-align: center; }
355
- .footer {
356
- text-align: center;
357
- margin-top: 20px;
358
- color: #666;
359
- }
360
  .important { font-weight: bold; color: red; }
361
- .download-section {
362
- background-color: #f0f0f0;
363
- padding: 15px;
364
- border-radius: 8px;
365
- margin-top: 10px;
366
- }
367
- .ocr-section {
368
- background-color: #e8f4fd;
369
- padding: 15px;
370
- border-radius: 8px;
371
- margin-top: 10px;
372
- }
373
  """
374
 
375
- with gr.Blocks(css=custom_css, title="YOLOv11 Motorcyclist Helmet Detection") as demo:
376
- gr.HTML("<h1 id='title'>YOLOv11 Motorcyclist Helmet Detection with Optional OCR</h1>")
377
- gr.HTML(f"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  <div id='description'>
379
- <p>This application uses YOLOv11 to detect Motorcyclists with and without Helmets in images.</p>
380
- <p>Upload an image, adjust the parameters, and view the detection results with detailed statistics.</p>
381
- <p><strong>Features:</strong> License plate cropping and optional text recognition!</p>
382
- <p><strong>OCR Status:</strong>
383
  {'✅ Advanced OCR Available' if ADVANCED_OCR_AVAILABLE else '🟡 Basic OCR Available' if OCR_AVAILABLE else '❌ OCR Not Available (install requirements)'}
384
  </p>
385
  </div>
386
- """)
387
-
388
- with gr.Row():
389
- with gr.Column(scale=1):
390
- gr.Markdown("### Input Parameters")
391
- input_image = gr.Image(type="filepath", label="Input Image", sources=["upload", "webcam"])
392
- with gr.Row():
393
- image_size = gr.Slider(minimum=320, maximum=1280, value=640, step=32, label="Image Size")
394
- conf_threshold = gr.Slider(minimum=0.0, maximum=1.0, value=0.4, step=0.05, label="Confidence Threshold")
395
  with gr.Row():
396
- iou_threshold = gr.Slider(minimum=0.0, maximum=1.0, value=0.5, step=0.05, label="IOU Threshold")
397
- show_stats = gr.Checkbox(value=True, label="Show Statistics on Image")
398
-
399
- gr.Markdown("### License Plate Options")
400
- crop_plates = gr.Checkbox(value=True, label="Crop License Plates")
401
-
402
- ocr_available = OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE
403
- if ocr_available:
404
- extract_text = gr.Checkbox(value=False, label="Extract Text from License Plates (OCR)")
405
- ocr_on_no_helmet = gr.Checkbox(value=True, label="🚨 Auto-OCR when No Helmet Detected")
406
-
407
- if ADVANCED_OCR_AVAILABLE:
408
- models = get_available_models()
409
- model_choices = [("Auto (Recommended)", "auto"), ("Basic EasyOCR", "basic")]
410
- for key, info in models.items():
411
- model_choices.append((info["name"], key))
412
-
413
- selected_ocr_model = gr.Dropdown(
414
- choices=model_choices,
415
- value="auto",
416
- label="OCR Model Selection",
417
- info="Choose OCR model (Advanced models require additional setup)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  )
419
- else:
420
- selected_ocr_model = gr.State("basic")
421
-
422
- gr.Markdown("*Note: OCR processing may take additional time*")
423
- else:
424
- extract_text = gr.Checkbox(value=False, label="Extract Text (OCR Not Available)", interactive=False)
425
- ocr_on_no_helmet = gr.Checkbox(value=False, label="🚨 Auto-OCR when No Helmet (Not Available)", interactive=False)
426
- selected_ocr_model = gr.State("basic")
427
- gr.Markdown("*Install requirements: `pip install torch transformers easyocr opencv-python`*")
428
-
429
- submit_btn = gr.Button("Detect Objects", variant="primary")
430
- clear_btn = gr.Button("Clear")
431
-
432
- with gr.Column(scale=2):
433
- gr.Markdown("### Output Results")
434
- output_image = gr.Image(type="pil", label="Annotated Image")
435
- output_table = gr.Dataframe(
436
- headers=["Object", "Confidence", "Position", "Dimensions"],
437
- label="Detection Details",
438
- interactive=False
439
- )
440
- output_stats = gr.Textbox(label="Detection Summary", interactive=False)
441
-
442
- gr.Markdown("### Cropped License Plates")
443
- license_gallery = gr.Gallery(
444
- label="Extracted License Plates",
445
- show_label=True,
446
- elem_id="license_gallery",
447
- columns=3,
448
- rows=2,
449
- object_fit="contain",
450
- height="auto"
 
 
 
 
 
 
 
451
  )
452
-
453
- with gr.Group(elem_classes="ocr-section"):
454
- gr.Markdown("### License Plate Text Recognition")
455
- plate_text_output = gr.Textbox(
456
- label="Extracted Text",
457
- placeholder="License plate text will appear here when OCR is enabled",
458
- lines=3,
459
- interactive=False
460
- )
461
-
462
- gr.Markdown("### Download Results")
463
- with gr.Group(elem_classes="download-section"):
464
- download_file = gr.File(
465
- label="Download Complete Results (ZIP)",
466
- interactive=False,
467
- visible=True
468
- )
469
- gr.Markdown("*The ZIP file contains: annotated image, cropped license plates, and detection report with OCR results*")
470
-
471
- gr.Markdown("### Example Images")
472
- gr.Examples(
473
- examples=[["sample_1.jpg"], ["sample_2.jpg"], ["sample_3.jpg"], ["sample_4.jpg"], ["sample_5.jpg"]],
474
- inputs=input_image,
475
- outputs=[output_image, output_table, output_stats, license_gallery, download_file, plate_text_output],
476
- fn=lambda img: yoloV8_func(img, 640, 0.4, 0.5, True, True, True, False),
477
- cache_examples=True,
478
- )
479
-
480
- gr.HTML("""
481
  <div class='footer'>
482
  <p>Built with Gradio and Ultralytics YOLO</p>
483
- <p>Note: This is a demonstration application. Detection accuracy may vary based on image quality and conditions.</p>
484
- <p><strong>License Plate Privacy:</strong> Extracted license plates and text are for demonstration purposes only.</p>
485
  <p><strong>Requirements for OCR:</strong> torch, transformers, easyocr, opencv-python</p>
486
  </div>
487
- """)
488
-
 
 
 
489
  submit_btn.click(
490
  fn=yoloV8_func,
491
- inputs=[input_image, image_size, conf_threshold, iou_threshold, show_stats, gr.State(True), crop_plates, extract_text, ocr_on_no_helmet, selected_ocr_model],
492
- outputs=[output_image, output_table, output_stats, license_gallery, download_file, plate_text_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  )
494
-
 
495
  clear_btn.click(
496
  fn=lambda: [None, None, None, None, None, None],
497
  inputs=[],
498
- outputs=[input_image, output_image, output_table, output_stats, license_gallery, download_file, plate_text_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  )
500
 
501
  if __name__ == "__main__":
502
- demo.launch(debug=True, share=True)
 
11
  import io
12
  from datetime import datetime
13
 
14
+ # ===== Optional OCR imports =====
15
  try:
16
  from license_plate_ocr import extract_license_plate_text
17
  OCR_AVAILABLE = True
 
21
  OCR_AVAILABLE = False
22
 
23
  try:
24
+ from advanced_ocr import (
25
+ extract_license_plate_text_advanced,
26
+ get_available_models,
27
+ set_ocr_model,
28
+ )
29
  ADVANCED_OCR_AVAILABLE = True
30
  print("Advanced OCR module loaded successfully")
31
  except ImportError as e:
32
  print(f"Advanced OCR module not available: {e}")
33
  ADVANCED_OCR_AVAILABLE = False
34
 
35
+ # ===== Sample images (optional) =====
36
+ torch.hub.download_url_to_file(
37
+ "https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-1.jpg?raw=true",
38
+ "sample_1.jpg",
39
+ )
40
+ torch.hub.download_url_to_file(
41
+ "https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-2.jpg?raw=true",
42
+ "sample_2.jpg",
43
+ )
44
+ torch.hub.download_url_to_file(
45
+ "https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-3.jpg?raw=true",
46
+ "sample_3.jpg",
47
+ )
48
+ torch.hub.download_url_to_file(
49
+ "https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-4.jpg?raw=true",
50
+ "sample_4.jpg",
51
+ )
52
+ torch.hub.download_url_to_file(
53
+ "https://github.com/Janno1402/Helmet-License-Plate-Detection/blob/main/Sample-Image-5.jpg?raw=true",
54
+ "sample_5.jpg",
55
+ )
56
+
57
+ # ===== Model & class names =====
58
+ model = YOLO("best.pt") # make sure best.pt is present
59
+ class_names = {0: "With Helmet", 1: "Without Helmet", 2: "License Plate"}
60
 
 
 
61
 
62
+ # ===== Helpers =====
63
+ def crop_license_plates(image, detections, extract_text=False, selected_ocr_model="auto"):
64
+ """Crop license plates and (optionally) run OCR on the crops."""
65
  cropped_plates = []
66
+
67
  try:
68
  if isinstance(image, str):
69
  if not os.path.exists(image):
 
75
  elif not isinstance(image, Image.Image):
76
  print(f"Error: Unsupported image type: {type(image)}")
77
  return cropped_plates
78
+
79
  if image.size[0] == 0 or image.size[1] == 0:
80
  print("Error: Image has zero dimensions")
81
  return cropped_plates
 
82
  except Exception as e:
83
  print(f"Error loading image: {e}")
84
  return cropped_plates
85
+
86
  for i, detection in enumerate(detections):
87
  try:
88
+ if detection["Object"] != "License Plate":
89
  continue
90
+
91
+ pos_str = detection["Position"].strip("()")
92
+ if "," not in pos_str:
93
+ print(
94
+ f"Error: Invalid position format for detection {i}: {detection['Position']}"
95
+ )
96
  continue
97
+
98
+ x1, y1 = map(int, pos_str.split(", "))
99
+
100
+ dims_str = detection["Dimensions"]
101
+ if "x" not in dims_str:
102
+ print(
103
+ f"Error: Invalid dimensions format for detection {i}: {detection['Dimensions']}"
104
+ )
105
  continue
106
+
107
+ width, height = map(int, dims_str.split("x"))
108
+
109
  if width <= 0 or height <= 0:
110
  print(f"Error: Invalid dimensions for detection {i}: {width}x{height}")
111
  continue
112
+
113
  x2, y2 = x1 + width, y1 + height
114
+
115
  if x1 < 0 or y1 < 0 or x2 > image.width or y2 > image.height:
116
+ print(
117
+ f"Warning: Bounding box extends beyond image boundaries for detection {i}"
118
+ )
119
  x1 = max(0, x1)
120
  y1 = max(0, y1)
121
  x2 = min(image.width, x2)
122
  y2 = min(image.height, y2)
123
+
124
  if x2 <= x1 or y2 <= y1:
125
+ print(
126
+ f"Error: Invalid crop coordinates for detection {i}: ({x1},{y1}) to ({x2},{y2})"
127
+ )
128
  continue
129
+
130
  cropped_plate = image.crop((x1, y1, x2, y2))
131
+
132
  if cropped_plate.size[0] == 0 or cropped_plate.size[1] == 0:
133
+ print(
134
+ f"Error: Cropped image has zero dimensions for detection {i}"
135
+ )
136
  continue
137
+
138
  plate_data = {
139
+ "image": cropped_plate,
140
+ "confidence": detection["Confidence"],
141
+ "position": detection["Position"],
142
+ "crop_coords": f"({x1},{y1}) to ({x2},{y2})",
143
+ "text": "Processing...",
144
  }
145
+
146
  if extract_text and (OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE):
147
  try:
148
+ print(
149
+ f"Extracting text from license plate {i+1} using {selected_ocr_model}..."
150
+ )
151
+
152
  if ADVANCED_OCR_AVAILABLE and selected_ocr_model != "basic":
153
  if selected_ocr_model != "auto":
154
  set_ocr_model(selected_ocr_model)
155
+ plate_text = extract_license_plate_text_advanced(
156
+ cropped_plate,
157
+ None if selected_ocr_model == "auto" else selected_ocr_model,
158
+ )
159
  else:
160
  plate_text = extract_license_plate_text(cropped_plate)
161
+
162
+ if (
163
+ plate_text
164
+ and plate_text.strip()
165
+ and not plate_text.startswith("Error")
166
+ ):
167
+ plate_data["text"] = plate_text.strip()
168
  print(f"Extracted text: {plate_text.strip()}")
169
  else:
170
+ plate_data["text"] = "No text detected"
171
  print(f"No text found in plate {i+1}")
172
  except Exception as e:
173
  print(f"OCR extraction failed for plate {i+1}: {e}")
174
+ plate_data["text"] = f"OCR Failed: {str(e)}"
175
  elif extract_text and not (OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE):
176
+ plate_data["text"] = "OCR not available"
177
  else:
178
+ plate_data["text"] = "OCR disabled"
179
+
180
  cropped_plates.append(plate_data)
181
+
182
  except ValueError as e:
183
  print(f"Error parsing coordinates for detection {i}: {e}")
184
  continue
185
  except Exception as e:
186
  print(f"Error cropping license plate {i}: {e}")
187
  continue
188
+
189
  return cropped_plates
190
 
191
+
192
  def create_download_files(annotated_image, cropped_plates, detections):
193
  try:
194
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
 
195
  os.makedirs("temp", exist_ok=True)
196
+
197
  annotated_path = f"temp/annotated_image_{timestamp}.jpg"
198
  try:
199
  annotated_image.save(annotated_path, quality=95)
200
  except Exception as e:
201
  print(f"Error saving annotated image: {e}")
202
  return None, None, []
203
+
204
  plate_paths = []
205
  for i, plate_data in enumerate(cropped_plates):
206
  try:
207
  plate_path = f"temp/license_plate_{i+1}_{timestamp}.jpg"
208
+ plate_data["image"].save(plate_path, quality=95)
209
  plate_paths.append(plate_path)
210
  except Exception as e:
211
  print(f"Error saving license plate {i+1}: {e}")
212
  continue
213
+
214
  report_data = []
215
  for detection in detections:
216
  report_data.append(detection)
217
+
218
  for i, plate_data in enumerate(cropped_plates):
219
+ report_data.append(
220
+ {
221
+ "Object": f"License Plate {i+1} - Text",
222
+ "Confidence": plate_data["confidence"],
223
+ "Position": plate_data["position"],
224
+ "Dimensions": "Extracted Text",
225
+ "Text": plate_data.get("text", "N/A"),
226
+ }
227
+ )
228
+
229
  report_path = f"temp/detection_report_{timestamp}.csv"
230
  if report_data:
231
  try:
 
234
  except Exception as e:
235
  print(f"Error creating detection report: {e}")
236
  report_path = None
237
+
238
  zip_path = f"temp/detection_results_{timestamp}.zip"
239
  try:
240
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
241
  if os.path.exists(annotated_path):
242
  zipf.write(annotated_path, f"annotated_image_{timestamp}.jpg")
243
  for plate_path in plate_paths:
 
248
  except Exception as e:
249
  print(f"Error creating ZIP file: {e}")
250
  return None, annotated_path, plate_paths
251
+
252
  return zip_path, annotated_path, plate_paths
253
+
254
  except Exception as e:
255
  print(f"Error in create_download_files: {e}")
256
  return None, None, []
257
 
258
+
259
+ # ===== Main inference =====
260
  def yoloV8_func(
261
+ image=None,
262
+ image_size=640,
263
+ conf_threshold=0.4,
264
  iou_threshold=0.5,
265
  show_stats=True,
266
  show_confidence=True,
267
  crop_plates=True,
268
  extract_text=False,
269
  ocr_on_no_helmet=False,
270
+ selected_ocr_model="auto",
271
  ):
272
  if image_size is None:
273
  image_size = 640
 
274
  if not isinstance(image_size, int):
275
  image_size = int(image_size)
 
 
276
 
277
+ imgsz = [image_size, image_size]
278
  results = model.predict(image, conf=conf_threshold, iou=iou_threshold, imgsz=imgsz)
279
 
280
  annotated_image = results[0].plot()
 
281
  if isinstance(annotated_image, np.ndarray):
282
  annotated_image = Image.fromarray(cv2.cvtColor(annotated_image, cv2.COLOR_BGR2RGB))
283
+
284
  boxes = results[0].boxes
285
  detections = []
 
286
  if boxes is not None and len(boxes) > 0:
287
  for i, (box, cls, conf) in enumerate(zip(boxes.xyxy, boxes.cls, boxes.conf)):
288
  x1, y1, x2, y2 = box.tolist()
289
  class_id = int(cls)
290
  confidence = float(conf)
291
  label = class_names.get(class_id, f"Class {class_id}")
292
+ detections.append(
293
+ {
294
+ "Object": label,
295
+ "Confidence": f"{confidence:.2f}",
296
+ "Position": f"({int(x1)}, {int(y1)})",
297
+ "Dimensions": f"{int(x2 - x1)}x{int(y2 - y1)}",
298
+ }
299
+ )
300
+
301
  cropped_plates = []
302
  license_plate_gallery = []
303
  plate_texts = []
304
  download_files = None
305
+
306
+ has_no_helmet = any(d["Object"] == "Without Helmet" for d in detections)
307
  should_extract_text = extract_text or (ocr_on_no_helmet and has_no_helmet)
308
  ocr_available = OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE
309
+
310
  if crop_plates and detections:
311
  try:
312
+ license_plate_count = len([d for d in detections if d["Object"] == "License Plate"])
313
  print(f"Processing {license_plate_count} license plates...")
314
+
315
  if ocr_on_no_helmet and has_no_helmet:
316
  print("⚠️ No helmet detected - OCR will be performed on license plates")
317
+
318
+ cropped_plates = crop_license_plates(
319
+ image, detections, should_extract_text, selected_ocr_model
320
+ )
321
  print(f"Successfully cropped {len(cropped_plates)} license plates")
322
+
323
+ license_plate_gallery = [plate_data["image"] for plate_data in cropped_plates]
324
+
325
  if should_extract_text and ocr_available:
326
  print("Extracting text from license plates...")
327
  plate_texts = []
328
  for i, plate_data in enumerate(cropped_plates):
329
+ text = plate_data.get("text", "No text detected")
330
  print(f"Plate {i+1} text: {text}")
331
  if ocr_on_no_helmet and has_no_helmet:
332
  plate_texts.append(f"🚨 No Helmet Violation - Plate {i+1}: {text}")
333
  else:
334
  plate_texts.append(f"Plate {i+1}: {text}")
335
  elif should_extract_text and not ocr_available:
336
+ plate_texts = [
337
+ "OCR not available - install requirements: pip install transformers easyocr"
338
+ ]
339
  elif not should_extract_text:
340
+ plate_texts = [
341
+ f"Plate {i+1}: Text extraction disabled" for i in range(len(cropped_plates))
342
+ ]
343
+
 
344
  if cropped_plates or detections:
345
+ download_files, _, _ = create_download_files(
346
+ annotated_image, cropped_plates, detections
347
+ )
348
  if download_files is None:
349
  print("Warning: Could not create download files")
350
  except Exception as e:
 
353
  license_plate_gallery = []
354
  plate_texts = ["Error processing license plates"]
355
  download_files = None
356
+
357
  stats_text = ""
358
  if show_stats and detections:
359
  df = pd.DataFrame(detections)
360
+ counts = df["Object"].value_counts().to_dict()
361
  stats_text = "Detection Summary:\n"
362
  for obj, count in counts.items():
363
  stats_text += f"- {obj}: {count}\n"
364
+
365
  if cropped_plates:
366
  stats_text += f"\nLicense Plates Cropped: {len(cropped_plates)}\n"
 
367
  if has_no_helmet:
368
  stats_text += "⚠️ HELMET VIOLATION DETECTED!\n"
369
+ if should_extract_text and (OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE):
 
370
  stats_text += "Extracted Text:\n"
371
  for i, plate_data in enumerate(cropped_plates):
372
+ text = plate_data.get("text", "No text")
373
  if has_no_helmet and ocr_on_no_helmet:
374
  stats_text += f"🚨 Violation - Plate {i+1}: {text}\n"
375
  else:
376
  stats_text += f"- Plate {i+1}: {text}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
+ detection_table = (
379
+ pd.DataFrame(detections)
380
+ if detections
381
+ else pd.DataFrame(columns=["Object", "Confidence", "Position", "Dimensions"])
382
+ )
383
+ plate_text_output = (
384
+ "\n".join(plate_texts)
385
+ if plate_texts
386
+ else "No license plates detected or OCR disabled"
387
+ )
388
+
389
+ return (
390
+ annotated_image,
391
+ detection_table,
392
+ stats_text,
393
+ license_plate_gallery,
394
+ download_files,
395
+ plate_text_output,
396
+ )
397
+
398
+
399
+ # ====== UI ======
400
  custom_css = """
401
  #title { text-align: center; }
402
  #description { text-align: center; }
403
+ .footer { text-align: center; margin-top: 20px; color: #666; }
 
 
 
 
404
  .important { font-weight: bold; color: red; }
405
+ .download-section { background-color: #f6f6f6; padding: 15px; border-radius: 10px; margin-top: 10px; }
406
+ .ocr-section { background-color: #eef7ff; padding: 15px; border-radius: 10px; margin-top: 10px; }
407
+ .card { background: white; border-radius: 16px; padding: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.06); }
 
 
 
 
 
 
 
 
 
408
  """
409
 
410
+ def toggle_sections(extract_text_checked, crop_checked):
411
+ """Control visibility of Cropped Plates & OCR sections.
412
+ Requirement: If user checks the OCR checkbox, show the cropped plates AND OCR text.
413
+ Otherwise, hide both sections. Crops also require crop_checkbox True.
414
+ """
415
+ show_gallery = bool(extract_text_checked and crop_checked)
416
+ show_ocr = bool(extract_text_checked)
417
+ return (
418
+ gr.update(visible=show_gallery), # license_gallery
419
+ gr.update(visible=show_ocr), # ocr group container (textbox)
420
+ )
421
+
422
+
423
+ with gr.Blocks(
424
+ css=custom_css,
425
+ title="YOLOv11 Motorcyclist Helmet Detection",
426
+ theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"),
427
+ ) as demo:
428
+ gr.HTML("<h1 id='title'>YOLOv11 Motorcyclist Helmet Detection</h1>")
429
+ gr.HTML(
430
+ f"""
431
  <div id='description'>
432
+ <p>This app detects motorcyclists <strong>with</strong> / <strong>without</strong> helmets and can optionally read license plates.</p>
433
+ <p><strong>OCR Status:</strong>
 
 
434
  {'✅ Advanced OCR Available' if ADVANCED_OCR_AVAILABLE else '🟡 Basic OCR Available' if OCR_AVAILABLE else '❌ OCR Not Available (install requirements)'}
435
  </p>
436
  </div>
437
+ """
438
+ )
439
+
440
+ with gr.Tabs():
441
+ with gr.TabItem("Inference"):
 
 
 
 
442
  with gr.Row():
443
+ with gr.Column(scale=1):
444
+ with gr.Group(elem_classes=["card"]):
445
+ gr.Markdown("### Input Parameters")
446
+ input_image = gr.Image(
447
+ type="filepath", label="Input Image", sources=["upload", "webcam"]
448
+ )
449
+ with gr.Row():
450
+ image_size = gr.Slider(
451
+ minimum=320, maximum=1280, value=640, step=32, label="Image Size"
452
+ )
453
+ conf_threshold = gr.Slider(
454
+ minimum=0.0, maximum=1.0, value=0.4, step=0.05, label="Confidence Threshold"
455
+ )
456
+ with gr.Row():
457
+ iou_threshold = gr.Slider(
458
+ minimum=0.0, maximum=1.0, value=0.5, step=0.05, label="IOU Threshold"
459
+ )
460
+ show_stats = gr.Checkbox(value=True, label="Show Stats on Image")
461
+
462
+ with gr.Group(elem_classes=["card"]):
463
+ gr.Markdown("### Options")
464
+ crop_plates = gr.Checkbox(value=True, label="Enable License Plate Cropping")
465
+
466
+ ocr_available = OCR_AVAILABLE or ADVANCED_OCR_AVAILABLE
467
+ if ocr_available:
468
+ extract_text = gr.Checkbox(
469
+ value=False,
470
+ label="Enable OCR (Show Cropped Plates & Text)",
471
+ info="When enabled: shows cropped plates + runs OCR",
472
+ )
473
+ ocr_on_no_helmet = gr.Checkbox(
474
+ value=True,
475
+ label="🚨 Auto-OCR when No Helmet Detected",
476
+ )
477
+
478
+ if ADVANCED_OCR_AVAILABLE:
479
+ models = get_available_models()
480
+ model_choices = [("Auto (Recommended)", "auto"), ("Basic EasyOCR", "basic")]
481
+ for key, info in models.items():
482
+ model_choices.append((info["name"], key))
483
+ selected_ocr_model = gr.Dropdown(
484
+ choices=model_choices,
485
+ value="auto",
486
+ label="OCR Model Selection",
487
+ info="Choose OCR model (Advanced models may require setup)",
488
+ )
489
+ else:
490
+ selected_ocr_model = gr.State("basic")
491
+
492
+ gr.Markdown("*Note: OCR may increase processing time*")
493
+ else:
494
+ extract_text = gr.Checkbox(
495
+ value=False,
496
+ label="OCR Not Available",
497
+ interactive=False,
498
+ )
499
+ ocr_on_no_helmet = gr.Checkbox(
500
+ value=False,
501
+ label="🚨 Auto-OCR when No Helmet (Not Available)",
502
+ interactive=False,
503
+ )
504
+ selected_ocr_model = gr.State("basic")
505
+
506
+ with gr.Row():
507
+ submit_btn = gr.Button("🔍 Detect", variant="primary")
508
+ clear_btn = gr.Button("🧹 Clear")
509
+
510
+ with gr.Column(scale=2):
511
+ with gr.Group(elem_classes=["card"]):
512
+ gr.Markdown("### Output")
513
+ output_image = gr.Image(type="pil", label="Annotated Image")
514
+ output_table = gr.Dataframe(
515
+ headers=["Object", "Confidence", "Position", "Dimensions"],
516
+ label="Detection Details",
517
+ interactive=False,
518
+ )
519
+ output_stats = gr.Textbox(
520
+ label="Detection Summary", interactive=False, lines=6
521
+ )
522
+
523
+ # ---- Cropped plates & OCR (conditionally visible) ----
524
+ license_gallery = gr.Gallery(
525
+ label="Extracted License Plates",
526
+ show_label=True,
527
+ elem_id="license_gallery",
528
+ columns=3,
529
+ rows=2,
530
+ object_fit="contain",
531
+ height="auto",
532
+ visible=False, # hidden until OCR checkbox is enabled
533
  )
534
+
535
+ ocr_group = gr.Group(elem_classes=["ocr-section"], visible=False)
536
+ with ocr_group:
537
+ gr.Markdown("### License Plate Text Recognition")
538
+ plate_text_output = gr.Textbox(
539
+ label="Extracted Text",
540
+ placeholder="License plate text will appear here when OCR is enabled",
541
+ lines=4,
542
+ interactive=False,
543
+ )
544
+
545
+ with gr.Group(elem_classes=["download-section", "card"]):
546
+ gr.Markdown("### Download Results")
547
+ download_file = gr.File(
548
+ label="Download Complete Results (ZIP)",
549
+ interactive=False,
550
+ visible=True,
551
+ )
552
+ gr.Markdown(
553
+ "*ZIP contains: annotated image, cropped plates (if any), and a CSV report with OCR results*"
554
+ )
555
+
556
+ with gr.TabItem("Examples"):
557
+ gr.Markdown("### Example Images")
558
+ gr.Examples(
559
+ examples=[["sample_1.jpg"], ["sample_2.jpg"], ["sample_3.jpg"], ["sample_4.jpg"], ["sample_5.jpg"]],
560
+ inputs=input_image,
561
+ outputs=[
562
+ output_image,
563
+ output_table,
564
+ output_stats,
565
+ license_gallery,
566
+ download_file,
567
+ plate_text_output,
568
+ ],
569
+ fn=lambda img: yoloV8_func(
570
+ img, 640, 0.4, 0.5, True, True, True, False
571
+ ),
572
+ cache_examples=True,
573
  )
574
+
575
+ gr.HTML(
576
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
  <div class='footer'>
578
  <p>Built with Gradio and Ultralytics YOLO</p>
579
+ <p><strong>License Plate Privacy:</strong> Extracted plate images & text are for demo purposes only.</p>
 
580
  <p><strong>Requirements for OCR:</strong> torch, transformers, easyocr, opencv-python</p>
581
  </div>
582
+ """
583
+ )
584
+
585
+ # ===== Wire events =====
586
+ # 1) Main click
587
  submit_btn.click(
588
  fn=yoloV8_func,
589
+ inputs=[
590
+ input_image,
591
+ image_size,
592
+ conf_threshold,
593
+ iou_threshold,
594
+ show_stats,
595
+ gr.State(True), # show_confidence placeholder
596
+ crop_plates,
597
+ extract_text,
598
+ ocr_on_no_helmet,
599
+ selected_ocr_model,
600
+ ],
601
+ outputs=[
602
+ output_image,
603
+ output_table,
604
+ output_stats,
605
+ license_gallery,
606
+ download_file,
607
+ plate_text_output,
608
+ ],
609
  )
610
+
611
+ # 2) Clear
612
  clear_btn.click(
613
  fn=lambda: [None, None, None, None, None, None],
614
  inputs=[],
615
+ outputs=[
616
+ input_image,
617
+ output_image,
618
+ output_table,
619
+ output_stats,
620
+ license_gallery,
621
+ download_file,
622
+ plate_text_output,
623
+ ],
624
+ )
625
+
626
+ # 3) Toggle visibility when user toggles OCR or Crop checkboxes
627
+ extract_text.change(
628
+ fn=toggle_sections,
629
+ inputs=[extract_text, crop_plates],
630
+ outputs=[license_gallery, ocr_group],
631
+ )
632
+ crop_plates.change(
633
+ fn=toggle_sections,
634
+ inputs=[extract_text, crop_plates],
635
+ outputs=[license_gallery, ocr_group],
636
  )
637
 
638
  if __name__ == "__main__":
639
+ demo.launch(debug=True, share=True)