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
| import numpy as np from typing import Tuple
def calculate_ela( upper_eyelid_3d: np.ndarray, lower_eyelid_3d: np.ndarray, eye_corner_left_3d: np.ndarray, eye_corner_right_3d: np.ndarray ) -> float: """ ELA (Eyelid Angle) 计算 - 基于 3D 面部关键点 核心思想:计算上眼睑切线与下眼睑切线之间的夹角 睁眼时角度大,闭眼时角度趋近于0 Args: upper_eyelid_3d: 上眼睑关键点 3D 坐标, shape=(N, 3) lower_eyelid_3d: 下眼睑关键点 3D 坐标, shape=(N, 3) eye_corner_left_3d: 左眼角 3D 坐标, shape=(3,) eye_corner_right_3d: 右眼角 3D 坐标, shape=(3,) Returns: ela: 眼睑角度(弧度),范围 [0, π/2] Notes: - 使用 MediaPipe Face Mesh 的 3D 关键点 - 关键点索引(左眼): 上眼睑: 159, 145, 153 下眼睑: 23, 27, 25 眼角: 33 (左), 133 (右) - 右眼对应索引镜像 """ def fit_line_direction(points_3d): """拟合3D点集的主方向向量""" centroid = np.mean(points_3d, axis=0) centered = points_3d - centroid _, _, vh = np.linalg.svd(centered) direction = vh[0] return direction upper_dir = fit_line_direction(upper_eyelid_3d) lower_dir = fit_line_direction(lower_eyelid_3d) cos_angle = np.dot(upper_dir, lower_dir) / ( np.linalg.norm(upper_dir) * np.linalg.norm(lower_dir) ) cos_angle = np.clip(cos_angle, -1.0, 1.0) angle_rad = np.arccos(cos_angle) return angle_rad
def calculate_ela_mediapipe(face_mesh_result): """ 使用 MediaPipe Face Mesh 结果计算 ELA Args: face_mesh_result: MediaPipe FaceMesh 输出 Returns: ela_left: 左眼 ELA ela_right: 右眼 ELA """ landmarks = face_mesh_result.multi_face_landmarks[0].landmark def get_3d_points(indices): return np.array([ [landmarks[i].x, landmarks[i].y, landmarks[i].z] for i in indices ]) LEFT_UPPER = [159, 145, 153] LEFT_LOWER = [23, 27, 25] LEFT_CORNER_L = 33 LEFT_CORNER_R = 133 RIGHT_UPPER = [386, 374, 380] RIGHT_LOWER = [253, 257, 255] RIGHT_CORNER_L = 362 RIGHT_CORNER_R = 263 upper_left = get_3d_points(LEFT_UPPER) lower_left = get_3d_points(LEFT_LOWER) corner_l = np.array([landmarks[LEFT_CORNER_L].x, landmarks[LEFT_CORNER_L].y, landmarks[LEFT_CORNER_L].z]) corner_r = np.array([landmarks[LEFT_CORNER_R].x, landmarks[LEFT_CORNER_R].y, landmarks[LEFT_CORNER_R].z]) ela_left = calculate_ela(upper_left, lower_left, corner_l, corner_r) upper_right = get_3d_points(RIGHT_UPPER) lower_right = get_3d_points(RIGHT_LOWER) corner_l_r = np.array([landmarks[RIGHT_CORNER_L].x, landmarks[RIGHT_CORNER_L].y, landmarks[RIGHT_CORNER_L].z]) corner_r_r = np.array([landmarks[RIGHT_CORNER_R].x, landmarks[RIGHT_CORNER_R].y, landmarks[RIGHT_CORNER_R].z]) ela_right = calculate_ela(upper_right, lower_right, corner_l_r, corner_r_r) return ela_left, ela_right
if __name__ == "__main__": upper = np.array([[0, 0.3, 0], [0.5, 0.35, 0], [1.0, 0.3, 0]], dtype=float) lower = np.array([[0, -0.3, 0], [0.5, -0.35, 0], [1.0, -0.3, 0]], dtype=float) cl = np.array([0, 0, 0], dtype=float) cr = np.array([1.0, 0, 0], dtype=float) ela_open = calculate_ela(upper, lower, cl, cr) print(f"睁眼 ELA: {np.degrees(ela_open):.1f}°") upper_closed = np.array([[0, 0.05, 0], [0.5, 0.05, 0], [1.0, 0.05, 0]], dtype=float) lower_closed = np.array([[0, -0.05, 0], [0.5, -0.05, 0], [1.0, -0.05, 0]], dtype=float) ela_closed = calculate_ela(upper_closed, lower_closed, cl, cr) print(f"闭眼 ELA: {np.degrees(ela_closed):.1f}°") theta = np.radians(30) R_yaw = np.array([ [np.cos(theta), 0, np.sin(theta)], [0, 1, 0], [-np.sin(theta), 0, np.cos(theta)] ]) upper_rot = upper @ R_yaw.T lower_rot = lower @ R_yaw.T ela_yaw = calculate_ela(upper_rot, lower_rot, cl @ R_yaw.T, cr @ R_yaw.T) print(f"偏航30° ELA: {np.degrees(ela_yaw):.1f}° (变化: {abs(np.degrees(ela_yaw - ela_open)):.2f}°)")
|