如何在Python中绕Roll、Pitch、Yaw轴旋转MediaPipe面部关键点?
面部关键点姿态校正(解决Roll/Pitch/Yaw旋转问题)
要校正人脸的Pitch(俯仰)和Yaw(偏航)旋转,你需要用到3D关键点数据(仅2D的x/y坐标无法准确完成这两个轴的旋转,因为缺少深度信息)。以下是具体的Python实现步骤:
前提准备
确保你的CSV文件包含MediaPipe输出的完整3D关键点(每个点有x、y、z三个值,z为相对于鼻尖的深度)。如果之前只存了2D数据,需要重新导出包含z列的CSV。
步骤1:计算人脸当前的姿态角(Roll/Pitch/Yaw)
通过OpenCV的solvePnP方法,利用几个关键面部点(鼻尖、双眼外角、下巴尖)来估算姿态角:
import numpy as np import math import cv2 import pandas as pd # 读取CSV中的3D关键点数据(假设每行是一个点的x,y,z) df = pd.read_csv("face_landmarks.csv") points_3d = df[["x", "y", "z"]].values # 图像尺寸(根据你的原始图像设置,若用归一化坐标可忽略,这里用示例值) img_w, img_h = 640, 480 # 定义标准正视人脸的3D参考点(相对位置即可) model_points = np.array([ (0.0, 0.0, 0.0), # 鼻尖(MediaPipe索引1) (-30.0, -125.0, -30.0), # 左眼外角(索引33) (30.0, -125.0, -30.0), # 右眼外角(索引263) (0.0, 150.0, -10.0) # 下巴尖(索引152) ], dtype=np.float64) # 从你的关键点中提取对应图像点(转换为像素坐标,若用归一化则跳过*img_w/h) image_points = np.array([ (points_3d[1][0] * img_w, points_3d[1][1] * img_h), (points_3d[33][0] * img_w, points_3d[33][1] * img_h), (points_3d[263][0] * img_w, points_3d[263][1] * img_h), (points_3d[152][0] * img_w, points_3d[152][1] * img_h) ], dtype=np.float64) # 相机内参(近似值,无畸变) camera_matrix = np.array( [[img_w, 0, img_w/2], [0, img_h, img_h/2], [0, 0, 1]], dtype=np.float64 ) dist_coeffs = np.zeros((4,1)) # 计算旋转向量和平移向量 success, rotation_vec, translation_vec = cv2.solvePnP(model_points, image_points, camera_matrix, dist_coeffs) # 将旋转向量转换为欧拉角(Roll, Pitch, Yaw) rotation_mat, _ = cv2.Rodrigues(rotation_vec) euler_angles = cv2.decomposeProjectionMatrix(np.hstack((rotation_mat, translation_vec)))[6] roll, pitch, yaw = euler_angles[0], euler_angles[1], euler_angles[2]
步骤2:构建逆旋转矩阵抵消姿态
用负的姿态角构建旋转矩阵,将关键点旋转至正视状态:
# 将欧拉角转换为弧度,并取负值(抵消原旋转) roll_rad = math.radians(-roll) pitch_rad = math.radians(-pitch) yaw_rad = math.radians(-yaw) # 绕X轴(Pitch)的旋转矩阵 Rx = np.array([ [1, 0, 0], [0, math.cos(pitch_rad), -math.sin(pitch_rad)], [0, math.sin(pitch_rad), math.cos(pitch_rad)] ]) # 绕Y轴(Yaw)的旋转矩阵 Ry = np.array([ [math.cos(yaw_rad), 0, math.sin(yaw_rad)], [0, 1, 0], [-math.sin(yaw_rad), 0, math.cos(yaw_rad)] ]) # 绕Z轴(Roll)的旋转矩阵(你之前用的2D矩阵其实是这个的2D版本) Rz = np.array([ [math.cos(roll_rad), -math.sin(roll_rad), 0], [math.sin(roll_rad), math.cos(roll_rad), 0], [0, 0, 1] ]) # 组合旋转矩阵(顺序为Z-Y-X,对应Roll→Yaw→Pitch的旋转抵消) total_rot_mat = Ry @ Rx @ Rz
步骤3:对所有关键点应用旋转校正
# 以鼻尖为中心平移(避免旋转后偏移) nose_point = points_3d[1] points_centered = points_3d - nose_point # 应用旋转 points_rotated = (total_rot_mat @ points_centered.T).T # 移回原位置 points_corrected = points_rotated + nose_point
步骤4:投影回2D平面(可选,用于可视化)
如果需要2D坐标进行可视化,用相机内参做透视投影:
# 3D转2D投影 points_2d_corrected = (camera_matrix @ points_corrected.T).T # 透视除法 points_2d_corrected = points_2d_corrected[:, :2] / points_2d_corrected[:, 2, np.newaxis]
注意事项
- 若你只有2D坐标,无法准确校正Pitch/Yaw,因为缺少深度信息会导致透视变形无法还原,建议重新提取MediaPipe的3D关键点。
- 欧拉角的旋转顺序可能需要根据实际情况调整,若校正后姿态不对,可以尝试调整旋转矩阵的组合顺序(比如
Rx @ Ry @ Rz)。 - MediaPipe的x/y是归一化到[0,1]的值,若需要像素坐标,记得乘以图像的宽和高。
内容的提问来源于stack exchange,提问作者Walu Gulu
相关产品推荐
相关产品推荐

