Spaces:
Sleeping
Sleeping
Improve face recognition accuracy with RetinaFace detector
Browse files
app.py
CHANGED
|
@@ -194,13 +194,12 @@ def detect_faces_yunet(image):
|
|
| 194 |
return []
|
| 195 |
|
| 196 |
def recognize_face_deepface(image, user_id, user_type='student'):
|
| 197 |
-
"""
|
| 198 |
global total_attempts, correct_recognitions, unauthorized_attempts, inference_times
|
| 199 |
|
| 200 |
temp_files = []
|
| 201 |
|
| 202 |
try:
|
| 203 |
-
# Lazy import DeepFace to save memory at startup
|
| 204 |
from deepface import DeepFace
|
| 205 |
|
| 206 |
start_time = time.time()
|
|
@@ -232,30 +231,62 @@ def recognize_face_deepface(image, user_id, user_type='student'):
|
|
| 232 |
del ref_image_array, ref_image
|
| 233 |
|
| 234 |
try:
|
| 235 |
-
# Use
|
| 236 |
result = DeepFace.verify(
|
| 237 |
img1_path=temp_img_path,
|
| 238 |
img2_path=temp_ref_path,
|
| 239 |
-
model_name="Facenet",
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
| 241 |
)
|
| 242 |
|
| 243 |
is_verified = result["verified"]
|
| 244 |
distance = result["distance"]
|
|
|
|
| 245 |
|
| 246 |
inference_time = time.time() - start_time
|
| 247 |
inference_times.append(inference_time)
|
| 248 |
total_attempts += 1
|
| 249 |
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
correct_recognitions += 1
|
| 252 |
-
return True, f"Face recognized (distance={distance:.3f}, time={inference_time:.2f}s)"
|
| 253 |
else:
|
| 254 |
unauthorized_attempts += 1
|
| 255 |
-
return False, f"
|
| 256 |
|
| 257 |
except Exception as e:
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
except Exception as e:
|
| 261 |
return False, f"Error in face recognition: {str(e)}"
|
|
@@ -271,30 +302,56 @@ def recognize_face_deepface(image, user_id, user_type='student'):
|
|
| 271 |
gc.collect()
|
| 272 |
|
| 273 |
def simple_liveness_check(image):
|
| 274 |
-
"""
|
| 275 |
if eye_cascade is None:
|
| 276 |
-
return 0.
|
| 277 |
|
| 278 |
try:
|
| 279 |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 280 |
eyes = eye_cascade.detectMultiScale(gray, 1.3, 5)
|
| 281 |
|
| 282 |
-
#
|
|
|
|
|
|
|
|
|
|
| 283 |
if len(eyes) >= 2:
|
| 284 |
-
|
| 285 |
elif len(eyes) == 1:
|
| 286 |
-
|
| 287 |
else:
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
# Clean up memory
|
| 291 |
del gray
|
| 292 |
gc.collect()
|
| 293 |
-
return
|
| 294 |
|
| 295 |
except Exception as e:
|
| 296 |
print(f"Error in liveness check: {e}")
|
| 297 |
-
return 0.
|
| 298 |
finally:
|
| 299 |
gc.collect()
|
| 300 |
|
|
@@ -658,6 +715,9 @@ def face_login():
|
|
| 658 |
|
| 659 |
users = collection.find({'face_image': {'$exists': True, '$ne': None}})
|
| 660 |
|
|
|
|
|
|
|
|
|
|
| 661 |
# Use DeepFace for face matching with improved temp file handling
|
| 662 |
temp_login_path = get_unique_temp_path("login_image")
|
| 663 |
cv2.imwrite(temp_login_path, image)
|
|
@@ -674,18 +734,29 @@ def face_login():
|
|
| 674 |
cv2.imwrite(temp_ref_path, ref_image)
|
| 675 |
|
| 676 |
try:
|
|
|
|
| 677 |
result = DeepFace.verify(
|
| 678 |
img1_path=temp_login_path,
|
| 679 |
img2_path=temp_ref_path,
|
| 680 |
model_name="Facenet",
|
| 681 |
-
|
|
|
|
|
|
|
| 682 |
)
|
| 683 |
|
| 684 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
# Create session token
|
| 686 |
token = create_session_token(user[id_field], face_role)
|
| 687 |
|
| 688 |
-
print(f"Face login successful for {user.get('name')},
|
| 689 |
flash('Face login successful!', 'success')
|
| 690 |
|
| 691 |
# Cleanup
|
|
@@ -694,13 +765,11 @@ def face_login():
|
|
| 694 |
os.remove(temp_file)
|
| 695 |
gc.collect()
|
| 696 |
|
| 697 |
-
|
| 698 |
-
return redirect(url_for(dashboard_route, token=token))
|
| 699 |
-
else:
|
| 700 |
-
return redirect(url_for(dashboard_route, token=token))
|
| 701 |
|
| 702 |
if os.path.exists(temp_ref_path):
|
| 703 |
os.remove(temp_ref_path)
|
|
|
|
| 704 |
except Exception as e:
|
| 705 |
print(f"Face verification error: {e}")
|
| 706 |
if os.path.exists(temp_ref_path):
|
|
@@ -717,8 +786,13 @@ def face_login():
|
|
| 717 |
finally:
|
| 718 |
gc.collect()
|
| 719 |
|
| 720 |
-
|
| 721 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
return redirect(url_for('login_page'))
|
| 723 |
|
| 724 |
except Exception as e:
|
|
@@ -768,10 +842,12 @@ def auto_face_login():
|
|
| 768 |
img1_path=temp_auto_path,
|
| 769 |
img2_path=temp_ref_path,
|
| 770 |
model_name="Facenet",
|
| 771 |
-
|
|
|
|
|
|
|
| 772 |
)
|
| 773 |
|
| 774 |
-
if result["
|
| 775 |
# Create session token
|
| 776 |
token = create_session_token(user[id_field], face_role)
|
| 777 |
|
|
@@ -960,14 +1036,20 @@ def mark_attendance():
|
|
| 960 |
)
|
| 961 |
return jsonify({'success': False, 'message': 'Failed to crop face for liveness', 'overlay': overlay})
|
| 962 |
|
| 963 |
-
# 2)
|
| 964 |
live_prob = simple_liveness_check(face_crop)
|
| 965 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 966 |
label = "LIVE" if is_live else "SPOOF"
|
| 967 |
color = (0, 200, 0) if is_live else (0, 0, 255)
|
| 968 |
draw_live_overlay(vis, (x1e, y1e, x2e, y2e), label, live_prob, color)
|
| 969 |
overlay_data = image_to_data_uri(vis)
|
| 970 |
|
|
|
|
|
|
|
| 971 |
if not is_live:
|
| 972 |
log_metrics_event_normalized(
|
| 973 |
event="reject_true",
|
|
@@ -981,7 +1063,11 @@ def mark_attendance():
|
|
| 981 |
client_ip=client_ip,
|
| 982 |
reason="liveness_fail"
|
| 983 |
)
|
| 984 |
-
return jsonify({
|
|
|
|
|
|
|
|
|
|
|
|
|
| 985 |
|
| 986 |
# 3) Face recognition using DeepFace
|
| 987 |
success, message = recognize_face_deepface(image, student_id, user_type='student')
|
|
@@ -1125,7 +1211,8 @@ def liveness_preview():
|
|
| 1125 |
})
|
| 1126 |
|
| 1127 |
live_prob = simple_liveness_check(face_crop)
|
| 1128 |
-
threshold
|
|
|
|
| 1129 |
label = "LIVE" if live_prob >= threshold else "SPOOF"
|
| 1130 |
color = (0, 200, 0) if label == "LIVE" else (0, 0, 255)
|
| 1131 |
|
|
@@ -1306,7 +1393,7 @@ def metrics_data():
|
|
| 1306 |
if r.get("decision") == "spoof_blocked":
|
| 1307 |
r["liveness_pass"] = False
|
| 1308 |
elif isinstance(r.get("live_prob"), (int, float)):
|
| 1309 |
-
r["liveness_pass"] = bool(r["live_prob"] >= 0.
|
| 1310 |
else:
|
| 1311 |
r["liveness_pass"] = None
|
| 1312 |
normalized_recent.append(r)
|
|
@@ -1378,6 +1465,7 @@ def health_check():
|
|
| 1378 |
'platform': 'hugging_face',
|
| 1379 |
'session_type': 'token_based',
|
| 1380 |
'proxy_fix': 'enabled',
|
|
|
|
| 1381 |
'timestamp': datetime.now().isoformat()
|
| 1382 |
}), 200
|
| 1383 |
|
|
@@ -1409,6 +1497,7 @@ def debug_session_detailed():
|
|
| 1409 |
'remote_addr': request.remote_addr,
|
| 1410 |
'flask_secret_key_length': len(app.secret_key),
|
| 1411 |
'session_interface_type': str(type(app.session_interface)),
|
|
|
|
| 1412 |
'timestamp': datetime.now().isoformat()
|
| 1413 |
})
|
| 1414 |
|
|
@@ -1430,6 +1519,49 @@ def test_session():
|
|
| 1430 |
'test_successful': True
|
| 1431 |
})
|
| 1432 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1433 |
@app.route('/cleanup', methods=['POST'])
|
| 1434 |
def manual_cleanup():
|
| 1435 |
"""Manual cleanup endpoint for memory management"""
|
|
@@ -1442,5 +1574,5 @@ def manual_cleanup():
|
|
| 1442 |
# MAIN APPLICATION ENTRY POINT
|
| 1443 |
if __name__ == '__main__':
|
| 1444 |
port = int(os.environ.get('PORT', 7860)) # Hugging Face uses port 7860
|
| 1445 |
-
print(f"Starting Flask app on port {port} with token-based authentication")
|
| 1446 |
app.run(host='0.0.0.0', port=port, debug=False)
|
|
|
|
| 194 |
return []
|
| 195 |
|
| 196 |
def recognize_face_deepface(image, user_id, user_type='student'):
|
| 197 |
+
"""Enhanced face recognition with better detector and alignment"""
|
| 198 |
global total_attempts, correct_recognitions, unauthorized_attempts, inference_times
|
| 199 |
|
| 200 |
temp_files = []
|
| 201 |
|
| 202 |
try:
|
|
|
|
| 203 |
from deepface import DeepFace
|
| 204 |
|
| 205 |
start_time = time.time()
|
|
|
|
| 231 |
del ref_image_array, ref_image
|
| 232 |
|
| 233 |
try:
|
| 234 |
+
# CRITICAL FIX: Use better detector and alignment for accuracy
|
| 235 |
result = DeepFace.verify(
|
| 236 |
img1_path=temp_img_path,
|
| 237 |
img2_path=temp_ref_path,
|
| 238 |
+
model_name="Facenet",
|
| 239 |
+
detector_backend="retinaface", # More robust than default opencv
|
| 240 |
+
enforce_detection=False, # Allow processing even if detection is uncertain
|
| 241 |
+
align=True, # Enable face alignment for better matching
|
| 242 |
+
distance_metric="cosine" # Often works better than euclidean
|
| 243 |
)
|
| 244 |
|
| 245 |
is_verified = result["verified"]
|
| 246 |
distance = result["distance"]
|
| 247 |
+
threshold = result["threshold"]
|
| 248 |
|
| 249 |
inference_time = time.time() - start_time
|
| 250 |
inference_times.append(inference_time)
|
| 251 |
total_attempts += 1
|
| 252 |
|
| 253 |
+
# Lower threshold for more lenient matching (adjust as needed)
|
| 254 |
+
custom_threshold = 0.45 # Default is usually 0.4, try 0.45-0.5 for more lenient
|
| 255 |
+
is_verified_custom = distance < custom_threshold
|
| 256 |
+
|
| 257 |
+
if is_verified_custom:
|
| 258 |
correct_recognitions += 1
|
| 259 |
+
return True, f"Face recognized (distance={distance:.3f}, threshold={custom_threshold}, time={inference_time:.2f}s)"
|
| 260 |
else:
|
| 261 |
unauthorized_attempts += 1
|
| 262 |
+
return False, f"Face not recognized (distance={distance:.3f}, required < {custom_threshold})"
|
| 263 |
|
| 264 |
except Exception as e:
|
| 265 |
+
# Fallback: try with opencv detector if retinaface fails
|
| 266 |
+
try:
|
| 267 |
+
print(f"RetinaFace failed, trying OpenCV detector: {e}")
|
| 268 |
+
result = DeepFace.verify(
|
| 269 |
+
img1_path=temp_img_path,
|
| 270 |
+
img2_path=temp_ref_path,
|
| 271 |
+
model_name="Facenet",
|
| 272 |
+
detector_backend="opencv",
|
| 273 |
+
enforce_detection=False,
|
| 274 |
+
align=True
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
distance = result["distance"]
|
| 278 |
+
custom_threshold = 0.5 # More lenient for fallback
|
| 279 |
+
is_verified_custom = distance < custom_threshold
|
| 280 |
+
|
| 281 |
+
if is_verified_custom:
|
| 282 |
+
correct_recognitions += 1
|
| 283 |
+
return True, f"Face recognized with fallback (distance={distance:.3f})"
|
| 284 |
+
else:
|
| 285 |
+
unauthorized_attempts += 1
|
| 286 |
+
return False, f"Face not recognized with fallback (distance={distance:.3f})"
|
| 287 |
+
|
| 288 |
+
except Exception as e2:
|
| 289 |
+
return False, f"DeepFace verification error: {str(e2)}"
|
| 290 |
|
| 291 |
except Exception as e:
|
| 292 |
return False, f"Error in face recognition: {str(e)}"
|
|
|
|
| 302 |
gc.collect()
|
| 303 |
|
| 304 |
def simple_liveness_check(image):
|
| 305 |
+
"""Improved liveness detection using multiple methods - memory optimized"""
|
| 306 |
if eye_cascade is None:
|
| 307 |
+
return 0.65 # Default score if cascade not available
|
| 308 |
|
| 309 |
try:
|
| 310 |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 311 |
eyes = eye_cascade.detectMultiScale(gray, 1.3, 5)
|
| 312 |
|
| 313 |
+
# Enhanced liveness scoring with multiple factors
|
| 314 |
+
liveness_score = 0.0
|
| 315 |
+
|
| 316 |
+
# Factor 1: Eye detection (40% weight)
|
| 317 |
if len(eyes) >= 2:
|
| 318 |
+
liveness_score += 0.4 # Both eyes detected
|
| 319 |
elif len(eyes) == 1:
|
| 320 |
+
liveness_score += 0.25 # One eye detected
|
| 321 |
else:
|
| 322 |
+
liveness_score += 0.1 # No eyes detected but still some base score
|
| 323 |
+
|
| 324 |
+
# Factor 2: Image quality assessment (30% weight)
|
| 325 |
+
# Check for blur and contrast
|
| 326 |
+
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
|
| 327 |
+
if laplacian_var > 100: # Good sharpness
|
| 328 |
+
liveness_score += 0.3
|
| 329 |
+
elif laplacian_var > 50: # Moderate sharpness
|
| 330 |
+
liveness_score += 0.2
|
| 331 |
+
else:
|
| 332 |
+
liveness_score += 0.1 # Poor sharpness but not zero
|
| 333 |
+
|
| 334 |
+
# Factor 3: Face size and position (30% weight)
|
| 335 |
+
# Larger faces are more likely to be real (closer to camera)
|
| 336 |
+
face_area = image.shape[0] * image.shape[1]
|
| 337 |
+
if face_area > 10000: # Decent face size
|
| 338 |
+
liveness_score += 0.3
|
| 339 |
+
elif face_area > 5000: # Smaller but acceptable
|
| 340 |
+
liveness_score += 0.2
|
| 341 |
+
else:
|
| 342 |
+
liveness_score += 0.1 # Very small face
|
| 343 |
+
|
| 344 |
+
# Ensure score is between 0 and 1
|
| 345 |
+
liveness_score = min(1.0, max(0.0, liveness_score))
|
| 346 |
|
| 347 |
# Clean up memory
|
| 348 |
del gray
|
| 349 |
gc.collect()
|
| 350 |
+
return liveness_score
|
| 351 |
|
| 352 |
except Exception as e:
|
| 353 |
print(f"Error in liveness check: {e}")
|
| 354 |
+
return 0.6 # Return neutral score on error
|
| 355 |
finally:
|
| 356 |
gc.collect()
|
| 357 |
|
|
|
|
| 715 |
|
| 716 |
users = collection.find({'face_image': {'$exists': True, '$ne': None}})
|
| 717 |
|
| 718 |
+
best_match = None
|
| 719 |
+
best_distance = float('inf')
|
| 720 |
+
|
| 721 |
# Use DeepFace for face matching with improved temp file handling
|
| 722 |
temp_login_path = get_unique_temp_path("login_image")
|
| 723 |
cv2.imwrite(temp_login_path, image)
|
|
|
|
| 734 |
cv2.imwrite(temp_ref_path, ref_image)
|
| 735 |
|
| 736 |
try:
|
| 737 |
+
# Use improved recognition settings
|
| 738 |
result = DeepFace.verify(
|
| 739 |
img1_path=temp_login_path,
|
| 740 |
img2_path=temp_ref_path,
|
| 741 |
model_name="Facenet",
|
| 742 |
+
detector_backend="retinaface",
|
| 743 |
+
enforce_detection=False,
|
| 744 |
+
align=True
|
| 745 |
)
|
| 746 |
|
| 747 |
+
distance = result["distance"]
|
| 748 |
+
|
| 749 |
+
# Keep track of best match
|
| 750 |
+
if distance < best_distance:
|
| 751 |
+
best_distance = distance
|
| 752 |
+
best_match = user
|
| 753 |
+
|
| 754 |
+
# More lenient threshold for face login
|
| 755 |
+
if distance < 0.5: # Increased from default 0.4
|
| 756 |
# Create session token
|
| 757 |
token = create_session_token(user[id_field], face_role)
|
| 758 |
|
| 759 |
+
print(f"Face login successful for {user.get('name')}, distance: {distance:.3f}")
|
| 760 |
flash('Face login successful!', 'success')
|
| 761 |
|
| 762 |
# Cleanup
|
|
|
|
| 765 |
os.remove(temp_file)
|
| 766 |
gc.collect()
|
| 767 |
|
| 768 |
+
return redirect(url_for(dashboard_route, token=token))
|
|
|
|
|
|
|
|
|
|
| 769 |
|
| 770 |
if os.path.exists(temp_ref_path):
|
| 771 |
os.remove(temp_ref_path)
|
| 772 |
+
|
| 773 |
except Exception as e:
|
| 774 |
print(f"Face verification error: {e}")
|
| 775 |
if os.path.exists(temp_ref_path):
|
|
|
|
| 786 |
finally:
|
| 787 |
gc.collect()
|
| 788 |
|
| 789 |
+
# Provide better error message with best match info
|
| 790 |
+
if best_match:
|
| 791 |
+
print(f"Closest match was {best_match.get('name')} with distance {best_distance:.3f}")
|
| 792 |
+
flash(f'Face recognition failed. Closest match distance: {best_distance:.3f}. Please try again with better lighting.', 'warning')
|
| 793 |
+
else:
|
| 794 |
+
flash('No face detected or face not found in database. Please try again.', 'danger')
|
| 795 |
+
|
| 796 |
return redirect(url_for('login_page'))
|
| 797 |
|
| 798 |
except Exception as e:
|
|
|
|
| 842 |
img1_path=temp_auto_path,
|
| 843 |
img2_path=temp_ref_path,
|
| 844 |
model_name="Facenet",
|
| 845 |
+
detector_backend="retinaface",
|
| 846 |
+
enforce_detection=False,
|
| 847 |
+
align=True
|
| 848 |
)
|
| 849 |
|
| 850 |
+
if result["distance"] < 0.5: # More lenient threshold
|
| 851 |
# Create session token
|
| 852 |
token = create_session_token(user[id_field], face_role)
|
| 853 |
|
|
|
|
| 1036 |
)
|
| 1037 |
return jsonify({'success': False, 'message': 'Failed to crop face for liveness', 'overlay': overlay})
|
| 1038 |
|
| 1039 |
+
# 2) CRITICAL FIX: Enhanced liveness check with lower threshold
|
| 1040 |
live_prob = simple_liveness_check(face_crop)
|
| 1041 |
+
|
| 1042 |
+
# FIXED: Lower threshold to reduce false positives (was 0.7, now 0.5)
|
| 1043 |
+
liveness_threshold = 0.5 # More lenient to avoid false spoof detection
|
| 1044 |
+
is_live = live_prob >= liveness_threshold
|
| 1045 |
+
|
| 1046 |
label = "LIVE" if is_live else "SPOOF"
|
| 1047 |
color = (0, 200, 0) if is_live else (0, 0, 255)
|
| 1048 |
draw_live_overlay(vis, (x1e, y1e, x2e, y2e), label, live_prob, color)
|
| 1049 |
overlay_data = image_to_data_uri(vis)
|
| 1050 |
|
| 1051 |
+
print(f"Liveness check - Score: {live_prob:.3f}, Threshold: {liveness_threshold}, Result: {label}")
|
| 1052 |
+
|
| 1053 |
if not is_live:
|
| 1054 |
log_metrics_event_normalized(
|
| 1055 |
event="reject_true",
|
|
|
|
| 1063 |
client_ip=client_ip,
|
| 1064 |
reason="liveness_fail"
|
| 1065 |
)
|
| 1066 |
+
return jsonify({
|
| 1067 |
+
'success': False,
|
| 1068 |
+
'message': f'Liveness check failed (score={live_prob:.2f}, need>={liveness_threshold}). Ensure good lighting and face visibility.',
|
| 1069 |
+
'overlay': overlay_data
|
| 1070 |
+
})
|
| 1071 |
|
| 1072 |
# 3) Face recognition using DeepFace
|
| 1073 |
success, message = recognize_face_deepface(image, student_id, user_type='student')
|
|
|
|
| 1211 |
})
|
| 1212 |
|
| 1213 |
live_prob = simple_liveness_check(face_crop)
|
| 1214 |
+
# Use same threshold as attendance marking
|
| 1215 |
+
threshold = 0.5
|
| 1216 |
label = "LIVE" if live_prob >= threshold else "SPOOF"
|
| 1217 |
color = (0, 200, 0) if label == "LIVE" else (0, 0, 255)
|
| 1218 |
|
|
|
|
| 1393 |
if r.get("decision") == "spoof_blocked":
|
| 1394 |
r["liveness_pass"] = False
|
| 1395 |
elif isinstance(r.get("live_prob"), (int, float)):
|
| 1396 |
+
r["liveness_pass"] = bool(r["live_prob"] >= 0.5) # Updated threshold
|
| 1397 |
else:
|
| 1398 |
r["liveness_pass"] = None
|
| 1399 |
normalized_recent.append(r)
|
|
|
|
| 1465 |
'platform': 'hugging_face',
|
| 1466 |
'session_type': 'token_based',
|
| 1467 |
'proxy_fix': 'enabled',
|
| 1468 |
+
'liveness_threshold': 0.5,
|
| 1469 |
'timestamp': datetime.now().isoformat()
|
| 1470 |
}), 200
|
| 1471 |
|
|
|
|
| 1497 |
'remote_addr': request.remote_addr,
|
| 1498 |
'flask_secret_key_length': len(app.secret_key),
|
| 1499 |
'session_interface_type': str(type(app.session_interface)),
|
| 1500 |
+
'liveness_threshold': 0.5,
|
| 1501 |
'timestamp': datetime.now().isoformat()
|
| 1502 |
})
|
| 1503 |
|
|
|
|
| 1519 |
'test_successful': True
|
| 1520 |
})
|
| 1521 |
|
| 1522 |
+
@app.route('/debug-liveness', methods=['POST'])
|
| 1523 |
+
def debug_liveness():
|
| 1524 |
+
"""Debug route to test liveness detection settings"""
|
| 1525 |
+
data = request.json or {}
|
| 1526 |
+
token = data.get('session_token')
|
| 1527 |
+
|
| 1528 |
+
if not token or not validate_session_token(token):
|
| 1529 |
+
return jsonify({'success': False, 'message': 'Not authenticated'})
|
| 1530 |
+
|
| 1531 |
+
try:
|
| 1532 |
+
face_image = data.get('face_image')
|
| 1533 |
+
if not face_image:
|
| 1534 |
+
return jsonify({'success': False, 'message': 'No image received'})
|
| 1535 |
+
|
| 1536 |
+
image = decode_image(face_image)
|
| 1537 |
+
if image is None:
|
| 1538 |
+
return jsonify({'success': False, 'message': 'Invalid image data'})
|
| 1539 |
+
|
| 1540 |
+
detections = detect_faces_yunet(image)
|
| 1541 |
+
if not detections:
|
| 1542 |
+
return jsonify({'success': False, 'message': 'No face detected'})
|
| 1543 |
+
|
| 1544 |
+
best = max(detections, key=lambda d: d["score"])
|
| 1545 |
+
x1, y1, x2, y2 = [int(v) for v in best["bbox"]]
|
| 1546 |
+
x1e, y1e, x2e, y2e = expand_and_clip_box((x1, y1, x2, y2), scale=1.2, w=image.shape[1], h=image.shape[0])
|
| 1547 |
+
face_crop = image[y1e:y2e, x1e:x2e]
|
| 1548 |
+
|
| 1549 |
+
if face_crop.size == 0:
|
| 1550 |
+
return jsonify({'success': False, 'message': 'Failed to crop face'})
|
| 1551 |
+
|
| 1552 |
+
live_prob = simple_liveness_check(face_crop)
|
| 1553 |
+
|
| 1554 |
+
return jsonify({
|
| 1555 |
+
'success': True,
|
| 1556 |
+
'liveness_score': float(live_prob),
|
| 1557 |
+
'threshold': 0.5,
|
| 1558 |
+
'would_pass': live_prob >= 0.5,
|
| 1559 |
+
'face_area': int(face_crop.shape[0] * face_crop.shape[1]),
|
| 1560 |
+
'debug_info': f"Score: {live_prob:.3f}, Threshold: 0.5, Result: {'PASS' if live_prob >= 0.5 else 'FAIL'}"
|
| 1561 |
+
})
|
| 1562 |
+
except Exception as e:
|
| 1563 |
+
return jsonify({'success': False, 'message': f'Debug error: {str(e)}'})
|
| 1564 |
+
|
| 1565 |
@app.route('/cleanup', methods=['POST'])
|
| 1566 |
def manual_cleanup():
|
| 1567 |
"""Manual cleanup endpoint for memory management"""
|
|
|
|
| 1574 |
# MAIN APPLICATION ENTRY POINT
|
| 1575 |
if __name__ == '__main__':
|
| 1576 |
port = int(os.environ.get('PORT', 7860)) # Hugging Face uses port 7860
|
| 1577 |
+
print(f"Starting Flask app on port {port} with token-based authentication and improved liveness detection")
|
| 1578 |
app.run(host='0.0.0.0', port=port, debug=False)
|