NOBODY204 commited on
Commit
b947f8a
·
verified ·
1 Parent(s): 41bc579

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +220 -102
app.py CHANGED
@@ -5,62 +5,50 @@ import cv2
5
  from scipy import ndimage
6
 
7
  # ═══════════════════════════════════════════════════
8
- # 🛡️ IMAGESHIELD PRO v2.4 – AUTHENTICITY & DEEPFAKE DETECTOR
9
  # ACoNum / Trusted Sound 2026 — Sami Meddeb
10
- # Fix v2.4 : seuils recalibrés pour éviter les faux positifs
11
- # (photos studio professionnelles, éclairage fort, JPEG haute qualité)
 
12
  # ═══════════════════════════════════════════════════
13
 
14
- # ── SEUILS CALIBRÉS v2.4 ────────────────────────────────────
15
- # Problème v2.3 : photos studio → grain faible → détecté comme IA
16
- # Solution : seuils plus stricts + score combiné obligatoire
 
17
 
18
- NOISE_THRESHOLD = 0.55 # était 1.8 — studio pro légit peut avoir < 1.0
19
- FREQ_THRESHOLD = 500 # était 150 — trop sensible aux JPEG
20
- ELA_THRESHOLD = 0.25 # était 1.0 — photos JPEG légit ont ELA faible
21
- MIN_SIGNALS = 2 # il faut AU MOINS 2 signaux pour crier deepfake
22
 
 
 
 
23
 
24
  def get_sensor_noise_fingerprint(img):
25
  """
26
- Extrait le bruit hautes fréquences pour vérifier si c'est un capteur physique.
27
- IMPORTANT : photos studio avec éclairage professionnel ont naturellement
28
- moins de grain — ne pas confondre avec signature IA.
29
- On normalise par la luminosité moyenne pour corriger ce biais.
30
  """
31
  gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
32
  blurred = cv2.medianBlur(gray.astype(np.uint8), 3).astype(np.float32)
33
  noise = cv2.absdiff(gray, blurred)
34
-
35
- # Normalisation par la luminosité pour compenser l'éclairage studio
36
  mean_lum = np.mean(gray)
37
  noise_density = np.std(noise)
38
- # Correction : photo très lumineuse → grain naturellement réduit
39
- # On ramène à une base commune
40
  corrected_density = noise_density * (128.0 / (mean_lum + 1e-8))
41
-
42
  return corrected_density, noise
43
 
44
 
45
  def analyze_frequency_domain(img):
46
  """
47
- Analyse FFT pour détecter les grilles de génération IA.
48
- Les GAN/Diffusion produisent des pics très réguliers à fréquences spécifiques.
49
- Une photo JPEG légitime même compressée n'a PAS ces pics réguliers.
50
  """
51
  gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32)
52
  f = np.fft.fft2(gray)
53
  fshift = np.fft.fftshift(f)
54
  mag = np.abs(fshift)
55
-
56
  h, w = gray.shape
57
  cy, cx = h // 2, w // 2
58
  mag[cy - 20:cy + 20, cx - 20:cx + 20] = 0
59
-
60
- # Ratio max/mean : un vrai GAN a des pics TRÈS anormaux (> 500)
61
- # Une photo réelle même avec artefacts JPEG reste < 400
62
  peak_score = np.max(mag) / (np.mean(mag) + 1e-8)
63
-
64
  vis = np.log(mag + 1)
65
  vis = cv2.normalize(vis, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
66
  return peak_score, cv2.applyColorMap(vis, cv2.COLORMAP_VIRIDIS)
@@ -68,11 +56,8 @@ def analyze_frequency_domain(img):
68
 
69
  def error_level_analysis(img, quality=92):
70
  """
71
- ELA : détecte les manipulations locales.
72
- IMPORTANT : une photo JPEG légit, même haute qualité, a une ELA faible.
73
- On cherche une ELA ANORMALEMENT basse (image purement synthétique)
74
- OU des zones avec ELA irrégulière (copier-coller, manipulation locale).
75
- Seuil abaissé à 0.25 pour ne pas pénaliser les vraies photos.
76
  """
77
  _, enc = cv2.imencode(
78
  '.jpg', cv2.cvtColor(img, cv2.COLOR_RGB2BGR),
@@ -81,124 +66,225 @@ def error_level_analysis(img, quality=92):
81
  dec = cv2.imdecode(enc, 1)
82
  diff = cv2.absdiff(img, cv2.cvtColor(dec, cv2.COLOR_BGR2RGB))
83
  ela_score = np.mean(diff)
84
-
85
- # Variance spatiale : une image IA a souvent une ELA trop UNIFORME
86
  ela_variance = np.std(diff)
87
-
88
  return ela_score, ela_variance, cv2.convertScaleAbs(diff, alpha=5.0)
89
 
90
 
91
  def detect_face_artifacts(img):
92
  """
93
- Détecte les artefacts typiques des deepfakes sur les visages :
94
- - Bords flous autour du visage
95
- - Transition peau/arrière-plan trop nette (upsampling)
96
- - Symétrie excessive
97
  """
98
  gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
99
- # Gradient de Sobel pour détecter les discontinuités anormales
100
  sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
101
  sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
102
  gradient_mag = np.sqrt(sobelx**2 + sobely**2)
103
-
104
- # Un deepfake facial a des gradients trop lisses dans les zones de peau
105
- # et trop nets aux bords du visage généré
106
  gradient_std = np.std(gradient_mag)
107
  gradient_mean = np.mean(gradient_mag)
108
  ratio = gradient_std / (gradient_mean + 1e-8)
109
-
110
- # Ratio < 1.2 = trop uniforme = suspect
111
  return ratio
112
 
113
 
114
- def detect_deepfake(img):
115
- # 1. Bruit de capteur (corrigé pour éclairage studio)
116
- noise_density, noise_map = get_sensor_noise_fingerprint(img)
117
 
118
- # 2. Analyse fréquentielle FFT
119
- freq_score, fft_map = analyze_frequency_domain(img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- # 3. ELA + variance
122
- ela_s, ela_var, ela_map = error_level_analysis(img)
123
 
124
- # 4. Artefacts de gradient (visage/bords)
125
- gradient_ratio = detect_face_artifacts(img)
126
 
127
- # ─── LOGIQUE DE SCORING v2.4 ─────────────────────────────
128
- # Règle : au moins 2 signaux positifs pour déclarer "deepfake"
129
- # Chaque signal contribue des points SEULEMENT s'il est significatif
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  signals_triggered = 0
132
  ai_confidence = 0
133
  reasons = []
134
 
135
- # Signal 1 — Bruit de capteur anormalement faible
136
- # Seuil 0.55 (corrigé luminosité) — les vrais studios restent > 0.6
137
  if noise_density < NOISE_THRESHOLD:
138
  ai_confidence += 35
139
  signals_triggered += 1
140
  reasons.append(
141
- f"⚠️ Bruit photonique absent (densité={noise_density:.2f} < {NOISE_THRESHOLD}) — "
142
- "Absence de grain capteur physique"
 
143
  )
144
 
145
- # Signal 2 — Pics de fréquence GAN (grille de diffusion)
146
- # Seuil 500 — uniquement les vraies grilles IA
147
  if freq_score > FREQ_THRESHOLD:
148
  ai_confidence += 30
149
  signals_triggered += 1
150
  reasons.append(
151
- f"⚠️ Grille IA détectée en FFT (score={freq_score:.0f} > {FREQ_THRESHOLD}) — "
152
- "Pattern de génération diffusion/GAN"
 
153
  )
154
 
155
- # Signal 3 — ELA trop parfaite ET variance nulle
156
- # Seuil 0.25 + variance < 0.5 = image purement synthétique
157
  if ela_s < ELA_THRESHOLD and ela_var < 0.5:
158
  ai_confidence += 25
159
  signals_triggered += 1
160
  reasons.append(
161
- f"⚠️ Compression parfaite ELA={ela_s:.3f} σ={ela_var:.3f} — "
162
- "Image non issue d'un capteur optique réel"
 
163
  )
164
 
165
- # Signal 4 — Gradient trop uniforme (deepfake facial)
166
  if gradient_ratio < 1.2:
167
  ai_confidence += 20
168
  signals_triggered += 1
169
  reasons.append(
170
- f"⚠️ Gradients trop lisses (ratio={gradient_ratio:.2f} < 1.2) — "
171
- "Absence de texture naturelle du capteur"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  )
173
 
174
- # ─── RÈGLE DE SÉCURITÉ : minimum 2 signaux ──────────────
175
  if signals_triggered < MIN_SIGNALS:
176
- # Un seul signal → suspicieux mais pas deepfake
177
  ai_confidence = min(ai_confidence, 28)
178
 
179
  final_score = min(ai_confidence, 100)
180
 
181
- # ─── VERDICT ─────────────────────────────────────────────
182
  if final_score > 55:
183
- label = "🚨 DEEPFAKE / GENERATED"
184
- color = "red"
185
  elif final_score > 28:
186
  label = "⚖️ SUSPICIEUX / MODIFIÉ"
187
- color = "orange"
188
  else:
189
  label = "✅ AUTHENTIQUE (CAMERA)"
190
- color = "green"
191
 
192
  return (
193
  final_score, label, reasons,
194
  fft_map, ela_map, noise_map,
195
- noise_density, freq_score, ela_s, signals_triggered
 
 
 
 
196
  )
197
 
198
 
199
- # ═══════════════════════════════════════════════════
200
- # 🎨 GRADIO INTERFACE
201
- # ═══════════════════════════════════════════════════
202
 
203
  def process(input_img):
204
  if input_img is None:
@@ -206,46 +292,78 @@ def process(input_img):
206
 
207
  (score, label, reasons,
208
  fft, ela, noise,
209
- noise_val, freq_val, ela_val, n_signals) = detect_deepfake(input_img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
- fig, axes = plt.subplots(2, 2, figsize=(12, 10))
212
- axes[0, 0].imshow(input_img); axes[0, 0].set_title("Original")
213
- axes[0, 1].imshow(fft); axes[0, 1].set_title("FFT — Grilles IA")
214
- axes[1, 0].imshow(ela); axes[1, 0].set_title("ELA — Compression")
215
- axes[1, 1].imshow(noise, cmap='gray'); axes[1, 1].set_title("Bruit Capteur Corrigé")
216
  for ax in axes.flatten():
217
  ax.axis('off')
 
 
 
218
  plt.tight_layout()
219
 
 
220
  report = f"RÉSULTAT : {label}\n"
221
- report += f"Probabilité IA : {score}% | Signaux déclenchés : {n_signals}/{MIN_SIGNALS} minimum\n"
222
  report += f"\nMesures brutes :\n"
223
- report += f" Bruit capteur corrigé : {noise_val:.3f} (seuil < {NOISE_THRESHOLD})\n"
224
- report += f" Pic FFT : {freq_val:.0f} (seuil > {FREQ_THRESHOLD})\n"
225
- report += f" ELA moyenne : {ela_val:.4f} (seuil < {ELA_THRESHOLD})\n"
 
 
 
 
 
 
226
  report += f"\nSignaux actifs :\n"
227
- report += "\n".join(reasons) if reasons else " Aucune trace de génération synthétique détectée."
228
- report += "\n\n── ImageShield PRO v2.4 · ACoNum / Trusted Sound 2026 ──"
229
 
230
  return fig, report
231
 
232
 
 
 
 
 
233
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
234
  gr.Markdown(
235
- "# 🛡️ ImageShield PRO v2.4\n"
236
- "### Analyse Forensic : Authentique vs Deepfake\n"
237
- "_Fix v2.4 : seuils recalibrésphotos studio, éclairage fort et JPEG haute qualité correctement classifiés_"
238
  )
239
  with gr.Row():
240
  with gr.Column():
241
  in_img = gr.Image(label="Charger une image (JPG/PNG)", type="numpy")
242
- run_btn = gr.Button("LANCER L'ANALYSE", variant="primary")
243
  with gr.Column():
244
  out_plot = gr.Plot()
245
- out_text = gr.Textbox(label="Rapport d'Expertise", lines=12)
246
 
247
  run_btn.click(process, inputs=in_img, outputs=[out_plot, out_text])
248
 
249
  demo.launch()
250
-
251
-
 
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)
 
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),
 
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) &
152
+ (v_ch >= 80)
153
+ ).astype(np.uint8)
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
 
265
  final_score = min(ai_confidence, 100)
266
 
 
267
  if final_score > 55:
268
+ label = "🚨 DEEPFAKE / GÉNÉRÉ / RETOUCHÉ"
 
269
  elif final_score > 28:
270
  label = "⚖️ SUSPICIEUX / MODIFIÉ"
 
271
  else:
272
  label = "✅ AUTHENTIQUE (CAMERA)"
 
273
 
274
  return (
275
  final_score, label, reasons,
276
  fft_map, ela_map, noise_map,
277
+ retouche_vis, skin_vis,
278
+ noise_density, freq_score, ela_s,
279
+ signals_triggered,
280
+ silent_ratio, inter_block_var,
281
+ skin_std, skin_ratio
282
  )
283
 
284
 
285
+ # ─────────────────────────────────────────────────────────────
286
+ # INTERFACE GRADIO
287
+ # ─────────────────────────────────────────────────────────────
288
 
289
  def process(input_img):
290
  if input_img is None:
 
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 ──"
345
 
346
  return fig, report
347
 
348
 
349
+ # ─────────────────────────────────────────────────────────────
350
+ # LANCEMENT
351
+ # ─────────────────────────────────────────────────────────────
352
+
353
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
354
  gr.Markdown(
355
+ "# 🛡️ ImageShield PRO v2.5\n"
356
+ "### Analyse Forensic : Authentique vs Deepfake / Retouche\n"
357
+ "_v2.5 : +2 signauxretouche locale (composite, fond remplacé) + skin smoothing (Remini, Facetune, FaceApp)_"
358
  )
359
  with gr.Row():
360
  with gr.Column():
361
  in_img = gr.Image(label="Charger une image (JPG/PNG)", type="numpy")
362
+ run_btn = gr.Button("🔍 LANCER L'ANALYSE", variant="primary")
363
  with gr.Column():
364
  out_plot = gr.Plot()
365
+ out_text = gr.Textbox(label="Rapport d'Expertise", lines=18)
366
 
367
  run_btn.click(process, inputs=in_img, outputs=[out_plot, out_text])
368
 
369
  demo.launch()