import face_recognition import numpy as np import math import base64 from io import BytesIO from PIL import Image def load_image_from_base64(base64_str): """ Converts a base64 encoded image string to a NumPy array in RGB format. Parameters: base64_str (str): The base64-encoded image string. It may optionally include a header like "data:image/jpeg;base64,". Returns: np.ndarray: The image as a NumPy array in RGB format. """ # If the base64 string includes a header (e.g., data:image/jpeg;base64,), remove it. if ',' in base64_str: base64_str = base64_str.split(',')[1] # Decode the base64 string to bytes image_data = base64.b64decode(base64_str) # Open the image using PIL image = Image.open(BytesIO(image_data)) # Convert the image to RGB (in case it is in another format) image = image.convert("RGB") # Convert the PIL image to a NumPy array return np.array(image) def compare_id_images(id_card_image_base64, id_holder_image_base64, threshold=0.6): """ Compares two images—one from an ID card and one of the ID holder—by extracting their facial features and computing a similarity score. A lower Euclidean distance between the face embeddings indicates higher similarity. Parameters: id_card_image_path (str): Path to the image on the ID card. id_holder_image_path (str): Path to the image of the ID holder. threshold (float): A distance threshold below which faces are considered similar. The default of 0.6 is commonly used with the face_recognition library. Returns: similarity_score (float): A score between 0 and 1 indicating similarity (1 is an exact match). distance (float): The Euclidean distance between the face embeddings. Raises: ValueError: If no face is detected in one of the images. """ # Load images from the provided file paths. id_card_image = load_image_from_base64(id_card_image_base64) id_holder_image = load_image_from_base64(id_holder_image_base64) # Extract face encodings. (Each encoding is a 128-dimension vector.) id_card_encodings = face_recognition.face_encodings(id_card_image) if not id_card_encodings: raise ValueError("No face found in the ID card image.") id_holder_encodings = face_recognition.face_encodings(id_holder_image) if not id_holder_encodings: raise ValueError("No face found in the ID holder image.") # Use the first detected face in each image. id_card_encoding = id_card_encodings[0] id_holder_encoding = id_holder_encodings[0] # Compute the Euclidean distance between the two face embeddings. distance = np.linalg.norm(id_card_encoding - id_holder_encoding) # Convert the Euclidean distance to a similarity score between 0 and 1. # One effective approach is to use a Gaussian (exponential decay) function. # # We want the similarity to be 1 when distance is 0, and to drop to ~0.5 when distance equals the threshold. # Using the Gaussian: # similarity = exp( - (distance**2) / (2 * sigma**2) ) # We solve for sigma such that similarity(threshold) = 0.5: # exp( - (threshold**2) / (2 * sigma**2) ) = 0.5 => sigma = threshold / sqrt(2*ln(2)) sigma = threshold / math.sqrt(2 * math.log(2)) similarity_score = math.exp(- (distance ** 2) / (2 * sigma ** 2)) return similarity_score, distance # Example usage: if __name__ == '__main__': # Replace these strings with your actual base64 encoded images. # They may include the data header (e.g., "data:image/jpeg;base64,...") or be raw base64 strings. id_card_base64 = 'image14.jpg' id_holder_base64 = 'image8.jpg' try: similarity, dist = compare_id_images(id_card_base64, id_holder_base64) print(f"Similarity score: {similarity:.2f} (Distance: {dist:.2f})") # Optionally, you can decide on a cutoff based on your validation. # For instance, if similarity is above 0.5, you might consider it a match. if similarity > 0.5: print("Faces match: The ID card photo and ID holder photo appear to be of the same person.") else: print("Faces do not match.") except ValueError as e: print("Error:", e)