Divyasri-18's picture
Update app.py
2215958 verified
Raw
History Blame Contribute Delete
20.6 kB
import gradio as gr
import torch
import numpy as np
import cv2
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from PIL import Image
from torchvision import transforms
from transformers import (AutoImageProcessor, AutoModelForImageClassification,
ViTMAEForPreTraining)
from facenet_pytorch import MTCNN
# ── Setup ─────────────────────────────────────────────────────────────────────
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
to_pil = transforms.ToPILImage()
IMG_MEAN = torch.tensor([0.485, 0.456, 0.406]).view(3,1,1)
IMG_STD = torch.tensor([0.229, 0.224, 0.225]).view(3,1,1)
# ── Load models ───────────────────────────────────────────────────────────────
print("⏳ Loading models …")
clf_processor = AutoImageProcessor.from_pretrained("Wvolf/ViT_Deepfake_Detection")
clf_model = AutoModelForImageClassification.from_pretrained(
"Wvolf/ViT_Deepfake_Detection").to(DEVICE)
clf_model.eval()
mae_processor = AutoImageProcessor.from_pretrained("facebook/vit-mae-base")
mae_model = ViTMAEForPreTraining.from_pretrained("facebook/vit-mae-base").to(DEVICE)
mae_model.eval()
mtcnn = MTCNN(image_size=224, margin=20, device=DEVICE, keep_all=False)
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
print("βœ… All models ready!")
# ── Helpers ───────────────────────────────────────────────────────────────────
def extract_face_image(pil_img):
face = mtcnn(pil_img)
if face is None:
return pil_img.resize((224, 224))
return to_pil(((face.clamp(-1,1)+1)/2.0).cpu()).resize((224,224))
def extract_face_video(bgr):
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(60,60))
if len(faces) == 0:
return Image.fromarray(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)).resize((224,224))
x,y,w,h = max(faces, key=lambda f: f[2]*f[3])
crop = bgr[max(0,y-20):y+h+20, max(0,x-20):x+w+20]
return Image.fromarray(cv2.cvtColor(crop, cv2.COLOR_BGR2RGB)).resize((224,224))
def classify(face_pil):
inp = clf_processor(images=face_pil, return_tensors="pt").to(DEVICE)
with torch.inference_mode():
probs = torch.softmax(clf_model(**inp).logits, dim=1).squeeze().tolist()
labels = clf_model.config.id2label
fake_idx = next((i for i,v in labels.items() if "fake" in v.lower()), 1)
return probs[fake_idx], probs[1-fake_idx]
def reconstruct(face_pil):
inp = mae_processor(images=face_pil, return_tensors="pt").to(DEVICE)
with torch.inference_mode():
out = mae_model(**inp)
recon = mae_model.unpatchify(out.logits).squeeze(0).cpu()
orig = inp.pixel_values.squeeze(0).cpu()
recon_dn = (recon*IMG_STD+IMG_MEAN).clamp(0,1)
orig_dn = (orig *IMG_STD+IMG_MEAN).clamp(0,1)
return to_pil(recon_dn), round(torch.mean((orig_dn-recon_dn)**2).item(), 6)
# ── Charts ────────────────────────────────────────────────────────────────────
def image_chart(fake_prob, mse):
fig = plt.figure(figsize=(12, 4.2), facecolor="#07080f")
gs = fig.add_gridspec(1, 3, wspace=0.4, left=0.04, right=0.97,
top=0.88, bottom=0.14)
is_fake = fake_prob > 0.5
c_accent = "#ff3b5c" if is_fake else "#00e5a0"
c_dim = "#151525"
# Donut
ax1 = fig.add_subplot(gs[0])
ax1.set_facecolor("#07080f")
ax1.pie([fake_prob, 1-fake_prob], colors=[c_accent, c_dim], startangle=90,
wedgeprops=dict(width=0.52, edgecolor="#07080f", linewidth=4))
ax1.text(0, 0.13, f"{fake_prob*100:.1f}%", ha="center", va="center",
fontsize=24, fontweight="bold", color=c_accent, fontfamily="monospace")
ax1.text(0, -0.22, "FAKE" if is_fake else "REAL",
ha="center", va="center", fontsize=13, fontweight="bold", color=c_accent,
bbox=dict(boxstyle="round,pad=0.35", fc=c_accent+"28", ec=c_accent, lw=1.3))
ax1.set_title("Classification", color="#9999bb", fontsize=11, fontweight="bold", pad=10)
# Probability bars
ax2 = fig.add_subplot(gs[1])
ax2.set_facecolor("#07080f")
ax2.set_xlim(0,1); ax2.set_ylim(-0.6, 1.6); ax2.axis("off")
for val, lbl, ypos, col in [(fake_prob,"FAKE",1.1,"#ff3b5c"),(1-fake_prob,"REAL",0.1,"#00e5a0")]:
ax2.add_patch(plt.Rectangle((0, ypos-0.18), 1.0, 0.32, fc="#151525", ec="#222238", lw=1))
ax2.add_patch(plt.Rectangle((0, ypos-0.18), val, 0.32, fc=col+"bb", ec="none"))
ax2.text(-0.03, ypos-0.01, lbl, ha="right", va="center",
fontsize=10, fontweight="bold", color=col)
ax2.text(val+0.02, ypos-0.01, f"{val:.3f}", ha="left", va="center",
fontsize=10, color="white", fontfamily="monospace")
ax2.set_title("Probabilities", color="#9999bb", fontsize=11, fontweight="bold")
ax2.text(0.5, -0.4, "0.0 ──────────────── 1.0",
ha="center", va="center", fontsize=8, color="#444466")
# MSE gauge
ax3 = fig.add_subplot(gs[2])
ax3.set_facecolor("#07080f"); ax3.axis("off")
ax3.set_xlim(0,1); ax3.set_ylim(0,1)
MAX_MSE = 0.06
fill = min(mse/MAX_MSE, 1.0)
mse_color = "#ff3b5c" if mse > 0.02 else "#00e5a0"
ax3.add_patch(plt.FancyBboxPatch((0.05,0.38), 0.90, 0.18,
boxstyle="round,pad=0.02", fc="#151525", ec="#222238", lw=1.2))
ax3.add_patch(plt.FancyBboxPatch((0.05,0.38), 0.90*fill, 0.18,
boxstyle="round,pad=0.02", fc=mse_color, ec="none"))
tx = 0.05 + 0.90*(0.02/MAX_MSE)
ax3.plot([tx,tx],[0.34,0.60], color="#ffa94d", lw=2, ls="--", zorder=5)
ax3.text(tx, 0.64, "threshold", ha="center", color="#ffa94d", fontsize=8.5, fontstyle="italic")
ax3.text(0.5, 0.82, f"{mse:.5f}", ha="center", va="center",
fontsize=22, fontweight="bold", color=mse_color, fontfamily="monospace")
ax3.text(0.5, 0.94, "Reconstruction MSE", ha="center",
color="#9999bb", fontsize=11, fontweight="bold")
status = "HIGH ⟢ Likely Fake" if mse > 0.02 else "LOW ⟢ Likely Real"
ax3.text(0.5, 0.20, status, ha="center", color=mse_color, fontsize=10, fontweight="bold",
bbox=dict(boxstyle="round,pad=0.3", fc=mse_color+"22", ec=mse_color, lw=1))
return fig
def video_chart(fake_probs, mse_scores):
fig, axes = plt.subplots(2, 1, figsize=(12, 6), facecolor="#07080f",
gridspec_kw={"hspace":0.55,"top":0.93,"bottom":0.10,
"left":0.07,"right":0.97})
x = list(range(len(fake_probs)))
for ax in axes:
ax.set_facecolor("#0d0e1c")
ax.tick_params(colors="#666688", labelsize=9)
for sp in ax.spines.values(): sp.set_color("#1e1e38")
ax.grid(alpha=0.10, color="#5555aa", lw=0.6, linestyle="--")
ax1, ax2 = axes
ax1.plot(x, fake_probs, color="#ff3b5c", lw=2.5, marker="o", ms=5,
markeredgecolor="#ff8899", markeredgewidth=1, zorder=4)
ax1.axhline(0.5, color="#ffa94d", ls="--", lw=1.8, label="Threshold 0.5", zorder=3)
ax1.fill_between(x, fake_probs, 0.5, where=[f>0.5 for f in fake_probs],
alpha=0.22, color="#ff3b5c", zorder=2, label="Fake zone")
ax1.fill_between(x, fake_probs, 0.5, where=[f<=0.5 for f in fake_probs],
alpha=0.14, color="#00e5a0", zorder=2, label="Real zone")
ax1.set_ylim(-0.05, 1.08)
ax1.set_ylabel("Fake Probability", color="#8888bb", fontsize=10)
ax1.set_title("Frame-by-Frame Fake Probability", color="#ccccee",
fontsize=12, fontweight="bold", pad=9)
ax1.legend(facecolor="#131325", labelcolor="#aaaacc", fontsize=9,
edgecolor="#2a2a48", loc="upper right")
ax1.yaxis.label.set_color("#8888bb")
mean_mse = float(np.mean(mse_scores))
ax2.plot(x, mse_scores, color="#4b9eff", lw=2.5, marker="s", ms=5,
markeredgecolor="#88ccff", markeredgewidth=1, zorder=4)
ax2.axhline(mean_mse, color="#00e5a0", ls="--", lw=1.8,
label=f"Mean {mean_mse:.4f}", zorder=3)
ax2.fill_between(x, mse_scores, mean_mse,
where=[m>mean_mse for m in mse_scores],
alpha=0.20, color="#ff3b5c", zorder=2, label="Above mean")
ax2.fill_between(x, mse_scores, mean_mse,
where=[m<=mean_mse for m in mse_scores],
alpha=0.12, color="#4b9eff", zorder=2)
ax2.set_xlabel("Frame Index", color="#8888bb", fontsize=10)
ax2.set_ylabel("Reconstruction MSE", color="#8888bb", fontsize=10)
ax2.set_title("Reconstruction Error per Frame", color="#ccccee",
fontsize=12, fontweight="bold", pad=9)
ax2.legend(facecolor="#131325", labelcolor="#aaaacc", fontsize=9,
edgecolor="#2a2a48", loc="upper right")
ax2.xaxis.label.set_color("#8888bb")
ax2.yaxis.label.set_color("#8888bb")
return fig
# ── Predictions ───────────────────────────────────────────────────────────────
def predict_image(image):
if image is None:
return "⚠️ Please upload an image first.", None, None, None, 0.0, 0.0
pil = Image.fromarray(image).convert("RGB")
face = extract_face_image(pil)
fake_p, real_p = classify(face)
recon, mse = reconstruct(face)
is_fake = fake_p > 0.5
icon = "πŸ”΄ DEEPFAKE DETECTED" if is_fake else "🟒 AUTHENTIC β€” Real Face"
conf = ("⚠️ Very high confidence β€” likely manipulated" if fake_p > 0.85 else
"⚑ High confidence β€” probably fake" if fake_p > 0.65 else
"πŸ”Ž Borderline β€” manual review recommended" if fake_p > 0.5 else
"βœ… Low fake score β€” appears genuine" if fake_p > 0.25 else
"βœ… Very low fake score β€” highly authentic")
verdict = (
f"{icon}\n"
f"{'━'*40}\n"
f" Fake probability β†’ {fake_p:.4f}\n"
f" Real probability β†’ {real_p:.4f}\n"
f" Reconstruction MSE β†’ {mse:.6f}\n"
f"{'━'*40}\n"
f" {conf}"
)
return verdict, face, recon, image_chart(fake_p, mse), round(fake_p,4), mse
def predict_video(video_path):
if video_path is None:
return "⚠️ Please upload a video first.", None, 0.0, 0.0
cap = cv2.VideoCapture(video_path)
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
n_samples = min(25, max(1, total))
sample_idx = set(np.linspace(0, total-1, n_samples, dtype=int))
fake_probs, mse_scores = [], []
idx = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret: break
if idx in sample_idx:
try:
face = extract_face_video(frame)
fp, _ = classify(face)
_, ms = reconstruct(face)
fake_probs.append(round(fp,4))
mse_scores.append(round(ms,6))
except Exception: pass
idx += 1
cap.release()
if not fake_probs:
return "⚠️ No faces detected in video.", None, 0.0, 0.0
avg_f = float(np.mean(fake_probs))
avg_m = float(np.mean(mse_scores))
n_fake = sum(1 for f in fake_probs if f > 0.5)
pct = n_fake / len(fake_probs) * 100
is_fake = avg_f > 0.5
icon = "πŸ”΄ DEEPFAKE DETECTED" if is_fake else "🟒 AUTHENTIC β€” Real Video"
conf = ("⚠️ Very high confidence β€” likely manipulated" if avg_f > 0.85 else
"⚑ High confidence β€” probably fake" if avg_f > 0.65 else
"πŸ”Ž Borderline β€” manual review recommended" if avg_f > 0.5 else
"βœ… Low fake score β€” appears genuine")
verdict = (
f"{icon}\n"
f"{'━'*40}\n"
f" Avg fake probability β†’ {avg_f:.4f}\n"
f" Avg reconstruction MSE β†’ {avg_m:.6f}\n"
f" Fake frames detected β†’ {n_fake}/{len(fake_probs)} ({pct:.1f}%)\n"
f" Total frames analyzed β†’ {len(fake_probs)} / {total}\n"
f"{'━'*40}\n"
f" {conf}"
)
return verdict, video_chart(fake_probs, mse_scores), round(avg_f,4), round(avg_m,6)
# ── CSS ───────────────────────────────────────────────────────────────────────
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap');
*, *::before, *::after { box-sizing: border-box; }
body, .gradio-container {
background: radial-gradient(ellipse at 20% 20%, #0e0a1f 0%, #07080f 60%, #050810 100%) !important;
font-family: 'Inter', sans-serif !important; min-height: 100vh;
}
.app-header {
background: linear-gradient(135deg, #130a2e 0%, #0a1530 40%, #0a1a1a 100%);
border: 1px solid rgba(120,80,255,0.25); border-radius: 20px;
padding: 36px 40px 28px; margin-bottom: 28px; text-align: center;
position: relative; overflow: hidden;
box-shadow: 0 0 60px rgba(100,60,255,0.12), inset 0 1px 0 rgba(255,255,255,0.06);
}
.app-header::before {
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
background: linear-gradient(90deg, transparent, #7b4fff, #ff3b5c, #7b4fff, transparent);
}
.app-header h1 {
font-size: 2.4rem !important; font-weight: 800 !important;
background: linear-gradient(90deg, #ff3b5c 0%, #ff9f43 40%, #7b4fff 80%, #ff3b5c 100%) !important;
background-size: 200% !important;
-webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important;
background-clip: text !important; letter-spacing: -0.5px !important; margin-bottom: 8px !important;
}
.app-header .subtitle { color: #7777aa !important; font-size: 14px !important; margin-bottom: 18px !important; }
.badge-row { display:flex; justify-content:center; gap:10px; flex-wrap:wrap; }
.badge { padding: 5px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; letter-spacing: 0.3px; }
.badge-purple { background:#7b4fff18; color:#a57fff; border:1px solid #7b4fff44; }
.badge-red { background:#ff3b5c18; color:#ff7090; border:1px solid #ff3b5c44; }
.badge-green { background:#00e5a018; color:#00e5a0; border:1px solid #00e5a044; }
.badge-blue { background:#4b9eff18; color:#7bb8ff; border:1px solid #4b9eff44; }
.gr-block, .block, .panel, [class*="block"] {
background: #0d0e1c !important; border: 1px solid #1a1b30 !important;
border-radius: 16px !important; box-shadow: 0 4px 24px rgba(0,0,0,0.4) !important;
}
button.primary, .gr-button-primary {
background: linear-gradient(135deg, #ff3b5c 0%, #cc2244 100%) !important;
border: none !important; border-radius: 12px !important; font-weight: 700 !important;
font-size: 15px !important; letter-spacing: 0.5px !important; height: 52px !important;
box-shadow: 0 4px 20px #ff3b5c44 !important; transition: all 0.25s ease !important; color: white !important;
}
button.primary:hover {
transform: translateY(-2px) !important; box-shadow: 0 8px 32px #ff3b5c66 !important;
background: linear-gradient(135deg, #ff5572 0%, #ee3355 100%) !important;
}
textarea, input[type="text"] {
background: #080912 !important; color: #d0d0f0 !important;
border: 1px solid #1e1f38 !important; border-radius: 10px !important;
font-family: 'JetBrains Mono', monospace !important; font-size: 13px !important; line-height: 1.8 !important;
}
input[type="number"] {
background: #080912 !important; color: #00e5a0 !important;
border: 1px solid #1e1f38 !important; border-radius: 10px !important;
font-family: 'JetBrains Mono', monospace !important;
font-size: 18px !important; font-weight: 700 !important; text-align: center !important;
}
label > span, .label-wrap span {
color: #555577 !important; font-size: 11px !important;
text-transform: uppercase !important; letter-spacing: 0.8px !important; font-weight: 600 !important;
}
.tab-nav { border-bottom: 1px solid #1a1b30 !important; }
.tab-nav button { color: #555577 !important; font-weight: 600 !important; font-size: 14px !important;
padding: 12px 24px !important; border-radius: 10px 10px 0 0 !important; }
.tab-nav button.selected {
color: #ffffff !important; background: linear-gradient(180deg, #1a1030 0%, #0d0e1c 100%) !important;
border-bottom: 2px solid #ff3b5c !important;
}
.section-divider { border: none; border-top: 1px solid #1a1b30; margin: 8px 0; }
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: #07080f; }
::-webkit-scrollbar-thumb { background: #2a2a44; border-radius: 3px; }
"""
# ── UI ─────────────────────────────────────────────────────────────────────────
with gr.Blocks(css=CSS, title="Deepfake Detector") as demo:
gr.HTML("""
<div class="app-header">
<h1>πŸ•΅οΈ Deepfake Face Detector</h1>
<p class="subtitle">AI-powered detection using Vision Transformer classification and Masked Autoencoder reconstruction</p>
<div class="badge-row">
<span class="badge badge-purple">🧠 ViT Model</span>
<span class="badge badge-red">⚑ 98.7% Accuracy</span>
<span class="badge badge-blue">πŸ”„ MAE Autoencoder</span>
<span class="badge badge-green">πŸ‘οΈ MTCNN Face Detection</span>
</div>
</div>
""")
with gr.Tabs():
with gr.Tab("πŸ–ΌοΈ Image Detection"):
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=320):
img_in = gr.Image(label="Upload Image", height=320)
img_btn = gr.Button("πŸ” Analyze Image", variant="primary")
with gr.Column(scale=1, min_width=320):
img_verdict = gr.Textbox(label="Detection Result", lines=10,
interactive=False, placeholder="Upload an image and click Analyze…")
with gr.Row():
img_fake_score = gr.Number(label="Fake Probability", precision=4)
img_mse_score = gr.Number(label="Reconstruction MSE", precision=6)
gr.HTML('<hr class="section-divider"/>')
with gr.Row():
img_face = gr.Image(label="πŸ”Ž Detected Face β†’ Model Input", height=260)
img_recon = gr.Image(label="πŸ”„ Reconstructed Face β†’ MAE Output", height=260)
img_chart = gr.Plot(label="πŸ“Š Score Analysis Dashboard")
img_btn.click(fn=predict_image, inputs=[img_in],
outputs=[img_verdict, img_face, img_recon, img_chart, img_fake_score, img_mse_score])
with gr.Tab("🎬 Video Detection"):
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=320):
vid_in = gr.Video(label="Upload Video", height=320)
vid_btn = gr.Button("πŸ” Analyze Video", variant="primary")
with gr.Column(scale=1, min_width=320):
vid_verdict = gr.Textbox(label="Detection Result", lines=10,
interactive=False, placeholder="Upload a video and click Analyze…")
with gr.Row():
vid_fake_score = gr.Number(label="Avg Fake Probability", precision=4)
vid_mse_score = gr.Number(label="Avg Reconstruction MSE", precision=6)
gr.HTML('<hr class="section-divider"/>')
vid_chart = gr.Plot(label="πŸ“ˆ Frame-by-Frame Analysis Dashboard")
vid_btn.click(fn=predict_video, inputs=[vid_in],
outputs=[vid_verdict, vid_chart, vid_fake_score, vid_mse_score])
demo.launch(server_name="0.0.0.0", server_port=7860, share=False)