Spaces:
Sleeping
Sleeping
| """ | |
| Face embedding service — extracted from ai_manager.py. | |
| Handles: CLAHE enhancement, multi-scale SCRFD detection, ArcFace + AdaFace embedding, | |
| IOU deduplication, face crop thumbnails. | |
| """ | |
| import base64 | |
| import io | |
| import os | |
| import sys | |
| import threading | |
| import cv2 | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| from PIL import Image | |
| # ── Config (env-tunable) ───────────────────────────────────────────────────── | |
| DET_SIZE_PRIMARY = (640, 640) | |
| DET_SCALES = [(1280, 1280), (960, 960), (640, 640)] | |
| IOU_DEDUP_THRESHOLD = float(os.getenv("IOU_DEDUP_THRESHOLD", "0.4")) | |
| MIN_FACE_SIZE = int(os.getenv("MIN_FACE_SIZE", "30")) | |
| MAX_FACES_PER_IMAGE = int(os.getenv("MAX_FACES_PER_IMAGE", "20")) | |
| FACE_CROP_THUMB_SIZE = int(os.getenv("FACE_CROP_THUMB_SIZE", "112")) | |
| FACE_CROP_QUALITY = int(os.getenv("FACE_CROP_QUALITY", "85")) | |
| FACE_CROP_PADDING = float(os.getenv("FACE_CROP_PADDING", "0.2")) | |
| ADAFACE_CROP_PADDING = float(os.getenv("ADAFACE_CROP_PADDING", "0.1")) | |
| ADAFACE_DIM = 512 | |
| ENABLE_ADAFACE = int(os.getenv("ENABLE_ADAFACE", "1")) | |
| HF_TOKEN = os.getenv("HF_TOKEN", "") | |
| FAST_DETECT = bool(int(os.getenv("FAST_DETECT", "1"))) | |
| # ── Utility functions ───────────────────────────────────────────────────────── | |
| def _clahe_enhance(bgr: np.ndarray) -> np.ndarray: | |
| lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB) | |
| l_ch, a_ch, b_ch = cv2.split(lab) | |
| clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) | |
| l_eq = clahe.apply(l_ch) | |
| return cv2.cvtColor(cv2.merge([l_eq, a_ch, b_ch]), cv2.COLOR_LAB2BGR) | |
| def _iou(box_a: list, box_b: list) -> float: | |
| xa, ya = max(box_a[0], box_b[0]), max(box_a[1], box_b[1]) | |
| xb, yb = min(box_a[2], box_b[2]), min(box_a[3], box_b[3]) | |
| inter = max(0, xb - xa) * max(0, yb - ya) | |
| if inter == 0: | |
| return 0.0 | |
| area_a = (box_a[2] - box_a[0]) * (box_a[3] - box_a[1]) | |
| area_b = (box_b[2] - box_b[0]) * (box_b[3] - box_b[1]) | |
| return inter / (area_a + area_b - inter) | |
| def _dedup_faces(faces_list: list, iou_thresh: float = IOU_DEDUP_THRESHOLD) -> list: | |
| if not faces_list: | |
| return [] | |
| faces_list = sorted(faces_list, key=lambda f: float(f.det_score), reverse=True) | |
| kept = [] | |
| for face in faces_list: | |
| b = face.bbox.astype(int) | |
| box = [b[0], b[1], b[2], b[3]] | |
| if not any(_iou(box, [k.bbox.astype(int)[i] for i in range(4)]) > iou_thresh for k in kept): | |
| kept.append(face) | |
| return kept | |
| def _crop_to_b64(img_bgr: np.ndarray, x1: int, y1: int, x2: int, y2: int) -> str: | |
| H, W = img_bgr.shape[:2] | |
| w, h = x2 - x1, y2 - y1 | |
| pad_x, pad_y = int(w * FACE_CROP_PADDING), int(h * FACE_CROP_PADDING) | |
| cx1, cy1 = max(0, x1 - pad_x), max(0, y1 - pad_y) | |
| cx2, cy2 = min(W, x2 + pad_x), min(H, y2 + pad_y) | |
| crop = img_bgr[cy1:cy2, cx1:cx2] | |
| if crop.size == 0: | |
| return "" | |
| pil = Image.fromarray(crop[:, :, ::-1]).resize((FACE_CROP_THUMB_SIZE, FACE_CROP_THUMB_SIZE), Image.LANCZOS) | |
| buf = io.BytesIO() | |
| pil.save(buf, format="JPEG", quality=FACE_CROP_QUALITY) | |
| return base64.b64encode(buf.getvalue()).decode() | |
| def _face_crop_for_adaface(img_bgr: np.ndarray, x1: int, y1: int, x2: int, y2: int): | |
| H, W = img_bgr.shape[:2] | |
| w, h = x2 - x1, y2 - y1 | |
| pad_x, pad_y = int(w * ADAFACE_CROP_PADDING), int(h * ADAFACE_CROP_PADDING) | |
| cx1, cy1 = max(0, x1 - pad_x), max(0, y1 - pad_y) | |
| cx2, cy2 = min(W, x2 + pad_x), min(H, y2 + pad_y) | |
| crop = img_bgr[cy1:cy2, cx1:cx2] | |
| if crop.size == 0: | |
| return None | |
| rgb = crop[:, :, ::-1].copy() | |
| pil = Image.fromarray(rgb).resize((112, 112), Image.LANCZOS) | |
| arr = np.array(pil, dtype=np.float32) / 255.0 | |
| arr = (arr - 0.5) / 0.5 | |
| return arr.transpose(2, 0, 1) | |
| # ── FaceEmbedder class ──────────────────────────────────────────────────────── | |
| class FaceEmbedder: | |
| def __init__(self): | |
| from insightface.app import FaceAnalysis | |
| self.device = "cuda" if torch.cuda.is_available() else "cpu" | |
| providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] if self.device == "cuda" else ["CPUExecutionProvider"] | |
| self.face_app = FaceAnalysis(name="buffalo_l", providers=providers) | |
| self.face_app.prepare(ctx_id=0 if self.device == "cuda" else -1, det_size=DET_SIZE_PRIMARY) | |
| self.face_app.get(np.zeros((112, 112, 3), dtype=np.uint8)) # warm-up | |
| self._face_lock = threading.Lock() | |
| self.adaface_model = None | |
| self._load_adaface() | |
| def _load_adaface(self) -> None: | |
| if not ENABLE_ADAFACE: | |
| return | |
| REPO_ID = "minchul/cvlface_adaface_ir50_ms1mv2" | |
| CACHE_PATH = os.path.expanduser("~/.cvlface_cache/minchul/cvlface_adaface_ir50_ms1mv2") | |
| try: | |
| from huggingface_hub import hf_hub_download | |
| from transformers import AutoModel as _HFAutoModel | |
| os.makedirs(CACHE_PATH, exist_ok=True) | |
| hf_hub_download(repo_id=REPO_ID, filename="files.txt", token=HF_TOKEN, local_dir=CACHE_PATH, local_dir_use_symlinks=False) | |
| with open(os.path.join(CACHE_PATH, "files.txt")) as f: | |
| extra = [x.strip() for x in f.read().split("\n") if x.strip()] | |
| for fname in extra + ["config.json", "wrapper.py", "model.safetensors"]: | |
| if not os.path.exists(os.path.join(CACHE_PATH, fname)): | |
| hf_hub_download(repo_id=REPO_ID, filename=fname, token=HF_TOKEN, local_dir=CACHE_PATH, local_dir_use_symlinks=False) | |
| cwd = os.getcwd() | |
| os.chdir(CACHE_PATH) | |
| sys.path.insert(0, CACHE_PATH) | |
| try: | |
| model = _HFAutoModel.from_pretrained(CACHE_PATH, trust_remote_code=True, token=HF_TOKEN) | |
| finally: | |
| os.chdir(cwd) | |
| if CACHE_PATH in sys.path: | |
| sys.path.remove(CACHE_PATH) | |
| self.adaface_model = model.to(self.device).eval() | |
| except Exception: | |
| self.adaface_model = None | |
| def _adaface_embed_single(self, face_arr_chw) -> np.ndarray | None: | |
| if self.adaface_model is None or face_arr_chw is None: | |
| return None | |
| try: | |
| t = torch.from_numpy(face_arr_chw).unsqueeze(0).to(self.device) | |
| if self.device == "cuda": | |
| t = t.half() | |
| with torch.no_grad(): | |
| out = self.adaface_model(t) | |
| emb = out if isinstance(out, torch.Tensor) else out.embedding | |
| return F.normalize(emb.float(), p=2, dim=1)[0].cpu().numpy() | |
| except Exception: | |
| return None | |
| def _adaface_embed_batch(self, face_arr_list: list) -> list: | |
| if self.adaface_model is None or not face_arr_list: | |
| return [None] * len(face_arr_list) | |
| valid_indices = [i for i, arr in enumerate(face_arr_list) if arr is not None] | |
| if not valid_indices: | |
| return [None] * len(face_arr_list) | |
| try: | |
| batch = np.stack([face_arr_list[i] for i in valid_indices]) | |
| t = torch.from_numpy(batch).to(self.device) | |
| if self.device == "cuda": | |
| t = t.half() | |
| with torch.no_grad(): | |
| out = self.adaface_model(t) | |
| emb = out if isinstance(out, torch.Tensor) else out.embedding | |
| normed = F.normalize(emb.float(), p=2, dim=1).cpu().numpy() | |
| results = [None] * len(face_arr_list) | |
| for batch_i, orig_i in enumerate(valid_indices): | |
| results[orig_i] = normed[batch_i] | |
| return results | |
| except Exception: | |
| return [self._adaface_embed_single(arr) for arr in face_arr_list] | |
| def embed(self, image_bytes: bytes, quality_gate: float = 0.35) -> list[dict]: | |
| try: | |
| pil = Image.open(io.BytesIO(image_bytes)).convert("RGB") | |
| img_np = np.array(pil) | |
| if img_np.dtype != np.uint8: | |
| img_np = (img_np * 255).astype(np.uint8) | |
| bgr = img_np[:, :, ::-1].copy() | |
| bgr_enhanced = _clahe_enhance(bgr) | |
| H, W = bgr.shape[:2] | |
| all_raw_faces = [] | |
| for scale in DET_SCALES: | |
| scale_w, scale_h = min(W, scale[0]), min(H, scale[1]) | |
| bgr_scaled = bgr_enhanced if scale_w == W and scale_h == H else cv2.resize(bgr_enhanced, (scale_w, scale_h)) | |
| try: | |
| self.face_app.det_model.input_size = scale | |
| with self._face_lock: | |
| faces_at_scale = self.face_app.get(bgr_scaled) | |
| sx, sy = W / scale_w, H / scale_h | |
| for f in faces_at_scale: | |
| if sx != 1.0 or sy != 1.0: | |
| f.bbox[0] *= sx; f.bbox[1] *= sy; f.bbox[2] *= sx; f.bbox[3] *= sy | |
| all_raw_faces.extend(faces_at_scale) | |
| except Exception: | |
| pass | |
| if FAST_DETECT and len(all_raw_faces) >= MAX_FACES_PER_IMAGE: | |
| break | |
| bgr_flip = cv2.flip(bgr_enhanced, 1) | |
| try: | |
| self.face_app.det_model.input_size = DET_SIZE_PRIMARY | |
| with self._face_lock: | |
| faces_flip = self.face_app.get(bgr_flip) | |
| for f in faces_flip: | |
| x1, y1, x2, y2 = f.bbox | |
| f.bbox[0], f.bbox[2] = W - x2, W - x1 | |
| all_raw_faces.extend(faces_flip) | |
| except Exception: | |
| pass | |
| self.face_app.det_model.input_size = DET_SIZE_PRIMARY | |
| faces = _dedup_faces(all_raw_faces) | |
| # Pass 1: validate and collect crops | |
| valid_faces = [] | |
| for face in faces: | |
| if len(valid_faces) >= MAX_FACES_PER_IMAGE: | |
| break | |
| bbox_raw = face.bbox.astype(int) | |
| x1, y1, x2, y2 = bbox_raw | |
| x1, y1 = max(0, x1), max(0, y1) | |
| x2, y2 = min(bgr.shape[1], x2), min(bgr.shape[0], y2) | |
| w, h = x2 - x1, y2 - y1 | |
| if w < MIN_FACE_SIZE or h < MIN_FACE_SIZE: | |
| continue | |
| det_score = float(face.det_score) if hasattr(face, "det_score") else 1.0 | |
| if det_score < quality_gate or face.embedding is None: | |
| continue | |
| arcface_vec = face.embedding.astype(np.float32) | |
| n = np.linalg.norm(arcface_vec) | |
| arcface_vec = arcface_vec / n if n > 0 else arcface_vec | |
| face_chw = _face_crop_for_adaface(bgr, x1, y1, x2, y2) | |
| valid_faces.append({ | |
| "x1": x1, "y1": y1, "x2": x2, "y2": y2, | |
| "w": w, "h": h, "det_score": det_score, | |
| "arcface_vec": arcface_vec, "face_chw": face_chw, | |
| }) | |
| # Pass 2: batch AdaFace | |
| adaface_vecs = self._adaface_embed_batch([f["face_chw"] for f in valid_faces]) | |
| # Pass 3: assemble fused vectors | |
| results = [] | |
| for idx, fd in enumerate(valid_faces): | |
| adaface_vec = adaface_vecs[idx] | |
| arcface_vec = fd["arcface_vec"] | |
| if adaface_vec is not None: | |
| fused_raw = np.concatenate([arcface_vec, adaface_vec]) | |
| vec_mode = "fused" | |
| else: | |
| fused_raw = np.concatenate([arcface_vec, arcface_vec.copy()]) | |
| vec_mode = "arcface_mirror" | |
| n2 = np.linalg.norm(fused_raw) | |
| final_vec = (fused_raw / n2) if n2 > 0 else fused_raw | |
| results.append({ | |
| "type": "face", | |
| "vector": final_vec.tolist(), | |
| "face_idx": idx, | |
| "bbox": [int(fd["x1"]), int(fd["y1"]), int(fd["w"]), int(fd["h"])], | |
| "face_crop": _crop_to_b64(bgr, fd["x1"], fd["y1"], fd["x2"], fd["y2"]), | |
| "det_score": fd["det_score"], | |
| "face_width_px": int(fd["w"]), | |
| "vec_mode": vec_mode, | |
| }) | |
| return results | |
| except Exception: | |
| return [] | |