Parth421's picture
Upload 3 files
d1d65d7 verified
"""
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
@app.post("/compare", response_model=FaceComparisonResponse)
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)}"
)
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {
"status": "healthy",
"model": "InsightFace buffalo_l",
"service": "Credify Face Comparison API"
}
@app.get("/")
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)