kkt-2002 commited on
Commit
0d03761
Β·
1 Parent(s): 9284977

Add robust ONNX Runtime error handling with CPU-only providers and fallbacks

Browse files
Files changed (1) hide show
  1. app.py +209 -105
app.py CHANGED
@@ -1,6 +1,5 @@
1
  from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
2
  from imutils.face_utils import FaceAligner, rect_to_bb # noqa: F401
3
- import onnxruntime as ort
4
  import imutils # noqa: F401
5
  import os
6
  import time
@@ -17,6 +16,16 @@ import bz2
17
  import requests
18
  from typing import Optional, Dict, Tuple, Any
19
 
 
 
 
 
 
 
 
 
 
 
20
  # --- Evaluation Metrics Counters (legacy, kept for compatibility display) ---
21
  total_attempts = 0
22
  correct_recognitions = 0
@@ -80,13 +89,64 @@ def init_mongodb():
80
  # Initialize MongoDB
81
  client, db, students_collection, teachers_collection, attendance_collection, metrics_events = init_mongodb()
82
 
83
- # ---------------- YOLOv5s-face + AntiSpoof (BINARY) FOR ATTENDANCE ONLY ----------------
84
  def _get_providers():
85
- available = ort.get_available_providers()
86
- if "CUDAExecutionProvider" in available:
87
- return ["CUDAExecutionProvider", "CPUExecutionProvider"]
88
- return ["CPUExecutionProvider"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
 
90
  def _letterbox(image, new_shape=(640, 640), color=(114, 114, 114), auto=False, scaleFill=False, scaleup=True):
91
  shape = image.shape[:2] # h, w
92
  if isinstance(new_shape, int):
@@ -143,12 +203,27 @@ class YoloV5FaceDetector:
143
  self.input_size = int(input_size)
144
  self.conf_threshold = float(conf_threshold)
145
  self.iou_threshold = float(iou_threshold)
146
- self.session = ort.InferenceSession(model_path, providers=_get_providers())
147
- self.input_name = self.session.get_inputs()[0].name
148
- self.output_names = [o.name for o in self.session.get_outputs()]
149
- shape = self.session.get_inputs()[0].shape
150
- if isinstance(shape[2], int):
151
- self.input_size = int(shape[2])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
  @staticmethod
154
  def _xywh2xyxy(x: np.ndarray) -> np.ndarray:
@@ -160,50 +235,58 @@ class YoloV5FaceDetector:
160
  return y
161
 
162
  def detect(self, image_bgr: np.ndarray, max_det: int = 20):
163
- h0, w0 = image_bgr.shape[:2]
164
- img, ratio, dwdh = _letterbox(image_bgr, new_shape=(self.input_size, self.input_size))
165
- img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
166
- img = img.astype(np.float32) / 255.0
167
- img = np.transpose(img, (2, 0, 1))
168
- img = np.expand_dims(img, 0)
169
- preds = self.session.run(self.output_names, {self.input_name: img})[0]
170
- if preds.ndim == 3 and preds.shape[0] == 1:
171
- preds = preds[0]
172
- if preds.ndim != 2:
173
- raise RuntimeError(f"Unexpected YOLO output shape: {preds.shape}")
174
- num_attrs = preds.shape[1]
175
- has_landmarks = num_attrs >= 15
176
- boxes_xywh = preds[:, 0:4]
177
- if has_landmarks:
178
- scores = preds[:, 4]
179
- else:
180
- obj = preds[:, 4:5]
181
- cls_scores = preds[:, 5:]
182
- if cls_scores.size == 0:
183
- scores = obj.squeeze(-1)
184
  else:
185
- class_conf = cls_scores.max(axis=1, keepdims=True)
186
- scores = (obj * class_conf).squeeze(-1)
187
- keep = scores > self.conf_threshold
188
- boxes_xywh = boxes_xywh[keep]
189
- scores = scores[keep]
190
- if boxes_xywh.shape[0] == 0:
191
- return []
192
- boxes_xyxy = self._xywh2xyxy(boxes_xywh)
193
- boxes_xyxy[:, [0, 2]] -= dwdh[0]
194
- boxes_xyxy[:, [1, 3]] -= dwdh[1]
195
- boxes_xyxy /= ratio
196
- boxes_xyxy[:, 0] = np.clip(boxes_xyxy[:, 0], 0, w0 - 1)
197
- boxes_xyxy[:, 1] = np.clip(boxes_xyxy[:, 1], 0, h0 - 1)
198
- boxes_xyxy[:, 2] = np.clip(boxes_xyxy[:, 2], 0, w0 - 1)
199
- boxes_xyxy[:, 3] = np.clip(boxes_xyxy[:, 3], 0, h0 - 1)
200
- keep_inds = _nms(boxes_xyxy, scores, self.iou_threshold)
201
- if len(keep_inds) > max_det:
202
- keep_inds = keep_inds[:max_det]
203
- dets = []
204
- for i in keep_inds:
205
- dets.append({"bbox": boxes_xyxy[i].tolist(), "score": float(scores[i])})
206
- return dets
 
 
 
 
 
 
 
 
207
 
208
  def _sigmoid(x: np.ndarray) -> np.ndarray:
209
  return 1.0 / (1.0 + np.exp(-x))
@@ -221,9 +304,24 @@ class AntiSpoofBinary:
221
  self.mean = np.array(mean, dtype=np.float32).reshape(1, 1, 3)
222
  self.std = np.array(std, dtype=np.float32).reshape(1, 1, 3)
223
  self.live_index = int(live_index)
224
- self.session = ort.InferenceSession(model_path, providers=_get_providers())
225
- self.input_name = self.session.get_inputs()[0].name
226
- self.output_names = [o.name for o in self.session.get_outputs()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
  def _preprocess(self, face_bgr: np.ndarray) -> np.ndarray:
229
  img = cv2.resize(face_bgr, (self.input_size, self.input_size), interpolation=cv2.INTER_LINEAR)
@@ -237,19 +335,27 @@ class AntiSpoofBinary:
237
  return img
238
 
239
  def predict_live_prob(self, face_bgr: np.ndarray) -> float:
240
- inp = self._preprocess(face_bgr)
241
- outs = self.session.run(self.output_names, {self.input_name: inp})
242
- out = outs[0]
243
- if out.ndim > 1:
244
- out = np.squeeze(out, axis=0)
245
- if out.size == 2:
246
- vec = out.astype(np.float32)
247
- probs = np.exp(vec - np.max(vec))
248
- probs = probs / (np.sum(probs) + 1e-9)
249
- live_prob = float(probs[self.live_index])
250
- else:
251
- live_prob = float(_sigmoid(out.astype(np.float32)))
252
- return max(0.0, min(1.0, live_prob))
 
 
 
 
 
 
 
 
253
 
254
  def expand_and_clip_box(bbox_xyxy, scale: float, w: int, h: int):
255
  x1, y1, x2, y2 = bbox_xyxy
@@ -310,49 +416,51 @@ def ensure_models_exist():
310
  except Exception as e:
311
  print(f"Failed to download dlib face recognition model: {e}")
312
 
313
- # Check other required models
314
- required_models = [
315
- SHAPE_PREDICTOR_PATH,
316
- FACE_RECOGNITION_MODEL_PATH,
317
- YOLO_FACE_MODEL_PATH,
318
- ANTI_SPOOF_BIN_MODEL_PATH
319
- ]
320
 
321
- missing_models = [model for model in required_models if not os.path.exists(model)]
 
322
 
323
- if missing_models:
324
- print(f"⚠️ Warning: Missing model files: {missing_models}")
325
- print("Please ensure all model files are uploaded to the Space.")
326
  return False
327
 
328
- print("βœ… All required models are available!")
 
 
 
 
329
  return True
330
 
331
  # Initialize models
332
  def init_models():
333
- """Initialize all ML models"""
334
  global yolo_face, anti_spoof_bin, detector, shape_predictor, face_recognition_model
335
 
336
  try:
337
  if not ensure_models_exist():
338
- print("❌ Cannot initialize models - some files are missing")
339
  return False
340
 
341
- # Initialize YOLO face detector
342
- print("Loading YOLOv5 face detector...")
343
- yolo_face = YoloV5FaceDetector(YOLO_FACE_MODEL_PATH, input_size=640, conf_threshold=0.3, iou_threshold=0.45)
344
-
345
- # Initialize anti-spoofing model
346
- print("Loading anti-spoofing model...")
347
- anti_spoof_bin = AntiSpoofBinary(ANTI_SPOOF_BIN_MODEL_PATH, input_size=128, rgb=True, normalize=True, live_index=1)
348
-
349
- # Initialize dlib models
350
  print("Loading dlib models...")
351
  detector = dlib.get_frontal_face_detector()
352
  shape_predictor = dlib.shape_predictor(SHAPE_PREDICTOR_PATH)
353
  face_recognition_model = dlib.face_recognition_model_v1(FACE_RECOGNITION_MODEL_PATH)
 
354
 
355
- print("βœ… All models loaded successfully!")
 
 
 
 
 
 
 
 
 
356
  return True
357
 
358
  except Exception as e:
@@ -362,7 +470,7 @@ def init_models():
362
  # Initialize models
363
  models_loaded = init_models()
364
 
365
- # Face processing functions
366
  def decode_image(base64_image):
367
  if ',' in base64_image:
368
  base64_image = base64_image.split(',')[1]
@@ -588,7 +696,7 @@ def compute_latency_avg(limit: int = 300) -> Optional[float]:
588
  return None
589
  return sum(vals) / len(vals)
590
 
591
- # Flask Routes (keeping all your existing routes with minor safety checks)
592
  @app.route('/')
593
  def home():
594
  return render_template('home.html')
@@ -670,9 +778,6 @@ def login():
670
  flash('Invalid credentials. Please try again.', 'danger')
671
  return redirect(url_for('login_page'))
672
 
673
- # (Continue with all your other routes - face_login, auto_face_login, mark_attendance, etc.)
674
- # For brevity, I'm including the key ones. The pattern is the same - add database checks
675
-
676
  @app.route('/face-login', methods=['POST'])
677
  def face_login():
678
  face_image = request.form.get('face_image')
@@ -779,7 +884,7 @@ def mark_attendance():
779
  h, w = image.shape[:2]
780
  vis = image.copy()
781
 
782
- # YOLO face detection
783
  detections = yolo_face.detect(image, max_det=20)
784
  if not detections:
785
  overlay = image_to_data_uri(vis)
@@ -806,7 +911,7 @@ def mark_attendance():
806
  overlay = image_to_data_uri(vis)
807
  return jsonify({'success': False, 'message': 'Failed to crop face for liveness', 'overlay': overlay})
808
 
809
- # Anti-spoofing check
810
  live_prob = anti_spoof_bin.predict_live_prob(face_crop)
811
  is_live = live_prob >= 0.7
812
  label = "LIVE" if is_live else "SPOOF"
@@ -850,9 +955,6 @@ def mark_attendance():
850
  else:
851
  return jsonify({'success': False, 'message': message, 'overlay': overlay_data})
852
 
853
- # Continue with all your other routes (teacher routes, metrics routes, etc.)
854
- # Add the same database availability checks to each route
855
-
856
  @app.route('/logout')
857
  def logout():
858
  session.clear()
@@ -885,6 +987,7 @@ def metrics_json():
885
  def health_check():
886
  return jsonify({
887
  'status': 'healthy',
 
888
  'models_loaded': models_loaded,
889
  'database_connected': bool(db),
890
  'timestamp': datetime.now().isoformat()
@@ -893,6 +996,7 @@ def health_check():
893
  if __name__ == '__main__':
894
  port = int(os.environ.get("PORT", 7860))
895
  print(f"πŸš€ Starting Face Recognition Attendance System on port {port}")
 
896
  print(f"πŸ“Š Models loaded: {models_loaded}")
897
  print(f"πŸ—„οΈ Database connected: {bool(db)}")
898
  app.run(debug=False, host='0.0.0.0', port=port)
 
1
  from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
2
  from imutils.face_utils import FaceAligner, rect_to_bb # noqa: F401
 
3
  import imutils # noqa: F401
4
  import os
5
  import time
 
16
  import requests
17
  from typing import Optional, Dict, Tuple, Any
18
 
19
+ # --- ONNX Runtime Import with Fallback Handling ---
20
+ try:
21
+ import onnxruntime as ort
22
+ ONNX_AVAILABLE = True
23
+ print("βœ… ONNX Runtime imported successfully")
24
+ except ImportError as e:
25
+ ONNX_AVAILABLE = False
26
+ print(f"⚠️ ONNX Runtime not available: {e}")
27
+ print("πŸ”„ Falling back to OpenCV-based alternatives")
28
+
29
  # --- Evaluation Metrics Counters (legacy, kept for compatibility display) ---
30
  total_attempts = 0
31
  correct_recognitions = 0
 
89
  # Initialize MongoDB
90
  client, db, students_collection, teachers_collection, attendance_collection, metrics_events = init_mongodb()
91
 
92
+ # ---------------- ONNX Runtime Provider Configuration ----------------
93
  def _get_providers():
94
+ """Get ONNX Runtime providers with CPU-only fallback"""
95
+ if not ONNX_AVAILABLE:
96
+ return []
97
+
98
+ try:
99
+ # Force CPU-only to avoid executable stack issues
100
+ return ["CPUExecutionProvider"]
101
+ except Exception as e:
102
+ print(f"Error getting ONNX providers: {e}")
103
+ return []
104
+
105
+ # ---------------- Fallback Face Detection with OpenCV ----------------
106
+ def detect_faces_opencv(image):
107
+ """OpenCV-based face detection fallback"""
108
+ try:
109
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
110
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
111
+ faces = face_cascade.detectMultiScale(gray, 1.1, 4)
112
+
113
+ detections = []
114
+ for (x, y, w, h) in faces:
115
+ detections.append({
116
+ "bbox": [x, y, x+w, y+h],
117
+ "score": 0.9 # Default confidence
118
+ })
119
+ return detections
120
+ except Exception as e:
121
+ print(f"OpenCV face detection error: {e}")
122
+ return []
123
+
124
+ def simple_liveness_check(face_crop):
125
+ """Simple liveness check without ONNX Runtime"""
126
+ try:
127
+ gray = cv2.cvtColor(face_crop, cv2.COLOR_BGR2GRAY)
128
+
129
+ # Check image sharpness (blurry might indicate photo of photo)
130
+ laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
131
+
132
+ # Check brightness distribution
133
+ mean_brightness = np.mean(gray)
134
+
135
+ # Simple heuristic scoring
136
+ sharpness_score = min(1.0, laplacian_var / 100.0)
137
+ brightness_score = 1.0 if 50 < mean_brightness < 200 else 0.5
138
+
139
+ # Combine scores
140
+ live_score = (sharpness_score + brightness_score) / 2.0
141
+
142
+ # Return a value between 0.3 and 0.9
143
+ return 0.3 + (live_score * 0.6)
144
+
145
+ except Exception as e:
146
+ print(f"Fallback liveness check error: {e}")
147
+ return 0.7 # Default to "probably live"
148
 
149
+ # ---------------- YOLOv5s-face + AntiSpoof (BINARY) FOR ATTENDANCE ONLY ----------------
150
  def _letterbox(image, new_shape=(640, 640), color=(114, 114, 114), auto=False, scaleFill=False, scaleup=True):
151
  shape = image.shape[:2] # h, w
152
  if isinstance(new_shape, int):
 
203
  self.input_size = int(input_size)
204
  self.conf_threshold = float(conf_threshold)
205
  self.iou_threshold = float(iou_threshold)
206
+ self.session = None
207
+
208
+ if ONNX_AVAILABLE:
209
+ try:
210
+ providers = _get_providers()
211
+ if providers:
212
+ self.session = ort.InferenceSession(model_path, providers=providers)
213
+ self.input_name = self.session.get_inputs()[0].name
214
+ self.output_names = [o.name for o in self.session.get_outputs()]
215
+ shape = self.session.get_inputs()[0].shape
216
+ if isinstance(shape[2], int):
217
+ self.input_size = int(shape[2])
218
+ print("βœ… YOLOv5 Face Detector initialized with ONNX Runtime")
219
+ else:
220
+ print("⚠️ No ONNX providers available for YOLOv5")
221
+ except Exception as e:
222
+ print(f"⚠️ Failed to initialize YOLOv5 with ONNX Runtime: {e}")
223
+ print("πŸ”„ Will use OpenCV fallback for face detection")
224
+ self.session = None
225
+ else:
226
+ print("⚠️ ONNX Runtime not available - using OpenCV fallback for face detection")
227
 
228
  @staticmethod
229
  def _xywh2xyxy(x: np.ndarray) -> np.ndarray:
 
235
  return y
236
 
237
  def detect(self, image_bgr: np.ndarray, max_det: int = 20):
238
+ if self.session is None:
239
+ # Fallback to OpenCV face detection
240
+ return detect_faces_opencv(image_bgr)
241
+
242
+ try:
243
+ h0, w0 = image_bgr.shape[:2]
244
+ img, ratio, dwdh = _letterbox(image_bgr, new_shape=(self.input_size, self.input_size))
245
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
246
+ img = img.astype(np.float32) / 255.0
247
+ img = np.transpose(img, (2, 0, 1))
248
+ img = np.expand_dims(img, 0)
249
+ preds = self.session.run(self.output_names, {self.input_name: img})[0]
250
+ if preds.ndim == 3 and preds.shape[0] == 1:
251
+ preds = preds[0]
252
+ if preds.ndim != 2:
253
+ raise RuntimeError(f"Unexpected YOLO output shape: {preds.shape}")
254
+ num_attrs = preds.shape[1]
255
+ has_landmarks = num_attrs >= 15
256
+ boxes_xywh = preds[:, 0:4]
257
+ if has_landmarks:
258
+ scores = preds[:, 4]
259
  else:
260
+ obj = preds[:, 4:5]
261
+ cls_scores = preds[:, 5:]
262
+ if cls_scores.size == 0:
263
+ scores = obj.squeeze(-1)
264
+ else:
265
+ class_conf = cls_scores.max(axis=1, keepdims=True)
266
+ scores = (obj * class_conf).squeeze(-1)
267
+ keep = scores > self.conf_threshold
268
+ boxes_xywh = boxes_xywh[keep]
269
+ scores = scores[keep]
270
+ if boxes_xywh.shape[0] == 0:
271
+ return []
272
+ boxes_xyxy = self._xywh2xyxy(boxes_xywh)
273
+ boxes_xyxy[:, [0, 2]] -= dwdh[0]
274
+ boxes_xyxy[:, [1, 3]] -= dwdh[1]
275
+ boxes_xyxy /= ratio
276
+ boxes_xyxy[:, 0] = np.clip(boxes_xyxy[:, 0], 0, w0 - 1)
277
+ boxes_xyxy[:, 1] = np.clip(boxes_xyxy[:, 1], 0, h0 - 1)
278
+ boxes_xyxy[:, 2] = np.clip(boxes_xyxy[:, 2], 0, w0 - 1)
279
+ boxes_xyxy[:, 3] = np.clip(boxes_xyxy[:, 3], 0, h0 - 1)
280
+ keep_inds = _nms(boxes_xyxy, scores, self.iou_threshold)
281
+ if len(keep_inds) > max_det:
282
+ keep_inds = keep_inds[:max_det]
283
+ dets = []
284
+ for i in keep_inds:
285
+ dets.append({"bbox": boxes_xyxy[i].tolist(), "score": float(scores[i])})
286
+ return dets
287
+ except Exception as e:
288
+ print(f"YOLOv5 detection error, falling back to OpenCV: {e}")
289
+ return detect_faces_opencv(image_bgr)
290
 
291
  def _sigmoid(x: np.ndarray) -> np.ndarray:
292
  return 1.0 / (1.0 + np.exp(-x))
 
304
  self.mean = np.array(mean, dtype=np.float32).reshape(1, 1, 3)
305
  self.std = np.array(std, dtype=np.float32).reshape(1, 1, 3)
306
  self.live_index = int(live_index)
307
+ self.session = None
308
+
309
+ if ONNX_AVAILABLE:
310
+ try:
311
+ providers = _get_providers()
312
+ if providers:
313
+ self.session = ort.InferenceSession(model_path, providers=providers)
314
+ self.input_name = self.session.get_inputs()[0].name
315
+ self.output_names = [o.name for o in self.session.get_outputs()]
316
+ print("βœ… Anti-spoofing model initialized with ONNX Runtime")
317
+ else:
318
+ print("⚠️ No ONNX providers available for anti-spoofing")
319
+ except Exception as e:
320
+ print(f"⚠️ Failed to initialize anti-spoofing with ONNX Runtime: {e}")
321
+ print("πŸ”„ Will use simple liveness check fallback")
322
+ self.session = None
323
+ else:
324
+ print("⚠️ ONNX Runtime not available - using simple liveness check fallback")
325
 
326
  def _preprocess(self, face_bgr: np.ndarray) -> np.ndarray:
327
  img = cv2.resize(face_bgr, (self.input_size, self.input_size), interpolation=cv2.INTER_LINEAR)
 
335
  return img
336
 
337
  def predict_live_prob(self, face_bgr: np.ndarray) -> float:
338
+ if self.session is None:
339
+ # Use simple fallback liveness check
340
+ return simple_liveness_check(face_bgr)
341
+
342
+ try:
343
+ inp = self._preprocess(face_bgr)
344
+ outs = self.session.run(self.output_names, {self.input_name: inp})
345
+ out = outs[0]
346
+ if out.ndim > 1:
347
+ out = np.squeeze(out, axis=0)
348
+ if out.size == 2:
349
+ vec = out.astype(np.float32)
350
+ probs = np.exp(vec - np.max(vec))
351
+ probs = probs / (np.sum(probs) + 1e-9)
352
+ live_prob = float(probs[self.live_index])
353
+ else:
354
+ live_prob = float(_sigmoid(out.astype(np.float32)))
355
+ return max(0.0, min(1.0, live_prob))
356
+ except Exception as e:
357
+ print(f"ONNX anti-spoofing error, using fallback: {e}")
358
+ return simple_liveness_check(face_bgr)
359
 
360
  def expand_and_clip_box(bbox_xyxy, scale: float, w: int, h: int):
361
  x1, y1, x2, y2 = bbox_xyxy
 
416
  except Exception as e:
417
  print(f"Failed to download dlib face recognition model: {e}")
418
 
419
+ # Check required models
420
+ required_models = [SHAPE_PREDICTOR_PATH, FACE_RECOGNITION_MODEL_PATH]
421
+ optional_models = [YOLO_FACE_MODEL_PATH, ANTI_SPOOF_BIN_MODEL_PATH]
 
 
 
 
422
 
423
+ missing_required = [model for model in required_models if not os.path.exists(model)]
424
+ missing_optional = [model for model in optional_models if not os.path.exists(model)]
425
 
426
+ if missing_required:
427
+ print(f"❌ Critical: Missing required model files: {missing_required}")
 
428
  return False
429
 
430
+ if missing_optional:
431
+ print(f"⚠️ Warning: Missing optional ONNX models: {missing_optional}")
432
+ print("πŸ”„ Will use fallback methods for these features")
433
+
434
+ print("βœ… All critical models are available!")
435
  return True
436
 
437
  # Initialize models
438
  def init_models():
439
+ """Initialize all ML models with robust error handling"""
440
  global yolo_face, anti_spoof_bin, detector, shape_predictor, face_recognition_model
441
 
442
  try:
443
  if not ensure_models_exist():
444
+ print("❌ Cannot initialize critical models - some files are missing")
445
  return False
446
 
447
+ # Initialize dlib models (required)
 
 
 
 
 
 
 
 
448
  print("Loading dlib models...")
449
  detector = dlib.get_frontal_face_detector()
450
  shape_predictor = dlib.shape_predictor(SHAPE_PREDICTOR_PATH)
451
  face_recognition_model = dlib.face_recognition_model_v1(FACE_RECOGNITION_MODEL_PATH)
452
+ print("βœ… Dlib models loaded successfully!")
453
 
454
+ # Initialize ONNX models (with fallback)
455
+ print("Loading ONNX models...")
456
+ try:
457
+ yolo_face = YoloV5FaceDetector(YOLO_FACE_MODEL_PATH, input_size=640, conf_threshold=0.3, iou_threshold=0.45)
458
+ anti_spoof_bin = AntiSpoofBinary(ANTI_SPOOF_BIN_MODEL_PATH, input_size=128, rgb=True, normalize=True, live_index=1)
459
+ except Exception as e:
460
+ print(f"⚠️ ONNX models initialization had issues: {e}")
461
+ print("πŸ”„ Fallback methods will be used")
462
+
463
+ print("βœ… Model initialization complete!")
464
  return True
465
 
466
  except Exception as e:
 
470
  # Initialize models
471
  models_loaded = init_models()
472
 
473
+ # Face processing functions (keeping all your existing functions)
474
  def decode_image(base64_image):
475
  if ',' in base64_image:
476
  base64_image = base64_image.split(',')[1]
 
696
  return None
697
  return sum(vals) / len(vals)
698
 
699
+ # Flask Routes (keeping all your existing routes)
700
  @app.route('/')
701
  def home():
702
  return render_template('home.html')
 
778
  flash('Invalid credentials. Please try again.', 'danger')
779
  return redirect(url_for('login_page'))
780
 
 
 
 
781
  @app.route('/face-login', methods=['POST'])
782
  def face_login():
783
  face_image = request.form.get('face_image')
 
884
  h, w = image.shape[:2]
885
  vis = image.copy()
886
 
887
+ # Face detection (YOLO or OpenCV fallback)
888
  detections = yolo_face.detect(image, max_det=20)
889
  if not detections:
890
  overlay = image_to_data_uri(vis)
 
911
  overlay = image_to_data_uri(vis)
912
  return jsonify({'success': False, 'message': 'Failed to crop face for liveness', 'overlay': overlay})
913
 
914
+ # Anti-spoofing check (ONNX or fallback)
915
  live_prob = anti_spoof_bin.predict_live_prob(face_crop)
916
  is_live = live_prob >= 0.7
917
  label = "LIVE" if is_live else "SPOOF"
 
955
  else:
956
  return jsonify({'success': False, 'message': message, 'overlay': overlay_data})
957
 
 
 
 
958
  @app.route('/logout')
959
  def logout():
960
  session.clear()
 
987
  def health_check():
988
  return jsonify({
989
  'status': 'healthy',
990
+ 'onnx_available': ONNX_AVAILABLE,
991
  'models_loaded': models_loaded,
992
  'database_connected': bool(db),
993
  'timestamp': datetime.now().isoformat()
 
996
  if __name__ == '__main__':
997
  port = int(os.environ.get("PORT", 7860))
998
  print(f"πŸš€ Starting Face Recognition Attendance System on port {port}")
999
+ print(f"πŸ“Š ONNX Runtime Available: {ONNX_AVAILABLE}")
1000
  print(f"πŸ“Š Models loaded: {models_loaded}")
1001
  print(f"πŸ—„οΈ Database connected: {bool(db)}")
1002
  app.run(debug=False, host='0.0.0.0', port=port)