Spaces:
Runtime error
Runtime error
File size: 7,572 Bytes
75557db | 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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | """
face_model/analyzer.py β InsightFace wrapper vα»i RAM cache vΓ fallback matching
Chα»©c nΔng:
- Khα»i tαΊ‘o InsightFace model (singleton)
- Detect khuΓ΄n mαΊ·t & trΓch xuαΊ₯t embedding
- RAM cache: known_embeddings, names, person_ids, etc.
- find_match_ram: cosine similarity fallback khi Supabase RPC thαΊ₯t bαΊ‘i
"""
import logging
import os
import cv2
import numpy as np
from insightface.app import FaceAnalysis
import config
logger = logging.getLogger("face_analyzer")
class FaceAnalyzer:
"""Singleton quαΊ£n lΓ½ InsightFace model vΓ RAM embedding cache."""
def __init__(self):
self._analyzer: FaceAnalysis | None = None
# RAM cache β parallel arrays (index-aligned)
self.known_embeddings: list[np.ndarray] = []
self.known_names: list[str] = []
self.known_person_ids: list[str] = []
self.known_embedding_ids: list[str] = []
self.known_mongo_ids: list[str] = []
# ββ Model Init ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def initialize(self) -> None:
"""Khα»i tαΊ‘o InsightFace model. Gα»i mα»t lαΊ§n khi khα»i Δα»ng server."""
model_root = os.getenv("INSIGHTFACE_ROOT", "~/.insightface")
logger.info(
f"[Model] Loading InsightFace '{config.MODEL_NAME}' from root '{model_root}' "
f"with provider '{config.MODEL_PROVIDER}'..."
)
self._analyzer = FaceAnalysis(
name=config.MODEL_NAME,
root=model_root,
providers=[config.MODEL_PROVIDER],
)
self._analyzer.prepare(ctx_id=0, det_size=config.DET_SIZE)
logger.info("[Model] InsightFace initialized successfully.")
@property
def is_ready(self) -> bool:
return self._analyzer is not None
# ββ Face Detection & Embedding ββββββββββββββββββββββββββββββββββββββββ
def get_faces(self, img: np.ndarray) -> list:
"""Detect tαΊ₯t cαΊ£ khuΓ΄n mαΊ·t trong αΊ£nh. Returns list of face objects."""
if not self.is_ready:
raise RuntimeError("Face analyzer not initialized.")
return self._analyzer.get(img)
# ββ RAM Cache βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def reload_cache(self, records: list[dict]) -> int:
"""
TαΊ£i lαΊ‘i RAM cache tα»« danh sΓ‘ch records.
records: list[{person_id, full_name, embedding_id, embedding, face_crop_mongo_id}]
Returns: sα» lượng embeddings ΔΓ£ load
"""
temp_embeddings: list[np.ndarray] = []
temp_names: list[str] = []
temp_person_ids: list[str] = []
temp_embedding_ids: list[str] = []
temp_mongo_ids: list[str] = []
for r in records:
emb = r.get("embedding", [])
if not emb:
continue
temp_embeddings.append(np.array(emb, dtype=np.float32))
temp_names.append(r.get("full_name", ""))
temp_person_ids.append(r.get("person_id", ""))
temp_embedding_ids.append(r.get("embedding_id", ""))
temp_mongo_ids.append(r.get("face_crop_mongo_id", ""))
self.known_embeddings = temp_embeddings
self.known_names = temp_names
self.known_person_ids = temp_person_ids
self.known_embedding_ids = temp_embedding_ids
self.known_mongo_ids = temp_mongo_ids
logger.info(f"[Cache] Reloaded {len(self.known_embeddings)} embeddings into RAM.")
return len(self.known_embeddings)
def reload_from_local_folder(self, folder: str) -> int:
"""
Fallback: Load embeddings tα»« thΖ° mα»₯c αΊ£nh cα»₯c bα» (khi Supabase offline).
"""
if not os.path.exists(folder):
return 0
self.known_embeddings = []
self.known_names = []
self.known_person_ids = []
self.known_embedding_ids = []
self.known_mongo_ids = []
files = sorted([
f for f in os.listdir(folder)
if f.lower().endswith((".png", ".jpg", ".jpeg"))
])
for filename in files:
img_path = os.path.join(folder, filename)
img = cv2.imread(img_path)
if img is None:
continue
faces = self._analyzer.get(img)
if faces:
self.known_embeddings.append(faces[0].normed_embedding)
name = os.path.splitext(filename)[0]
self.known_names.append(name)
self.known_person_ids.append("")
self.known_embedding_ids.append("")
self.known_mongo_ids.append("")
logger.info(f"[Cache Fallback] Loaded {len(self.known_embeddings)} from '{folder}'.")
return len(self.known_embeddings)
def add_to_cache(
self,
embedding: np.ndarray,
name: str,
person_id: str,
embedding_id: str,
mongo_id: str,
) -> None:
"""ThΓͺm mα»t embedding mα»i vΓ o RAM cache."""
self.known_embeddings.append(embedding)
self.known_names.append(name)
self.known_person_ids.append(person_id)
self.known_embedding_ids.append(embedding_id)
self.known_mongo_ids.append(mongo_id)
def update_name_in_cache(self, person_id: str, new_name: str) -> None:
"""CαΊp nhαΊt tΓͺn trong RAM cache sau khi update trΓͺn DB."""
for i, pid in enumerate(self.known_person_ids):
if pid == person_id:
self.known_names[i] = new_name
def remove_from_cache(self, person_id: str) -> None:
"""XΓ³a tαΊ₯t cαΊ£ entries cα»§a person khα»i RAM cache."""
indices = [i for i, pid in enumerate(self.known_person_ids) if pid == person_id]
for i in reversed(indices):
self.known_embeddings.pop(i)
self.known_names.pop(i)
self.known_person_ids.pop(i)
self.known_embedding_ids.pop(i)
self.known_mongo_ids.pop(i)
@property
def total(self) -> int:
return len(self.known_embeddings)
# ββ RAM-based Matching (fallback) βββββββββββββββββββββββββββββββββββββ
def find_match_ram(
self,
embedding: np.ndarray,
threshold: float,
) -> tuple[float, int]:
"""
TΓ¬m khuΓ΄n mαΊ·t khα»p nhαΊ₯t trong RAM cache bαΊ±ng cosine similarity.
Returns:
(max_similarity, best_index) β best_index = -1 nαΊΏu khΓ΄ng khα»p
"""
if not self.known_embeddings:
return 0.0, -1
similarities = [float(np.dot(embedding, e)) for e in self.known_embeddings]
max_sim = max(similarities)
best_idx = similarities.index(max_sim)
if max_sim >= threshold:
return max_sim, best_idx
return max_sim, -1
# ββ Singleton βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
face_analyzer = FaceAnalyzer()
|