Spaces:
Runtime error
Runtime error
| """ | |
| 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.") | |
| 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) | |
| 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() | |