hardiksharma6555 commited on
Commit
829c6e8
Β·
verified Β·
1 Parent(s): 15c9cdf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -264
app.py CHANGED
@@ -1,9 +1,16 @@
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
 
7
  import numpy as np
8
  import cv2
9
  import onnxruntime as ort
@@ -14,363 +21,221 @@ import time
14
  import sys
15
  from typing import Optional, Tuple, Any
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
 
119
  if not ONNX_SESSIONS:
120
  raise FileNotFoundError("No ONNX deepfake models could be loaded.")
121
 
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
 
130
- # --- 2C. IDENTITY VERIFICATION HELPER FUNCTIONS (Similarity Check) ---
131
-
 
132
  def get_largest_face(faces: list) -> Optional[Any]:
133
- """Returns the largest face detected."""
134
  if not faces: return None
135
- def get_area(face):
136
- bbox = face.bbox.astype(np.int32)
137
- return (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
138
- return max(faces, key=get_area)
139
 
140
- def get_face_data(img_array_rgb: np.ndarray) -> Tuple[Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray], Optional[np.ndarray]]:
141
- """Extracts embedding, landmarks (5-point), BGR image, and bbox."""
142
  if app is None: return None, None, None, None
143
  img_bgr = cv2.cvtColor(img_array_rgb, cv2.COLOR_RGB2BGR)
144
- all_faces = app.get(img_bgr)
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:
152
- """Calculates cosine similarity."""
153
- if embedding1 is None or embedding2 is None: return 0.0
154
- # Ensure embeddings are normalized before dot product for true cosine sim
155
- e1_norm = embedding1 / np.linalg.norm(embedding1)
156
- e2_norm = embedding2 / np.linalg.norm(embedding2)
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
-
177
- # Calculate similarity transform matrix
178
  M, _ = cv2.estimateAffinePartial2D(src, dst, method=cv2.LMEDS)
179
-
180
- # Apply transformation
181
- aligned_face = cv2.warpAffine(img_bgr, M, (output_size, output_size), flags=cv2.INTER_CUBIC)
182
-
183
- return aligned_face
184
-
185
- def get_liveness_score(img_array_rgb: np.ndarray, landmarks_5pt: np.ndarray, model_choice: str) -> float:
186
- """Extracts aligned face and runs deepfake inference using the selected model."""
187
  if model_choice not in ONNX_SESSIONS: return 0.0
188
  try:
189
  session = ONNX_SESSIONS[model_choice]
190
- img_bgr = cv2.cvtColor(img_array_rgb, cv2.COLOR_RGB2BGR)
191
-
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
-
209
- return score
210
-
211
  except Exception as e:
212
- print(f"Liveness check failed: {e}", file=sys.stderr)
213
- return 0.0 # Fail safe
214
 
215
  # ==============================================================================
216
- # 3. UNIFIED eKYC ANALYSIS AND GRADIO INTERFACE
217
  # ==============================================================================
218
-
219
- def unified_ekyc_analysis(model_choice: str, img_A_pil: Image.Image, img_B_pil: Image.Image) -> Tuple[Optional[Image.Image], Optional[Image.Image], str]:
220
- """
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()
228
 
229
- # --- Data Prep and Feature Extraction ---
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
 
242
-
243
- # --- STEP 1: IDENTITY VERIFICATION (Similarity Check) ---
244
  match_score = calculate_similarity(e1, e2)
245
- match_is_success = match_score > ID_MATCH_THRESHOLD
246
-
247
- liveness_score_A = 0.0
248
- liveness_score_B = 0.0
249
- is_acceptance_ok = False
250
- rejection_reason = None
251
-
252
- if not match_is_success:
253
- # Stop here and REJECT
254
- rejection_reason = "Identity Mismatch (Step 1 Failed)"
255
-
256
- else:
257
- # --- STEP 2: FORGERY/LIVENESS CHECK (Conditional) ---
258
- liveness_score_A = get_liveness_score(img_A_array, lmk_A, model_choice)
259
- liveness_score_B = get_liveness_score(img_B_array, lmk_B, model_choice)
260
-
261
- # FAKE score <= 0.50 means it is classified as REAL
262
- liveness_is_real_A = liveness_score_A <= FAKE_SCORE_THRESHOLD
263
- liveness_is_real_B = liveness_score_B <= FAKE_SCORE_THRESHOLD
264
-
265
- is_acceptance_ok = liveness_is_real_A and liveness_is_real_B
266
- rejection_reason = "Forgery Detected (Step 2 Failed)" if not is_acceptance_ok else "All checks passed."
267
-
268
- # --- Final Labeling and Visualization ---
269
- final_label_text = "βœ… **ACCEPT IDENTITY**" if is_acceptance_ok else "❌ **REJECT IDENTITY**"
270
- final_label_color = "#28a745" if is_acceptance_ok else "#dc3545"
271
-
272
- # Set BBox Color: Red for any rejection, Green for full acceptance
273
- bbox_color = (0, 255, 0) if is_acceptance_ok else (0, 0, 255) # Green (BGR) or Red (BGR)
274
 
275
- # Drawing BBoxes
276
- bbox_A_int = bbox_A.astype(np.int32)
277
- cv2.rectangle(vis_A_bgr, (bbox_A_int[0], bbox_A_int[1]), (bbox_A_int[2], bbox_A_int[3]), bbox_color, 4)
278
- bbox_B_int = bbox_B.astype(np.int32)
279
- cv2.rectangle(vis_B_bgr, (bbox_B_int[0], bbox_B_int[1]), (bbox_B_int[2], bbox_B_int[3]), bbox_color, 4)
280
 
281
- end_time = time.time()
 
282
 
283
- # --- Final Report Generation ---
 
 
 
284
 
285
- match_status_text = "MATCH" if match_is_success else "MISMATCH"
286
- liveness_is_real_A = liveness_score_A <= FAKE_SCORE_THRESHOLD
287
- liveness_is_real_B = liveness_score_B <= FAKE_SCORE_THRESHOLD
288
- liveness_A_text = "REAL" if liveness_is_real_A else "FAKE"
289
- liveness_B_text = "REAL" if liveness_is_real_B else "FAKE"
290
 
 
 
 
 
 
 
291
 
292
- final_report = f"""
293
- ## 🏦 ZenTej eKYC Verification Report
294
-
295
- <div style='border: 2px solid {final_label_color}; padding: 10px; border-radius: 5px; background-color: {'#e6ffe6' if is_acceptance_ok else '#ffe6e6'};'>
296
- <h3 style='color: {final_label_color}; margin-top: 0;'>{final_label_text}</h3>
297
- <p><strong>Reason:</strong> {rejection_reason}</p>
298
- </div>
299
-
300
- ---
301
-
302
- ### Step 1: Identity Similarity Check
303
-
304
- | Metric | Value | Status |
305
- | :--- | :--- | :--- |
306
- | **Match Score (Cosine)** | `{match_score:.4f}` | **<span style='color:{'#28a745' if match_is_success else '#dc3545'};'>{match_status_text}</span>** |
307
- | **Required Threshold** | $>{ID_MATCH_THRESHOLD}$ | |
308
-
309
- <details>
310
- <summary style="font-weight: bold;">Metrics Interpretation</summary>
311
- <p>The score is a **Cosine Similarity** value between 0.0 and 1.0. The system requires the score to be **greater than {ID_MATCH_THRESHOLD:.2f}** to proceed to the next step.</p>
312
- </details>
313
-
314
- ---
315
-
316
- ### Step 2: Forgery / Liveness Check (Conditional)
317
-
318
- (Run only if Step 1 is MATCH)
319
-
320
- | Image | FAKE Confidence Score | Liveness Status |
321
- | :--- | :--- | :--- |
322
- | **Input 1 (Live)** | `{liveness_score_A:.4f}` | **<span style='color:{'#28a745' if liveness_is_real_A else '#dc3545'};'>{liveness_A_text}</span>** |
323
- | **Input 2 (Document)** | `{liveness_score_B:.4f}` | **<span style='color:{'#28a745' if liveness_is_real_B else '#dc3545'};'>{liveness_B_text}</span>** |
324
-
325
- <details>
326
- <summary style="font-weight: bold;">Metrics Interpretation</summary>
327
- <p>The score is the model's **Confidence that the image is a FAKE** (range: 0.0 to 1.0). A score greater than {FAKE_SCORE_THRESHOLD:.2f} results in a **FAKE** classification. Both images must be classified as **REAL** (score $\le {FAKE_SCORE_THRESHOLD:.2f}$) for Step 2 to pass.</p>
328
- <p style="font-size: 0.9em;">*Model Used: {model_choice.upper()}*</p>
329
- </details>
330
-
331
- ---
332
-
333
- *Total Processing Time: {end_time - start_time:.3f} seconds*
334
  """
335
 
336
- # Prepare images for Gradio (convert BGR to RGB)
337
- vis_A_rgb = cv2.cvtColor(vis_A_bgr, cv2.COLOR_BGR2RGB)
338
- vis_B_rgb = cv2.cvtColor(vis_B_bgr, cv2.COLOR_BGR2RGB)
339
 
340
- return Image.fromarray(vis_A_rgb), Image.fromarray(vis_B_rgb), final_report
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(
353
  fn=unified_ekyc_analysis,
354
  inputs=[
355
- gr.Dropdown(
356
- label="Select Deepfake Model",
357
- choices=available_models,
358
- value=default_model,
359
- interactive=True
360
- ),
361
- gr.Image(
362
- type="pil",
363
- label="Input 1: Live Selfie (Checked for Liveness)",
364
- sources=['upload', 'webcam']
365
- ),
366
- gr.Image(type="pil", label="Input 2: Document Photo (Checked for Identity)")
367
  ],
368
  outputs=[
369
- gr.Image(label="Input 1 Face (Final Status BBox)", height=256, width=256),
370
- gr.Image(label="Input 2 Face (Final Status BBox)", height=256, width=256),
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
- iface.launch(debug=False)
 
1
+ """
2
+ Deepfake-Proof eKYC System (DLIB-Free)
3
+ -------------------------------------
4
+ This script:
5
+ βœ… Installs required dependencies
6
+ βœ… Downloads ONNX & auxiliary models via gdown
7
+ βœ… Initializes FaceAnalysis (buffalo_l)
8
+ βœ… Runs a Gradio web interface for identity + liveness verification
9
+ """
10
 
11
  import os
12
  import warnings
13
+ import subprocess
14
  import numpy as np
15
  import cv2
16
  import onnxruntime as ort
 
21
  import sys
22
  from typing import Optional, Tuple, Any
23
 
24
+ # ==============================================================================
25
+ # 1. INSTALLATION (for Colab / Local Run)
26
+ # ==============================================================================
27
+ print("--- 1. Installing Required Libraries ---")
28
+ try:
29
+ subprocess.run([
30
+ "pip", "install",
31
+ "insightface==0.7.3", "numpy", "onnxruntime",
32
+ "opencv-python", "matplotlib", "tqdm", "gdown", "gradio"
33
+ ], check=True)
34
+ except Exception as e:
35
+ print(f"⚠️ Installation failed: {e}")
36
+
37
  # Suppress warnings for a cleaner output
38
  warnings.filterwarnings("ignore")
39
 
40
  # ==============================================================================
41
+ # 2. MODEL DOWNLOAD SETUP
42
  # ==============================================================================
43
+ TARGET_DIR = './models'
44
+ os.makedirs(TARGET_DIR, exist_ok=True)
45
 
 
 
 
 
 
 
 
 
46
  MODEL_PATHS = {
47
+ "mobilenetv3": os.path.join(TARGET_DIR, "mobilenetv3_small_100_final.onnx"),
48
+ "efficientnet_b0": os.path.join(TARGET_DIR, "efficientnet_b0_final.onnx"),
49
+ "edgenext": os.path.join(TARGET_DIR, "edgenext_small_final.onnx"),
50
  }
51
 
 
 
 
52
  MODEL_FILES = {
53
+ # Deepfake Detector Components (for compatibility; not used in DLIB-free flow)
54
  "deploy.prototxt": "1V02QA7eOnrkKixTdnP6cvIBx4Qxqwhmw",
55
  "res10_300x300_ssd_iter_140000_fp16.caffemodel": "14n7DryxHqwqac9z0HzpIqtipBp5EfRvA",
56
  "shape_predictor_81_face_landmarks.dat": "1sixwbA4oOn7Ijmm85sAODL8AtwjCq6a9",
57
+ # Deepfake Classification Models
 
58
  "mobilenetv3_small_100_final.onnx": "1spFbTIL8nRmIBG_F6j6-aF01fWGVGo_f",
59
  "efficientnet_b0_final.onnx": "1TsHUbx0cd-55XDygQIAmEbXFUGHxBT_x",
60
  "edgenext_small_final.onnx": "15hnhznZVyASYhSOYOFSsgMGEfsyh1MBY"
61
  }
62
 
63
  def download_models_from_drive():
64
+ """Downloads all required model files from Google Drive."""
65
+ print(f"\n--- 2. Downloading Deepfake Models to {TARGET_DIR} ---")
66
+ import gdown
 
 
 
 
 
 
 
 
 
 
67
  for filename, file_id in MODEL_FILES.items():
68
  local_path = os.path.join(TARGET_DIR, filename)
 
69
  if os.path.exists(local_path) and os.path.getsize(local_path) > 0:
 
 
70
  continue
 
71
  try:
72
+ print(f"⬇️ Downloading {filename} ...")
73
  gdown.download(id=file_id, output=local_path, quiet=True, fuzzy=True)
 
 
74
  except Exception as e:
75
+ print(f"⚠️ Failed to download {filename}: {e}")
76
 
77
  download_models_from_drive()
78
+ print("βœ… Model files are ready.")
79
 
80
+ # ==============================================================================
81
+ # 3. MODEL INITIALIZATION
82
+ # ==============================================================================
83
+ SIM_MODEL_NAME = 'buffalo_l'
 
84
  CTX_ID = -1 # CPU
85
+ ID_MATCH_THRESHOLD = 0.50
86
+ FAKE_SCORE_THRESHOLD = 0.50
87
 
88
  ONNX_SESSIONS = {}
89
  app: Optional[FaceAnalysis] = None
90
 
91
+ print("\n--- 3. Initializing Face and Deepfake Models ---")
 
 
92
  try:
 
 
93
  app = FaceAnalysis(name=SIM_MODEL_NAME, providers=['CPUExecutionProvider'])
 
 
 
94
  app.prepare(ctx_id=CTX_ID, det_size=(640, 640), det_thresh=0.5,
95
  allowed_modules=['detection', 'landmark', 'recognition'])
96
 
 
97
  for model_name, path in MODEL_PATHS.items():
98
  if os.path.exists(path):
99
  ONNX_SESSIONS[model_name] = ort.InferenceSession(path, providers=['CPUExecutionProvider'])
100
+ print(f"βœ… Loaded {model_name.upper()} model.")
101
  else:
102
+ print(f"⚠️ Missing {model_name.upper()} at {path}")
103
 
104
  if not ONNX_SESSIONS:
105
  raise FileNotFoundError("No ONNX deepfake models could be loaded.")
106
 
107
  except Exception as e:
108
+ print(f"❌ Model initialization failed: {e}")
109
  app = None
 
 
110
 
111
  print("βœ… Model initialization complete.")
112
 
113
+ # ==============================================================================
114
+ # 4. HELPER FUNCTIONS
115
+ # ==============================================================================
116
  def get_largest_face(faces: list) -> Optional[Any]:
 
117
  if not faces: return None
118
+ def area(face): bbox = face.bbox.astype(np.int32); return (bbox[2]-bbox[0])*(bbox[3]-bbox[1])
119
+ return max(faces, key=area)
 
 
120
 
121
+ def get_face_data(img_array_rgb: np.ndarray):
 
122
  if app is None: return None, None, None, None
123
  img_bgr = cv2.cvtColor(img_array_rgb, cv2.COLOR_RGB2BGR)
124
+ faces = app.get(img_bgr)
125
+ if not faces: return None, None, img_bgr, None
126
+ face = get_largest_face(faces)
 
 
127
  return face.embedding, face.lmk, img_bgr, face.bbox
128
 
129
+ def calculate_similarity(e1, e2):
130
+ if e1 is None or e2 is None: return 0.0
131
+ e1_norm = e1 / np.linalg.norm(e1)
132
+ e2_norm = e2 / np.linalg.norm(e2)
133
+ return float(np.dot(e1_norm, e2_norm))
 
 
 
134
 
135
+ def align_face_insightface(img_bgr, landmarks_5pt, output_size=160):
 
 
 
 
 
 
136
  dst = np.array([
137
+ [30.2946, 51.6963],
138
+ [65.5318, 51.6963],
139
+ [48.0252, 71.7366],
140
+ [33.5493, 92.3655],
141
+ [62.7299, 92.3655]
142
+ ], dtype=np.float32) * (output_size / 96)
 
143
  src = landmarks_5pt.astype(np.float32)
 
 
144
  M, _ = cv2.estimateAffinePartial2D(src, dst, method=cv2.LMEDS)
145
+ return cv2.warpAffine(img_bgr, M, (output_size, output_size), flags=cv2.INTER_CUBIC)
146
+
147
+ def get_liveness_score(img_rgb, landmarks_5pt, model_choice):
 
 
 
 
 
148
  if model_choice not in ONNX_SESSIONS: return 0.0
149
  try:
150
  session = ONNX_SESSIONS[model_choice]
151
+ img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
152
+ face_crop = align_face_insightface(img_bgr, landmarks_5pt, 160)
153
+ face_rgb = cv2.cvtColor(face_crop, cv2.COLOR_BGR2RGB)
154
+ normalized = (face_rgb / 255.0 - 0.5) / 0.5
155
+ input_tensor = np.transpose(normalized, (2, 0, 1))[None, ...].astype("float32")
 
 
 
 
 
 
156
  input_name = session.get_inputs()[0].name
157
  output_name = session.get_outputs()[0].name
158
  logit = session.run([output_name], {input_name: input_tensor})[0]
 
 
159
  probability = 1 / (1 + np.exp(-logit))
160
+ return float(np.ravel(probability)[0])
 
 
 
161
  except Exception as e:
162
+ print(f"Liveness check failed: {e}")
163
+ return 0.0
164
 
165
  # ==============================================================================
166
+ # 5. UNIFIED eKYC LOGIC
167
  # ==============================================================================
168
+ def unified_ekyc_analysis(model_choice: str, img_A_pil: Image.Image, img_B_pil: Image.Image):
 
 
 
 
169
  if app is None or not ONNX_SESSIONS or model_choice not in ONNX_SESSIONS:
170
+ err = "# ❌ Models not initialized. Please restart or check logs."
171
+ return None, None, err
 
 
172
 
173
+ start = time.time()
174
+ img_A, img_B = np.array(img_A_pil.convert('RGB')), np.array(img_B_pil.convert('RGB'))
 
175
 
176
+ e1, lmk_A, visA, bboxA = get_face_data(img_A)
177
+ e2, lmk_B, visB, bboxB = get_face_data(img_B)
 
178
 
 
179
  if e1 is None or e2 is None or lmk_A is None or lmk_B is None:
180
+ return img_A_pil, img_B_pil, "πŸ›‘ **Face not detected properly in one/both images.**"
 
181
 
 
 
182
  match_score = calculate_similarity(e1, e2)
183
+ match_ok = match_score > ID_MATCH_THRESHOLD
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
+ liveness_A = get_liveness_score(img_A, lmk_A, model_choice) if match_ok else 0.0
186
+ liveness_B = get_liveness_score(img_B, lmk_B, model_choice) if match_ok else 0.0
 
 
 
187
 
188
+ is_real_A, is_real_B = liveness_A <= FAKE_SCORE_THRESHOLD, liveness_B <= FAKE_SCORE_THRESHOLD
189
+ accept = match_ok and is_real_A and is_real_B
190
 
191
+ bbox_color = (0, 255, 0) if accept else (0, 0, 255)
192
+ for vis, bbox in [(visA, bboxA), (visB, bboxB)]:
193
+ b = bbox.astype(int)
194
+ cv2.rectangle(vis, (b[0], b[1]), (b[2], b[3]), bbox_color, 3)
195
 
196
+ report = f"""
197
+ ## 🏦 ZenTej eKYC Report
198
+ **Final Decision:** {'βœ… ACCEPT' if accept else '❌ REJECT'}
199
+ **Reason:** {'All checks passed' if accept else 'Mismatch or Forgery detected'}
 
200
 
201
+ | Check | Value | Status |
202
+ |:--|:--|:--|
203
+ | Cosine Similarity | `{match_score:.4f}` | {'MATCH' if match_ok else 'MISMATCH'} |
204
+ | Live Image Fake Score | `{liveness_A:.4f}` | {'REAL' if is_real_A else 'FAKE'} |
205
+ | Doc Image Fake Score | `{liveness_B:.4f}` | {'REAL' if is_real_B else 'FAKE'} |
206
+ | Thresholds | ID>{ID_MATCH_THRESHOLD}, FAKE<={FAKE_SCORE_THRESHOLD} | |
207
 
208
+ ⏱ Time: {time.time()-start:.3f}s | Model: **{model_choice.upper()}**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  """
210
 
211
+ return Image.fromarray(cv2.cvtColor(visA, cv2.COLOR_BGR2RGB)), Image.fromarray(cv2.cvtColor(visB, cv2.COLOR_BGR2RGB)), report
 
 
212
 
213
+ # ==============================================================================
214
+ # 6. GRADIO INTERFACE
215
+ # ==============================================================================
216
+ if __name__ == "__main__":
217
+ print("\n--- 4. Launching Gradio App ---")
218
+ models = list(ONNX_SESSIONS.keys())
219
+ default = "edgenext" if "edgenext" in models else (models[0] if models else None)
220
 
221
+ if not default:
222
+ print("❌ No deepfake models available.")
223
+ sys.exit(1)
224
 
 
 
 
225
  iface = gr.Interface(
226
  fn=unified_ekyc_analysis,
227
  inputs=[
228
+ gr.Dropdown(label="Select Deepfake Model", choices=models, value=default),
229
+ gr.Image(label="Input 1: Live Selfie", type="pil", sources=["upload", "webcam"]),
230
+ gr.Image(label="Input 2: Document Photo", type="pil")
 
 
 
 
 
 
 
 
 
231
  ],
232
  outputs=[
233
+ gr.Image(label="Processed Input 1"),
234
+ gr.Image(label="Processed Input 2"),
235
+ gr.Markdown(label="Verification Report")
236
  ],
237
+ title="DLIB-Free eKYC Deepfake-Proof Verification",
238
+ description="Performs two-step verification: Identity Match + Deepfake/Liveness Detection.",
239
+ )
240
 
241
+ iface.launch(server_name="0.0.0.0", server_port=7860)