Spaces:
Running
Running
| import cv2 | |
| import numpy as np | |
| _mp_face_mesh = None | |
| _face_mesh_instance = None | |
| def _get_face_mesh(): | |
| global _mp_face_mesh, _face_mesh_instance | |
| if _face_mesh_instance is None: | |
| try: | |
| import mediapipe as mp | |
| _mp_face_mesh = mp.solutions.face_mesh | |
| _face_mesh_instance = _mp_face_mesh.FaceMesh( | |
| static_image_mode=True, | |
| max_num_faces=2, | |
| refine_landmarks=True, | |
| min_detection_confidence=0.5 | |
| ) | |
| except Exception: | |
| _face_mesh_instance = None | |
| return _face_mesh_instance | |
| def extract_landmarks_468(image: np.ndarray) -> np.ndarray | None: | |
| """ | |
| Extract 468 facial landmarks using MediaPipe Face Mesh. | |
| Returns array of shape (468, 2) with (x, y) pixel coords, or None. | |
| """ | |
| face_mesh = _get_face_mesh() | |
| if face_mesh is None: | |
| return _fallback_landmarks(image) | |
| h, w = image.shape[:2] | |
| rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) | |
| results = face_mesh.process(rgb) | |
| if not results.multi_face_landmarks: | |
| return _fallback_landmarks(image) | |
| lm = results.multi_face_landmarks[0].landmark | |
| pts = np.array([[l.x * w, l.y * h] for l in lm], dtype=np.float32) | |
| return pts # shape (468, 2) | |
| def extract_landmarks_68(image: np.ndarray) -> np.ndarray | None: | |
| """ | |
| Extract 68 facial landmarks. Uses the first 68 points from MediaPipe | |
| mapped to dlib-compatible indices. | |
| Returns array of shape (68, 2), or None. | |
| """ | |
| pts468 = extract_landmarks_468(image) | |
| if pts468 is None: | |
| return None | |
| # MediaPipe to dlib 68-point index mapping (approximate) | |
| MP_TO_DLIB_68 = [ | |
| 162, 234, 93, 58, 172, 136, 149, 148, 152, 377, 378, 365, | |
| 397, 288, 323, 454, 389, 71, 63, 105, 66, 107, 336, 296, | |
| 334, 293, 301, 168, 197, 5, 4, 75, 97, 2, 326, 305, | |
| 33, 160, 158, 133, 153, 144, 362, 385, 387, 263, 373, 380, | |
| 61, 39, 37, 0, 267, 269, 291, 405, 314, 17, 84, 181, | |
| 78, 82, 13, 312, 308, 317, 14, 87 | |
| ] | |
| return pts468[MP_TO_DLIB_68] | |
| def _fallback_landmarks(image: np.ndarray) -> np.ndarray | None: | |
| """ | |
| Fallback when MediaPipe is unavailable: use InsightFace 5-point keypoints | |
| (left eye, right eye, nose, left mouth, right mouth) mapped to a sparse | |
| 468-point array so downstream callers get real anatomical positions. | |
| Returns None if InsightFace is also unavailable — callers must handle None. | |
| The previous implementation returned random uniform noise inside the face | |
| bounding box, which silently corrupted alignment transforms and quality | |
| scores whenever MediaPipe failed. | |
| """ | |
| from .detector import _get_insightface | |
| app = _get_insightface() | |
| if app is None: | |
| return None | |
| try: | |
| faces = app.get(image) | |
| if not faces: | |
| return None | |
| face = max(faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1])) | |
| kps = np.asarray(face.kps, dtype=np.float32) # shape (5, 2) | |
| # Map the 5 InsightFace keypoints to their nearest MediaPipe indices so | |
| # _get_key_indices() in aligner.py selects them correctly. | |
| # kps[0] = left eye → MP 33 | |
| # kps[1] = right eye → MP 263 | |
| # kps[2] = nose tip → MP 1 | |
| # kps[3] = left mouth → MP 61 | |
| # kps[4] = right mouth→ MP 291 | |
| pts = np.zeros((468, 2), dtype=np.float32) | |
| for mp_idx, kp in zip([33, 263, 1, 61, 291], kps): | |
| pts[mp_idx] = kp | |
| return pts | |
| except Exception: | |
| return None |