Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
| 1 |
-
#
|
|
|
|
|
|
|
| 2 |
|
| 3 |
import os
|
| 4 |
import warnings
|
|
@@ -15,46 +17,102 @@ from typing import Optional, Tuple, Any
|
|
| 15 |
# Suppress warnings for a cleaner output
|
| 16 |
warnings.filterwarnings("ignore")
|
| 17 |
|
| 18 |
-
#
|
| 19 |
-
#
|
| 20 |
-
#
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
#
|
|
|
|
|
|
|
|
|
|
| 24 |
MODEL_PATHS = {
|
| 25 |
-
"mobilenetv3":
|
| 26 |
-
"efficientnet_b0":
|
| 27 |
-
"edgenext":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
# --- 2A. CONFIGURATION ---
|
| 31 |
-
#
|
| 32 |
-
SIM_MODEL_NAME = 'buffalo_l'
|
| 33 |
CTX_ID = -1 # CPU
|
| 34 |
ID_MATCH_THRESHOLD = 0.50 # Similarity Threshold
|
| 35 |
FAKE_SCORE_THRESHOLD = 0.5 # Deepfake Score Threshold
|
| 36 |
|
| 37 |
-
# Fixed paths for shared Deepfake models (Caffe files are no longer used for detection,
|
| 38 |
-
# as we switch to the more robust insightface detector)
|
| 39 |
-
# NOTE: We keep the ONNX paths as they are the deepfake models.
|
| 40 |
ONNX_SESSIONS = {}
|
|
|
|
| 41 |
|
| 42 |
# --- 2B. INITIALIZE MODELS ---
|
| 43 |
-
app: Optional[FaceAnalysis] = None
|
| 44 |
|
| 45 |
-
print("\n---
|
| 46 |
try:
|
| 47 |
-
#
|
|
|
|
| 48 |
app = FaceAnalysis(name=SIM_MODEL_NAME, providers=['CPUExecutionProvider'])
|
| 49 |
-
|
|
|
|
|
|
|
| 50 |
app.prepare(ctx_id=CTX_ID, det_size=(640, 640), det_thresh=0.5,
|
| 51 |
-
|
| 52 |
|
| 53 |
# Initialize ONNX Deepfake Classification Models
|
| 54 |
for model_name, path in MODEL_PATHS.items():
|
| 55 |
if os.path.exists(path):
|
| 56 |
ONNX_SESSIONS[model_name] = ort.InferenceSession(path, providers=['CPUExecutionProvider'])
|
| 57 |
-
print(f"Loaded {model_name.upper()} model.")
|
| 58 |
else:
|
| 59 |
print(f"Warning: Deepfake model {model_name.upper()} not found at {path}")
|
| 60 |
|
|
@@ -64,7 +122,8 @@ try:
|
|
| 64 |
except Exception as e:
|
| 65 |
print(f"β FATAL ERROR: Failed to load models. Detail: {e}")
|
| 66 |
app = None
|
| 67 |
-
sys.exit(1)
|
|
|
|
| 68 |
|
| 69 |
print("β
Model initialization complete.")
|
| 70 |
|
|
@@ -86,7 +145,7 @@ def get_face_data(img_array_rgb: np.ndarray) -> Tuple[Optional[np.ndarray], Opti
|
|
| 86 |
if not all_faces:
|
| 87 |
return None, None, img_bgr, None
|
| 88 |
face = get_largest_face(all_faces)
|
| 89 |
-
# The 'face' object from FaceAnalysis
|
| 90 |
return face.embedding, face.lmk, img_bgr, face.bbox
|
| 91 |
|
| 92 |
def calculate_similarity(embedding1: Optional[np.ndarray], embedding2: Optional[np.ndarray]) -> float:
|
|
@@ -98,22 +157,20 @@ def calculate_similarity(embedding1: Optional[np.ndarray], embedding2: Optional[
|
|
| 98 |
similarity = np.dot(e1_norm, e2_norm)
|
| 99 |
return float(similarity)
|
| 100 |
|
| 101 |
-
# --- 2D. DEEPFAKE DETECTION HELPER FUNCTIONS (Liveness Check) ---
|
| 102 |
|
| 103 |
def align_face_insightface(img_bgr: np.ndarray, landmarks_5pt: np.ndarray, output_size: int = 160) -> np.ndarray:
|
| 104 |
"""
|
| 105 |
-
|
| 106 |
-
This replaces the complex dlib/81-point alignment.
|
| 107 |
-
The goal is a centered, roughly aligned 160x160 face crop.
|
| 108 |
"""
|
| 109 |
-
# Standard 5-point template for
|
| 110 |
dst = np.array([
|
| 111 |
[30.2946, 51.6963], # Left Eye
|
| 112 |
[65.5318, 51.6963], # Right Eye
|
| 113 |
[48.0252, 71.7366], # Nose Tip
|
| 114 |
[33.5493, 92.3655], # Left Mouth Corner
|
| 115 |
[62.7299, 92.3655] # Right Mouth Corner
|
| 116 |
-
], dtype=np.float32) * (output_size / 96)
|
| 117 |
|
| 118 |
src = landmarks_5pt.astype(np.float32)
|
| 119 |
|
|
@@ -135,18 +192,17 @@ def get_liveness_score(img_array_rgb: np.ndarray, landmarks_5pt: np.ndarray, mod
|
|
| 135 |
# Align the face using the 5-point landmarks
|
| 136 |
face_crop_bgr = align_face_insightface(img_bgr, landmarks_5pt, output_size=160)
|
| 137 |
|
| 138 |
-
# Pre-process for ONNX model
|
| 139 |
face_crop_rgb = cv2.cvtColor(face_crop_bgr, cv2.COLOR_BGR2RGB)
|
| 140 |
-
# Assuming the deepfake model expects a [-1, 1] normalization
|
| 141 |
normalized_img = (face_crop_rgb / 255.0 - 0.5) / 0.5
|
| 142 |
-
input_tensor = np.transpose(normalized_img, (2, 0, 1))
|
| 143 |
-
input_tensor = np.expand_dims(input_tensor, axis=0).astype("float32")
|
| 144 |
|
| 145 |
input_name = session.get_inputs()[0].name
|
| 146 |
output_name = session.get_outputs()[0].name
|
| 147 |
logit = session.run([output_name], {input_name: input_tensor})[0]
|
| 148 |
|
| 149 |
-
# Convert logit to probability (Fake Confidence Score) using sigmoid
|
| 150 |
probability = 1 / (1 + np.exp(-logit))
|
| 151 |
score = float(np.ravel(probability)[0]) if probability.size > 0 else 0.0
|
| 152 |
|
|
@@ -165,7 +221,7 @@ def unified_ekyc_analysis(model_choice: str, img_A_pil: Image.Image, img_B_pil:
|
|
| 165 |
Performs Identity Verification (Step 1) then Forgery Check (Step 2).
|
| 166 |
"""
|
| 167 |
if app is None or not ONNX_SESSIONS or model_choice not in ONNX_SESSIONS:
|
| 168 |
-
error_msg = f"""# β CRITICAL FAILURE: Models failed to load. Please ensure all ONNX models are
|
| 169 |
return None, None, error_msg
|
| 170 |
|
| 171 |
start_time = time.time()
|
|
@@ -174,15 +230,12 @@ def unified_ekyc_analysis(model_choice: str, img_A_pil: Image.Image, img_B_pil:
|
|
| 174 |
img_A_array = np.array(img_A_pil.convert('RGB'))
|
| 175 |
img_B_array = np.array(img_B_pil.convert('RGB'))
|
| 176 |
|
| 177 |
-
# e1, e2:
|
| 178 |
-
# lmk_A, lmk_B: 5-point Landmarks for Alignment
|
| 179 |
-
# vis_A_bgr, vis_B_bgr: BGR images for visualization
|
| 180 |
-
# bbox_A, bbox_B: Bounding Boxes
|
| 181 |
e1, lmk_A, vis_A_bgr, bbox_A = get_face_data(img_A_array)
|
| 182 |
e2, lmk_B, vis_B_bgr, bbox_B = get_face_data(img_B_array)
|
| 183 |
|
| 184 |
# 1. Basic Face Detection Check (Pre-Step)
|
| 185 |
-
if e1 is None or e2 is None:
|
| 186 |
report = "π **PRE-CHECK FAILED:** Face detection failed on one or both images. Cannot proceed."
|
| 187 |
return Image.fromarray(img_A_array), Image.fromarray(img_B_array), report
|
| 188 |
|
|
@@ -288,13 +341,12 @@ def unified_ekyc_analysis(model_choice: str, img_A_pil: Image.Image, img_B_pil:
|
|
| 288 |
|
| 289 |
|
| 290 |
# --- GRADIO FRONTEND ---
|
| 291 |
-
print("\n---
|
| 292 |
|
| 293 |
available_models = list(ONNX_SESSIONS.keys())
|
| 294 |
-
|
| 295 |
-
default_model = "edgenext" if "edgenext" in available_models else ("efficientnet_b0" if "efficientnet_b0" in available_models else (available_models[0] if available_models else ""))
|
| 296 |
|
| 297 |
-
if
|
| 298 |
print("FATAL: No deepfake models were loaded. Cannot launch Gradio.")
|
| 299 |
else:
|
| 300 |
iface = gr.Interface(
|
|
@@ -319,7 +371,7 @@ else:
|
|
| 319 |
gr.Markdown(label="Final eKYC Report")
|
| 320 |
],
|
| 321 |
title="Deepfake-Proof eKYC System (Unified Analysis)",
|
| 322 |
-
description="Performs two-step conditional verification: Step 1: Identity Match. Step 2 (if Match): Forgery/Liveness Check on both images.
|
| 323 |
)
|
| 324 |
|
| 325 |
iface.launch(debug=False)
|
|
|
|
| 1 |
+
# Combined Script for Colab/Notebook Execution
|
| 2 |
+
# This script handles installation, model download via gdown,
|
| 3 |
+
# and runs the dlib-free eKYC application with the buffalo_l model.
|
| 4 |
|
| 5 |
import os
|
| 6 |
import warnings
|
|
|
|
| 17 |
# Suppress warnings for a cleaner output
|
| 18 |
warnings.filterwarnings("ignore")
|
| 19 |
|
| 20 |
+
# ==============================================================================
|
| 21 |
+
# 1. SETUP, INSTALLATION, AND MODEL DOWNLOAD (Combined Cell)
|
| 22 |
+
# ==============================================================================
|
| 23 |
+
|
| 24 |
+
print("--- 1. Installing Required Libraries ---")
|
| 25 |
+
# Install core libraries and gdown, gradio. dlib is removed from the requirements
|
| 26 |
+
!pip install insightface==0.7.3 numpy onnxruntime opencv-python matplotlib tqdm gdown gradio --quiet
|
| 27 |
|
| 28 |
+
# --- Configuration: Model File IDs and Target Paths ---
|
| 29 |
+
TARGET_DIR = '/content/'
|
| 30 |
+
|
| 31 |
+
# Deepfake Model Paths (All ONNX models are downloaded for runtime switching)
|
| 32 |
MODEL_PATHS = {
|
| 33 |
+
"mobilenetv3": "/content/mobilenetv3_small_100_final.onnx",
|
| 34 |
+
"efficientnet_b0": "/content/efficientnet_b0_final.onnx",
|
| 35 |
+
"edgenext": "/content/edgenext_small_final.onnx",
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
# Mapping of file names to their corresponding Google Drive File IDs
|
| 39 |
+
# NOTE: The DLIB and Caffe files are kept for download continuity,
|
| 40 |
+
# but are NOT used in the dlib-free logic.
|
| 41 |
+
MODEL_FILES = {
|
| 42 |
+
# Deepfake Detector Components (Downloaded but not used in final logic)
|
| 43 |
+
"deploy.prototxt": "1V02QA7eOnrkKixTdnP6cvIBx4Qxqwhmw",
|
| 44 |
+
"res10_300x300_ssd_iter_140000_fp16.caffemodel": "14n7DryxHqwqac9z0HzpIqtipBp5EfRvA",
|
| 45 |
+
"shape_predictor_81_face_landmarks.dat": "1sixwbA4oOn7Ijmm85sAODL8AtwjCq6a9",
|
| 46 |
+
|
| 47 |
+
# Deepfake Classification ONNX Models
|
| 48 |
+
"mobilenetv3_small_100_final.onnx": "1spFbTIL8nRmIBG_F6j6-aF01fWGVGo_f",
|
| 49 |
+
"efficientnet_b0_final.onnx": "1TsHUbx0cd-55XDygQIAmEbXFUGHxBT_x",
|
| 50 |
+
"edgenext_small_final.onnx": "15hnhznZVyASYhSOYOFSsgMGEfsyh1MBY"
|
| 51 |
}
|
| 52 |
|
| 53 |
+
def download_models_from_drive():
|
| 54 |
+
"""Downloads all required model files from the provided Drive IDs."""
|
| 55 |
+
print(f"\n--- 2. Starting Deepfake Model Download to {TARGET_DIR} ---")
|
| 56 |
+
|
| 57 |
+
try:
|
| 58 |
+
import gdown
|
| 59 |
+
except ImportError:
|
| 60 |
+
# Should be installed by pip above, but double check
|
| 61 |
+
!pip install gdown --quiet
|
| 62 |
+
import gdown
|
| 63 |
+
|
| 64 |
+
os.makedirs(TARGET_DIR, exist_ok=True)
|
| 65 |
+
downloaded_files = 0
|
| 66 |
+
|
| 67 |
+
for filename, file_id in MODEL_FILES.items():
|
| 68 |
+
local_path = os.path.join(TARGET_DIR, filename)
|
| 69 |
+
|
| 70 |
+
if os.path.exists(local_path) and os.path.getsize(local_path) > 0:
|
| 71 |
+
# print(f"Skipping download, {filename} already exists.")
|
| 72 |
+
downloaded_files += 1
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
print(f"Downloading {filename}...")
|
| 77 |
+
gdown.download(id=file_id, output=local_path, quiet=True, fuzzy=True)
|
| 78 |
+
if os.path.exists(local_path) and os.path.getsize(local_path) > 0:
|
| 79 |
+
downloaded_files += 1
|
| 80 |
+
except Exception as e:
|
| 81 |
+
print(f"Warning: Failed to download {filename}. Error: {e}", file=sys.stderr)
|
| 82 |
+
|
| 83 |
+
download_models_from_drive()
|
| 84 |
+
print("β
Initial setup complete. Proceeding to model initialization.")
|
| 85 |
+
|
| 86 |
+
# --- 2. MODEL INITIALIZATION AND CORE HELPER FUNCTIONS ---
|
| 87 |
+
|
| 88 |
# --- 2A. CONFIGURATION ---
|
| 89 |
+
# IMPORTANT: Using 'buffalo_l' as requested, which provides 5-point landmarks (lmk)
|
| 90 |
+
SIM_MODEL_NAME = 'buffalo_l'
|
| 91 |
CTX_ID = -1 # CPU
|
| 92 |
ID_MATCH_THRESHOLD = 0.50 # Similarity Threshold
|
| 93 |
FAKE_SCORE_THRESHOLD = 0.5 # Deepfake Score Threshold
|
| 94 |
|
|
|
|
|
|
|
|
|
|
| 95 |
ONNX_SESSIONS = {}
|
| 96 |
+
app: Optional[FaceAnalysis] = None
|
| 97 |
|
| 98 |
# --- 2B. INITIALIZE MODELS ---
|
|
|
|
| 99 |
|
| 100 |
+
print("\n--- 3. Initializing Models ---")
|
| 101 |
try:
|
| 102 |
+
# Face Analysis/Recognition Model (downloads buffalo_l)
|
| 103 |
+
print(f"Initializing FaceAnalysis model: {SIM_MODEL_NAME}")
|
| 104 |
app = FaceAnalysis(name=SIM_MODEL_NAME, providers=['CPUExecutionProvider'])
|
| 105 |
+
|
| 106 |
+
# Configure app to get detection, 5-point landmark (lmk), and recognition (embedding)
|
| 107 |
+
# The 'det_model' argument is removed for compatibility with insightface==0.7.3
|
| 108 |
app.prepare(ctx_id=CTX_ID, det_size=(640, 640), det_thresh=0.5,
|
| 109 |
+
allowed_modules=['detection', 'landmark', 'recognition'])
|
| 110 |
|
| 111 |
# Initialize ONNX Deepfake Classification Models
|
| 112 |
for model_name, path in MODEL_PATHS.items():
|
| 113 |
if os.path.exists(path):
|
| 114 |
ONNX_SESSIONS[model_name] = ort.InferenceSession(path, providers=['CPUExecutionProvider'])
|
| 115 |
+
print(f"Loaded {model_name.upper()} deepfake model.")
|
| 116 |
else:
|
| 117 |
print(f"Warning: Deepfake model {model_name.upper()} not found at {path}")
|
| 118 |
|
|
|
|
| 122 |
except Exception as e:
|
| 123 |
print(f"β FATAL ERROR: Failed to load models. Detail: {e}")
|
| 124 |
app = None
|
| 125 |
+
# We don't sys.exit(1) here to allow Gradio to show the error, but the function will handle it.
|
| 126 |
+
|
| 127 |
|
| 128 |
print("β
Model initialization complete.")
|
| 129 |
|
|
|
|
| 145 |
if not all_faces:
|
| 146 |
return None, None, img_bgr, None
|
| 147 |
face = get_largest_face(all_faces)
|
| 148 |
+
# The 'face' object from FaceAnalysis provides embedding and 5-point landmarks ('lmk')
|
| 149 |
return face.embedding, face.lmk, img_bgr, face.bbox
|
| 150 |
|
| 151 |
def calculate_similarity(embedding1: Optional[np.ndarray], embedding2: Optional[np.ndarray]) -> float:
|
|
|
|
| 157 |
similarity = np.dot(e1_norm, e2_norm)
|
| 158 |
return float(similarity)
|
| 159 |
|
| 160 |
+
# --- 2D. DEEPFAKE DETECTION HELPER FUNCTIONS (Liveness Check - DLIB FREE) ---
|
| 161 |
|
| 162 |
def align_face_insightface(img_bgr: np.ndarray, landmarks_5pt: np.ndarray, output_size: int = 160) -> np.ndarray:
|
| 163 |
"""
|
| 164 |
+
Alignment using 5-point landmarks provided by insightface.
|
|
|
|
|
|
|
| 165 |
"""
|
| 166 |
+
# Standard 5-point template for alignment scaled for 160x160 output
|
| 167 |
dst = np.array([
|
| 168 |
[30.2946, 51.6963], # Left Eye
|
| 169 |
[65.5318, 51.6963], # Right Eye
|
| 170 |
[48.0252, 71.7366], # Nose Tip
|
| 171 |
[33.5493, 92.3655], # Left Mouth Corner
|
| 172 |
[62.7299, 92.3655] # Right Mouth Corner
|
| 173 |
+
], dtype=np.float32) * (output_size / 96)
|
| 174 |
|
| 175 |
src = landmarks_5pt.astype(np.float32)
|
| 176 |
|
|
|
|
| 192 |
# Align the face using the 5-point landmarks
|
| 193 |
face_crop_bgr = align_face_insightface(img_bgr, landmarks_5pt, output_size=160)
|
| 194 |
|
| 195 |
+
# Pre-process for ONNX model (C, H, W, normalization [-1, 1])
|
| 196 |
face_crop_rgb = cv2.cvtColor(face_crop_bgr, cv2.COLOR_BGR2RGB)
|
|
|
|
| 197 |
normalized_img = (face_crop_rgb / 255.0 - 0.5) / 0.5
|
| 198 |
+
input_tensor = np.transpose(normalized_img, (2, 0, 1))
|
| 199 |
+
input_tensor = np.expand_dims(input_tensor, axis=0).astype("float32")
|
| 200 |
|
| 201 |
input_name = session.get_inputs()[0].name
|
| 202 |
output_name = session.get_outputs()[0].name
|
| 203 |
logit = session.run([output_name], {input_name: input_tensor})[0]
|
| 204 |
|
| 205 |
+
# Convert logit to probability (Fake Confidence Score) using sigmoid
|
| 206 |
probability = 1 / (1 + np.exp(-logit))
|
| 207 |
score = float(np.ravel(probability)[0]) if probability.size > 0 else 0.0
|
| 208 |
|
|
|
|
| 221 |
Performs Identity Verification (Step 1) then Forgery Check (Step 2).
|
| 222 |
"""
|
| 223 |
if app is None or not ONNX_SESSIONS or model_choice not in ONNX_SESSIONS:
|
| 224 |
+
error_msg = f"""# β CRITICAL FAILURE: Models failed to load. Please check console output and ensure all ONNX models are present and FaceAnalysis initialized successfully."""
|
| 225 |
return None, None, error_msg
|
| 226 |
|
| 227 |
start_time = time.time()
|
|
|
|
| 230 |
img_A_array = np.array(img_A_pil.convert('RGB'))
|
| 231 |
img_B_array = np.array(img_B_pil.convert('RGB'))
|
| 232 |
|
| 233 |
+
# e1, e2: Embeddings; lmk_A, lmk_B: Landmarks (5-point); vis_A_bgr, vis_B_bgr: BGR images; bbox_A, bbox_B: Bounding Boxes
|
|
|
|
|
|
|
|
|
|
| 234 |
e1, lmk_A, vis_A_bgr, bbox_A = get_face_data(img_A_array)
|
| 235 |
e2, lmk_B, vis_B_bgr, bbox_B = get_face_data(img_B_array)
|
| 236 |
|
| 237 |
# 1. Basic Face Detection Check (Pre-Step)
|
| 238 |
+
if e1 is None or e2 is None or lmk_A is None or lmk_B is None:
|
| 239 |
report = "π **PRE-CHECK FAILED:** Face detection failed on one or both images. Cannot proceed."
|
| 240 |
return Image.fromarray(img_A_array), Image.fromarray(img_B_array), report
|
| 241 |
|
|
|
|
| 341 |
|
| 342 |
|
| 343 |
# --- GRADIO FRONTEND ---
|
| 344 |
+
print("\n--- 4. Initializing Gradio interface ---")
|
| 345 |
|
| 346 |
available_models = list(ONNX_SESSIONS.keys())
|
| 347 |
+
default_model = "edgenext" if "edgenext" in available_models else (available_models[0] if available_models else None)
|
|
|
|
| 348 |
|
| 349 |
+
if default_model is None:
|
| 350 |
print("FATAL: No deepfake models were loaded. Cannot launch Gradio.")
|
| 351 |
else:
|
| 352 |
iface = gr.Interface(
|
|
|
|
| 371 |
gr.Markdown(label="Final eKYC Report")
|
| 372 |
],
|
| 373 |
title="Deepfake-Proof eKYC System (Unified Analysis)",
|
| 374 |
+
description="Performs two-step conditional verification: Step 1: Identity Match. Step 2 (if Match): Forgery/Liveness Check on both images. Includes automatic model download and is DLIB-free.",
|
| 375 |
)
|
| 376 |
|
| 377 |
iface.launch(debug=False)
|