stashface / models /face_recognition.py
cc1234's picture
refactor: remove get_face_embeddings method from EnsembleFaceRecognition class
f5c9283
raw
history blame
6.38 kB
import numpy as np
from typing import Dict, List, Tuple
import cv2
from deepface import DeepFace
from deepface.modules import modeling, preprocessing
class EnsembleFaceRecognition:
def __init__(self, model_weights: Dict[str, float] = None):
"""
Initialize ensemble face recognition system.
Parameters:
model_weights: Dictionary mapping model names to their weights
If None, all models are weighted equally
"""
self.model_weights = model_weights or {}
self.boost_factor = 1.8
def normalize_distances(self, distances: np.ndarray) -> np.ndarray:
"""Normalize distances to [0,1] range within each model's predictions"""
min_dist = np.min(distances)
max_dist = np.max(distances)
if max_dist == min_dist:
return np.zeros_like(distances)
return (distances - min_dist) / (max_dist - min_dist)
def compute_model_confidence(self,
distances: np.ndarray,
temperature: float = 0.1) -> np.ndarray:
"""Convert distances to confidence scores for a single model"""
normalized_distances = self.normalize_distances(distances)
exp_distances = np.exp(-normalized_distances / temperature)
return exp_distances / np.sum(exp_distances)
def _preprocess_face_batch(self, faces: np.ndarray, target_size: Tuple[int, int], normalization: str) -> np.ndarray:
"""Preprocess a batch of face images for model inference"""
batch_size = faces.shape[0]
processed_faces = []
for i in range(batch_size):
face = faces[i]
# Convert RGB to BGR (DeepFace expects BGR)
face = face[:, :, ::-1]
# Resize to model input size
resized = preprocessing.resize_image(face, target_size)
# Normalize
normalized = preprocessing.normalize_input(resized, normalization)
processed_faces.append(normalized)
# Stack into batch and remove the extra dimension added by resize_image
batch = np.vstack(processed_faces)
return batch
def get_face_embeddings_batch(self, faces: np.ndarray) -> Dict[str, np.ndarray]:
"""Get face embeddings for a batch of images efficiently
Args:
faces: np.ndarray of shape (batch_size, height, width, channels)
Returns:
Dict with 'facenet' and 'arc' keys containing batched embeddings
"""
# Load models (cached by DeepFace)
facenet_model = modeling.build_model(task="facial_recognition", model_name="Facenet512")
arcface_model = modeling.build_model(task="facial_recognition", model_name="ArcFace")
# Preprocess faces for each model
facenet_batch = self._preprocess_face_batch(faces, facenet_model.input_shape, "Facenet2018")
arcface_batch = self._preprocess_face_batch(faces, arcface_model.input_shape, "ArcFace")
# Get embeddings using direct model inference (bypassing DeepFace.represent)
facenet_embeddings = facenet_model.model(facenet_batch, training=False).numpy()
arcface_embeddings = arcface_model.model(arcface_batch, training=False).numpy()
return {
'facenet': facenet_embeddings,
'arc': arcface_embeddings
}
def ensemble_prediction(self,
model_predictions: Dict[str, Tuple[List[str], List[float]]],
temperature: float = 0.1,
min_agreement: float = 0.5) -> List[Tuple[str, float]]:
"""
Combine predictions from multiple models.
Parameters:
model_predictions: Dictionary mapping model names to their (distances, names) predictions
temperature: Temperature parameter for softmax scaling
min_agreement: Minimum agreement threshold between models
Returns:
final_predictions: List of (name, confidence) tuples
"""
# Initialize vote counting
vote_dict = {}
confidence_dict = {}
# Process each model's predictions
for model_name, (names, distances) in model_predictions.items():
# Get model weight (default to 1.0 if not specified)
model_weight = self.model_weights.get(model_name, 1.0)
# Compute confidence scores for this model
confidences = self.compute_model_confidence(np.array(distances), temperature)
# Add weighted votes for top prediction
top_name = names[0]
top_confidence = confidences[0]
vote_dict[top_name] = vote_dict.get(top_name, 0) + model_weight
confidence_dict[top_name] = confidence_dict.get(top_name, [])
confidence_dict[top_name].append(top_confidence)
# Normalize votes
total_weight = sum(self.model_weights.values()) if self.model_weights else len(model_predictions)
# Compute final results with minimum agreement check
final_results = []
for name, votes in vote_dict.items():
normalized_votes = votes / total_weight
# Only include results that meet minimum agreement threshold
if normalized_votes >= min_agreement:
avg_confidence = np.mean(confidence_dict[name])
final_score = normalized_votes * avg_confidence * self.boost_factor
final_score = min(final_score, 1.0) # Cap at 1.0
final_results.append((name, final_score))
# Sort by final score
final_results.sort(key=lambda x: x[1], reverse=True)
return final_results
def extract_faces(image):
"""Extract faces from an image using DeepFace"""
return DeepFace.extract_faces(image, detector_backend="yolov8")
def extract_faces_mediapipe(image, enforce_detection=False, align=False):
"""Extract faces from an image using MediaPipe backend"""
return DeepFace.extract_faces(image, detector_backend="mediapipe",
enforce_detection=enforce_detection,
align=align)