如何在Blender中利用SolvePnP向量修正相机位姿?
问题:OpenCV SolvePnP计算相机位姿后在Blender中还原出错,相机位置偏离预期
我正在学习OpenCV,并在Blender中做实践测试,尝试还原朝向平面的相机位姿。参考案例写了包含SolvePnP计算、3D/2D点匹配逻辑的代码,但生成的相机旋转和平移参数有问题,相机位置和预期不符,求修正位姿的方法。
原始代码
import bpy import cv2 import numpy as np from mathutils import Matrix, Vector from scipy.spatial.transform import Rotation def focalMM_to_focalPixel(focalMM, sensorWidth, imageWidth): pixelPitch = sensorWidth / imageWidth return focalMM / pixelPitch # Read Image im = cv2.imread("assets/cameraView.jpg") imageWidth = 1920 imageHeight = 1080 imageSize = [imageWidth, imageHeight] points_2D = np.array([ (949, 49), (1415, 45), (962, 913), (1398, 977) ], dtype="double") points_3D = np.array([ (-.30208, -1.8218, 8.3037), (-.30208, -1.8303, 8.3037), (-.30208, -1.8218, 1.381), (-.30208, -1.8303, 1.381) ]) focalLengthMM = 50 sensorWidth = 36 fLength = focalMM_to_focalPixel(focalLengthMM, sensorWidth, imageWidth) print("focalLengthPixel", fLength) K = np.array([ [fLength, 0, imageWidth/2], [0, fLength, imageHeight/2], [0, 0, 1] ]) distCoeffs = np.zeros((5, 1)) success, rvecs, tvecs = cv2.solvePnP(points_3D, points_2D, K, distCoeffs, flags=cv2.SOLVEPNP_ITERATIVE) np_rodrigues = np.asarray(rvecs[:,:], np.float64) rmat = cv2.Rodrigues(np_rodrigues)[0] camera_position = -np.matrix(rmat).T @ np.matrix(tvecs) # Test the solvePnP by projecting the 3D Points to camera projPoints = cv2.projectPoints(points_3D, rvecs, tvecs, K, distCoeffs)[0] for p in points_2D: cv2.circle(im, (int(p[0]), int(p[1])), 3, (0, 255, 0), -1) for p in projPoints: cv2.circle(im, (int(p[0][0]), int(p[0][1])), 3, (255, 0, 0), -1) # cv2.imshow("image", im) # cv2.waitKey(0) r = Rotation.from_rotvec([rvecs[0][0], rvecs[1][0], rvecs[2][0]]) rot = r.as_euler('xyz', degrees=True) tx = camera_position[0][0] ty = camera_position[1][0] tz = camera_position[2][0] rx = round(180 - rot[0], 5) ry = round(rot[1], 5) rz = round(rot[2], 5) # Creating the camera in Blender bpy.ops.object.camera_add() camera = bpy.context.object camera.location = (tx, ty, tz) # Convert rotation from degrees to radians for Blender camera.rotation_euler = (np.radians(rx), np.radians(ry), np.radians(rz)) # Setting the camera parameters camera.data.lens = focalLengthMM camera.data.sensor_width = sensorWidth camera.data.sensor_height = sensorWidth * (imageHeight / imageWidth) camera.data.shift_x = (imageWidth / 2 - K[0, 2]) / imageWidth camera.data.shift_y = (imageHeight / 2 - K[1, 2]) / imageHeight
核心问题分析与修正方案
1. 坐标系不匹配是根本原因
OpenCV与Blender的坐标系轴定义存在差异:
- OpenCV相机坐标系:右手系,X轴向右、Y轴向下、Z轴向前(指向场景)
- Blender世界坐标系:右手系,X轴向右、Y轴向前、Z轴向上
直接将OpenCV计算的位姿参数导入Blender,必然出现轴方向错位,导致相机位置和旋转偏离预期。
2. 具体修正步骤
(1)修正相机位置坐标映射
将OpenCV计算出的相机位置转换为Blender坐标系:
# 原代码的位置计算保留,仅修改轴映射 camera_position = -np.matrix(rmat).T @ np.matrix(tvecs) # OpenCV → Blender 轴映射:X不变,Z→Y,Y→-Z tx = camera_position[0][0] ty = camera_position[2][0] # OpenCV的Z轴对应Blender的Y轴 tz = -camera_position[1][0] # OpenCV的Y轴对应Blender的-Z轴
(2)修正旋转参数适配Blender
通过坐标系转换矩阵,将OpenCV的旋转矩阵转换为Blender兼容的形式:
# 替换原旋转计算部分 r = Rotation.from_rotvec(rvecs.flatten()) r_mat = r.as_matrix() # 定义OpenCV到Blender的坐标系转换矩阵 cv_to_blender = np.array([ [1, 0, 0], [0, 0, -1], [0, 1, 0] ]) # 转换旋转矩阵 r_mat_blender = cv_to_blender @ r_mat @ cv_to_blender.T r_blender = Rotation.from_matrix(r_mat_blender) rot = r_blender.as_euler('xyz', degrees=True) # 直接使用转换后的欧拉角,无需额外180度偏移 camera.rotation_euler = (np.radians(rot[0]), np.radians(rot[1]), np.radians(rot[2]))
(3)验证SolvePnP计算准确性
先启用投影验证代码,确认OpenCV的SolvePnP计算本身是否准确:
cv2.imshow("Projection Check", im) cv2.waitKey(0) cv2.destroyAllWindows()
如果绿色原始点和蓝色投影点重合度高,说明SolvePnP计算没问题,问题出在Blender的坐标系转换上。
(4)确认3D点坐标系一致性
确保points_3D是Blender世界坐标系下的坐标,如果原始3D点来自其他软件(如CAD),需要先转换到Blender的坐标系(Y向前、Z向上)。
3. 完整修正后代码
import bpy import cv2 import numpy as np from scipy.spatial.transform import Rotation def focalMM_to_focalPixel(focalMM, sensorWidth, imageWidth): pixelPitch = sensorWidth / imageWidth return focalMM / pixelPitch # Read Image im = cv2.imread("assets/cameraView.jpg") imageWidth = 1920 imageHeight = 1080 imageSize = [imageWidth, imageHeight] points_2D = np.array([ (949, 49), (1415, 45), (962, 913), (1398, 977) ], dtype="double") points_3D = np.array([ (-.30208, -1.8218, 8.3037), (-.30208, -1.8303, 8.3037), (-.30208, -1.8218, 1.381), (-.30208, -1.8303, 1.381) ]) focalLengthMM = 50 sensorWidth = 36 fLength = focalMM_to_focalPixel(focalLengthMM, sensorWidth, imageWidth) print("focalLengthPixel", fLength) K = np.array([ [fLength, 0, imageWidth/2], [0, fLength, imageHeight/2], [0, 0, 1] ]) distCoeffs = np.zeros((5, 1)) success, rvecs, tvecs = cv2.solvePnP(points_3D, points_2D, K, distCoeffs, flags=cv2.SOLVEPNP_ITERATIVE) # 计算相机在世界坐标系的位置(OpenCV坐标系) rmat = cv2.Rodrigues(rvecs)[0] camera_position = -np.matrix(rmat).T @ np.matrix(tvecs) # 投影验证 projPoints = cv2.projectPoints(points_3D, rvecs, tvecs, K, distCoeffs)[0] for p in points_2D: cv2.circle(im, (int(p[0]), int(p[1])), 3, (0, 255, 0), -1) for p in projPoints: cv2.circle(im, (int(p[0][0]), int(p[0][1])), 3, (255, 0, 0), -1) # 启用验证窗口 cv2.imshow("Projection Check", im) cv2.waitKey(0) cv2.destroyAllWindows() # 坐标系转换:OpenCV → Blender # 位置转换 tx = camera_position[0][0] ty = camera_position[2][0] tz = -camera_position[1][0] # 旋转转换 r = Rotation.from_rotvec(rvecs.flatten()) r_mat = r.as_matrix() cv_to_blender = np.array([[1,0,0],[0,0,-1],[0,1,0]]) r_mat_blender = cv_to_blender @ r_mat @ cv_to_blender.T r_blender = Rotation.from_matrix(r_mat_blender) rot = r_blender.as_euler('xyz', degrees=True) # 创建Blender相机 bpy.ops.object.camera_add() camera = bpy.context.object camera.location = (tx, ty, tz) camera.rotation_euler = (np.radians(rot[0]), np.radians(rot[1]), np.radians(rot[2])) # 设置相机参数 camera.data.lens = focalLengthMM camera.data.sensor_width = sensorWidth camera.data.sensor_height = sensorWidth * (imageHeight / imageWidth) # 原代码的shift参数计算正确,因为K的主点是图像中心,所以shift为0,可保留 camera.data.shift_x = (imageWidth / 2 - K[0, 2]) / imageWidth camera.data.shift_y = (imageHeight / 2 - K[1, 2]) / imageHeight
内容的提问来源于stack exchange,提问作者Fieldie101
相关产品推荐
相关产品推荐

