| import gradio as gr |
| import numpy as np |
| import tensorflow as tf |
| import cv2 |
| import os |
| import dlib |
| from imutils import face_utils |
| from scipy.spatial import distance as dist |
|
|
| |
| |
| |
| MODEL_PATH = "./models/deepfake_detector_multi_input.h5" |
| IMG_SIZE = (224, 224) |
| NUM_FRAMES = 20 |
| DLIB_MODEL = "shape_predictor_68_face_landmarks.dat" |
| CASCADE_PATH = cv2.data.haarcascades + "haarcascade_frontalface_default.xml" |
| EAR_THRESHOLD = 0.25 |
| EAR_CONSEC_FRAMES = 3 |
|
|
| |
| |
| |
| if not os.path.exists(DLIB_MODEL): |
| raise FileNotFoundError( |
| "Missing 'shape_predictor_68_face_landmarks.dat'. " |
| "Upload it to your Space root directory." |
| ) |
|
|
| |
| |
| |
| print("[INFO] Loading deepfake detection model...") |
| model = tf.keras.models.load_model(MODEL_PATH) |
| print("[INFO] Model loaded successfully!") |
|
|
| |
| gpus = tf.config.experimental.list_physical_devices('GPU') |
| if gpus: |
| for g in gpus: |
| tf.config.experimental.set_memory_growth(g, True) |
|
|
| |
| face_cascade = cv2.CascadeClassifier(CASCADE_PATH) |
| dlib_detector = dlib.get_frontal_face_detector() |
| dlib_predictor = dlib.shape_predictor(DLIB_MODEL) |
|
|
| |
| |
| |
| def eye_aspect_ratio(eye): |
| A = dist.euclidean(eye[1], eye[5]) |
| B = dist.euclidean(eye[2], eye[4]) |
| C = dist.euclidean(eye[0], eye[3]) |
| return (A + B) / (2.0 * C) |
|
|
|
|
| def extract_blink_features(video_path): |
| """Extract blink count, blink frequency, and EAR variance.""" |
| cap = cv2.VideoCapture(video_path) |
| (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"] |
| (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"] |
|
|
| ear_values, blink_count, closed = [], 0, 0 |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) |
|
|
| for _ in range(total_frames): |
| ret, frame = cap.read() |
| if not ret: |
| break |
| gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) |
| rects = dlib_detector(gray, 0) |
| if len(rects) > 0: |
| shape = dlib_predictor(gray, rects[0]) |
| shape = face_utils.shape_to_np(shape) |
| leftEye, rightEye = shape[lStart:lEnd], shape[rStart:rEnd] |
| ear = (eye_aspect_ratio(leftEye) + eye_aspect_ratio(rightEye)) / 2.0 |
| ear_values.append(ear) |
|
|
| if ear < EAR_THRESHOLD: |
| closed += 1 |
| else: |
| if closed >= EAR_CONSEC_FRAMES: |
| blink_count += 1 |
| closed = 0 |
|
|
| cap.release() |
| if not ear_values: |
| return np.zeros((1, 3), dtype=np.float32) |
|
|
| blink_freq = blink_count / max(len(ear_values), 1) |
| ear_var = np.var(ear_values) |
| return np.array([[blink_count, blink_freq, ear_var]], dtype=np.float32) |
|
|
|
|
| def extract_faces(video_path, num_frames=NUM_FRAMES, size=IMG_SIZE): |
| """Extract faces (or fallback to full frame) from the video.""" |
| cap = cv2.VideoCapture(video_path) |
| total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) |
| step = max(1, total // num_frames) |
| frames = [] |
|
|
| count = 0 |
| while cap.isOpened() and len(frames) < num_frames: |
| ret, frame = cap.read() |
| if not ret: |
| break |
| if count % step == 0: |
| gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) |
| faces = face_cascade.detectMultiScale(gray, 1.3, 5) |
| if len(faces) > 0: |
| x, y, w, h = sorted(faces, key=lambda b: b[2]*b[3], reverse=True)[0] |
| face = frame[y:y+h, x:x+w] |
| else: |
| face = frame |
| face = cv2.resize(face, size) |
| frames.append(face / 255.0) |
| count += 1 |
| cap.release() |
|
|
| if not frames: |
| raise ValueError("No frames extracted.") |
| return np.array(frames) |
|
|
| |
| |
| |
| def predict(video): |
| if not video: |
| return "⚠️ Please upload a video." |
|
|
| try: |
| faces = extract_faces(video) |
| blink = extract_blink_features(video) |
| blink_features = np.tile(blink, (faces.shape[0], 1)) |
|
|
| preds = model.predict([faces, blink_features], verbose=0) |
| score = float(np.mean(preds)) |
| label = "🧠 FAKE" if score > 0.5 else "✅ REAL" |
|
|
| blink_info = f"👁️ Blinks: {int(blink[0,0])}, Freq: {blink[0,1]:.3f}, EAR Var: {blink[0,2]:.4f}" |
| return f"{blink_info}\n\n**Prediction:** {label}\nConfidence: {score:.2f}" |
|
|
| except Exception as e: |
| return f"❌ Error processing video: {e}" |
|
|
| |
| |
| |
| demo = gr.Interface( |
| fn=predict, |
| inputs=gr.Video(label="🎥 Upload a short video (≤ 20 s)"), |
| outputs=gr.Markdown(), |
| title="Multimodal Deepfake Detection Demo", |
| description=( |
| "Uploads a video, detects faces, analyzes eye-blink patterns " |
| "using dlib landmarks, and combines both cues to classify " |
| "REAL vs FAKE with your trained deepfake detector." |
| ), |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|