NOBODY204 commited on
Commit
c7e7937
·
verified ·
1 Parent(s): de3ae17

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +131 -212
app.py CHANGED
@@ -1,151 +1,103 @@
 
 
 
 
1
  import gradio as gr
2
  import numpy as np
3
- import matplotlib.pyplot as plt
4
  import cv2
5
- from scipy import ndimage
6
 
7
- # ═══════════════════════════════════════════════════
8
  # 🛡️ IMAGESHIELD PRO v2.5 – AUTHENTICITY & DEEPFAKE DETECTOR
9
  # ACoNum / Trusted Sound 2026 — Sami Meddeb
10
- # v2.5 : +2 signaux retouche locale (composite / skin smoothing)
11
- # Détecte les photos très retouchées (Remini, Facetune, FaceApp…)
12
- # même si bruit capteur global reste élevé
13
- # ═══════════════════════════════════════════════════
14
-
15
- NOISE_THRESHOLD = 0.55 # bruit capteur corrigé
16
- FREQ_THRESHOLD = 500 # pic FFT GAN
17
- ELA_THRESHOLD = 0.25 # ELA trop parfaite
18
- MIN_SIGNALS = 2 # seuil de déclenchement
19
 
 
 
 
 
20
 
21
- # ─────────────────────────────────────────────────────────────
22
- # SIGNAUX EXISTANTS (inchangés)
23
- # ─────────────────────────────────────────────────────────────
24
 
 
 
 
25
  def get_sensor_noise_fingerprint(img):
26
- """
27
- Extrait le bruit hautes fréquences du capteur.
28
- Corrige le biais luminosité (studio pro).
29
- """
30
- gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
31
  blurred = cv2.medianBlur(gray.astype(np.uint8), 3).astype(np.float32)
32
- noise = cv2.absdiff(gray, blurred)
33
- mean_lum = np.mean(gray)
34
- noise_density = np.std(noise)
35
- corrected_density = noise_density * (128.0 / (mean_lum + 1e-8))
36
  return corrected_density, noise
37
 
38
 
 
 
 
39
  def analyze_frequency_domain(img):
40
- """
41
- FFT : détecte les grilles de génération GAN/Diffusion.
42
- Ratio max/mean > 500 = pic anormal = signature IA.
43
- """
44
- gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
45
- f = np.fft.fft2(gray)
46
- fshift = np.fft.fftshift(f)
47
- mag = np.abs(fshift)
48
- h, w = gray.shape
49
  cy, cx = h // 2, w // 2
50
  mag[cy - 20:cy + 20, cx - 20:cx + 20] = 0
51
  peak_score = np.max(mag) / (np.mean(mag) + 1e-8)
52
- vis = np.log(mag + 1)
53
- vis = cv2.normalize(vis, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
54
  return peak_score, cv2.applyColorMap(vis, cv2.COLORMAP_VIRIDIS)
55
 
56
 
 
 
 
57
  def error_level_analysis(img, quality=92):
58
- """
59
- ELA : détecte manipulations locales.
60
- ELA très basse + variance nulle = image synthétique pure.
61
- """
62
- _, enc = cv2.imencode(
63
- '.jpg', cv2.cvtColor(img, cv2.COLOR_RGB2BGR),
64
- [int(cv2.IMWRITE_JPEG_QUALITY), quality]
65
- )
66
- dec = cv2.imdecode(enc, 1)
67
  diff = cv2.absdiff(img, cv2.cvtColor(dec, cv2.COLOR_BGR2RGB))
68
- ela_score = np.mean(diff)
69
- ela_variance = np.std(diff)
70
- return ela_score, ela_variance, cv2.convertScaleAbs(diff, alpha=5.0)
71
 
72
 
 
 
 
73
  def detect_face_artifacts(img):
74
- """
75
- Gradients Sobel : trop uniforme = deepfake facial.
76
- ratio std/mean < 1.2 = texture synthétique.
77
- """
78
  gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
79
- sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
80
- sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
81
- gradient_mag = np.sqrt(sobelx**2 + sobely**2)
82
- gradient_std = np.std(gradient_mag)
83
- gradient_mean = np.mean(gradient_mag)
84
- ratio = gradient_std / (gradient_mean + 1e-8)
85
- return ratio
86
 
87
 
88
- # ─────────────────────────────────────────────────────────────
89
- # NOUVEAUX SIGNAUX v2.5
90
- # ─────────────────────────────────────────────────────────────
91
-
92
  def detect_local_retouching(img, noise_map):
93
- """
94
- Signal 5 — Retouche locale : composite, background replacement, skin smoothing.
95
-
96
- Principe : une vraie photo a un bruit de capteur COHÉRENT sur toute l'image.
97
- Une image retouchée crée des "îles de silence" (bruit ≈ 0) dans une mer de bruit,
98
- typique d'un masquage par zone (fond remplacé, visage lissé, objet inséré).
99
-
100
- Deux métriques :
101
- - silent_ratio : pourcentage de pixels avec bruit < 4% du max
102
- - inter_block_var : variance de bruit entre blocs 32×32
103
- (grande variance = zones hétérogènes = composite)
104
- """
105
- noise_f = noise_map.astype(np.float32)
106
- noise_max = np.max(noise_f) + 1e-8
107
- noise_norm = noise_f / noise_max
108
-
109
- # Zones quasi sans bruit
110
  silent_mask = (noise_norm < 0.04).astype(np.uint8)
111
  silent_ratio = float(np.mean(silent_mask))
112
 
113
- # Variance inter-blocs 32×32
114
  h, w = noise_norm.shape
115
- block_stds = []
116
- for y in range(0, h - 32, 32):
117
- for x in range(0, w - 32, 32):
118
- block = noise_norm[y:y + 32, x:x + 32]
119
- block_stds.append(float(np.std(block)))
120
-
121
  inter_block_var = float(np.std(block_stds)) if block_stds else 0.0
122
 
123
- # Carte visuelle : zones silencieuses en rouge sur fond gris
124
  vis = cv2.cvtColor((noise_norm * 255).astype(np.uint8), cv2.COLOR_GRAY2RGB)
125
- vis[silent_mask == 1] = [220, 50, 50] # rouge = zone suspecte
126
 
127
  return silent_ratio, inter_block_var, vis
128
 
129
 
 
 
 
130
  def detect_skin_smoothing(img):
131
- """
132
- Signal 6 Skin smoothing (Remini, Facetune, FaceApp, Photoshop liquify…).
133
-
134
- Principe : la peau humaine réelle a une micro-texture naturelle mesurable
135
- via la variance du Laplacien dans les zones chair.
136
- Après lissage artificiel, cette variance s'effondre (< 8.0).
137
-
138
- Retourne :
139
- skin_texture_std : variance de texture (< 8.0 = suspect)
140
- skin_ratio : pourcentage de zones chair dans l'image
141
- skin_vis : carte de chaleur des zones analysées
142
- """
143
- hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
144
- h_ch = hsv[:, :, 0].astype(np.float32)
145
- s_ch = hsv[:, :, 1].astype(np.float32)
146
- v_ch = hsv[:, :, 2].astype(np.float32)
147
-
148
- # Masque peau : teinte chair, saturation modérée, luminosité > 80
149
  skin_mask = (
150
  (h_ch >= 0) & (h_ch <= 25) &
151
  (s_ch >= 40) & (s_ch <= 200) &
@@ -154,111 +106,76 @@ def detect_skin_smoothing(img):
154
 
155
  skin_ratio = float(np.mean(skin_mask))
156
 
157
- # Visualisation
158
- skin_vis = img.copy()
159
- skin_vis[skin_mask == 0] = (skin_vis[skin_mask == 0] * 0.35).astype(np.uint8)
160
- skin_vis[skin_mask == 1, 0] = np.clip(
161
- skin_vis[skin_mask == 1, 0].astype(np.int32) + 60, 0, 255
162
- ).astype(np.uint8)
163
 
164
  if skin_ratio < 0.02:
165
- return -1.0, skin_ratio, skin_vis # pas assez de peau
166
 
167
  gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
168
- lap = np.abs(cv2.Laplacian(gray, cv2.CV_64F))
169
  skin_texture = lap[skin_mask == 1]
170
 
171
  if len(skin_texture) < 100:
172
  return -1.0, skin_ratio, skin_vis
173
 
174
- texture_std = float(np.std(skin_texture))
175
- return texture_std, skin_ratio, skin_vis
176
 
177
 
178
- # ─────────────────────────────────────────────────────────────
179
  # MOTEUR PRINCIPAL
180
- # ─────────────────────────────────────────────────────────────
181
-
182
  def detect_deepfake(img):
183
-
184
- # ── Calcul des 6 signaux ─────────────────────────────────
185
- noise_density, noise_map = get_sensor_noise_fingerprint(img)
186
- freq_score, fft_map = analyze_frequency_domain(img)
187
- ela_s, ela_var, ela_map = error_level_analysis(img)
188
- gradient_ratio = detect_face_artifacts(img)
189
  silent_ratio, inter_block_var, retouche_vis = detect_local_retouching(img, noise_map)
190
- skin_std, skin_ratio, skin_vis = detect_skin_smoothing(img)
191
 
192
- # ── Scoring ──────────────────────────────────────────────
193
  signals_triggered = 0
194
- ai_confidence = 0
195
- reasons = []
196
 
197
- # Signal 1 — Bruit capteur absent
198
  if noise_density < NOISE_THRESHOLD:
199
- ai_confidence += 35
200
- signals_triggered += 1
201
- reasons.append(
202
- f"⚠️ Bruit photonique absent "
203
- f"(densité={noise_density:.2f} < {NOISE_THRESHOLD}) — "
204
- "aucun grain capteur physique"
205
- )
206
 
207
- # Signal 2 — Grille FFT GAN
208
  if freq_score > FREQ_THRESHOLD:
209
- ai_confidence += 30
210
- signals_triggered += 1
211
- reasons.append(
212
- f"⚠️ Grille IA en FFT "
213
- f"(score={freq_score:.0f} > {FREQ_THRESHOLD}) — "
214
- "pattern de génération diffusion/GAN"
215
- )
216
 
217
- # Signal 3 — ELA trop parfaite + variance nulle
218
  if ela_s < ELA_THRESHOLD and ela_var < 0.5:
219
- ai_confidence += 25
220
- signals_triggered += 1
221
- reasons.append(
222
- f"⚠️ Compression parfaite "
223
- f"ELA={ela_s:.3f} σ={ela_var:.3f} — "
224
- "image non issue d'un capteur optique réel"
225
- )
226
 
227
- # Signal 4 — Gradients trop lisses (deepfake facial)
228
  if gradient_ratio < 1.2:
229
- ai_confidence += 20
230
- signals_triggered += 1
231
- reasons.append(
232
- f"⚠️ Gradients trop uniformes "
233
- f"(ratio={gradient_ratio:.2f} < 1.2) — "
234
- "absence de texture naturelle"
235
- )
236
 
237
- # Signal 5 — Retouche locale (composite / fond remplacé) [NOUVEAU v2.5]
238
- # Critère : > 20 % zones silencieuses ET forte hétérogénéité entre blocs
239
  if silent_ratio > 0.20 and inter_block_var > 0.08:
240
- ai_confidence += 30
241
- signals_triggered += 1
242
  reasons.append(
243
- f"⚠️ Retouche locale détectée — "
244
- f"{silent_ratio * 100:.0f}% zones sans bruit, "
245
- f"variance inter-blocs={inter_block_var:.3f} — "
246
- "composite, fond remplacé ou masquage par zone"
247
  )
248
 
249
- # Signal 6 — Skin smoothing [NOUVEAU v2.5]
250
- # Texture peau std < 8.0 = lissage artificiel (Facetune, Remini, FaceApp…)
251
  if skin_ratio >= 0.02 and 0 <= skin_std < 8.0:
252
- ai_confidence += 25
253
- signals_triggered += 1
254
  reasons.append(
255
- f"⚠️ Skin smoothing détecté — "
256
- f"texture peau std={skin_std:.1f} < 8.0 "
257
- f"(zone peau={skin_ratio * 100:.0f}%) — "
258
- "filtrage Facetune / Remini / FaceApp"
259
  )
260
 
261
- # Règle de sécurité : min 2 signaux pour crier deepfake
262
  if signals_triggered < MIN_SIGNALS:
263
  ai_confidence = min(ai_confidence, 28)
264
 
@@ -282,63 +199,66 @@ def detect_deepfake(img):
282
  )
283
 
284
 
285
- # ─────────────────────────────────────────────────────────────
286
- # INTERFACE GRADIO
287
- # ─────────────────────────────────────────────────────────────
288
-
289
  def process(input_img):
290
  if input_img is None:
291
- return None, "Veuillez charger une image."
292
 
293
- (score, label, reasons,
294
- fft, ela, noise,
295
- retouche_vis, skin_vis,
296
- noise_val, freq_val, ela_val,
297
- n_signals,
298
- silent_ratio, inter_block_var,
299
- skin_std, skin_ratio) = detect_deepfake(input_img)
 
300
 
301
- # ── Figure 3×2 : 6 vues forensiques ──────────────────────
 
 
 
302
  fig, axes = plt.subplots(2, 3, figsize=(16, 10))
303
 
304
  axes[0, 0].imshow(input_img)
305
- axes[0, 0].set_title("Original", fontsize=11)
306
 
307
  axes[0, 1].imshow(fft)
308
- axes[0, 1].set_title("FFT — Grilles IA (Signal 2)", fontsize=11)
309
 
310
  axes[0, 2].imshow(ela)
311
- axes[0, 2].set_title("ELA — Compression (Signal 3)", fontsize=11)
312
 
313
  axes[1, 0].imshow(noise, cmap='gray')
314
- axes[1, 0].set_title("Bruit Capteur Corrigé (Signal 1)", fontsize=11)
315
 
316
  axes[1, 1].imshow(retouche_vis)
317
- axes[1, 1].set_title("Zones Sans Bruit — Retouche (Signal 5)", fontsize=11)
318
 
319
  axes[1, 2].imshow(skin_vis)
320
- axes[1, 2].set_title("Zones Peau — Skin Smoothing (Signal 6)", fontsize=11)
321
 
322
  for ax in axes.flatten():
323
  ax.axis('off')
324
 
325
- plt.suptitle(f"{label} — {score}%", fontsize=13, fontweight='bold',
326
- color='red' if score > 55 else ('orange' if score > 28 else 'green'))
327
  plt.tight_layout()
328
 
329
- # ── Rapport texte ─────────────────────────────────────────
330
  report = f"RÉSULTAT : {label}\n"
331
  report += f"Probabilité manipulation : {score}% | Signaux : {n_signals}/{MIN_SIGNALS} minimum\n"
332
  report += f"\nMesures brutes :\n"
333
- report += f" [S1] Bruit capteur corrigé : {noise_val:.3f} (seuil < {NOISE_THRESHOLD})\n"
334
- report += f" [S2] Pic FFT : {freq_val:.0f} (seuil > {FREQ_THRESHOLD})\n"
335
- report += f" [S3] ELA moyenne : {ela_val:.4f} (seuil < {ELA_THRESHOLD})\n"
336
- report += f" [S5] Zones sans bruit : {silent_ratio * 100:.1f}% (seuil > 20%)\n"
337
- report += f" [S5] Variance inter-blocs : {inter_block_var:.3f} (seuil > 0.08)\n"
338
  if skin_ratio >= 0.02:
339
- report += f" [S6] Texture peau (std) : {skin_std:.1f} (seuil < 8.0, zone={skin_ratio * 100:.0f}%)\n"
340
  else:
341
- report += f" [S6] Texture peau : zone chair insuffisante (<2%)\n"
342
  report += f"\nSignaux actifs :\n"
343
  report += "\n".join(reasons) if reasons else " Aucune trace de manipulation détectée."
344
  report += "\n\n── ImageShield PRO v2.5 · ACoNum / Trusted Sound 2026 ──"
@@ -346,15 +266,14 @@ def process(input_img):
346
  return fig, report
347
 
348
 
349
- # ─────────────────────────────────────────────────────────────
350
- # LANCEMENT
351
- # ─────────────────────────────────────────────────────────────
352
-
353
  with gr.Blocks() as demo:
354
  gr.Markdown(
355
  "# 🛡️ ImageShield PRO v2.5\n"
356
  "### Analyse Forensic : Authentique vs Deepfake / Retouche\n"
357
- "_v2.5 : +2 signaux — retouche locale (composite, fond remplacé) + skin smoothing (Remini, Facetune, FaceApp)_"
358
  )
359
  with gr.Row():
360
  with gr.Column():
 
1
+ import matplotlib
2
+ matplotlib.use('Agg') # OBLIGATOIRE avant tout import plt — évite crash HF Spaces
3
+ import matplotlib.pyplot as plt
4
+
5
  import gradio as gr
6
  import numpy as np
 
7
  import cv2
 
8
 
9
+ # ═══════════════════════════════════════════════════════════════
10
  # 🛡️ IMAGESHIELD PRO v2.5 – AUTHENTICITY & DEEPFAKE DETECTOR
11
  # ACoNum / Trusted Sound 2026 — Sami Meddeb
12
+ # v2.5 : +2 signaux retouche locale + skin smoothing
13
+ # Fix skin_vis numpy, try/except robuste
14
+ # ═══════════════════════════════════════════════════════════════
 
 
 
 
 
 
15
 
16
+ NOISE_THRESHOLD = 0.55
17
+ FREQ_THRESHOLD = 500
18
+ ELA_THRESHOLD = 0.25
19
+ MIN_SIGNALS = 2
20
 
 
 
 
21
 
22
+ # ──────────────────────────────────────────────────────────────
23
+ # SIGNAL 1 — Bruit de capteur (corrigé luminosité studio)
24
+ # ──────────────────────────────────────────────────────────────
25
  def get_sensor_noise_fingerprint(img):
26
+ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
 
 
 
 
27
  blurred = cv2.medianBlur(gray.astype(np.uint8), 3).astype(np.float32)
28
+ noise = cv2.absdiff(gray, blurred)
29
+ mean_lum = np.mean(gray)
30
+ corrected_density = np.std(noise) * (128.0 / (mean_lum + 1e-8))
 
31
  return corrected_density, noise
32
 
33
 
34
+ # ──────────────────────────────────────────────────────────────
35
+ # SIGNAL 2 — FFT : grille GAN/Diffusion
36
+ # ──────────────────────────────────────────────────────────────
37
  def analyze_frequency_domain(img):
38
+ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
39
+ fshift = np.fft.fftshift(np.fft.fft2(gray))
40
+ mag = np.abs(fshift)
41
+ h, w = gray.shape
 
 
 
 
 
42
  cy, cx = h // 2, w // 2
43
  mag[cy - 20:cy + 20, cx - 20:cx + 20] = 0
44
  peak_score = np.max(mag) / (np.mean(mag) + 1e-8)
45
+ vis = cv2.normalize(np.log(mag + 1), None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
 
46
  return peak_score, cv2.applyColorMap(vis, cv2.COLORMAP_VIRIDIS)
47
 
48
 
49
+ # ──────────────────────────────────────────────────────────────
50
+ # SIGNAL 3 — ELA : manipulation locale
51
+ # ──────────────────────────────────────────────────────────────
52
  def error_level_analysis(img, quality=92):
53
+ _, enc = cv2.imencode('.jpg', cv2.cvtColor(img, cv2.COLOR_RGB2BGR),
54
+ [int(cv2.IMWRITE_JPEG_QUALITY), quality])
55
+ dec = cv2.imdecode(enc, 1)
 
 
 
 
 
 
56
  diff = cv2.absdiff(img, cv2.cvtColor(dec, cv2.COLOR_BGR2RGB))
57
+ return np.mean(diff), np.std(diff), cv2.convertScaleAbs(diff, alpha=5.0)
 
 
58
 
59
 
60
+ # ──────────────────────────────────────────────────────────────
61
+ # SIGNAL 4 — Gradients Sobel : texture trop lisse
62
+ # ──────────────────────────────────────────────────────────────
63
  def detect_face_artifacts(img):
 
 
 
 
64
  gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
65
+ sx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
66
+ sy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
67
+ mag = np.sqrt(sx**2 + sy**2)
68
+ return np.std(mag) / (np.mean(mag) + 1e-8)
 
 
 
69
 
70
 
71
+ # ─────────────────────────────────────────────────────────────
72
+ # SIGNAL 5 — Retouche locale : composite / fond remplacé
73
+ # ─────────────────────────────────────────────────────────────
 
74
  def detect_local_retouching(img, noise_map):
75
+ noise_norm = noise_map.astype(np.float32) / (np.max(noise_map) + 1e-8)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  silent_mask = (noise_norm < 0.04).astype(np.uint8)
77
  silent_ratio = float(np.mean(silent_mask))
78
 
 
79
  h, w = noise_norm.shape
80
+ block_stds = [
81
+ float(np.std(noise_norm[y:y + 32, x:x + 32]))
82
+ for y in range(0, h - 32, 32)
83
+ for x in range(0, w - 32, 32)
84
+ ]
 
85
  inter_block_var = float(np.std(block_stds)) if block_stds else 0.0
86
 
87
+ # Visualisation : zones suspectes en rouge
88
  vis = cv2.cvtColor((noise_norm * 255).astype(np.uint8), cv2.COLOR_GRAY2RGB)
89
+ vis[silent_mask == 1] = [220, 50, 50]
90
 
91
  return silent_ratio, inter_block_var, vis
92
 
93
 
94
+ # ──────────────────────────────────────────────────────────────
95
+ # SIGNAL 6 — Skin smoothing : Facetune / Remini / FaceApp
96
+ # ──────────────────────────────────────────────────────────────
97
  def detect_skin_smoothing(img):
98
+ hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV).astype(np.float32)
99
+ h_ch, s_ch, v_ch = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2]
100
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  skin_mask = (
102
  (h_ch >= 0) & (h_ch <= 25) &
103
  (s_ch >= 40) & (s_ch <= 200) &
 
106
 
107
  skin_ratio = float(np.mean(skin_mask))
108
 
109
+ # Visualisation sûre (pas d'assignment sur copie)
110
+ darkened = (img * 0.35).astype(np.uint8)
111
+ skin_vis = darkened.copy()
112
+ skin_vis[skin_mask == 1] = img[skin_mask == 1] # ← fix numpy : vue directe
 
 
113
 
114
  if skin_ratio < 0.02:
115
+ return -1.0, skin_ratio, skin_vis
116
 
117
  gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
118
+ lap = np.abs(cv2.Laplacian(gray, cv2.CV_64F))
119
  skin_texture = lap[skin_mask == 1]
120
 
121
  if len(skin_texture) < 100:
122
  return -1.0, skin_ratio, skin_vis
123
 
124
+ return float(np.std(skin_texture)), skin_ratio, skin_vis
 
125
 
126
 
127
+ # ─────────────────────────────────────────────────────────────
128
  # MOTEUR PRINCIPAL
129
+ # ─────────────────────────────────────────────────────────────
 
130
  def detect_deepfake(img):
131
+ noise_density, noise_map = get_sensor_noise_fingerprint(img)
132
+ freq_score, fft_map = analyze_frequency_domain(img)
133
+ ela_s, ela_var, ela_map = error_level_analysis(img)
134
+ gradient_ratio = detect_face_artifacts(img)
 
 
135
  silent_ratio, inter_block_var, retouche_vis = detect_local_retouching(img, noise_map)
136
+ skin_std, skin_ratio, skin_vis = detect_skin_smoothing(img)
137
 
 
138
  signals_triggered = 0
139
+ ai_confidence = 0
140
+ reasons = []
141
 
142
+ # S1 — Bruit capteur absent
143
  if noise_density < NOISE_THRESHOLD:
144
+ ai_confidence += 35; signals_triggered += 1
145
+ reasons.append(f"⚠️ Bruit photonique absent (densité={noise_density:.2f} < {NOISE_THRESHOLD})")
 
 
 
 
 
146
 
147
+ # S2 — Grille IA FFT
148
  if freq_score > FREQ_THRESHOLD:
149
+ ai_confidence += 30; signals_triggered += 1
150
+ reasons.append(f"⚠️ Grille IA en FFT (score={freq_score:.0f} > {FREQ_THRESHOLD})")
 
 
 
 
 
151
 
152
+ # S3 — ELA trop parfaite
153
  if ela_s < ELA_THRESHOLD and ela_var < 0.5:
154
+ ai_confidence += 25; signals_triggered += 1
155
+ reasons.append(f"⚠️ ELA parfaite={ela_s:.3f} σ={ela_var:.3f} — image synthétique")
 
 
 
 
 
156
 
157
+ # S4 — Gradients trop uniformes
158
  if gradient_ratio < 1.2:
159
+ ai_confidence += 20; signals_triggered += 1
160
+ reasons.append(f"⚠️ Gradients trop lisses (ratio={gradient_ratio:.2f} < 1.2)")
 
 
 
 
 
161
 
162
+ # S5 — Retouche locale
 
163
  if silent_ratio > 0.20 and inter_block_var > 0.08:
164
+ ai_confidence += 30; signals_triggered += 1
 
165
  reasons.append(
166
+ f"⚠️ Retouche locale — {silent_ratio*100:.0f}% zones sans bruit, "
167
+ f"variance inter-blocs={inter_block_var:.3f}"
 
 
168
  )
169
 
170
+ # S6 — Skin smoothing
 
171
  if skin_ratio >= 0.02 and 0 <= skin_std < 8.0:
172
+ ai_confidence += 25; signals_triggered += 1
 
173
  reasons.append(
174
+ f"⚠️ Skin smoothing — texture peau std={skin_std:.1f} < 8.0 "
175
+ f"(zone={skin_ratio*100:.0f}%)"
 
 
176
  )
177
 
178
+ # Règle sécurité : min 2 signaux
179
  if signals_triggered < MIN_SIGNALS:
180
  ai_confidence = min(ai_confidence, 28)
181
 
 
199
  )
200
 
201
 
202
+ # ─────────────────────────────────────────────────────────────
203
+ # CALLBACK GRADIO
204
+ # ─────────────────────────────────────────────────────────────
 
205
  def process(input_img):
206
  if input_img is None:
207
+ return None, "⚠️ Veuillez charger une image."
208
 
209
+ try:
210
+ (score, label, reasons,
211
+ fft, ela, noise,
212
+ retouche_vis, skin_vis,
213
+ noise_val, freq_val, ela_val,
214
+ n_signals,
215
+ silent_ratio, inter_block_var,
216
+ skin_std, skin_ratio) = detect_deepfake(input_img)
217
 
218
+ except Exception as e:
219
+ return None, f"❌ Erreur d'analyse : {str(e)}"
220
+
221
+ # ── Figure 2×3 ──────────────────────────────────────────
222
  fig, axes = plt.subplots(2, 3, figsize=(16, 10))
223
 
224
  axes[0, 0].imshow(input_img)
225
+ axes[0, 0].set_title("Original")
226
 
227
  axes[0, 1].imshow(fft)
228
+ axes[0, 1].set_title("FFT — Grille IA [S2]")
229
 
230
  axes[0, 2].imshow(ela)
231
+ axes[0, 2].set_title("ELA — Compression [S3]")
232
 
233
  axes[1, 0].imshow(noise, cmap='gray')
234
+ axes[1, 0].set_title("Bruit Capteur Corrigé [S1]")
235
 
236
  axes[1, 1].imshow(retouche_vis)
237
+ axes[1, 1].set_title("Zones Sans Bruit — Retouche [S5]")
238
 
239
  axes[1, 2].imshow(skin_vis)
240
+ axes[1, 2].set_title("Zones Peau — Skin Smoothing [S6]")
241
 
242
  for ax in axes.flatten():
243
  ax.axis('off')
244
 
245
+ color = 'red' if score > 55 else ('orange' if score > 28 else 'green')
246
+ plt.suptitle(f"{label} — {score}%", fontsize=13, fontweight='bold', color=color)
247
  plt.tight_layout()
248
 
249
+ # ── Rapport ─────────────────────────────────────────────
250
  report = f"RÉSULTAT : {label}\n"
251
  report += f"Probabilité manipulation : {score}% | Signaux : {n_signals}/{MIN_SIGNALS} minimum\n"
252
  report += f"\nMesures brutes :\n"
253
+ report += f" [S1] Bruit capteur corrigé : {noise_val:.3f} (seuil < {NOISE_THRESHOLD})\n"
254
+ report += f" [S2] Pic FFT : {freq_val:.0f} (seuil > {FREQ_THRESHOLD})\n"
255
+ report += f" [S3] ELA moyenne : {ela_val:.4f} (seuil < {ELA_THRESHOLD})\n"
256
+ report += f" [S5] Zones sans bruit : {silent_ratio*100:.1f}% (seuil > 20%)\n"
257
+ report += f" [S5] Variance inter-blocs : {inter_block_var:.3f} (seuil > 0.08)\n"
258
  if skin_ratio >= 0.02:
259
+ report += f" [S6] Texture peau (std) : {skin_std:.1f} (seuil < 8.0, zone={skin_ratio*100:.0f}%)\n"
260
  else:
261
+ report += f" [S6] Texture peau : zone chair insuffisante (<2%)\n"
262
  report += f"\nSignaux actifs :\n"
263
  report += "\n".join(reasons) if reasons else " Aucune trace de manipulation détectée."
264
  report += "\n\n── ImageShield PRO v2.5 · ACoNum / Trusted Sound 2026 ──"
 
266
  return fig, report
267
 
268
 
269
+ # ─────────────────────────────────────────────────────────────
270
+ # INTERFACE GRADIO
271
+ # ─────────────────────────────────────────────────────────────
 
272
  with gr.Blocks() as demo:
273
  gr.Markdown(
274
  "# 🛡️ ImageShield PRO v2.5\n"
275
  "### Analyse Forensic : Authentique vs Deepfake / Retouche\n"
276
+ "_v2.5 : 6 signaux — bruit capteur, FFT, ELA, gradients, retouche locale, skin smoothing_"
277
  )
278
  with gr.Row():
279
  with gr.Column():