bank-thief-detection / src /face_module.py
dineth554's picture
Upload src/face_module.py with huggingface_hub
ffd27c3 verified
Raw
History Blame Contribute Delete
5.76 kB
"""
Face Detection and Recognition Module using InsightFace.
CPU-only inference via ONNX Runtime backend.
- FaceDetection: SCRFD (RetinaFace-based)
- FaceRecognition: ArcFace (512-dim embeddings)
"""
from typing import List, Dict, Optional, Tuple
import numpy as np
import cv2
import os
import sys
class FaceModule:
"""
Face detection and recognition using InsightFace models.
Downloads buffalo_l model pack on first use.
Falls back to ONNX Runtime for CPU inference.
"""
def __init__(self, det_threshold: float = 0.5, rec_threshold: float = 0.6):
"""
Initialize face detection and recognition models.
Args:
det_threshold: Face detection confidence threshold.
rec_threshold: Face recognition matching threshold.
"""
self.det_threshold = det_threshold
self.rec_threshold = rec_threshold
# Suppress insightface warnings
self._model_loaded = False
self.detector = None
self.recognizer = None
try:
import insightface
from insightface.model_zoo import get_model
from insightface.app import FaceAnalysis
# Initialize FaceAnalysis with buffalo_l (scrfd detection + arcface recognition)
self.app = FaceAnalysis(
name='buffalo_l',
root=os.path.join(os.path.dirname(__file__), '..', 'models', 'insightface'),
providers=['CPUExecutionProvider']
)
self.app.prepare(ctx_id=-1, det_thresh=self.det_threshold)
self._model_loaded = True
print(f"[FaceModule] InsightFace loaded successfully (buffalo_l)")
print(f"[FaceModule] Detection threshold: {self.det_threshold}, Recognition threshold: {self.rec_threshold}")
except Exception as e:
print(f"[FaceModule] InsightFace initialization warning: {e}")
print(f"[FaceModule] Will use lightweight fallback detection")
def detect_faces(
self,
frame: np.ndarray,
person_bboxes: Optional[List[List[float]]] = None
) -> List[Dict]:
"""
Detect faces in a frame, optionally within person bounding boxes.
Args:
frame: Input image (H, W, C) in BGR format.
person_bboxes: Optional list of person bboxes [x1,y1,x2,y2] to constrain search.
Returns:
List of face dicts with keys: face_bbox, landmarks, embedding, det_score.
"""
if not self._model_loaded or frame is None or frame.size == 0:
return []
try:
all_faces = self.app.get(frame)
if person_bboxes:
# Filter faces to those within person bounding boxes
filtered_faces = []
for face in all_faces:
face_bbox = face.bbox.astype(float).tolist()
face_cx = (face_bbox[0] + face_bbox[2]) / 2
face_cy = (face_bbox[1] + face_bbox[3]) / 2
for pbox in person_bboxes:
if pbox[0] <= face_cx <= pbox[2] and pbox[1] <= face_cy <= pbox[3]:
filtered_faces.append(face)
break
faces = filtered_faces
else:
faces = all_faces
results = []
for face in faces:
result = {
'face_bbox': face.bbox.astype(float).tolist(),
'landmarks': face.landmark.astype(float).tolist() if face.landmark is not None else None,
'embedding': face.normed_embedding.astype(np.float32) if face.normed_embedding is not None else None,
'det_score': float(face.det_score),
}
results.append(result)
return results
except Exception as e:
print(f"[FaceModule] Detection error: {e}")
return []
def get_embedding(self, face_crop: np.ndarray) -> Optional[np.ndarray]:
"""
Get face embedding from a cropped face image.
Args:
face_crop: Cropped face image (BGR).
Returns:
512-dim embedding vector or None if no face found.
"""
if not self._model_loaded or face_crop is None or face_crop.size == 0:
return None
try:
faces = self.app.get(face_crop)
if len(faces) > 0:
return faces[0].normed_embedding.astype(np.float32)
return None
except Exception as e:
print(f"[FaceModule] Embedding error: {e}")
return None
def compare_embeddings(self, emb1: np.ndarray, emb2: np.ndarray) -> float:
"""
Compare two face embeddings using cosine similarity.
Args:
emb1: First embedding vector.
emb2: Second embedding vector.
Returns:
Cosine similarity score (0-1).
"""
if emb1 is None or emb2 is None:
return 0.0
emb1 = emb1.flatten()
emb2 = emb2.flatten()
norm1 = np.linalg.norm(emb1)
norm2 = np.linalg.norm(emb2)
if norm1 < 1e-10 or norm2 < 1e-10:
return 0.0
similarity = float(np.dot(emb1, emb2) / (norm1 * norm2))
return max(0.0, min(1.0, similarity))
def is_loaded(self) -> bool:
"""Check if models are loaded."""
return self._model_loaded
if __name__ == "__main__":
# Quick test
import numpy as np
fm = FaceModule()
test_frame = np.zeros((480, 640, 3), dtype=np.uint8)
faces = fm.detect_faces(test_frame)
print(f"Face detection test: {len(faces)} faces found")
print("FaceModule OK!")