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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
| import numpy as np import cv2
class DepthFromDefocus: """ 散焦深度估计 原理: - 物体在不同距离时,成像模糊程度不同 - 近距离物体更清晰,远距离更模糊 - 通过模糊度估计深度 → 头部姿态 优势: - 单摄像头,无需深度传感器 - 无需外部标记 - 计算量小(CPU可运行) """ def __init__(self, focal_length_mm=6, aperture_f=2.8, sensor_width_mm=4.8): self.focal_length = focal_length_mm self.aperture = aperture_f self.sensor_width = sensor_width_mm def estimate_depth(self, image: np.ndarray, focus_region: tuple = None) -> float: """ 从模糊度估计深度 Args: image: 输入图像 focus_region: (x, y, w, h) 对焦区域 Returns: depth_mm: 估计深度(毫米) """ if focus_region: x, y, w, h = focus_region region = image[y:y+h, x:x+w] else: region = image gray = cv2.cvtColor(region, cv2.COLOR_BGR2GRAY) laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var() sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) tenengrad = np.mean(sobel_x**2 + sobel_y**2) blur_metric = 1.0 / (laplacian_var + 1e-6) depth_mm = self._blur_to_depth(blur_metric, tenengrad) return depth_mm def _blur_to_depth(self, blur: float, tenengrad: float) -> float: """模糊度到深度的映射(需标定)""" clarity = np.sqrt(tenengrad) K = 500 return K / (clarity + 1e-6) def estimate_head_pose(self, image, face_landmarks=None): """ 从散焦深度估计头部姿态 返回: (pitch, yaw, roll, tx, ty, tz) """ depth = self.estimate_depth(image) if face_landmarks is not None: rvec, tvec = self._solve_pnp(face_landmarks, depth) return rvec, tvec return np.array([0, 0, 0]), np.array([0, 0, depth])
class IrisDisplacement: """虹膜位移估计""" def __init__(self): self.face_detector = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_eye.xml' ) def estimate_iris_displacement(self, image, face_box): """ 估计虹膜位移(相对于眼眶中心) Returns: (dx, dy): 虹膜在眼眶中的位移 """ x, y, w, h = face_box roi = image[y:y+h, x:x+w] eyes = self.face_detector.detectMultiScale( roi, 1.1, 5, minSize=(30, 30) ) if len(eyes) < 2: return 0.0, 0.0 displacements = [] for (ex, ey, ew, eh) in eyes[:2]: eye_center_x = ex + ew / 2 eye_center_y = ey + eh / 2 eye_roi = roi[ey:ey+eh, ex:ex+ew] gray = cv2.cvtColor(eye_roi, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY_INV) contours, _ = cv2.findContours( thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) if contours: iris = max(contours, key=cv2.contourArea) M = cv2.moments(iris) if M['m00'] > 0: iris_x = M['m10'] / M['m00'] iris_y = M['m01'] / M['m00'] dx = iris_x - ew / 2 dy = iris_y - eh / 2 displacements.append((dx, dy)) if displacements: return np.mean(displacements, axis=0) return 0.0, 0.0
class VariationalBayesGaze: """ 变分贝叶斯多项逻辑回归 8维特征 → 注视点 (x, y) 特征向量: [pitch, yaw, roll, tx, ty, tz, iris_dx, iris_dy] """ def __init__(self, n_features=8, n_classes_x=15, n_classes_y=15): self.n_features = n_features self.n_classes_x = n_classes_x self.n_classes_y = n_classes_y self.W_x = np.random.randn(n_features, n_classes_x) * 0.01 self.W_y = np.random.randn(n_features, n_classes_y) * 0.01 self.alpha = np.ones(n_features) def predict(self, features: np.ndarray) -> tuple: """ 预测注视点 Args: features: 8维特征向量 Returns: (gaze_x, gaze_y): 注视点坐标 """ logits_x = features @ self.W_x logits_y = features @ self.W_y probs_x = np.exp(logits_x) / np.sum(np.exp(logits_x)) probs_y = np.exp(logits_y) / np.sum(np.exp(logits_y)) gaze_x = np.sum(probs_x * np.arange(self.n_classes_x)) / self.n_classes_x gaze_y = np.sum(probs_y * np.arange(self.n_classes_y)) / self.n_classes_y return gaze_x, gaze_y def train(self, features: np.ndarray, gaze_x: np.ndarray, gaze_y: np.ndarray, n_epochs=100): """变分贝叶斯训练""" for epoch in range(n_epochs): grad_x = features.T @ (self._softmax(features @ self.W_x) - self._onehot(gaze_x, self.n_classes_x)) grad_y = features.T @ (self._softmax(features @ self.W_y) - self._onehot(gaze_y, self.n_classes_y)) self.W_x += 0.01 * grad_x self.W_y += 0.01 * grad_y def _softmax(self, x): return np.exp(x) / np.sum(np.exp(x), axis=-1, keepdims=True) def _onehot(self, idx, n): oh = np.zeros(n) oh[int(idx * n)] = 1 return oh
if __name__ == "__main__": dfd = DepthFromDefocus() iris = IrisDisplacement() vbg = VariationalBayesGaze(n_features=8) image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8) face_box = (200, 150, 100, 100) depth = dfd.estimate_depth(image, (200, 150, 100, 100)) print(f"估计深度: {depth:.1f}mm") dx, dy = iris.estimate_iris_displacement(image, face_box) print(f"虹膜位移: dx={dx:.2f}, dy={dy:.2f}") features = np.array([0, 0, 0, 0, 0, depth, dx, dy]) gaze_x, gaze_y = vbg.predict(features) print(f"注视点: ({gaze_x:.3f}, {gaze_y:.3f})")
|