Files changed (1) hide show
  1. app.py +434 -37
app.py CHANGED
@@ -4,7 +4,8 @@ import streamlit as st
4
  st.set_page_config(
5
  page_title="SafeLicensing - License Plate Encryption",
6
  page_icon="🛡️",
7
- layout="wide"
 
8
  )
9
 
10
  import os
@@ -172,44 +173,429 @@ def create_video_from_frames(frames, fps, size, output_path, audio_path=None):
172
 
173
  clip.write_videofile(output_path, codec='libx264')
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  ###############################################################################
176
  # 4. Streamlit App
177
  ###############################################################################
178
 
179
  def main():
180
- st.title("YOLOv8 + Chaotic Encryption for Images and Videos")
181
- st.write(
182
- """
183
- **Instructions**:
184
- 1. Provide an image or video (URL or file upload).
185
- 2. If a license plate is detected, only that region will be **encrypted** using Chaotic Logistic Map.
186
- 3. Download the final result (image or video).
187
- """
188
- )
189
  default_model_path = "best.pt"
190
- model_path = st.sidebar.text_input(
191
- "YOLOv8 Weights (.pt)", value=default_model_path)
 
192
  if not os.path.isfile(model_path):
193
  st.warning(
194
- f"Model file '{model_path}' not found. Please upload or provide a correct path.")
195
  st.stop()
196
  with st.spinner("Loading YOLOv8 model..."):
197
  model = load_model(model_path)
198
- st.success("Model loaded successfully!")
199
-
200
- st.subheader("Input")
201
- input_type = st.radio("Select input type", ["Image", "Video"])
202
-
203
- if input_type == "Image":
204
- image_url = st.text_input("Image URL (optional)")
205
- uploaded_file = st.file_uploader("Or upload an image file", type=["jpg", "jpeg", "png"])
206
- else:
207
- video_url = st.text_input("Video URL (optional)")
208
- uploaded_file = st.file_uploader("Or upload a video file", type=["mp4", "avi", "mov"])
209
-
210
- key_seed = st.slider("Encryption Key Seed (0 < seed < 1)", 0.001, 0.999, 0.5, step=0.001)
211
-
212
- if st.button("Detect & Encrypt"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  if input_type == "Image":
214
  if image_url and not uploaded_file:
215
  try:
@@ -228,15 +614,12 @@ def main():
228
  else:
229
  st.warning("Please either paste a valid URL or upload an image.")
230
  return
231
-
232
- st.image(pil_image, caption="Original Image", use_column_width=True)
233
  start_time = time.time()
234
 
235
  with st.spinner("Detecting license plates..."):
236
  image_with_boxes, bboxes = detect_license_plates(model, pil_image.copy())
237
-
238
- st.image(image_with_boxes, caption="Detected Plate(s)", use_column_width=True)
239
-
240
  if not bboxes:
241
  st.warning("No license plates detected.")
242
  return
@@ -258,8 +641,16 @@ def main():
258
  encrypted_image = Image.fromarray(encrypted_np)
259
 
260
  elapsed_time = time.time() - start_time
261
- st.write(f"Total time taken for detection and encryption: **{elapsed_time:.2f} seconds**")
262
- st.image(encrypted_image, caption="Encrypted Image", use_column_width=True)
 
 
 
 
 
 
 
 
263
 
264
  buf = io.BytesIO()
265
  encrypted_image.save(buf, format="PNG")
@@ -304,7 +695,13 @@ def main():
304
  create_video_from_frames(processed_frames, fps, size, "encrypted_video.mp4", video_path)
305
  elapsed_time = time.time() - start_time
306
 
307
- st.write(f"Total time taken for video processing: **{elapsed_time:.2f} seconds**")
 
 
 
 
 
 
308
  st.video("encrypted_video.mp4")
309
 
310
  with open("encrypted_video.mp4", "rb") as f:
@@ -320,4 +717,4 @@ def main():
320
  os.remove("encrypted_video.mp4")
321
 
322
  if __name__ == "__main__":
323
- main()
 
4
  st.set_page_config(
5
  page_title="SafeLicensing - License Plate Encryption",
6
  page_icon="🛡️",
7
+ layout="wide",
8
+ initial_sidebar_state="collapsed"
9
  )
10
 
11
  import os
 
173
 
174
  clip.write_videofile(output_path, codec='libx264')
175
 
176
+
177
+ def inject_styles():
178
+ st.markdown(
179
+ """
180
+ <style>
181
+ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=DM+Sans:wght@400;500;700&display=swap');
182
+
183
+ :root {
184
+ --bg: var(--background-color, #0e1117);
185
+ --surface: var(--secondary-background-color, #262730);
186
+ --text: var(--text-color, #fafafa);
187
+ --muted: rgba(160, 165, 180, 0.9);
188
+ --accent: #0ea5a4;
189
+ --accent-2: #2563eb;
190
+ --danger: #dc4965;
191
+ --stroke: rgba(127, 127, 127, 0.22);
192
+ --panel: var(--surface);
193
+ --panel-strong: var(--surface);
194
+ --header-bg: transparent;
195
+ --hero-glow: radial-gradient(circle, rgba(37, 99, 235, 0.18), transparent 60%);
196
+ --eyebrow-bg: rgba(14, 165, 164, 0.12);
197
+ --eyebrow-text: var(--text);
198
+ --input-bg: var(--surface);
199
+ --button-text: #04111f;
200
+ }
201
+
202
+ .stApp {
203
+ background-color: var(--bg);
204
+ background-image:
205
+ radial-gradient(circle at top left, rgba(14, 165, 164, 0.12), transparent 24%),
206
+ radial-gradient(circle at top right, rgba(37, 99, 235, 0.14), transparent 26%);
207
+ color: var(--text);
208
+ font-family: "DM Sans", sans-serif;
209
+ }
210
+
211
+ [data-testid="stHeader"] {
212
+ background: var(--header-bg);
213
+ }
214
+
215
+ .block-container {
216
+ padding-top: 2rem;
217
+ padding-bottom: 2rem;
218
+ max-width: 1220px;
219
+ }
220
+
221
+ h1, h2, h3 {
222
+ font-family: "Space Grotesk", sans-serif;
223
+ letter-spacing: -0.03em;
224
+ color: var(--text);
225
+ }
226
+
227
+ .hero-shell {
228
+ position: relative;
229
+ overflow: hidden;
230
+ padding: 2rem 2rem 1.75rem;
231
+ border-radius: 28px;
232
+ border: 1px solid var(--stroke);
233
+ background:
234
+ linear-gradient(135deg, var(--surface), var(--bg)),
235
+ linear-gradient(120deg, rgba(14, 165, 164, 0.08), rgba(37, 99, 235, 0.08));
236
+ box-shadow: 0 28px 80px rgba(0, 0, 0, 0.28);
237
+ margin-bottom: 1.25rem;
238
+ }
239
+
240
+ .hero-shell::after {
241
+ content: "";
242
+ position: absolute;
243
+ inset: auto -8% -35% auto;
244
+ width: 340px;
245
+ height: 340px;
246
+ border-radius: 999px;
247
+ background: var(--hero-glow);
248
+ pointer-events: none;
249
+ }
250
+
251
+ .eyebrow {
252
+ display: inline-flex;
253
+ align-items: center;
254
+ gap: 0.45rem;
255
+ padding: 0.45rem 0.85rem;
256
+ border-radius: 999px;
257
+ background: var(--eyebrow-bg);
258
+ color: var(--accent);
259
+ font-size: 0.82rem;
260
+ font-weight: 700;
261
+ text-transform: uppercase;
262
+ letter-spacing: 0.08em;
263
+ }
264
+
265
+ .hero-title {
266
+ margin: 0.9rem 0 0.6rem;
267
+ font-size: clamp(2.2rem, 4vw, 3.6rem);
268
+ line-height: 1.05;
269
+ }
270
+
271
+ .hero-copy {
272
+ max-width: 760px;
273
+ color: var(--muted);
274
+ font-size: 1rem;
275
+ line-height: 1.7;
276
+ margin-bottom: 1.4rem;
277
+ }
278
+
279
+ .hero-grid {
280
+ display: grid;
281
+ grid-template-columns: repeat(3, minmax(0, 1fr));
282
+ gap: 0.85rem;
283
+ }
284
+
285
+ .glass-card {
286
+ background: rgba(255, 255, 255, 0.04);
287
+ border: 1px solid var(--stroke);
288
+ border-radius: 18px;
289
+ padding: 0.9rem 1rem;
290
+ }
291
+
292
+ .stat-label {
293
+ color: var(--muted);
294
+ text-transform: uppercase;
295
+ letter-spacing: 0.08em;
296
+ font-size: 0.72rem;
297
+ font-weight: 700;
298
+ margin-bottom: 0.35rem;
299
+ }
300
+
301
+ .stat-value {
302
+ font-family: "Space Grotesk", sans-serif;
303
+ font-size: 1rem;
304
+ font-weight: 700;
305
+ color: var(--text);
306
+ }
307
+
308
+ .section-card,
309
+ .meta-card {
310
+ background: var(--panel);
311
+ border: 1px solid var(--stroke);
312
+ border-radius: 20px;
313
+ padding: 1.25rem;
314
+ margin-bottom: 1rem;
315
+ box-shadow: 0 12px 36px rgba(0, 0, 0, 0.14);
316
+ display: flex;
317
+ flex-direction: column;
318
+ justify-content: flex-start;
319
+ }
320
+
321
+ div[data-testid="stVerticalBlockBorderWrapper"] {
322
+ background: var(--panel);
323
+ border: 1px solid var(--stroke) !important;
324
+ border-radius: 20px;
325
+ padding: 1.25rem;
326
+ box-shadow: 0 12px 36px rgba(0, 0, 0, 0.14);
327
+ display: flex;
328
+ flex-direction: column;
329
+ }
330
+
331
+ .control-head {
332
+ margin-bottom: 1rem;
333
+ }
334
+
335
+ .section-kicker {
336
+ color: var(--accent);
337
+ text-transform: uppercase;
338
+ letter-spacing: 0.08em;
339
+ font-size: 0.74rem;
340
+ font-weight: 700;
341
+ margin-bottom: 0.3rem;
342
+ }
343
+
344
+ .section-title {
345
+ font-family: "Space Grotesk", sans-serif;
346
+ font-size: 1.25rem;
347
+ font-weight: 700;
348
+ line-height: 1.2;
349
+ margin-bottom: 0.45rem;
350
+ }
351
+
352
+ .section-copy {
353
+ color: var(--muted);
354
+ margin-bottom: 0;
355
+ line-height: 1.6;
356
+ font-size: 0.95rem;
357
+ }
358
+
359
+ .results-head {
360
+ margin: 1.25rem 0 0.75rem;
361
+ font-family: "Space Grotesk", sans-serif;
362
+ font-size: 1.4rem;
363
+ font-weight: 700;
364
+ }
365
+
366
+ .meta-grid {
367
+ display: grid;
368
+ grid-template-columns: repeat(2, minmax(0, 1fr));
369
+ gap: 1rem;
370
+ margin-bottom: 1.25rem;
371
+ }
372
+
373
+ .meta-title {
374
+ font-family: "Space Grotesk", sans-serif;
375
+ font-size: 1.1rem;
376
+ font-weight: 700;
377
+ line-height: 1.25;
378
+ margin-bottom: 0.5rem;
379
+ }
380
+
381
+ .meta-copy {
382
+ color: var(--muted);
383
+ line-height: 1.7;
384
+ margin: 0;
385
+ font-size: 0.95rem;
386
+ }
387
+
388
+ .meta-copy a, .credit-link {
389
+ color: var(--accent-2) !important;
390
+ text-decoration: none;
391
+ font-weight: 600;
392
+ }
393
+
394
+ .credit-row {
395
+ display: flex;
396
+ flex-wrap: wrap;
397
+ gap: 0.6rem 1rem;
398
+ margin-top: 0.75rem;
399
+ }
400
+
401
+ .stTextInput label, .stRadio label, .stSlider label, .stFileUploader label, .stMarkdown, .stCaption, .stAlert {
402
+ color: var(--text) !important;
403
+ }
404
+
405
+ .stTextInput label, .stRadio label, .stSlider label, .stFileUploader label {
406
+ font-weight: 600;
407
+ }
408
+
409
+ div[data-baseweb="input"] > div,
410
+ div[data-baseweb="select"] > div,
411
+ [data-testid="stFileUploaderDropzone"] {
412
+ background: var(--input-bg) !important;
413
+ border: 1px solid var(--stroke) !important;
414
+ }
415
+
416
+ .stTextInput input,
417
+ .stNumberInput input,
418
+ textarea {
419
+ color: var(--text) !important;
420
+ }
421
+
422
+ .stButton > button, .stDownloadButton > button {
423
+ border-radius: 14px;
424
+ border: 1px solid rgba(37, 99, 235, 0.24);
425
+ background: linear-gradient(135deg, #2dd4bf, #60a5fa);
426
+ color: var(--button-text);
427
+ font-family: "Space Grotesk", sans-serif;
428
+ font-weight: 700;
429
+ padding: 0.75rem 1.25rem;
430
+ box-shadow: 0 8px 24px rgba(37, 99, 235, 0.18);
431
+ transition: box-shadow 0.15s ease, border-color 0.15s ease;
432
+ }
433
+
434
+ .stButton > button:hover, .stDownloadButton > button:hover {
435
+ border-color: rgba(37, 99, 235, 0.45);
436
+ box-shadow: 0 12px 32px rgba(37, 99, 235, 0.28);
437
+ color: var(--button-text);
438
+ }
439
+
440
+ div[data-testid="stImage"] img, div[data-testid="stVideo"] video {
441
+ border-radius: 16px;
442
+ border: 1px solid var(--stroke);
443
+ overflow: hidden;
444
+ }
445
+
446
+ div[data-testid="stMetric"] {
447
+ background: var(--panel-strong);
448
+ border: 1px solid var(--stroke);
449
+ border-radius: 16px;
450
+ padding: 0.8rem 1rem;
451
+ }
452
+
453
+ div[data-testid="stExpander"] {
454
+ border: 1px solid var(--stroke) !important;
455
+ border-radius: 16px !important;
456
+ background: var(--panel);
457
+ margin-bottom: 1rem;
458
+ }
459
+
460
+ @media (max-width: 900px) {
461
+ .hero-grid {
462
+ grid-template-columns: 1fr;
463
+ }
464
+
465
+ .meta-grid {
466
+ grid-template-columns: 1fr;
467
+ }
468
+ }
469
+ </style>
470
+ """,
471
+ unsafe_allow_html=True,
472
+ )
473
+
474
+
475
+
476
+ def render_hero():
477
+ st.markdown(
478
+ """
479
+ <section class="hero-shell">
480
+ <div class="eyebrow">Privacy-first plate protection</div>
481
+ <h1 class="hero-title">SafeLicensing Space</h1>
482
+ <p class="hero-copy">
483
+ Detect license plates in images and videos, then encrypt only sensitive regions with
484
+ chaotic-map masking. Built for fast redaction previews, clean exports, and modern operator flow.
485
+ </p>
486
+ <div class="hero-grid">
487
+ <div class="glass-card">
488
+ <div class="stat-label">Vision Core</div>
489
+ <div class="stat-value">YOLOv8 detection pipeline</div>
490
+ </div>
491
+ <div class="glass-card">
492
+ <div class="stat-label">Protection Layer</div>
493
+ <div class="stat-value">Dual-pass chaotic encryption</div>
494
+ </div>
495
+ <div class="glass-card">
496
+ <div class="stat-label">Supported Media</div>
497
+ <div class="stat-value">Image and video workflows</div>
498
+ </div>
499
+ </div>
500
+ </section>
501
+ """,
502
+ unsafe_allow_html=True,
503
+ )
504
+
505
+
506
+ def render_research_meta():
507
+ st.markdown(
508
+ """
509
+ <section class="meta-grid">
510
+ <div class="meta-card">
511
+ <div class="section-kicker">Conference Paper</div>
512
+ <div class="meta-title">Research foundation behind this workflow</div>
513
+ <p class="meta-copy">
514
+ Vehicle Number Plate Detection and Encryption in Digital Images Using YOLOv8
515
+ and Chaotic-Based Encryption Scheme.
516
+ <a href="https://ieeexplore.ieee.org/abstract/document/10534375/" target="_blank">
517
+ View on IEEE Xplore
518
+ </a>
519
+ </p>
520
+ </div>
521
+ <div class="meta-card">
522
+ <div class="section-kicker">Credits</div>
523
+ <div class="meta-title">Project authors</div>
524
+ <div class="credit-row">
525
+ <a class="credit-link" href="https://www.fahimbinamin.com" target="_blank">Md. Fahim Bin Amin</a>
526
+ <a class="credit-link" href="https://www.isratjahankhan.com" target="_blank">Israt Jahan Khan</a>
527
+ </div>
528
+ </div>
529
+ </section>
530
+ """,
531
+ unsafe_allow_html=True,
532
+ )
533
+
534
  ###############################################################################
535
  # 4. Streamlit App
536
  ###############################################################################
537
 
538
  def main():
539
+ inject_styles()
540
+ render_hero()
541
+ render_research_meta()
542
+
 
 
 
 
 
543
  default_model_path = "best.pt"
544
+ with st.expander("Model Settings", expanded=False):
545
+ model_path = st.text_input("YOLOv8 Weights (.pt)", value=default_model_path)
546
+
547
  if not os.path.isfile(model_path):
548
  st.warning(
549
+ f"Model file '{model_path}' not found. Please provide a correct path.")
550
  st.stop()
551
  with st.spinner("Loading YOLOv8 model..."):
552
  model = load_model(model_path)
553
+
554
+ left_col, right_col = st.columns(2, gap="large")
555
+
556
+ with left_col:
557
+ with st.container(border=True):
558
+ st.markdown(
559
+ """
560
+ <div class="control-head">
561
+ <div class="section-kicker">Mission Flow</div>
562
+ <div class="section-title">Load media, detect plate, encrypt region</div>
563
+ <p class="section-copy">
564
+ Choose image or video input, then run targeted protection. Original content stays intact
565
+ outside detected plate bounds.
566
+ </p>
567
+ </div>
568
+ """,
569
+ unsafe_allow_html=True,
570
+ )
571
+ input_type = st.radio("Media Type", ["Image", "Video"], horizontal=True)
572
+
573
+ if input_type == "Image":
574
+ image_url = st.text_input("Image URL")
575
+ uploaded_file = st.file_uploader("Upload image", type=["jpg", "jpeg", "png"])
576
+ else:
577
+ video_url = st.text_input("Video URL")
578
+ uploaded_file = st.file_uploader("Upload video", type=["mp4", "avi", "mov"])
579
+
580
+ with right_col:
581
+ with st.container(border=True):
582
+ st.markdown(
583
+ """
584
+ <div class="control-head">
585
+ <div class="section-kicker">Encryption Dial</div>
586
+ <div class="section-title">Chaotic key control</div>
587
+ <p class="section-copy">
588
+ Smaller seed changes create different chaotic sequences for plate masking.
589
+ </p>
590
+ </div>
591
+ """,
592
+ unsafe_allow_html=True,
593
+ )
594
+ key_seed = st.slider("Encryption Key Seed", 0.001, 0.999, 0.5, step=0.001)
595
+ detect_clicked = st.button("Detect & Encrypt", use_container_width=True)
596
+ st.caption("Supports URL fetch or direct upload.")
597
+
598
+ if detect_clicked:
599
  if input_type == "Image":
600
  if image_url and not uploaded_file:
601
  try:
 
614
  else:
615
  st.warning("Please either paste a valid URL or upload an image.")
616
  return
617
+
 
618
  start_time = time.time()
619
 
620
  with st.spinner("Detecting license plates..."):
621
  image_with_boxes, bboxes = detect_license_plates(model, pil_image.copy())
622
+
 
 
623
  if not bboxes:
624
  st.warning("No license plates detected.")
625
  return
 
641
  encrypted_image = Image.fromarray(encrypted_np)
642
 
643
  elapsed_time = time.time() - start_time
644
+ st.markdown('<div class="results-head">Result Overview</div>', unsafe_allow_html=True)
645
+ metric_cols = st.columns(3)
646
+ metric_cols[0].metric("Media Type", "Image")
647
+ metric_cols[1].metric("Plates Found", len(bboxes))
648
+ metric_cols[2].metric("Runtime", f"{elapsed_time:.2f}s")
649
+
650
+ preview_cols = st.columns(3, gap="large")
651
+ preview_cols[0].image(pil_image, caption="Original", use_container_width=True)
652
+ preview_cols[1].image(image_with_boxes, caption="Detected", use_container_width=True)
653
+ preview_cols[2].image(encrypted_image, caption="Encrypted", use_container_width=True)
654
 
655
  buf = io.BytesIO()
656
  encrypted_image.save(buf, format="PNG")
 
695
  create_video_from_frames(processed_frames, fps, size, "encrypted_video.mp4", video_path)
696
  elapsed_time = time.time() - start_time
697
 
698
+ st.markdown('<div class="results-head">Result Overview</div>', unsafe_allow_html=True)
699
+ metric_cols = st.columns(4)
700
+ metric_cols[0].metric("Media Type", "Video")
701
+ metric_cols[1].metric("Frames", frame_count)
702
+ metric_cols[2].metric("FPS", f"{fps:.2f}")
703
+ metric_cols[3].metric("Runtime", f"{elapsed_time:.2f}s")
704
+
705
  st.video("encrypted_video.mp4")
706
 
707
  with open("encrypted_video.mp4", "rb") as f:
 
717
  os.remove("encrypted_video.mp4")
718
 
719
  if __name__ == "__main__":
720
+ main()