DegenGamer1702 commited on
Commit
fd31d3e
·
verified ·
1 Parent(s): 59bfff1

Upload 9 files

Browse files
.gitattributes CHANGED
@@ -35,3 +35,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  Docker/models/audio_model.keras filter=lfs diff=lfs merge=lfs -text
37
  Docker/shape_predictor_68_face_landmarks.dat filter=lfs diff=lfs merge=lfs -text
 
 
 
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  Docker/models/audio_model.keras filter=lfs diff=lfs merge=lfs -text
37
  Docker/shape_predictor_68_face_landmarks.dat filter=lfs diff=lfs merge=lfs -text
38
+ models/audio_model.keras filter=lfs diff=lfs merge=lfs -text
39
+ shape_predictor_68_face_landmarks.dat filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================================
2
+ # GPU-ENABLED SELF-CONTAINED IMAGE FOR DEEPFAKE VIDEO DETECTOR
3
+ # ============================================================
4
+ FROM pytorch/pytorch:2.2.2-cuda12.1-cudnn8-runtime
5
+
6
+ ENV DEBIAN_FRONTEND=noninteractive \
7
+ PYTHONDONTWRITEBYTECODE=1 \
8
+ PYTHONUNBUFFERED=1
9
+
10
+ # Force reinstall Gradio 4.44.1 every time (avoids cache)
11
+ ARG GRADIO_FORCE_REINSTALL=1
12
+ RUN pip install --no-cache-dir --upgrade gradio==4.44.1
13
+
14
+
15
+ # System deps for OpenCV + dlib
16
+ RUN apt-get update && apt-get install -y --no-install-recommends \
17
+ build-essential cmake \
18
+ libgl1 libglib2.0-0 libsm6 libxext6 libxrender1 \
19
+ && rm -rf /var/lib/apt/lists/*
20
+
21
+ WORKDIR /app
22
+
23
+ # Copy app + requirements
24
+ COPY requirements.txt /app/requirements.txt
25
+ COPY app.py /app/app.py
26
+
27
+ # ✅ Copy your model + landmark file directly into the image
28
+ COPY models /app/models
29
+ COPY shape_predictor_68_face_landmarks.dat /app/
30
+
31
+ # Install TensorFlow (GPU-capable) + requirements
32
+ RUN pip install --no-cache-dir tensorflow==2.18.0
33
+ RUN pip install --no-cache-dir -r requirements.txt
34
+
35
+ EXPOSE 7860
36
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, io, tempfile, warnings
2
+ import numpy as np
3
+ import gradio as gr
4
+
5
+ # =========================
6
+ # TensorFlow / Keras
7
+ # =========================
8
+ import tensorflow as tf
9
+ from tensorflow.keras.applications.resnet50 import preprocess_input as resnet_preprocess
10
+
11
+ # GPU config for TensorFlow (enable if available)
12
+ gpus = tf.config.experimental.list_physical_devices("GPU")
13
+ if gpus:
14
+ try:
15
+ for g in gpus:
16
+ tf.config.experimental.set_memory_growth(g, True)
17
+ # Mixed precision can speed up on modern GPUs
18
+ try:
19
+ tf.keras.mixed_precision.set_global_policy("mixed_float16")
20
+ print("[INFO] TF mixed precision enabled.")
21
+ except Exception as e:
22
+ print(f"[WARN] Could not enable mixed precision: {e}")
23
+ except Exception as e:
24
+ print(f"[WARN] Could not set memory growth: {e}")
25
+
26
+ # =========================
27
+ # Vision stack
28
+ # =========================
29
+ import cv2
30
+ import torch
31
+ from facenet_pytorch import MTCNN
32
+ import dlib
33
+ from imutils import face_utils
34
+ from scipy.spatial import distance as dist
35
+
36
+ warnings.filterwarnings("ignore")
37
+
38
+ # =========================
39
+ # Paths / Config
40
+ # =========================
41
+ VIDEO_MODEL_PATH = "models/video_model.h5"
42
+ DLIB_LANDMARK_MODEL = "shape_predictor_68_face_landmarks.dat"
43
+
44
+ IMG_SIZE = (224, 224)
45
+ FRAME_STEP = 5 # every 5th frame
46
+ NUM_MAX_FACES = 300 # cap on faces collected
47
+
48
+ # Blink (EAR) features
49
+ EAR_THRESHOLD = 0.25
50
+ EAR_CONSEC_FRAMES = 3
51
+
52
+ # Prediction threshold
53
+ PRED_THRESHOLD = 0.5
54
+
55
+ # =========================
56
+ # Lazy-loaded state
57
+ # =========================
58
+ _video_model = None
59
+ _mtcnn = None
60
+ _dlib_detector = None
61
+ _dlib_predictor = None
62
+
63
+ # Torch / MTCNN device selection
64
+ _torch_device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
65
+ if torch.cuda.is_available():
66
+ torch.backends.cudnn.benchmark = True
67
+ try:
68
+ torch.set_float32_matmul_precision("medium")
69
+ except Exception:
70
+ pass
71
+ print(f"[INFO] PyTorch device: {_torch_device}")
72
+
73
+ def lazy_load():
74
+ global _video_model, _mtcnn, _dlib_detector, _dlib_predictor
75
+
76
+ if _video_model is None:
77
+ if not os.path.exists(VIDEO_MODEL_PATH):
78
+ raise FileNotFoundError(f"Missing: {VIDEO_MODEL_PATH}")
79
+ # Allow tf to place on GPU if available
80
+ _video_model = tf.keras.models.load_model(VIDEO_MODEL_PATH)
81
+ print("[INFO] Video model loaded.")
82
+
83
+ if _mtcnn is None:
84
+ _mtcnn = MTCNN(keep_all=True, device=_torch_device, image_size=IMG_SIZE[0])
85
+ print(f"[INFO] MTCNN ready on {_torch_device}.")
86
+
87
+ if _dlib_detector is None or _dlib_predictor is None:
88
+ if not os.path.exists(DLIB_LANDMARK_MODEL):
89
+ raise FileNotFoundError(
90
+ f"Missing dlib predictor: {DLIB_LANDMARK_MODEL}. Place it beside app.py."
91
+ )
92
+ _dlib_detector = dlib.get_frontal_face_detector()
93
+ _dlib_predictor = dlib.shape_predictor(DLIB_LANDMARK_MODEL)
94
+ print("[INFO] dlib detector + predictor ready.")
95
+
96
+ # =========================
97
+ # VIDEO: faces + blink features
98
+ # =========================
99
+ def _eye_aspect_ratio(eye_pts):
100
+ A = dist.euclidean(eye_pts[1], eye_pts[5])
101
+ B = dist.euclidean(eye_pts[2], eye_pts[4])
102
+ C = dist.euclidean(eye_pts[0], eye_pts[3])
103
+ return (A + B) / (2.0 * C + 1e-9)
104
+
105
+ def extract_faces_all(path):
106
+ """Sample every 5th frame, keep ALL faces per frame, resize to 224x224."""
107
+ cap = cv2.VideoCapture(path)
108
+ total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
109
+ if total <= 0:
110
+ cap.release()
111
+ return None
112
+
113
+ faces = []
114
+ for idx in range(0, total, FRAME_STEP):
115
+ if len(faces) >= NUM_MAX_FACES:
116
+ break
117
+ cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
118
+ ok, frame = cap.read()
119
+ if not ok:
120
+ break
121
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
122
+ boxes, _ = _mtcnn.detect(rgb)
123
+ if boxes is None:
124
+ continue
125
+ for (x1, y1, x2, y2) in boxes.astype(int):
126
+ x1, y1 = max(0, x1), max(0, y1)
127
+ x2, y2 = min(frame.shape[1], x2), min(frame.shape[0], y2)
128
+ crop = frame[y1:y2, x1:x2]
129
+ if crop.size > 0:
130
+ faces.append(cv2.resize(crop, IMG_SIZE))
131
+ if len(faces) >= NUM_MAX_FACES:
132
+ break
133
+ cap.release()
134
+ if not faces:
135
+ return None
136
+ return np.stack(faces, axis=0).astype(np.uint8)
137
+
138
+ def blink_features_from_crops(faces):
139
+ """Compute EAR on cropped faces; denominator = #frames with valid EAR."""
140
+ (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
141
+ (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
142
+
143
+ ear_values = []
144
+ blink_counter = 0
145
+ total_blinks = 0
146
+
147
+ for face in faces:
148
+ gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
149
+ rects = _dlib_detector(gray, 0)
150
+ if len(rects) == 0:
151
+ continue
152
+ shape = _dlib_predictor(gray, rects[0])
153
+ shape = face_utils.shape_to_np(shape)
154
+
155
+ left_eye = shape[lStart:lEnd]
156
+ right_eye = shape[rStart:rEnd]
157
+ ear = 0.5 * (_eye_aspect_ratio(left_eye) + _eye_aspect_ratio(right_eye))
158
+ ear_values.append(ear)
159
+
160
+ if ear < EAR_THRESHOLD:
161
+ blink_counter += 1
162
+ else:
163
+ if blink_counter >= EAR_CONSEC_FRAMES:
164
+ total_blinks += 1
165
+ blink_counter = 0
166
+
167
+ n_ear = len(ear_values)
168
+ if n_ear == 0:
169
+ return np.array([0, 0.0, 0.0], dtype=np.float32)
170
+
171
+ blink_freq = total_blinks / float(n_ear)
172
+ ear_var = float(np.var(np.array(ear_values, dtype=np.float32)))
173
+ return np.array([total_blinks, blink_freq, ear_var], dtype=np.float32)
174
+
175
+ def predict_video_prob(video_path):
176
+ faces = extract_faces_all(video_path)
177
+ if faces is None or len(faces) == 0:
178
+ return None, "No faces detected."
179
+
180
+ blink_feats = blink_features_from_crops(faces)
181
+ # Tile to per-face samples
182
+ tiled = np.tile(blink_feats, (faces.shape[0], 1)).astype(np.float32)
183
+ imgs = resnet_preprocess(faces.astype(np.float32))
184
+
185
+ # Batch size heuristic (bigger if GPU is present)
186
+ has_tf_gpu = len(tf.config.experimental.list_physical_devices("GPU")) > 0
187
+ bs = 128 if has_tf_gpu else 32
188
+
189
+ preds = []
190
+ for i in range(0, imgs.shape[0], bs):
191
+ p = _video_model.predict([imgs[i:i+bs], tiled[i:i+bs]], verbose=0)
192
+ preds.append(p.reshape(-1))
193
+ return float(np.mean(np.concatenate(preds, axis=0))), None
194
+
195
+ def to_verdict(score):
196
+ return "DEEPFAKE" if score >= PRED_THRESHOLD else "REAL"
197
+
198
+ # =========================
199
+ # Inference entry
200
+ # =========================
201
+ def run_inference(video_file):
202
+ lazy_load()
203
+ if video_file is None:
204
+ return None, None, "Please upload a video file."
205
+
206
+ video_prob, vmsg = predict_video_prob(video_file)
207
+ if video_prob is None:
208
+ return None, None, vmsg or "Unable to process the video."
209
+
210
+ verdict = f"VIDEO ONLY: {to_verdict(video_prob)}"
211
+ fmt = lambda x: None if x is None else round(float(x), 4)
212
+ return fmt(video_prob), verdict, None
213
+
214
+ # =========================
215
+ # Gradio UI
216
+ # =========================
217
+ with gr.Blocks(title="Deepfake Detector — Video Only (GPU-ready)") as demo:
218
+ gr.Markdown(
219
+ "### 🎭 Deepfake Detector — **Video Only**\n"
220
+ "- **Compute**: Uses GPU automatically if available (PyTorch MTCNN, TensorFlow model; mixed precision on TF).\n"
221
+ "- **Video path**: samples every 5th frame, keeps **all** faces, resizes to 224×224.\n"
222
+ "- **Features**: ResNet50 preprocessing + blink EAR features on cropped faces via dlib.\n"
223
+ f"- **Threshold**: {PRED_THRESHOLD} (≥ means DEEPFAKE).\n"
224
+ )
225
+ with gr.Row():
226
+ video_in = gr.Video(label="Video")
227
+ go = gr.Button("Analyze")
228
+ with gr.Row():
229
+ v_out = gr.Number(label="Video probability (deepfake)", precision=4)
230
+ verdict_out = gr.Textbox(label="Verdict", interactive=False)
231
+ msg_out = gr.Textbox(label="Message / Warnings", interactive=False)
232
+
233
+ go.click(run_inference, inputs=[video_in], outputs=[v_out, verdict_out, msg_out])
234
+
235
+ if __name__ == "__main__":
236
+ lazy_load()
237
+ demo.launch(server_name="0.0.0.0", server_port=7860)
models/audio_model.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e6be7d6e4320dce4e1e74edd0f8f3632aafd7b72be1f0973af72aef7cd52fd08
3
+ size 25015854
models/audio_model/config.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"module": "keras", "class_name": "Sequential", "config": {"name": "sequential", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "layers": [{"module": "keras.layers", "class_name": "InputLayer", "config": {"batch_shape": [null, 1000, 55], "dtype": "float32", "sparse": false, "ragged": false, "name": "input_layer"}, "registered_name": null}, {"module": "keras.layers", "class_name": "Conv1D", "config": {"name": "conv1d", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "filters": 64, "kernel_size": [7], "strides": [1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 1000, 55]}}, {"module": "keras.layers", "class_name": "MaxPooling1D", "config": {"name": "max_pooling1d", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "pool_size": [2], "padding": "valid", "strides": [2], "data_format": "channels_last"}, "registered_name": null}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "rate": 0.30000000000000004, "seed": null, "noise_shape": null}, "registered_name": null}, {"module": "keras.layers", "class_name": "Conv1D", "config": {"name": "conv1d_1", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "filters": 128, "kernel_size": [5], "strides": [1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 497, 64]}}, {"module": "keras.layers", "class_name": "MaxPooling1D", "config": {"name": "max_pooling1d_1", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "pool_size": [2], "padding": "valid", "strides": [2], "data_format": "channels_last"}, "registered_name": null}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout_1", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "rate": 0.30000000000000004, "seed": null, "noise_shape": null}, "registered_name": null}, {"module": "keras.layers", "class_name": "Flatten", "config": {"name": "flatten", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "data_format": "channels_last"}, "registered_name": null, "build_config": {"input_shape": [null, 246, 128]}}, {"module": "keras.layers", "class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "units": 64, "activation": "relu", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 31488]}}, {"module": "keras.layers", "class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null, "shared_object_id": 139413435787232}, "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 64]}}], "build_input_shape": [null, 1000, 55]}, "registered_name": null, "build_config": {"input_shape": [null, 1000, 55]}, "compile_config": {"optimizer": {"module": "keras.optimizers", "class_name": "Adam", "config": {"name": "adam", "learning_rate": 0.0010000000474974513, "weight_decay": null, "clipnorm": null, "global_clipnorm": null, "clipvalue": null, "use_ema": false, "ema_momentum": 0.99, "ema_overwrite_frequency": null, "loss_scale_factor": null, "gradient_accumulation_steps": null, "beta_1": 0.9, "beta_2": 0.999, "epsilon": 1e-07, "amsgrad": false}, "registered_name": null}, "loss": "binary_crossentropy", "loss_weights": null, "metrics": ["accuracy"], "weighted_metrics": null, "run_eagerly": false, "steps_per_execution": 1, "jit_compile": false}}
models/audio_model/metadata.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"keras_version": "3.10.0", "date_saved": "2025-09-30@02:28:41"}
models/audio_model/model.weights.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e9a4c54ac044bcb96c43b9947c05af09fc1de6cceb3f2881a5761a2b2da8768c
3
+ size 25009192
models/video_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f623abf62c659bcfd8364bcc4b233680b6834d5371b925bfe62a9859efb7bd39
3
+ size 285090744
requirements.txt ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------
2
+ # Core scientific stack
3
+ # -----------------------------
4
+ numpy==1.26.4
5
+ scipy==1.11.4
6
+ pandas==2.2.2
7
+
8
+ # -----------------------------
9
+ # TensorFlow (GPU-enabled if CUDA present)
10
+ # -----------------------------
11
+ tensorflow==2.18.0
12
+
13
+ # -----------------------------
14
+ # PyTorch + facenet-pytorch
15
+ # (CPU wheels by default; CUDA wheels provided in Dockerfile)
16
+ # -----------------------------
17
+ facenet-pytorch==2.6.0
18
+ pillow==10.2.0
19
+
20
+ # -----------------------------
21
+ # Vision / utility
22
+ # -----------------------------
23
+ opencv-python-headless==4.9.0.80
24
+ imutils==0.5.4
25
+ dlib==19.24.4
26
+
27
+ # -----------------------------
28
+ # Web UI
29
+ # -----------------------------
30
+ gradio==4.4.1
shape_predictor_68_face_landmarks.dat ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fbdc2cb80eb9aa7a758672cbfdda32ba6300efe9b6e6c7a299ff7e736b11b92f
3
+ size 99693937