""" Face Verification Module Uses DeepFace or face_recognition for face embedding extraction and comparison """ import cv2 import numpy as np import logging from typing import Optional, Tuple from scipy.spatial.distance import cosine logger = logging.getLogger(__name__) class FaceVerifier: """ Face verification using deep learning embeddings """ def __init__(self, model_name: str = "Facenet"): """ Initialize face verifier Args: model_name: Model to use ('Facenet', 'VGG-Face', 'OpenFace', 'DeepFace') """ self.model_name = model_name self.backend = None self._initialize_backend() def _initialize_backend(self): """Initialize the face recognition backend""" try: # Use custom implementation (no external dependencies) self.backend = "custom" logger.info("✓ Using custom face verification (OpenCV-based)") except Exception as e: logger.error(f"Backend initialization error: {e}") self.backend = "custom" def extract_embedding(self, image: np.ndarray, face: Tuple[int, int, int, int]) -> Optional[np.ndarray]: """ Extract face embedding vector Args: image: Input image (BGR format) face: Face bounding box (x, y, w, h) Returns: Face embedding vector or None """ try: return self._extract_custom(image, face) except Exception as e: logger.error(f"Embedding extraction error: {e}") return None def _extract_custom(self, image: np.ndarray, face: Tuple[int, int, int, int]) -> Optional[np.ndarray]: """ Extract custom embedding using OpenCV and traditional CV features Optimized for face verification without heavy ML libraries """ try: x, y, w, h = face face_roi = image[y:y+h, x:x+w] # Resize to standard size face_resized = cv2.resize(face_roi, (128, 128)) # Convert to grayscale gray = cv2.cvtColor(face_resized, cv2.COLOR_BGR2GRAY) # Extract multiple features for robust embedding features = [] # 1. HOG (Histogram of Oriented Gradients) features hog = cv2.HOGDescriptor((128, 128), (16, 16), (8, 8), (8, 8), 9) hog_features = hog.compute(gray) features.append(hog_features.flatten()) # 2. LBP (Local Binary Patterns) features lbp = self._compute_lbp(gray) lbp_hist, _ = np.histogram(lbp.ravel(), bins=256, range=(0, 256)) lbp_hist = lbp_hist.astype("float") lbp_hist /= (lbp_hist.sum() + 1e-7) features.append(lbp_hist) # 3. Pixel intensity histogram hist = cv2.calcHist([gray], [0], None, [256], [0, 256]) hist = hist.flatten() hist /= (hist.sum() + 1e-7) features.append(hist) # 4. Color histograms (RGB channels) for i in range(3): color_hist = cv2.calcHist([face_resized], [i], None, [64], [0, 256]) color_hist = color_hist.flatten() color_hist /= (color_hist.sum() + 1e-7) features.append(color_hist) # 5. Edge features edges = cv2.Canny(gray, 50, 150) edge_hist, _ = np.histogram(edges.ravel(), bins=64, range=(0, 256)) edge_hist = edge_hist.astype("float") edge_hist /= (edge_hist.sum() + 1e-7) features.append(edge_hist) # Concatenate all features embedding = np.concatenate(features) # Normalize to unit length embedding = embedding / (np.linalg.norm(embedding) + 1e-7) return embedding except Exception as e: logger.error(f"Custom extraction error: {e}") return None def _compute_lbp(self, image: np.ndarray, radius: int = 1, n_points: int = 8) -> np.ndarray: """Compute Local Binary Pattern""" h, w = image.shape lbp = np.zeros((h, w), dtype=np.uint8) for i in range(radius, h - radius): for j in range(radius, w - radius): center = image[i, j] code = 0 for k in range(n_points): angle = 2 * np.pi * k / n_points x = int(round(i + radius * np.cos(angle))) y = int(round(j + radius * np.sin(angle))) if 0 <= x < h and 0 <= y < w: if image[x, y] >= center: code |= (1 << k) lbp[i, j] = code return lbp def compare_embeddings(self, embedding1: np.ndarray, embedding2: np.ndarray) -> float: """ Compare two face embeddings Args: embedding1: First face embedding embedding2: Second face embedding Returns: Similarity score (0 to 1, higher is more similar) """ try: # Ensure embeddings are numpy arrays emb1 = np.array(embedding1).flatten() emb2 = np.array(embedding2).flatten() # Check if embeddings have same dimension if emb1.shape != emb2.shape: logger.error(f"Embedding dimension mismatch: {emb1.shape} vs {emb2.shape}") return 0.0 # Compute cosine similarity # cosine distance = 1 - cosine similarity distance = cosine(emb1, emb2) similarity = 1 - distance # Ensure similarity is in [0, 1] similarity = max(0.0, min(1.0, similarity)) return similarity except Exception as e: logger.error(f"Embedding comparison error: {e}") return 0.0 def verify(self, image1: np.ndarray, face1: Tuple[int, int, int, int], image2: np.ndarray, face2: Tuple[int, int, int, int], threshold: float = 0.6) -> Tuple[bool, float]: """ Verify if two faces belong to the same person Args: image1: First image face1: Face bounding box in first image image2: Second image face2: Face bounding box in second image threshold: Similarity threshold for verification Returns: (is_same_person, similarity_score) """ try: # Extract embeddings emb1 = self.extract_embedding(image1, face1) emb2 = self.extract_embedding(image2, face2) if emb1 is None or emb2 is None: logger.error("Failed to extract embeddings") return False, 0.0 # Compare embeddings similarity = self.compare_embeddings(emb1, emb2) # Determine if same person is_same = similarity >= threshold return is_same, similarity except Exception as e: logger.error(f"Verification error: {e}") return False, 0.0