import gradio as gr import numpy as np import matplotlib.pyplot as plt import cv2 from scipy import ndimage # ═══════════════════════════════════════════════════ # 🛡️ IMAGESHIELD PRO v2.4 – AUTHENTICITY & DEEPFAKE DETECTOR # ACoNum / Trusted Sound 2026 — Sami Meddeb # Fix v2.4 : seuils recalibrés pour éviter les faux positifs # (photos studio professionnelles, éclairage fort, JPEG haute qualité) # ═══════════════════════════════════════════════════ # ── SEUILS CALIBRÉS v2.4 ──────────────────────────────────── # Problème v2.3 : photos studio → grain faible → détecté comme IA # Solution : seuils plus stricts + score combiné obligatoire NOISE_THRESHOLD = 0.55 # était 1.8 — studio pro légit peut avoir < 1.0 FREQ_THRESHOLD = 500 # était 150 — trop sensible aux JPEG ELA_THRESHOLD = 0.25 # était 1.0 — photos JPEG légit ont ELA faible MIN_SIGNALS = 2 # il faut AU MOINS 2 signaux pour crier deepfake def get_sensor_noise_fingerprint(img): """ Extrait le bruit hautes fréquences pour vérifier si c'est un capteur physique. IMPORTANT : photos studio avec éclairage professionnel ont naturellement moins de grain — ne pas confondre avec signature IA. On normalise par la luminosité moyenne pour corriger ce biais. """ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32) blurred = cv2.medianBlur(gray.astype(np.uint8), 3).astype(np.float32) noise = cv2.absdiff(gray, blurred) # Normalisation par la luminosité pour compenser l'éclairage studio mean_lum = np.mean(gray) noise_density = np.std(noise) # Correction : photo très lumineuse → grain naturellement réduit # On ramène à une base commune corrected_density = noise_density * (128.0 / (mean_lum + 1e-8)) return corrected_density, noise def analyze_frequency_domain(img): """ Analyse FFT pour détecter les grilles de génération IA. Les GAN/Diffusion produisent des pics très réguliers à fréquences spécifiques. Une photo JPEG légitime même compressée n'a PAS ces pics réguliers. """ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32) f = np.fft.fft2(gray) fshift = np.fft.fftshift(f) mag = np.abs(fshift) h, w = gray.shape cy, cx = h // 2, w // 2 mag[cy - 20:cy + 20, cx - 20:cx + 20] = 0 # Ratio max/mean : un vrai GAN a des pics TRÈS anormaux (> 500) # Une photo réelle même avec artefacts JPEG reste < 400 peak_score = np.max(mag) / (np.mean(mag) + 1e-8) vis = np.log(mag + 1) vis = cv2.normalize(vis, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) return peak_score, cv2.applyColorMap(vis, cv2.COLORMAP_VIRIDIS) def error_level_analysis(img, quality=92): """ ELA : détecte les manipulations locales. IMPORTANT : une photo JPEG légit, même haute qualité, a une ELA faible. On cherche une ELA ANORMALEMENT basse (image purement synthétique) OU des zones avec ELA irrégulière (copier-coller, manipulation locale). Seuil abaissé à 0.25 pour ne pas pénaliser les vraies photos. """ _, enc = cv2.imencode( '.jpg', cv2.cvtColor(img, cv2.COLOR_RGB2BGR), [int(cv2.IMWRITE_JPEG_QUALITY), quality] ) dec = cv2.imdecode(enc, 1) diff = cv2.absdiff(img, cv2.cvtColor(dec, cv2.COLOR_BGR2RGB)) ela_score = np.mean(diff) # Variance spatiale : une image IA a souvent une ELA trop UNIFORME ela_variance = np.std(diff) return ela_score, ela_variance, cv2.convertScaleAbs(diff, alpha=5.0) def detect_face_artifacts(img): """ Détecte les artefacts typiques des deepfakes sur les visages : - Bords flous autour du visage - Transition peau/arrière-plan trop nette (upsampling) - Symétrie excessive """ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # Gradient de Sobel pour détecter les discontinuités anormales sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) gradient_mag = np.sqrt(sobelx**2 + sobely**2) # Un deepfake facial a des gradients trop lisses dans les zones de peau # et trop nets aux bords du visage généré gradient_std = np.std(gradient_mag) gradient_mean = np.mean(gradient_mag) ratio = gradient_std / (gradient_mean + 1e-8) # Ratio < 1.2 = trop uniforme = suspect return ratio def detect_deepfake(img): # 1. Bruit de capteur (corrigé pour éclairage studio) noise_density, noise_map = get_sensor_noise_fingerprint(img) # 2. Analyse fréquentielle FFT freq_score, fft_map = analyze_frequency_domain(img) # 3. ELA + variance ela_s, ela_var, ela_map = error_level_analysis(img) # 4. Artefacts de gradient (visage/bords) gradient_ratio = detect_face_artifacts(img) # ─── LOGIQUE DE SCORING v2.4 ───────────────────────────── # Règle : au moins 2 signaux positifs pour déclarer "deepfake" # Chaque signal contribue des points SEULEMENT s'il est significatif signals_triggered = 0 ai_confidence = 0 reasons = [] # Signal 1 — Bruit de capteur anormalement faible # Seuil 0.55 (corrigé luminosité) — les vrais studios restent > 0.6 if noise_density < NOISE_THRESHOLD: ai_confidence += 35 signals_triggered += 1 reasons.append( f"⚠️ Bruit photonique absent (densité={noise_density:.2f} < {NOISE_THRESHOLD}) — " "Absence de grain capteur physique" ) # Signal 2 — Pics de fréquence GAN (grille de diffusion) # Seuil 500 — uniquement les vraies grilles IA if freq_score > FREQ_THRESHOLD: ai_confidence += 30 signals_triggered += 1 reasons.append( f"⚠️ Grille IA détectée en FFT (score={freq_score:.0f} > {FREQ_THRESHOLD}) — " "Pattern de génération diffusion/GAN" ) # Signal 3 — ELA trop parfaite ET variance nulle # Seuil 0.25 + variance < 0.5 = image purement synthétique if ela_s < ELA_THRESHOLD and ela_var < 0.5: ai_confidence += 25 signals_triggered += 1 reasons.append( f"⚠️ Compression parfaite ELA={ela_s:.3f} σ={ela_var:.3f} — " "Image non issue d'un capteur optique réel" ) # Signal 4 — Gradient trop uniforme (deepfake facial) if gradient_ratio < 1.2: ai_confidence += 20 signals_triggered += 1 reasons.append( f"⚠️ Gradients trop lisses (ratio={gradient_ratio:.2f} < 1.2) — " "Absence de texture naturelle du capteur" ) # ─── RÈGLE DE SÉCURITÉ : minimum 2 signaux ────────────── if signals_triggered < MIN_SIGNALS: # Un seul signal → suspicieux mais pas deepfake ai_confidence = min(ai_confidence, 28) final_score = min(ai_confidence, 100) # ─── VERDICT ───────────────────────────────────────────── if final_score > 55: label = "🚨 DEEPFAKE / GENERATED" color = "red" elif final_score > 28: label = "⚖️ SUSPICIEUX / MODIFIÉ" color = "orange" else: label = "✅ AUTHENTIQUE (CAMERA)" color = "green" return ( final_score, label, reasons, fft_map, ela_map, noise_map, noise_density, freq_score, ela_s, signals_triggered ) # ═══════════════════════════════════════════════════ # 🎨 GRADIO INTERFACE # ═══════════════════════════════════════════════════ def process(input_img): if input_img is None: return None, "Veuillez charger une image." (score, label, reasons, fft, ela, noise, noise_val, freq_val, ela_val, n_signals) = detect_deepfake(input_img) fig, axes = plt.subplots(2, 2, figsize=(12, 10)) axes[0, 0].imshow(input_img); axes[0, 0].set_title("Original") axes[0, 1].imshow(fft); axes[0, 1].set_title("FFT — Grilles IA") axes[1, 0].imshow(ela); axes[1, 0].set_title("ELA — Compression") axes[1, 1].imshow(noise, cmap='gray'); axes[1, 1].set_title("Bruit Capteur Corrigé") for ax in axes.flatten(): ax.axis('off') plt.tight_layout() report = f"RÉSULTAT : {label}\n" report += f"Probabilité IA : {score}% | Signaux déclenchés : {n_signals}/{MIN_SIGNALS} minimum\n" report += f"\nMesures brutes :\n" report += f" • Bruit capteur corrigé : {noise_val:.3f} (seuil < {NOISE_THRESHOLD})\n" report += f" • Pic FFT : {freq_val:.0f} (seuil > {FREQ_THRESHOLD})\n" report += f" • ELA moyenne : {ela_val:.4f} (seuil < {ELA_THRESHOLD})\n" report += f"\nSignaux actifs :\n" report += "\n".join(reasons) if reasons else " Aucune trace de génération synthétique détectée." report += "\n\n── ImageShield PRO v2.4 · ACoNum / Trusted Sound 2026 ──" return fig, report with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown( "# 🛡️ ImageShield PRO v2.4\n" "### Analyse Forensic : Authentique vs Deepfake\n" "_Fix v2.4 : seuils recalibrés — photos studio, éclairage fort et JPEG haute qualité correctement classifiés_" ) with gr.Row(): with gr.Column(): in_img = gr.Image(label="Charger une image (JPG/PNG)", type="numpy") run_btn = gr.Button("LANCER L'ANALYSE", variant="primary") with gr.Column(): out_plot = gr.Plot() out_text = gr.Textbox(label="Rapport d'Expertise", lines=12) run_btn.click(process, inputs=in_img, outputs=[out_plot, out_text]) demo.launch()