Spaces:
Sleeping
Sleeping
| """ | |
| Face Comparison Service using InsightFace - Hugging Face Spaces Deployment | |
| Compares two face images and returns similarity score | |
| """ | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| import base64 | |
| import numpy as np | |
| from io import BytesIO | |
| from PIL import Image | |
| from insightface.app import FaceAnalysis | |
| import os | |
| import logging | |
| # Setup logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Suppress InsightFace verbose output | |
| os.environ['INSIGHTFACE_VERBOSE'] = '0' | |
| import warnings | |
| warnings.filterwarnings('ignore') | |
| app = FastAPI( | |
| title="Credify Face Comparison API", | |
| description="Face comparison using InsightFace for KYC verification", | |
| version="1.0.0" | |
| ) | |
| # Initialize model (loads once at startup) | |
| logger.info("Loading InsightFace model...") | |
| face_app = FaceAnalysis(name="buffalo_l", providers=['CPUExecutionProvider']) | |
| face_app.prepare(ctx_id=-1) # -1 = CPU | |
| logger.info("Model loaded successfully!") | |
| # Request/Response models | |
| class FaceComparisonRequest(BaseModel): | |
| aadhaarFaceImage: str # base64 encoded image | |
| liveImage: str # base64 encoded image | |
| class FaceComparisonResponse(BaseModel): | |
| success: bool | |
| similarity: float = None | |
| match: bool = None | |
| threshold: int = None | |
| raw_similarity: float = None | |
| error: str = None | |
| def decode_base64_image(base64_string: str): | |
| """Decode base64 string to PIL Image""" | |
| try: | |
| # Remove data URL prefix if present | |
| if ',' in base64_string: | |
| base64_string = base64_string.split(',')[1] | |
| # Decode base64 | |
| image_data = base64.b64decode(base64_string) | |
| image = Image.open(BytesIO(image_data)) | |
| # Convert to RGB if necessary | |
| if image.mode != 'RGB': | |
| image = image.convert('RGB') | |
| # Convert to numpy array | |
| return np.array(image) | |
| except Exception as e: | |
| logger.error(f"Error decoding image: {e}") | |
| raise ValueError(f"Invalid image format: {str(e)}") | |
| def get_face_embedding(image_array): | |
| """Extract face embedding from image""" | |
| try: | |
| faces = face_app.get(image_array) | |
| if len(faces) == 0: | |
| return None | |
| # Get the first (largest) face | |
| face = faces[0] | |
| # Return the 512-dimensional embedding | |
| return face.embedding | |
| except Exception as e: | |
| logger.error(f"Error extracting face embedding: {e}") | |
| raise | |
| def cosine_similarity(embedding1, embedding2): | |
| """Calculate cosine similarity between two embeddings""" | |
| try: | |
| # Normalize embeddings | |
| norm1 = np.linalg.norm(embedding1) | |
| norm2 = np.linalg.norm(embedding2) | |
| if norm1 == 0 or norm2 == 0: | |
| return 0.0 | |
| # Calculate cosine similarity | |
| similarity = np.dot(embedding1, embedding2) / (norm1 * norm2) | |
| return float(similarity) | |
| except Exception as e: | |
| logger.error(f"Error calculating cosine similarity: {e}") | |
| raise | |
| async def compare_faces(request: FaceComparisonRequest): | |
| """ | |
| Compare two face images and return similarity score | |
| - **aadhaarFaceImage**: Base64 encoded Aadhaar/ID face image | |
| - **liveImage**: Base64 encoded live captured face image | |
| Returns: | |
| - **success**: Whether comparison succeeded | |
| - **similarity**: Similarity percentage (0-100) | |
| - **match**: Whether faces match (based on threshold) | |
| - **threshold**: Matching threshold used | |
| - **error**: Error message if any | |
| """ | |
| try: | |
| logger.info("Starting face comparison...") | |
| # Decode images | |
| aadhaar_image = decode_base64_image(request.aadhaarFaceImage) | |
| live_image = decode_base64_image(request.liveImage) | |
| # Extract face embeddings | |
| logger.info("Extracting embeddings from Aadhaar image...") | |
| aadhaar_embedding = get_face_embedding(aadhaar_image) | |
| logger.info("Extracting embeddings from live image...") | |
| live_embedding = get_face_embedding(live_image) | |
| # Validate embeddings | |
| if aadhaar_embedding is None: | |
| logger.warning("No face detected in Aadhaar image") | |
| return FaceComparisonResponse( | |
| success=False, | |
| error="No face detected in Aadhaar image. Please ensure the image clearly shows your face." | |
| ) | |
| if live_embedding is None: | |
| logger.warning("No face detected in live image") | |
| return FaceComparisonResponse( | |
| success=False, | |
| error="No face detected in live image. Please ensure your face is clearly visible in the camera." | |
| ) | |
| # Calculate similarity | |
| logger.info("Calculating similarity...") | |
| similarity = cosine_similarity(aadhaar_embedding, live_embedding) | |
| # Convert similarity (-1 to 1) to percentage (0-100%) | |
| # InsightFace embeddings typically give similarity in range 0.3-1.0 for same person | |
| similarity_percent = max(0, min(100, ((similarity + 1) / 2) * 100)) | |
| # Threshold for face matching (typically 0.6-0.7 for InsightFace) | |
| # We use 75% as threshold (which corresponds to ~0.5 cosine similarity) | |
| threshold = 75 | |
| is_match = similarity_percent >= threshold | |
| logger.info(f"Comparison complete: {similarity_percent}% match={is_match}") | |
| return FaceComparisonResponse( | |
| success=True, | |
| similarity=round(similarity_percent, 2), | |
| threshold=threshold, | |
| match=is_match, | |
| raw_similarity=round(similarity, 4) # Raw cosine similarity for debugging | |
| ) | |
| except Exception as e: | |
| logger.error(f"Face comparison failed: {e}", exc_info=True) | |
| return FaceComparisonResponse( | |
| success=False, | |
| error=f"Face comparison failed: {str(e)}" | |
| ) | |
| async def health_check(): | |
| """Health check endpoint""" | |
| return { | |
| "status": "healthy", | |
| "model": "InsightFace buffalo_l", | |
| "service": "Credify Face Comparison API" | |
| } | |
| async def root(): | |
| """Root endpoint - shows API info""" | |
| return { | |
| "name": "Credify Face Comparison API", | |
| "version": "1.0.0", | |
| "description": "Face comparison using InsightFace for KYC verification", | |
| "endpoints": { | |
| "POST /compare": "Compare two face images", | |
| "GET /health": "Health check", | |
| "GET /docs": "API documentation" | |
| } | |
| } | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |