Spaces:
Sleeping
Sleeping
| # π― Using the Trained Model β Real-Time Deepfake Interview Detection | |
| This guide explains how to export your trained model, run it as a backend API, and connect it to a website or browser extension for **real-time interview video analysis**. | |
| --- | |
| ## π Architecture Overview | |
| ``` | |
| Browser / Extension | |
| β | |
| β JPEG frame (base64) via WebSocket / HTTP POST | |
| βΌ | |
| βββββββββββββββββββββββββββ | |
| β FastAPI Backend β β Loads your .pt model once at startup | |
| β (Python, localhost or β β MTCNN detects face in each frame | |
| β hosted server) β β InceptionResnetV1 classifies: REAL / FAKE | |
| βββββββββββββββββββββββββββ | |
| β | |
| β JSON: { label, confidence, latency_ms } | |
| βΌ | |
| Website UI / Extension Overlay | |
| ``` | |
| --- | |
| ## STEP 1 β Export the Trained Model After Training | |
| Run this in a new notebook cell **after Cell 8 finishes training**: | |
| ```python | |
| # ββ CELL: Export model for deployment ββββββββββββββββββββββββββββββ | |
| import torch | |
| # Load best weights into the model (already built in Cell 4) | |
| model.load_state_dict(torch.load('models/best_model.pt', map_location='cpu')) | |
| model.eval() | |
| # Save as TorchScript (faster inference, no class definition needed at load time) | |
| scripted = torch.jit.script(model) | |
| scripted.save('models/deepfake_model_scripted.pt') | |
| # Also save a plain state dict copy (easier to load in server) | |
| torch.save(model.state_dict(), 'models/deepfake_model_weights.pt') | |
| print("β Exported: models/deepfake_model_scripted.pt") | |
| print("β Exported: models/deepfake_model_weights.pt") | |
| ``` | |
| > **Note:** If TorchScript fails (InceptionResnetV1 has some dynamic ops), use the **plain weights** approach in the server below β it works just as well. | |
| --- | |
| ## STEP 2 β Backend: FastAPI Inference Server | |
| ### 2a. Project structure | |
| ``` | |
| deepfake_main/ | |
| βββ models/ | |
| β βββ best_model.pt β your trained weights | |
| β βββ deepfake_model_weights.pt | |
| βββ server/ | |
| β βββ main.py β FastAPI app (create below) | |
| β βββ requirements_server.txt | |
| βββ Deepfake_Detection.ipynb | |
| ``` | |
| ### 2b. Install server dependencies | |
| ```bash | |
| pip install fastapi uvicorn[standard] python-multipart Pillow facenet-pytorch torch torchvision | |
| ``` | |
| Or create `server/requirements_server.txt`: | |
| ``` | |
| fastapi>=0.110.0 | |
| uvicorn[standard]>=0.29.0 | |
| python-multipart>=0.0.9 | |
| Pillow>=10.2.0 | |
| torch>=2.2.0 | |
| torchvision>=0.17.0 | |
| facenet-pytorch>=2.6.0 | |
| numpy>=1.26.0 | |
| ``` | |
| ### 2c. Create `server/main.py` | |
| ```python | |
| """ | |
| Deepfake Detection API β real-time interview frame analysis | |
| Run with: uvicorn main:app --host 0.0.0.0 --port 8000 | |
| """ | |
| import io, base64, time | |
| import torch | |
| import torch.nn as nn | |
| import numpy as np | |
| from PIL import Image | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from torchvision import transforms | |
| from facenet_pytorch import InceptionResnetV1, fixed_image_standardization, MTCNN | |
| # ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| WEIGHTS_PATH = "../models/best_model.pt" # adjust path if needed | |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| IMG_SIZE = 299 | |
| CONFIDENCE_THRESHOLD = 0.60 # below this β "uncertain" | |
| print(f"[SERVER] Device: {DEVICE}") | |
| # ββ Model definition (must match Cell 4 exactly) ββββββββββββββββββ | |
| class DeepfakeClassifier(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| self.backbone = InceptionResnetV1( | |
| classify=False, pretrained='vggface2' | |
| ).to(DEVICE) | |
| self.head = nn.Sequential( | |
| nn.Linear(512, 256), nn.BatchNorm1d(256), nn.GELU(), nn.Dropout(0.4), | |
| nn.Linear(256, 128), nn.BatchNorm1d(128), nn.GELU(), nn.Dropout(0.3), | |
| nn.Linear(128, 2) | |
| ).to(DEVICE) | |
| def forward(self, x): | |
| return self.head(self.backbone(x)) | |
| # ββ Load model ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| model = DeepfakeClassifier() | |
| model.load_state_dict(torch.load(WEIGHTS_PATH, map_location=DEVICE)) | |
| model.eval() | |
| print(f"[SERVER] Model loaded from {WEIGHTS_PATH}") | |
| # ββ MTCNN face detector βββββββββββββββββββββββββββββββββββββββββββ | |
| mtcnn = MTCNN( | |
| image_size=IMG_SIZE, keep_all=False, min_face_size=40, | |
| device=DEVICE, post_process=False, margin=20 | |
| ) | |
| # ββ Transform (same as eval_transform in Cell 3) βββββββββββββββββ | |
| transform = transforms.Compose([ | |
| transforms.Resize((IMG_SIZE, IMG_SIZE)), | |
| transforms.ToTensor(), | |
| fixed_image_standardization, | |
| ]) | |
| CLASS_NAMES = {0: "FAKE", 1: "REAL"} | |
| # ββ FastAPI app βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app = FastAPI(title="Deepfake Detection API", version="1.0") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # restrict to your domain in production | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| class FrameRequest(BaseModel): | |
| image_b64: str # base64-encoded JPEG/PNG frame from browser | |
| class PredictionResponse(BaseModel): | |
| label: str # "REAL" or "FAKE" | |
| confidence: float # 0.0 β 1.0 | |
| uncertain: bool # True if below threshold | |
| face_detected: bool | |
| latency_ms: float | |
| @app.get("/health") | |
| def health(): | |
| return {"status": "ok", "device": str(DEVICE)} | |
| @app.post("/predict", response_model=PredictionResponse) | |
| def predict(req: FrameRequest): | |
| t0 = time.perf_counter() | |
| # 1. Decode base64 β PIL image | |
| try: | |
| img_bytes = base64.b64decode(req.image_b64) | |
| img = Image.open(io.BytesIO(img_bytes)).convert("RGB") | |
| except Exception as e: | |
| raise HTTPException(status_code=400, detail=f"Bad image: {e}") | |
| # 2. Detect face with MTCNN | |
| face_tensor = mtcnn(img) | |
| if face_tensor is None: | |
| return PredictionResponse( | |
| label="UNKNOWN", confidence=0.0, | |
| uncertain=True, face_detected=False, | |
| latency_ms=round((time.perf_counter()-t0)*1000, 1) | |
| ) | |
| # 3. Prepare tensor | |
| face_pil = Image.fromarray(face_tensor.permute(1,2,0).byte().cpu().numpy()) | |
| inp = transform(face_pil).unsqueeze(0).to(DEVICE) | |
| # 4. Inference | |
| with torch.no_grad(): | |
| logits = model(inp) | |
| probs = torch.softmax(logits, dim=1)[0] | |
| pred = int(torch.argmax(probs).item()) | |
| conf = float(probs[pred].item()) | |
| label = CLASS_NAMES[pred] | |
| latency = round((time.perf_counter() - t0) * 1000, 1) | |
| return PredictionResponse( | |
| label=label, | |
| confidence=round(conf, 3), | |
| uncertain=(conf < CONFIDENCE_THRESHOLD), | |
| face_detected=True, | |
| latency_ms=latency | |
| ) | |
| ``` | |
| ### 2d. Run the server | |
| ```bash | |
| # From the server/ directory: | |
| cd server | |
| uvicorn main:app --host 0.0.0.0 --port 8000 --reload | |
| ``` | |
| Test it's alive: | |
| ``` | |
| http://localhost:8000/health β {"status":"ok","device":"cuda"} | |
| http://localhost:8000/docs β Swagger UI for manual testing | |
| ``` | |
| --- | |
| ## STEP 3 β Frontend: Capture Webcam Frames and Send to API | |
| Add this JavaScript to your website (works with any HTML page or React/Next.js app): | |
| ```html | |
| <!-- index.html β Minimal real-time deepfake detector UI --> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <title>Deepfake Interview Detector</title> | |
| </head> | |
| <body> | |
| <video id="video" autoplay playsinline width="640" height="480"></video> | |
| <canvas id="canvas" width="640" height="480" style="display:none"></canvas> | |
| <div id="result">Waiting...</div> | |
| <script> | |
| const video = document.getElementById('video'); | |
| const canvas = document.getElementById('canvas'); | |
| const result = document.getElementById('result'); | |
| const ctx = canvas.getContext('2d'); | |
| const API = 'http://localhost:8000/predict'; | |
| // Start webcam | |
| navigator.mediaDevices.getUserMedia({ video: true }) | |
| .then(stream => { video.srcObject = stream; }); | |
| // Send a frame every 1 second (adjust as needed) | |
| setInterval(async () => { | |
| ctx.drawImage(video, 0, 0, canvas.width, canvas.height); | |
| const b64 = canvas.toDataURL('image/jpeg', 0.8) | |
| .replace('data:image/jpeg;base64,', ''); | |
| try { | |
| const res = await fetch(API, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ image_b64: b64 }) | |
| }); | |
| const data = await res.json(); | |
| const color = data.label === 'REAL' ? 'green' : 'red'; | |
| result.innerHTML = ` | |
| <span style="color:${color}; font-size:2em; font-weight:bold"> | |
| ${data.label} (${(data.confidence * 100).toFixed(1)}%) | |
| </span> | |
| <br>Face: ${data.face_detected} | Latency: ${data.latency_ms}ms | |
| ${data.uncertain ? '<br>β οΈ Low confidence' : ''} | |
| `; | |
| } catch(e) { | |
| result.textContent = 'API error: ' + e.message; | |
| } | |
| }, 1000); | |
| </script> | |
| </body> | |
| </html> | |
| ``` | |
| --- | |
| ## STEP 4 β Browser Extension (for Google Meet / Zoom etc.) | |
| A Chrome extension can **overlay the prediction** directly on top of a Google Meet or Zoom tab. | |
| ### File structure | |
| ``` | |
| deepfake_extension/ | |
| βββ manifest.json | |
| βββ content.js β injects into Google Meet page | |
| βββ popup.html β extension popup (optional) | |
| ``` | |
| ### `manifest.json` | |
| ```json | |
| { | |
| "manifest_version": 3, | |
| "name": "Deepfake Interview Detector", | |
| "version": "1.0", | |
| "permissions": ["activeTab", "scripting"], | |
| "host_permissions": ["https://meet.google.com/*"], | |
| "content_scripts": [ | |
| { | |
| "matches": ["https://meet.google.com/*"], | |
| "js": ["content.js"] | |
| } | |
| ] | |
| } | |
| ``` | |
| ### `content.js` | |
| ```javascript | |
| // Runs on Google Meet pages β captures the remote video stream | |
| const API = 'http://localhost:8000/predict'; | |
| function findRemoteVideo() { | |
| // Google Meet uses <video> elements; pick the largest (main speaker) | |
| const videos = Array.from(document.querySelectorAll('video')); | |
| return videos.sort((a,b) => (b.videoWidth*b.videoHeight) - (a.videoWidth*a.videoHeight))[0]; | |
| } | |
| function createOverlay() { | |
| const el = document.createElement('div'); | |
| el.id = 'deepfake-overlay'; | |
| el.style.cssText = ` | |
| position:fixed; top:12px; right:12px; z-index:99999; | |
| background:rgba(0,0,0,0.75); color:white; | |
| padding:8px 16px; border-radius:8px; font-size:16px; | |
| font-family:monospace; pointer-events:none; | |
| `; | |
| el.textContent = 'π Deepfake Detector Loading...'; | |
| document.body.appendChild(el); | |
| return el; | |
| } | |
| async function analyzeFrame(video, overlay) { | |
| if (!video || video.readyState < 2) return; | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = video.videoWidth || 640; | |
| canvas.height = video.videoHeight || 480; | |
| canvas.getContext('2d').drawImage(video, 0, 0); | |
| const b64 = canvas.toDataURL('image/jpeg', 0.7).split(',')[1]; | |
| try { | |
| const res = await fetch(API, { | |
| method:'POST', | |
| headers:{'Content-Type':'application/json'}, | |
| body: JSON.stringify({image_b64: b64}) | |
| }); | |
| const d = await res.json(); | |
| const emoji = d.label === 'REAL' ? 'β ' : 'π¨'; | |
| const color = d.label === 'REAL' ? '#00ff88' : '#ff4444'; | |
| overlay.style.borderLeft = `4px solid ${color}`; | |
| overlay.innerHTML = `${emoji} <b style="color:${color}">${d.label}</b> ${(d.confidence*100).toFixed(0)}%` | |
| + (d.uncertain ? ' β οΈ' : '') | |
| + `<br><small>${d.latency_ms}ms</small>`; | |
| } catch (e) { | |
| overlay.textContent = 'β API offline'; | |
| } | |
| } | |
| // Wait for Meet to load, then start | |
| setTimeout(() => { | |
| const overlay = createOverlay(); | |
| setInterval(() => { | |
| const video = findRemoteVideo(); | |
| analyzeFrame(video, overlay); | |
| }, 1500); // analyze every 1.5 seconds | |
| }, 4000); | |
| ``` | |
| ### Load the extension in Chrome | |
| 1. Go to `chrome://extensions` | |
| 2. Enable **Developer mode** (top right) | |
| 3. Click **"Load unpacked"** β select the `deepfake_extension/` folder | |
| 4. Open Google Meet β the overlay will appear in the top-right corner | |
| --- | |
| ## STEP 5 β Deployment Options | |
| | Mode | How | When to Use | | |
| |---|---|---| | |
| | **Local** | `uvicorn main:app --port 8000` | Development & demos | | |
| | **Docker** | See Dockerfile below | Portable, shareable | | |
| | **Cloud (Render/Railway)** | Push to GitHub β connect to Render | Hosting for others | | |
| | **ngrok tunnel** | `ngrok http 8000` β share public URL | Quick demo without hosting | | |
| ### Dockerfile (optional) | |
| ```dockerfile | |
| FROM python:3.11-slim | |
| WORKDIR /app | |
| COPY server/requirements_server.txt . | |
| RUN pip install --no-cache-dir -r requirements_server.txt | |
| COPY server/main.py . | |
| COPY models/best_model.pt ./models/best_model.pt | |
| EXPOSE 8000 | |
| CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] | |
| ``` | |
| ```bash | |
| docker build -t deepfake-api . | |
| docker run -p 8000:8000 --gpus all deepfake-api | |
| ``` | |
| --- | |
| ## STEP 6 β Performance Tuning Tips | |
| | Tip | Impact | | |
| |---|---| | |
| | Analyze every **1β2 seconds** (not every frame) | Reduces API load, GPU stays cool | | |
| | Send frames at **480p max** (not 1080p) | ~4Γ smaller payload, faster inference | | |
| | Use `torch.compile(model)` (PyTorch 2.x) | 10-30% faster inference | | |
| | Run MTCNN on CPU, model on GPU | Frees GPU VRAM for model | | |
| | Add a **moving average** over last 5 predictions | Smoother, less flickery output | | |
| ### Moving average example (add to `content.js` or `index.html`) | |
| ```javascript | |
| const history = []; | |
| function smoothPrediction(label, conf) { | |
| history.push({ label, conf }); | |
| if (history.length > 5) history.shift(); | |
| const fakeCount = history.filter(h => h.label === 'FAKE').length; | |
| return fakeCount >= 3 ? 'FAKE' : 'REAL'; // majority vote over last 5 | |
| } | |
| ``` | |
| --- | |
| ## Quick Start Checklist | |
| ``` | |
| [ ] Training complete β best_model.pt exists in models/ | |
| [ ] pip install fastapi uvicorn[standard] facenet-pytorch ... | |
| [ ] Create server/main.py (copy code from Step 2c) | |
| [ ] Run: uvicorn main:app --port 8000 | |
| [ ] Check: http://localhost:8000/health | |
| [ ] Open index.html in browser β grant webcam permission | |
| [ ] See REAL / FAKE label update every second | |
| [ ] (Optional) Load Chrome extension for Google Meet detection | |
| ``` | |
| C:\Users\SHINJAN\anaconda3\envs\deepfake_gpu\python.exe -m uvicorn main:app --host 0.0.0.0 --port 8000 | |
| uvicorn main:app --host 0.0.0.0 --port 8000 | |
| --- | |
| *Model trained on DFD video dataset + InceptionResnetV1 (VGGFace2) backbone.* | |
| *RTX 4050 GPU inference: ~15β30ms per frame including face detection.* | |