Spaces:
Paused
Paused
| # facial_diagnostics.py | |
| import cv2 | |
| import numpy as np | |
| import math | |
| from statistics import median | |
| # Facial metric helpers | |
| LEFT_EYE_IDX = [33, 160, 158, 133, 153, 144] | |
| RIGHT_EYE_IDX = [362, 385, 387, 263, 373, 380] | |
| MOUTH_IDX = [13, 14, 78, 308] | |
| SMILE_LEFT = 61 | |
| SMILE_RIGHT = 291 | |
| def eye_aspect_ratio(pts, idx): | |
| a = np.linalg.norm(np.array(pts[idx[1]]) - np.array(pts[idx[5]])) | |
| b = np.linalg.norm(np.array(pts[idx[2]]) - np.array(pts[idx[4]])) | |
| c = np.linalg.norm(np.array(pts[idx[0]]) - np.array(idx[3])) + 1e-8 | |
| return float((a + b) / (2.0 * c)) | |
| def mouth_ratio(pts): | |
| top, bottom, left, right = pts[MOUTH_IDX[0]], pts[MOUTH_IDX[1]], pts[MOUTH_IDX[2]], pts[MOUTH_IDX[3]] | |
| return float(np.linalg.norm(np.array(top) - np.array(bottom)) / | |
| (np.linalg.norm(np.array(left) - np.array(right)) + 1e-8)) | |
| def head_tilt_angle(pts): | |
| left = np.mean([pts[33], pts[133]], axis=0) | |
| right = np.mean([pts[362], pts[263]], axis=0) | |
| diff = np.array(right) - np.array(left) | |
| return float(math.degrees(math.atan2(diff[1], diff[0]))) | |
| def smile_symmetry(pts): | |
| left = np.array(pts[SMILE_LEFT]) | |
| right = np.array(pts[SMILE_RIGHT]) | |
| center = (left + right) / 2 | |
| L = np.linalg.norm(left - center) | |
| R = np.linalg.norm(right - center) | |
| if L + R == 0: | |
| return 1.0 | |
| return float(min(L, R) / max(L, R)) | |
| # Face mesh | |
| import mediapipe as mp | |
| mp_face_mesh = mp.solutions.face_mesh | |
| FACE_MESH = mp_face_mesh.FaceMesh( | |
| static_image_mode=False, | |
| max_num_faces=1, | |
| refine_landmarks=True, | |
| min_detection_confidence=0.6, | |
| min_tracking_confidence=0.6 | |
| ) | |
| def analyze_frame(frame): | |
| h, w, _ = frame.shape | |
| rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| res = FACE_MESH.process(rgb) | |
| if not res.multi_face_landmarks: | |
| return {"found": False} | |
| lm = res.multi_face_landmarks[0] | |
| pts = [(int(p.x * w), int(p.y * h)) for p in lm.landmark] | |
| ear = (eye_aspect_ratio(pts, LEFT_EYE_IDX) + eye_aspect_ratio(pts, RIGHT_EYE_IDX)) / 2 | |
| mratio = mouth_ratio(pts) | |
| tilt = head_tilt_angle(pts) | |
| symmetry = smile_symmetry(pts) | |
| return { | |
| "found": True, | |
| "ear": float(ear), | |
| "mouth_ratio": float(mratio), | |
| "head_tilt": float(tilt), | |
| "smile_sym": float(symmetry) | |
| } | |
| def aggregate_metrics(metrics): | |
| if not metrics: | |
| return {} | |
| ears = [m["ear"] for m in metrics] | |
| mouths = [m["mouth_ratio"] for m in metrics] | |
| tilts = [m["head_tilt"] for m in metrics] | |
| smiles = [m["smile_sym"] for m in metrics] | |
| return { | |
| "count": len(metrics), | |
| "ear_median": median(ears), | |
| "mouth_median": median(mouths), | |
| "tilt_median": median(tilts), | |
| "smile_median": median(smiles) | |
| } | |