如何将MediaPipe人脸3D关键点点云对齐并缩放至场景点云正确位置
解决方案
一、获取正确的平移坐标
MediaPipe输出的人脸3D关键点是相机坐标系下的归一化相对坐标(x/y范围0-1对应图像宽高占比,z是相对深度值),需要先将其转换为与场景点云一致的世界坐标系,才能正确平移到对应位置。
关键步骤:
用真实相机内参转换到相机坐标系
不要用代码中估算的内参,直接使用你已标定好的相机内参矩阵K(格式为[[fx, 0, cx], [0, fy, cy], [0,0,1]],其中fx/fy是焦距,cx/cy是光心像素坐标)。
从之前获取的深度图中,提取人脸中心(比如鼻子关键点)的实际深度值,用来将MediaPipe的相对z值转换为真实相机空间深度。通过相机外参转换到世界坐标系
相机外参extrinsic是世界坐标系到相机坐标系的变换矩阵,取其逆矩阵即可将相机坐标系下的点转换为场景点云使用的世界坐标系坐标。
代码修改:
替换原代码中人脸关键点处理部分的points生成逻辑,改为:
# 替换为你已知的真实相机内参 K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]]) fx, fy = K[0,0], K[1,1] cx, cy = K[0,2], K[1,2] # 获取鼻子关键点(idx=1)的像素坐标,提取深度图中对应的实际深度 nose_lm = faceLms.landmark[1] nose_px = (int(nose_lm.x * img_w), int(nose_lm.y * img_h)) actual_depth = depth[nose_px[1], nose_px[0]] # depth是你之前捕获的深度图 # 将MediaPipe关键点转换到相机坐标系 camera_space_points = [] for lm in faceLms.landmark: # 归一化坐标转像素坐标 px = lm.x * img_w py = lm.y * img_h # 计算相机空间下的真实3D坐标 X_cam = (px - cx) / fx * actual_depth * lm.z Y_cam = (py - cy) / fy * actual_depth * lm.z Z_cam = actual_depth * lm.z camera_space_points.append([X_cam, Y_cam, Z_cam]) # 转换相机坐标系到世界坐标系(场景点云的坐标系) extrinsic = vis.get_view_point_extrinsics() inv_extrinsic = np.linalg.inv(extrinsic) world_space_points = [] for point in camera_space_points: # 转齐次坐标进行矩阵运算 point_hom = np.array([point[0], point[1], point[2], 1.0]) world_point = inv_extrinsic @ point_hom world_space_points.append(world_point[:3]) # 取前3个元素作为世界坐标 # 创建人脸关键点的Open3D点云 pcd1 = o3d.geometry.PointCloud() pcd1.points = o3d.utility.Vector3dVector(world_space_points)
二、根据相机距离缩放人脸点云
如果MediaPipe的相对深度比例与实际场景不符,可以通过以下两种方式校准缩放:
方式1:基于实际人脸尺寸缩放
- 计算MediaPipe中固定关键点的距离(比如左右眼角的距离),记为
mp_dist - 在场景点云中找到对应关键点的实际距离,记为
real_dist - 计算缩放比例
scale = real_dist / mp_dist,对人脸点云进行缩放:
# 示例:取左右眼角关键点(idx=33和idx=263) lm_left_eye = faceLms.landmark[33] lm_right_eye = faceLms.landmark[263] # 计算MediaPipe中的距离 mp_dist = np.sqrt((lm_left_eye.x - lm_right_eye.x)**2 + (lm_left_eye.y - lm_right_eye.y)**2 + (lm_left_eye.z - lm_right_eye.z)**2) # 在场景点云中找到对应位置的实际距离(需手动定位或通过匹配获取) real_dist = 0.06 # 示例:实际人脸眼距约6cm # 缩放点云 scale = real_dist / mp_dist pcd1.scale(scale, center=pcd1.get_center())
方式2:基于相机实际距离缩放
直接用人脸中心到相机的实际深度,除以MediaPipe中该点的相对z值得到缩放比例:
# 鼻子关键点的实际深度和相对z值 scale = actual_depth / nose_lm.z # 以人脸中心为基准缩放,避免偏移 pcd1.scale(scale, center=np.array(world_space_points)[1]) # world_space_points[1]是鼻子的世界坐标
内容的提问来源于stack exchange,提问作者Alberto Fasan
相关产品推荐
相关产品推荐

