MusinguziBenard commited on
Commit
fa320d2
·
verified ·
1 Parent(s): 52cae38

Upload face_service.py

Browse files
Files changed (1) hide show
  1. services/face_service.py +100 -0
services/face_service.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # services/face_service.py
2
+ import cv2
3
+ import numpy as np
4
+ import insightface
5
+ from insightface.app import FaceAnalysis
6
+ import logging
7
+
8
+ from config import Config
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class FaceService:
13
+ def __init__(self):
14
+ self.app = None
15
+ self._initialize()
16
+
17
+ def _initialize(self):
18
+ """Initialize the face recognition model"""
19
+ try:
20
+ self.app = FaceAnalysis(name=Config.MODEL_NAME)
21
+ # ctx_id: -1 = CPU, 0 = first GPU. Defaults to CPU to match
22
+ # the plain `onnxruntime` package in requirements.txt.
23
+ self.app.prepare(ctx_id=Config.CTX_ID, det_size=Config.DETECTION_SIZE)
24
+ logger.info(
25
+ f"Face recognition model '{Config.MODEL_NAME}' initialized "
26
+ f"(ctx_id={Config.CTX_ID}, det_size={Config.DETECTION_SIZE})"
27
+ )
28
+ except Exception as e:
29
+ logger.error(f"Error initializing face recognition: {e}")
30
+ raise
31
+
32
+ def extract_face(self, image_bytes):
33
+ """Extract face embedding from image bytes"""
34
+ try:
35
+ nparr = np.frombuffer(image_bytes, np.uint8)
36
+ img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
37
+
38
+ if img is None:
39
+ return None, "Invalid image format"
40
+
41
+ faces = self.app.get(img)
42
+
43
+ if len(faces) == 0:
44
+ return None, "No face detected"
45
+
46
+ if len(faces) > 1:
47
+ return None, "Multiple faces detected. Please ensure only one person is in frame."
48
+
49
+ face = faces[0]
50
+ embedding = face.embedding
51
+ embedding = embedding / np.linalg.norm(embedding)
52
+
53
+ return {
54
+ 'embedding': embedding.tolist(),
55
+ 'bbox': face.bbox.tolist()
56
+ }, None
57
+
58
+ except Exception as e:
59
+ return None, f"Error extracting face: {str(e)}"
60
+
61
+ def compare_faces(self, face_embedding, stored_embeddings):
62
+ """Compare face embedding against stored embeddings"""
63
+ best_match = None
64
+ best_similarity = 0
65
+
66
+ face_embedding = np.array(face_embedding)
67
+ face_embedding = face_embedding / np.linalg.norm(face_embedding)
68
+
69
+ for item in stored_embeddings:
70
+ stored_embedding = np.array(item['embedding'])
71
+ stored_embedding = stored_embedding / np.linalg.norm(stored_embedding)
72
+
73
+ similarity = np.dot(face_embedding, stored_embedding)
74
+
75
+ if similarity > best_similarity:
76
+ best_similarity = similarity
77
+ best_match = item
78
+
79
+ return best_match, best_similarity
80
+
81
+ def health_check(self):
82
+ try:
83
+ if self.app is None:
84
+ return False, "Model not initialized"
85
+ return True, "Healthy"
86
+ except Exception as e:
87
+ return False, str(e)
88
+
89
+
90
+ # --- Singleton accessor -----------------------------------------------
91
+ # The model is large and slow to load (~seconds, significant RAM/VRAM).
92
+ # Every router used to instantiate its own FaceService(), which loaded
93
+ # the model 4 separate times. Use one shared instance instead.
94
+ _face_service_instance = None
95
+
96
+ def get_face_service() -> "FaceService":
97
+ global _face_service_instance
98
+ if _face_service_instance is None:
99
+ _face_service_instance = FaceService()
100
+ return _face_service_instance