deepshield-api / backend /detector.py
Venkatkalyan21
Deploy clean backend to Hugging Face
1970a5b
Raw
History Blame Contribute Delete
4.42 kB
"""
DeepfakeDetector β€” orchestrates the full video analysis pipeline
"""
import os, uuid, time
import numpy as np
import torch
import torchvision.transforms as T
from pathlib import Path
from typing import Optional
from model import HybridDeepfakeDetector
from face_extractor import FaceExtractor
from gradcam import GradCAM
# ── Image pre-processing ─────────────────────────────────────────────
MEAN = [0.485, 0.456, 0.406]
STD = [0.229, 0.224, 0.225]
transform = T.Compose([
T.ToTensor(),
T.Normalize(mean=MEAN, std=STD),
])
class DeepfakeDetector:
"""
End-to-end video deepfake detector.
Steps:
1. Sample frames from video
2. Crop face regions
3. Run hybrid model inference
4. Generate Grad-CAM heatmaps
5. Aggregate per-frame scores into video-level verdict
"""
def __init__(
self,
model_path: Optional[str] = None,
device: Optional[str] = None,
max_frames: int = 32,
threshold: float = 0.5,
):
self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
self.threshold = threshold
self.max_frames = max_frames
# Load model
self.model = HybridDeepfakeDetector(pretrained=(model_path is None))
if model_path and Path(model_path).exists():
state = torch.load(model_path, map_location=self.device)
self.model.load_state_dict(state)
print(f"[Detector] Loaded weights from {model_path}")
else:
print("[Detector] Using ImageNet pretrained weights (demo mode). "
"Train on FF++ for research-quality results.")
self.model.to(self.device).eval()
self.extractor = FaceExtractor()
self.gradcam = GradCAM(self.model)
# ── Single frame inference ───────────────────────────────────────
def _infer_frame(self, face_rgb: np.ndarray) -> float:
"""Returns fake probability for one face crop."""
tensor = transform(face_rgb).unsqueeze(0).to(self.device)
with torch.no_grad():
prob = self.model.predict_proba(tensor).item()
return float(prob)
# ── Main analysis ────────────────────────────────────────────────
def analyze(self, video_path: str, session_id: Optional[str] = None) -> dict:
t0 = time.time()
session_id = session_id or str(uuid.uuid4())
results_dir = Path("results") / session_id
results_dir.mkdir(parents=True, exist_ok=True)
# 1. Extract faces
faces = self.extractor.extract(video_path, max_frames=self.max_frames)
if not faces:
return {
"session_id": session_id,
"verdict": "UNKNOWN",
"confidence": 0.0,
"error": "No faces detected in video.",
"frames": [],
}
# 2. Per-frame inference + Grad-CAM
frame_results = []
scores = []
for i, item in enumerate(faces):
face_rgb = item["face"]
fidx = item["frame_idx"]
prob = self._infer_frame(face_rgb)
scores.append(prob)
# Generate and save Grad-CAM heatmap
cam_path = str(results_dir / f"frame_{i:04d}_cam.jpg")
self.gradcam.generate(face_rgb, cam_path)
frame_results.append({
"frame_idx": fidx,
"fake_prob": round(prob, 4),
"verdict": "FAKE" if prob >= self.threshold else "REAL",
"cam_path": f"/results/{session_id}/frame_{i:04d}_cam.jpg",
})
# 3. Video-level score
video_score = float(np.mean(scores))
verdict = "FAKE" if video_score >= self.threshold else "REAL"
elapsed = round(time.time() - t0, 2)
return {
"session_id": session_id,
"verdict": verdict,
"confidence": round(video_score * 100, 2),
"fake_prob": round(video_score, 4),
"frames_analyzed": len(faces),
"elapsed_sec": elapsed,
"frame_scores": frame_results,
}