import os, io, tempfile, warnings import numpy as np import gradio as gr # ========================= # TensorFlow / Keras Setup # ========================= import tensorflow as tf from tensorflow.keras.applications.resnet50 import preprocess_input as resnet_preprocess # Limit TensorFlow thread usage (prevents OOM) os.environ["OMP_NUM_THREADS"] = "2" os.environ["MKL_NUM_THREADS"] = "2" os.environ["OPENBLAS_NUM_THREADS"] = "2" os.environ["NUMEXPR_NUM_THREADS"] = "2" os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" # Try to enable GPU safely for TensorFlow try: gpus = tf.config.experimental.list_physical_devices("GPU") if gpus: for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) tf.keras.mixed_precision.set_global_policy("mixed_float16") print("[INFO] ✅ TensorFlow GPU detected & configured (mixed precision ON).") else: print("[WARN] ⚠️ No TensorFlow GPU detected, running on CPU.") except Exception as e: print(f"[WARN] TensorFlow GPU init skipped: {e}") # ========================= # PyTorch / MTCNN Setup # ========================= import torch from facenet_pytorch import MTCNN import cv2 import dlib from imutils import face_utils from scipy.spatial import distance as dist warnings.filterwarnings("ignore") # Use GPU for MTCNN if available if torch.cuda.is_available(): _torch_device = torch.device("cuda") torch.backends.cudnn.benchmark = True try: torch.set_float32_matmul_precision("medium") except Exception: pass print("[INFO] ✅ PyTorch GPU detected — MTCNN will use CUDA.") else: _torch_device = torch.device("cpu") print("[WARN] ⚠️ No GPU detected — MTCNN running on CPU.") # ========================= # Paths / Config # ========================= VIDEO_MODEL_PATH = "models/video_model.h5" DLIB_LANDMARK_MODEL = "shape_predictor_68_face_landmarks.dat" IMG_SIZE = (224, 224) FRAME_STEP = 5 NUM_MAX_FACES = 300 EAR_THRESHOLD = 0.25 EAR_CONSEC_FRAMES = 3 PRED_THRESHOLD = 0.5 # Lazy-loaded globals _video_model = None _mtcnn = None _dlib_detector = None _dlib_predictor = None def lazy_load(): global _video_model, _mtcnn, _dlib_detector, _dlib_predictor if _video_model is None: if not os.path.exists(VIDEO_MODEL_PATH): raise FileNotFoundError(f"Missing: {VIDEO_MODEL_PATH}") # Allow tf to place on GPU if available _video_model = tf.keras.models.load_model(VIDEO_MODEL_PATH) print("[INFO] Video model loaded.") if _mtcnn is None: _mtcnn = MTCNN(keep_all=True, device=_torch_device, image_size=IMG_SIZE[0]) print(f"[INFO] MTCNN ready on {_torch_device}.") if _dlib_detector is None or _dlib_predictor is None: if not os.path.exists(DLIB_LANDMARK_MODEL): raise FileNotFoundError( f"Missing dlib predictor: {DLIB_LANDMARK_MODEL}. Place it beside app.py." ) _dlib_detector = dlib.get_frontal_face_detector() _dlib_predictor = dlib.shape_predictor(DLIB_LANDMARK_MODEL) print("[INFO] dlib detector + predictor ready.") # ========================= # VIDEO: faces + blink features # ========================= def _eye_aspect_ratio(eye_pts): A = dist.euclidean(eye_pts[1], eye_pts[5]) B = dist.euclidean(eye_pts[2], eye_pts[4]) C = dist.euclidean(eye_pts[0], eye_pts[3]) return (A + B) / (2.0 * C + 1e-9) def extract_faces_all(path): """Sample every 5th frame, keep ALL faces per frame, resize to 224x224.""" cap = cv2.VideoCapture(path) total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) if total <= 0: cap.release() return None faces = [] for idx in range(0, total, FRAME_STEP): if len(faces) >= NUM_MAX_FACES: break cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ok, frame = cap.read() if not ok: break rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes, _ = _mtcnn.detect(rgb) if boxes is None: continue for (x1, y1, x2, y2) in boxes.astype(int): x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(frame.shape[1], x2), min(frame.shape[0], y2) crop = frame[y1:y2, x1:x2] if crop.size > 0: faces.append(cv2.resize(crop, IMG_SIZE)) if len(faces) >= NUM_MAX_FACES: break cap.release() if not faces: return None return np.stack(faces, axis=0).astype(np.uint8) def blink_features_from_crops(faces): """Compute EAR on cropped faces; denominator = #frames with valid EAR.""" (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"] (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"] ear_values = [] blink_counter = 0 total_blinks = 0 for face in faces: gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY) rects = _dlib_detector(gray, 0) if len(rects) == 0: continue shape = _dlib_predictor(gray, rects[0]) shape = face_utils.shape_to_np(shape) left_eye = shape[lStart:lEnd] right_eye = shape[rStart:rEnd] ear = 0.5 * (_eye_aspect_ratio(left_eye) + _eye_aspect_ratio(right_eye)) ear_values.append(ear) if ear < EAR_THRESHOLD: blink_counter += 1 else: if blink_counter >= EAR_CONSEC_FRAMES: total_blinks += 1 blink_counter = 0 n_ear = len(ear_values) if n_ear == 0: return np.array([0, 0.0, 0.0], dtype=np.float32) blink_freq = total_blinks / float(n_ear) ear_var = float(np.var(np.array(ear_values, dtype=np.float32))) return np.array([total_blinks, blink_freq, ear_var], dtype=np.float32) def predict_video_prob(video_path): faces = extract_faces_all(video_path) if faces is None or len(faces) == 0: return None, "No faces detected." blink_feats = blink_features_from_crops(faces) # Tile to per-face samples tiled = np.tile(blink_feats, (faces.shape[0], 1)).astype(np.float32) imgs = resnet_preprocess(faces.astype(np.float32)) # Batch size heuristic (bigger if GPU is present) has_tf_gpu = len(tf.config.experimental.list_physical_devices("GPU")) > 0 bs = 128 if has_tf_gpu else 32 preds = [] for i in range(0, imgs.shape[0], bs): p = _video_model.predict([imgs[i:i+bs], tiled[i:i+bs]], verbose=0) preds.append(p.reshape(-1)) return float(np.mean(np.concatenate(preds, axis=0))), None def to_verdict(score): return "DEEPFAKE" if score >= PRED_THRESHOLD else "REAL" # ========================= # Inference entry # ========================= def run_inference(video_file): lazy_load() if video_file is None: return None, None, "Please upload a video file." video_prob, vmsg = predict_video_prob(video_file) if video_prob is None: return None, None, vmsg or "Unable to process the video." verdict = f"VIDEO ONLY: {to_verdict(video_prob)}" fmt = lambda x: None if x is None else round(float(x), 4) return fmt(video_prob), verdict, None # ========================= # Gradio UI # ========================= with gr.Blocks(title="Deepfake Detector — Video Only (GPU-ready)") as demo: gr.Markdown( "### 🎭 Deepfake Detector — **Video Only**\n" "- **Compute**: Uses GPU automatically if available (PyTorch MTCNN, TensorFlow model; mixed precision on TF).\n" "- **Video path**: samples every 5th frame, keeps **all** faces, resizes to 224×224.\n" "- **Features**: ResNet50 preprocessing + blink EAR features on cropped faces via dlib.\n" f"- **Threshold**: {PRED_THRESHOLD} (≥ means DEEPFAKE).\n" ) with gr.Row(): video_in = gr.Video(label="Video") go = gr.Button("Analyze") with gr.Row(): v_out = gr.Number(label="Video probability (deepfake)", precision=4) verdict_out = gr.Textbox(label="Verdict", interactive=False) msg_out = gr.Textbox(label="Message / Warnings", interactive=False) go.click(run_inference, inputs=[video_in], outputs=[v_out, verdict_out, msg_out]) if __name__ == "__main__": lazy_load() demo.launch(server_name="0.0.0.0", server_port=7860)