Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |