| |
| |
| |
| |
| import gradio as gr |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from torchvision import transforms, models |
| from PIL import Image, ImageFilter |
| import numpy as np |
|
|
| |
| try: |
| import spaces |
| HAS_ZEROGPU = True |
| print("ZeroGPU detected β GPU mode enabled") |
| except ImportError: |
| HAS_ZEROGPU = False |
| print("No ZeroGPU β running on CPU") |
|
|
| DEVICE = "cpu" |
|
|
| |
| |
| |
| |
| |
| class DeepfakeDetector(nn.Module): |
| def __init__(self): |
| super().__init__() |
| self.backbone = models.efficientnet_b0(weights="IMAGENET1K_V1") |
| n = self.backbone.classifier[1].in_features |
| self.backbone.classifier = nn.Sequential( |
| nn.Dropout(0.3), |
| nn.Linear(n, 256), |
| nn.ReLU(), |
| nn.Dropout(0.2), |
| nn.Linear(256, 1), |
| ) |
|
|
| def forward(self, x): |
| return self.backbone(x) |
|
|
|
|
| print("Loading model...") |
| model = DeepfakeDetector() |
| model.eval() |
| print(f"Model loaded ({sum(p.numel() for p in model.parameters()):,} params)") |
|
|
| |
| |
| |
| preprocess = transforms.Compose([ |
| transforms.Resize((224, 224)), |
| transforms.ToTensor(), |
| transforms.Normalize([0.485, 0.456, 0.406], |
| [0.229, 0.224, 0.225]), |
| ]) |
|
|
|
|
| |
| |
| |
| def frequency_analysis(img: Image.Image): |
| """FFT-based frequency domain analysis.""" |
| gray = np.array(img.convert("L"), dtype=np.float32) |
| f = np.fft.fftshift(np.fft.fft2(gray)) |
| magnitude = np.log1p(np.abs(f)) |
|
|
| |
| mag_norm = (magnitude - magnitude.min()) / (magnitude.max() - magnitude.min() + 1e-8) |
| freq_img = Image.fromarray((mag_norm * 255).astype(np.uint8)) |
|
|
| |
| h, w = magnitude.shape |
| center = magnitude[h // 4:3 * h // 4, w // 4:3 * w // 4] |
| outer = np.concatenate([ |
| magnitude[:h // 4].flatten(), |
| magnitude[3 * h // 4:].flatten(), |
| magnitude[:, :w // 4].flatten(), |
| magnitude[:, 3 * w // 4:].flatten(), |
| ]) |
| ratio = float(np.mean(outer) / (np.mean(center) + 1e-8)) |
| spectral_std = float(np.std(magnitude)) |
|
|
| return freq_img, ratio, spectral_std |
|
|
|
|
| def edge_analysis(img: Image.Image): |
| """Edge density analysis β AI images often have smoother edges.""" |
| gray = np.array(img.convert("L"), dtype=np.float32) |
| |
| gx = np.abs(gray[:, 1:] - gray[:, :-1]) |
| gy = np.abs(gray[1:, :] - gray[:-1, :]) |
| edge_density = float((np.mean(gx) + np.mean(gy)) / 2) |
| return edge_density |
|
|
|
|
| def noise_analysis(img: Image.Image): |
| """ |
| Noise residual analysis. |
| Real photos have sensor noise; AI images have different patterns. |
| """ |
| arr = np.array(img.convert("RGB"), dtype=np.float32) |
| |
| denoised = np.array(img.filter(ImageFilter.MedianFilter(3)), dtype=np.float32) |
| noise = arr - denoised |
| noise_level = float(np.std(noise)) |
| |
| h, w = noise.shape[:2] |
| quadrants = [ |
| noise[:h // 2, :w // 2], |
| noise[:h // 2, w // 2:], |
| noise[h // 2:, :w // 2], |
| noise[h // 2:, w // 2:], |
| ] |
| q_stds = [float(np.std(q)) for q in quadrants] |
| uniformity = 1.0 - (max(q_stds) - min(q_stds)) / (max(q_stds) + 1e-8) |
| return noise_level, uniformity |
|
|
|
|
| |
| |
| |
| def _detect(image_array): |
| """Core detection logic β called with or without GPU.""" |
| if image_array is None: |
| return "β οΈ Please upload an image to analyze.", None |
|
|
| img = Image.fromarray(image_array).convert("RGB") |
|
|
| |
| tensor = preprocess(img).unsqueeze(0) |
| if torch.cuda.is_available(): |
| tensor = tensor.cuda() |
| model.cuda() |
|
|
| with torch.no_grad(): |
| logit = model(tensor) |
| cnn_score = torch.sigmoid(logit).item() |
|
|
| if torch.cuda.is_available(): |
| model.cpu() |
|
|
| |
| freq_img, freq_ratio, spectral_std = frequency_analysis(img) |
|
|
| |
| edge_density = edge_analysis(img) |
|
|
| |
| noise_level, noise_uniformity = noise_analysis(img) |
|
|
| |
| |
| signals = { |
| "cnn": cnn_score, |
| "freq_anomaly": min(max((freq_ratio - 0.15) * 3, 0), 1), |
| "edge_smooth": min(max(1.0 - (edge_density / 30), 0), 1), |
| "noise_uniform": noise_uniformity, |
| } |
|
|
| |
| weights = {"cnn": 0.50, "freq_anomaly": 0.20, "edge_smooth": 0.15, "noise_uniform": 0.15} |
| final_score = sum(signals[k] * weights[k] for k in weights) |
| final_score = min(max(final_score, 0), 1) |
|
|
| fake_pct = final_score * 100 |
| real_pct = (1 - final_score) * 100 |
|
|
| if final_score > 0.65: |
| verdict = f"β οΈ LIKELY AI-GENERATED" |
| emoji = "π΄" |
| elif final_score > 0.40: |
| verdict = f"β‘ INCONCLUSIVE" |
| emoji = "π‘" |
| else: |
| verdict = f"β
LIKELY AUTHENTIC" |
| emoji = "π’" |
|
|
| analysis = f"""## {emoji} {verdict} |
| |
| **Composite Score: {fake_pct:.1f}% fake / {real_pct:.1f}% real** |
| |
| --- |
| |
| ### Signal Breakdown |
| |
| | Signal | Score | Weight | Contribution | |
| |--------|-------|--------|-------------| |
| | π§ CNN Spatial | {signals['cnn']:.3f} | 50% | {signals['cnn'] * weights['cnn']:.3f} | |
| | π Frequency Anomaly | {signals['freq_anomaly']:.3f} | 20% | {signals['freq_anomaly'] * weights['freq_anomaly']:.3f} | |
| | βοΈ Edge Smoothness | {signals['edge_smooth']:.3f} | 15% | {signals['edge_smooth'] * weights['edge_smooth']:.3f} | |
| | π¬ Noise Uniformity | {signals['noise_uniform']:.3f} | 15% | {signals['noise_uniform'] * weights['noise_uniform']:.3f} | |
| |
| ### Detailed Metrics |
| |
| | Metric | Value | Interpretation | |
| |--------|-------|---------------| |
| | Frequency Ratio | {freq_ratio:.4f} | {'Unusual' if freq_ratio > 0.25 else 'Normal'} | |
| | Spectral Std | {spectral_std:.2f} | {'Low variance (suspicious)' if spectral_std < 1.5 else 'Natural'} | |
| | Edge Density | {edge_density:.2f} | {'Smooth (AI-like)' if edge_density < 15 else 'Textured (photo-like)'} | |
| | Noise Level | {noise_level:.2f} | {'Low (suspicious)' if noise_level < 3 else 'Natural sensor noise'} | |
| | Noise Uniformity | {noise_uniformity:.3f} | {'Uniform (AI-like)' if noise_uniformity > 0.85 else 'Varied (natural)'} | |
| |
| --- |
| |
| *TruthLens v1.0 β Multi-signal analysis (CNN + frequency + edge + noise)* |
| *[API Access β](https://rapidapi.com) Β· Built by [AgenticEdge.in](https://agenticedge.in)* |
| """ |
| return analysis, freq_img |
|
|
|
|
| |
| if HAS_ZEROGPU: |
| @spaces.GPU(duration=20) |
| def detect_deepfake(image): |
| return _detect(image) |
| else: |
| def detect_deepfake(image): |
| return _detect(image) |
|
|
|
|
| |
| |
| |
| CUSTOM_CSS = """ |
| .gradio-container { max-width: 920px !important; margin: auto; } |
| .gr-button-primary { |
| background: linear-gradient(135deg, #7c3aed, #2563eb) !important; |
| font-size: 1.1em !important; |
| } |
| footer { display: none !important; } |
| """ |
|
|
| with gr.Blocks( |
| title="π TruthLens β Deepfake Detector", |
| theme=gr.themes.Soft(primary_hue="violet", neutral_hue="slate"), |
| css=CUSTOM_CSS, |
| ) as demo: |
| gr.Markdown(""" |
| # π TruthLens β Is This Image Real or AI-Generated? |
| |
| Multi-signal deepfake detection combining **deep learning** (EfficientNet CNN), |
| **frequency domain analysis** (FFT), **edge density**, and **noise pattern** analysis. |
| |
| Upload any image to get an instant analysis. **Free.** No signup needed. |
| |
| > π **Developer?** Use the API tab below to integrate into your app β |
| > or get a production API key at [RapidAPI β](https://rapidapi.com) |
| """) |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| input_image = gr.Image( |
| label="Upload an image to analyze", |
| type="numpy", |
| height=380, |
| ) |
| with gr.Row(): |
| detect_btn = gr.Button( |
| "π Analyze Image", |
| variant="primary", |
| size="lg", |
| scale=2, |
| ) |
| clear_btn = gr.ClearButton( |
| [input_image], |
| value="Clear", |
| size="lg", |
| scale=1, |
| ) |
|
|
| with gr.Column(scale=1): |
| result_text = gr.Markdown( |
| label="Analysis Result", |
| value="*Upload an image and click Analyze to begin.*", |
| ) |
| freq_image = gr.Image( |
| label="π Frequency Spectrum (FFT)", |
| height=220, |
| type="pil", |
| ) |
|
|
| detect_btn.click( |
| fn=detect_deepfake, |
| inputs=[input_image], |
| outputs=[result_text, freq_image], |
| ) |
|
|
| with gr.Accordion("βΉοΈ How it works", open=False): |
| gr.Markdown(""" |
| **4 signals are analyzed and fused:** |
| |
| 1. **CNN Spatial Analysis** β EfficientNet trained to spot pixel-level artifacts |
| that human eyes miss (blending boundaries, texture inconsistencies) |
| 2. **Frequency Domain (FFT)** β AI-generated images leave signatures in the |
| frequency spectrum: grid artifacts, unusual symmetry, sharp cutoffs |
| 3. **Edge Density** β Real photos have complex, varied edges from natural scenes. |
| AI images tend to be smoother with less micro-texture |
| 4. **Noise Pattern Analysis** β Camera sensors produce characteristic noise. |
| AI generators produce different (often more uniform) noise patterns |
| |
| Each signal is weighted and combined into a composite score. |
| """) |
|
|
| gr.Markdown(""" |
| --- |
| **Built by** [@AnubhavBharadwaaj](https://github.com/AnubhavBharadwaaj) |
| | Powered by ImageCLEF research |
| | [AgenticEdge.in](https://agenticedge.in) |
| | [GitHub](https://github.com/AnubhavBharadwaaj) |
| """) |
|
|
| demo.launch() |
|
|