Spaces:
Sleeping
Sleeping
File size: 3,164 Bytes
d2885a7 ba73462 d2885a7 ba73462 d2885a7 ba73462 d2885a7 ba73462 d2885a7 ba73462 d2885a7 ba73462 d2885a7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | """
Embedding and similarity utilities for face recognition.
Uses OpenCV HSV color histograms for face embeddings (114-dimensional vectors).
No external deep learning libraries required β cv2 and numpy only.
Note: facenet-pytorch has been removed. Face cropping is handled by the ML model
(runtime_detector.py). Embeddings here are used only for deduplication.
"""
import cv2
import numpy as np
def cosine_similarity(embedding1: list[float], embedding2: list[float]) -> float:
"""
Compute cosine similarity between two embeddings.
Args:
embedding1: First embedding vector
embedding2: Second embedding vector
Returns:
Similarity score (0.0 to 1.0, higher = more similar)
"""
e1 = np.array(embedding1, dtype=np.float32)
e2 = np.array(embedding2, dtype=np.float32)
e1_norm = e1 / (np.linalg.norm(e1) + 1e-8)
e2_norm = e2 / (np.linalg.norm(e2) + 1e-8)
similarity = float(np.dot(e1_norm, e2_norm))
return max(0.0, min(1.0, similarity))
def euclidean_distance(embedding1: list[float], embedding2: list[float]) -> float:
"""
Compute Euclidean distance between two embeddings.
Args:
embedding1: First embedding vector
embedding2: Second embedding vector
Returns:
Distance score (lower = more similar)
"""
e1 = np.array(embedding1, dtype=np.float32)
e2 = np.array(embedding2, dtype=np.float32)
return float(np.linalg.norm(e1 - e2))
def generate_face_embedding(face_crop: np.ndarray) -> list[float]:
"""
Generate a 114-D embedding from a face crop using HSV color histograms.
Uses OpenCV HSV histograms β no external deep learning libraries needed.
Sufficient for face deduplication within a session.
Embedding breakdown:
- H channel: 50 bins
- S channel: 32 bins
- V channel: 32 bins
- Total: 114 dimensions, L2-normalized
Args:
face_crop: Face image as numpy array (BGR, any size β resized internally)
Returns:
List of 114 floats representing the face embedding
Raises:
ValueError: If face_crop is None or invalid
"""
if face_crop is None or face_crop.size == 0:
raise ValueError("generate_face_embedding: face_crop is None or empty")
# Resize to fixed size for consistent histograms
face_resized = cv2.resize(face_crop, (64, 64), interpolation=cv2.INTER_LINEAR)
# Convert BGR β HSV
# HSV is more robust to lighting changes than raw RGB
face_hsv = cv2.cvtColor(face_resized, cv2.COLOR_BGR2HSV)
# Compute per-channel histograms
# H: 0β179 in OpenCV, S: 0β255, V: 0β255
h_hist = cv2.calcHist([face_hsv], [0], None, [50], [0, 180]).flatten()
s_hist = cv2.calcHist([face_hsv], [1], None, [32], [0, 256]).flatten()
v_hist = cv2.calcHist([face_hsv], [2], None, [32], [0, 256]).flatten()
# Concatenate into one vector
embedding = np.concatenate([h_hist, s_hist, v_hist]).astype(np.float32)
# L2-normalize so cosine similarity works correctly
norm = np.linalg.norm(embedding) + 1e-8
embedding = embedding / norm
return embedding.tolist()
|