ImageShield / app.py
NOBODY204's picture
Update app.py
41bc579 verified
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()