一张图可以估计“现在偏向哪边、嘴是否张开、眼睛是否闭合”。“刚完成一次转头”或“眨了一次眼”包含时间变化,需要连续图像。
1. 先得到关键点
关键点网络预测眼角、鼻尖、嘴角等位置。常见输出是图像坐标,MediaPipe Face Landmarker 还提供人脸网格及变换矩阵。
若坐标归一化到图像宽高,先还原成像素:
x_pixel = landmark.x * image_widthy_pixel = landmark.y * image_height不能直接把归一化的 x、y 当作同单位距离:在非正方形图片中,横向 0.1 和纵向 0.1 对应的像素长度不同。网格中的 z 也是模型估计量,不能当作深度传感器实测距离。MediaPipe 说明
2. 用鼻子位置粗估偏转
只有二维点时,可以观察鼻尖相对双眼中点的横向偏移。设左右眼中心为 L、R,鼻尖为 N:
q 能作为粗略偏转指标。除以眼距后,图片等比放大不会改变结果;但脸型、透视、歪头都会影响它,不能把 q×某个常数 当作通用角度。
更好的二维实现会先修正画面内倾斜,再计算鼻尖相对眼睛连线的投影。需要角度时,通常采用三维模板加 PnP,或使用姿态网络。
3. 用 PnP 求头部旋转
准备一组标准三维脸点 P_i,以及图上与它们对应的二维点 p_i,再给出相机内参 K、畸变参数。透视投影关系为:
solvePnP 寻找旋转 R 和平移 t,使投影点尽量贴近观察点。三维点和二维点的顺序必须一致,不能把右眼角对应成左嘴角。
ok, rvec, tvec = cv2.solvePnP(points_3d, points_2d, K, distortion)if not ok: raise ValueError("姿态求解失败")R, _ = cv2.Rodrigues(rvec)rvec 是旋转向量,不能把其中某一项直接当作 yaw。先转成旋转矩阵,再按约定分解。OpenCV PnP 文档
本文采用 R = Rz(roll) Ry(yaw) Rx(pitch) 的旋转顺序,在非万向节锁区域:
yaw = math.degrees(math.atan2( -R[2, 0], math.hypot(R[0, 0], R[1, 0])))这个公式与旋转顺序绑定。atan2(R[0,2], R[2,2]) 在纯 Y 轴旋转时可给出相同值,但在混合转动时不能直接套用。
相机内参最好经标定获得;用图像宽度猜焦距、用通用脸模板,都只能得到近似姿态。把三维点重新投影到图上,可以检查平均重投影误差,但低误差不代表模板和相机参数就一定真实。
可运行的 pnp_pose.py 用已知 25° 旋转合成二维点,再反求旋转。它验证计算与坐标约定,不测真人角度精度。
4. 左右怎样定义
图像通常 x 向右、y 向下。PnP 的相机坐标、三维脸模板坐标、MediaPipe 的坐标约定不一定相同。
先让受试者面对相机,再明确做一次“本人左转”,观察实际角度的符号,才给正负值贴上左右标签。自拍预览还可能镜像:只翻转显示与翻转算法输入是两件事。
识别小幅变化可以使用 Δyaw = yaw - yaw_neutral,减少起始姿态的偏差。这个基线修正不解决错误的坐标约定。
5. 张嘴与闭眼怎样量化
嘴的开合可以用内唇间距除以嘴角距离:
例如嘴宽 50 像素、内唇间距 10 像素,M=0.2。等比放大后结果不变。不同脸型、表情和侧脸会改变比例,不存在适合所有人的固定张嘴阈值。
闭眼常用六个眼轮廓点计算 EAR。按眼周顺序编号 p1…p6,p1、p4 为两侧眼角:
眼睛闭合时,两条竖向距离减小,EAR 通常下降。眼角点取错、镜片遮挡、脸部侧转都会影响结果。这个比例描述当前开合,完整眨眼需要观察“开→闭→开”。EAR 原始方法
def eye_aspect_ratio(p): p1, p2, p3, p4, p5, p6 = p width = math.dist(p1, p4) if width <= 1e-8: raise ValueError("眼角重合,不能计算") return (math.dist(p2, p6) + math.dist(p3, p5)) / (2 * width)6. 姿态怎样变成动作
连续帧里,可以跟踪“正脸→目标角度→回正”,并要求每阶段持续若干帧,过滤单帧抖动。指数平滑也是一个简单选择:y_t = αx_t + (1-α)y_(t-1);减小 α 会更平滑,也会增加响应延迟。
单张图只提供一个姿态值,无法验证上述顺序。录像里的人同样能转头、张嘴,所以动作完成也不能代替单图防伪的分类依据。
几何函数与图片模型的运行方式见算法示例。
评论