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
| import numpy as np
class FacialLandmarkDMS: """ 基于面部关键点的可解释DMS 不使用原始图像→CNN黑盒 而是提取68/468关键点→几何特征→轻量分类器 优势: - 可解释(每个判断可追溯到具体几何特征) - 轻量(关键点<1KB,图像>100KB) - 隐私友好(不存储图像) - 跨平台(关键点是通用表示) """ LANDMARK_INDICES = { "left_eye": [36, 37, 38, 39, 40, 41], "right_eye": [42, 43, 44, 45, 46, 47], "mouth": [48, 49, 50, 51, 52, 53, 54, 55, 56, 57], "left_eyebrow": [17, 18, 19, 20, 21], "right_eyebrow": [22, 23, 24, 25, 26], "nose_tip": [30], "chin": [8], "face_left": [1, 2, 3], "face_right": [13, 14, 15] } def extract_geometric_features(self, landmarks): """ 从关键点提取几何特征 Args: landmarks: (68, 2) 面部关键点坐标 Returns: features: 几何特征向量 """ features = {} features['left_ear'] = self._ear(landmarks[36:42]) features['right_ear'] = self._ear(landmarks[42:48]) features['avg_ear'] = (features['left_ear'] + features['right_ear']) / 2 features['mar'] = self._mar(landmarks[48:68]) features['pitch'], features['yaw'], features['roll'] = \ self._estimate_head_pose(landmarks) features['left_brow_height'] = \ landmarks[21, 1] - landmarks[36, 1] features['right_brow_height'] = \ landmarks[26, 1] - landmarks[45, 1] features['nose_deviation_x'] = \ landmarks[30, 0] - np.mean([landmarks[1, 0], landmarks[15, 0]]) features['symmetry'] = self._symmetry_score(landmarks) return features def _ear(self, eye_points): """眼睛纵横比(EAR)""" A = np.linalg.norm(eye_points[1] - eye_points[5]) B = np.linalg.norm(eye_points[2] - eye_points[4]) C = np.linalg.norm(eye_points[0] - eye_points[3]) return (A + B) / (2 * C + 1e-8) def _mar(self, mouth_points): """嘴部纵横比(MAR)""" A = np.linalg.norm(mouth_points[2] - mouth_points[8]) B = np.linalg.norm(mouth_points[0] - mouth_points[6]) return A / (B + 1e-8) def _estimate_head_pose(self, landmarks): """从关键点估计头部姿态""" nose = landmarks[30] chin = landmarks[8] left_face = landmarks[1] right_face = landmarks[15] left_width = abs(nose[0] - left_face[0]) right_width = abs(right_face[0] - nose[0]) yaw = np.arctan2(right_width - left_width, right_width + left_width + 1e-8) pitch = np.arctan2(chin[1] - nose[1], np.linalg.norm(chin - nose) + 1e-8) roll = np.arctan2( landmarks[45, 1] - landmarks[36, 1], landmarks[45, 0] - landmarks[36, 0] ) return np.degrees(pitch), np.degrees(yaw), np.degrees(roll) def _symmetry_score(self, landmarks): """面部对称性评分""" nose_x = landmarks[30, 0] left_mirror = np.flip(landmarks[:30], axis=0) right = landmarks[30:] return 1.0 - np.mean(np.abs(left_mirror[:, 0] - (2*nose_x - right[:, 0])))
if __name__ == "__main__": dms = FacialLandmarkDMS() np.random.seed(42) landmarks = np.random.randint(100, 200, (68, 2)) features = dms.extract_geometric_features(landmarks) print("提取的几何特征:") for k, v in features.items(): print(f" {k}: {v:.3f}")
|