File size: 4,742 Bytes
7f77870
 
 
 
b606265
7f77870
 
 
b606265
7f77870
 
b606265
 
 
7f77870
 
b606265
 
 
7f77870
 
 
 
 
 
 
b606265
 
7f77870
 
 
 
 
 
 
b606265
7f77870
b606265
7f77870
 
 
 
 
 
b606265
 
7f77870
 
 
 
 
b606265
7f77870
 
 
b606265
 
 
 
 
 
 
 
 
 
 
7f77870
b606265
 
7f77870
b606265
 
7f77870
 
 
 
 
 
 
b606265
7f77870
b606265
7f77870
b606265
7f77870
 
 
 
b606265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52a4c35
b606265
7f77870
 
48cc13b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# app_yolo11_blur_nomargin.py
from pathlib import Path
import cv2
import numpy as np
import os, time, tempfile
import gradio as gr
from ultralytics import YOLO
from huggingface_hub import hf_hub_download
from PIL import Image



# ========= CONFIG =========
DEVICE = "cpu"        # "cpu" ou "cuda:0" si GPU visible
CONF_DEFAULT = 0.30
IOU_DEFAULT  = 0.50
BLUR_DEFAULT = 0.28
FEATHER_DEFAULT = 8


weights_path = hf_hub_download(
    repo_id="morsetechlab/yolov11-license-plate-detection",
    filename="license-plate-finetune-v1l.pt"
)
model = YOLO(weights_path)

# ========= BLUR SANS MARGE (bord doux interne) =========
def blur_bbox_nomargin(img, x1, y1, x2, y2, blur_strength=BLUR_DEFAULT, feather=FEATHER_DEFAULT):
    H, W = img.shape[:2]
    x1, y1 = max(0, x1), max(0, y1)
    x2, y2 = min(W, x2), min(H, y2)
    w, h = x2 - x1, y2 - y1
    if w <= 0 or h <= 0:
        return img

    # flou global puis composition dans la bbox via masque (aucune marge)
    k = int(max(w, h) * blur_strength)
    k = max(31, (k // 2) * 2 + 1)  # noyau impair, min 31 => flou smooth
    blurred_full = cv2.GaussianBlur(img, (k, k), 0)

    mask = np.zeros((H, W), dtype=np.uint8)
    cv2.rectangle(mask, (x1, y1), (x2, y2), 255, thickness=-1)

    if feather > 0:
        # feather vers l'intérieur du rectangle (ne déborde pas)
        obj = (mask > 0).astype(np.uint8)
        dist = cv2.distanceTransform(obj, distanceType=cv2.DIST_L2, maskSize=3)
        alpha = np.clip(dist / float(feather), 0.0, 1.0)
    else:
        alpha = (mask.astype(np.float32) / 255.0)

    alpha = alpha[..., None]
    out = (alpha * blurred_full + (1.0 - alpha) * img).astype(np.uint8)
    return out

# ========= PIPELINE (avec étapes UI) =========
def process_and_toggle(image):
    """

    Générateur pour:

      1) cacher l'étape 1, montrer l'étape 2 (message 'Traitement...')

      2) faire le traitement, produire l'image + fichier .jpg + log

    """
    # 1) toggle immédiat
    yield (None, None, gr.update(visible=False), gr.update(visible=True))

    # 2) traitement
    if image is None:
        yield (None, None, gr.update(visible=True), gr.update(visible=False))
        return

    bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
    results = model.predict(source=bgr, conf=CONF_DEFAULT, iou=IOU_DEFAULT, device=DEVICE, verbose=False)

    out = bgr.copy()
    total = 0
    for r in results:
        if r.boxes is None:
            continue
        for (x1, y1, x2, y2) in r.boxes.xyxy.cpu().numpy().astype(int):
            # feather effectif borné à la moitié de la bbox (évite artefacts)
            w, h = x2 - x1, y2 - y1
            f_eff = min(int(FEATHER_DEFAULT), max(w // 2 - 1, 0), max(h // 2 - 1, 0))
            out = blur_bbox_nomargin(out, x1, y1, x2, y2,
                                     blur_strength=float(BLUR_DEFAULT),
                                     feather=f_eff)
            total += 1

    out_rgb = cv2.cvtColor(out, cv2.COLOR_BGR2RGB)

    # Fichier .JPG
    ts = time.strftime("%Y%m%d_%H%M%S")
    fname = f"plaque_floutee_{ts}.jpg"
    fpath = os.path.join(tempfile.gettempdir(), fname)
    Image.fromarray(out_rgb).save(fpath, format="JPEG", quality=92)

    yield (out_rgb, fpath, gr.update(visible=False), gr.update(visible=True))

def reset_ui():
    # réinitialise l'affichage (retour à l'étape 1)
    return None, None, gr.update(visible=True), gr.update(visible=False)

# ========= UI =========
with gr.Blocks(title="🚗 Floutage de plaques d'immatriculation") as demo:
    gr.Markdown("### 📸 Dépose une image 🟦 Clique sur **Flouter** 💾 Télécharge ta voiture avec plaque d'immatriculation flouté")
    step1 = gr.Column(visible=True)
    with step1:
        with gr.Row():
            with gr.Column():
                inp   = gr.Image(type="numpy", label="Image")
                btn   = gr.Button("Flouter 🔒")
    step2 = gr.Column(visible=False)
    with step2:
        out_img  = gr.Image(type="numpy", label="Aperçu flouté 👀")
        out_file = gr.File(label="Télécharger en JPG 💾")
        back_btn = gr.Button("Recommencer 🔄")

    # clic: on stream d'abord le toggle, puis l'image/fichier
    btn.click(
        fn=process_and_toggle,
        inputs=[inp],
        outputs=[out_img, out_file, step1, step2],
        queue=True  # permet le 'yield'
    )

    # bouton retour
    back_btn.click(
        fn=reset_ui,
        inputs=[],
        outputs=[out_img, out_file, step1, step2],
    )

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")), show_footer=False)