cv2.omnidir.projectPoints两种投影路径返回结果不一致问题
全景相机标定中cv2.omnidir.projectPoints投影结果偏差问题
问题背景
使用棋盘格靶标开展全景相机与其他传感器的外参标定工作时,两次调用cv2.omnidir.projectPoints函数得到的返回结果存在明显不符合数学逻辑的差异。
测试了两种棋盘格外角点到图像的投影实现:
- 实现1:直接将角点从棋盘格坐标系投影到图像坐标系
- 实现2:先手动将角点从棋盘格坐标系转换到相机坐标系,再传入rvec、tvec均为零向量的参数完成到图像的投影
按照坐标变换的数学逻辑,两种投影路径的返回结果应当完全一致,但实际运行得到的像素坐标存在明显偏差。
测试代码与运行结果
import numpy as np import cv2 # --- 相机参数 K = np.array([[939.265, 0. , 965.693], [ 0. , 942.402, 645.578], [ 0. , 0. , 1. ]]) D = np.array([-0.156, -0.03 , 0. , 0.001], dtype=float32) # --- 棋盘格坐标系下的外角点坐标 outer_corners_bcs = np.array([[-0.175, -0.17 , 0. ], [ 0.8 , -0.17 , 0. ], [-0.175, 0.67 , 0. ], [ 0.8 , 0.67 , 0. ]]) # --- 棋盘格靶标到相机的旋转向量、平移向量 rvec = np.array([[-1.138, -1.421, 2.827]]) tvec = np.array([[-6.852, -5.473, 4.549]]) # --- 实现1:直接从棋盘格坐标系投影到图像 uv, _ = cv2.omnidir.projectPoints(outer_corners_bcs.reshape(1, -1, 3).astype('float64'), rvec.reshape(1, 3), tvec.reshape(1, 3), K, 1, D) print(uv) # 输出结果: # [[[556.417 320.275] # [504.347 320.397] # [562.968 272.863] # [509.031 272.395]]] # --- 实现2:先转换到相机坐标系再投影到图像 R = eulerAnglesToRotationMatrix(rvec) outer_corners_ccs = (R @ outer_corners_bcs.T + tvec).T uv, _ = cv2.omnidir.projectPoints(outer_corners_ccs.reshape(1, -1, 3), np.zeros(3).reshape(1, 3), np.zeros(3).reshape(1, 3), K, 1, D) print(uv) # 输出结果: # [[[699.384 304.698] # [639.438 180.463] # [876.787 527.013] # [752.581 334.091]]]
问题根因与解决方案
- 核心错误:手动坐标变换环节的旋转矩阵转换逻辑错误。OpenCV标定类接口传入的
rvec为罗德里格斯旋转向量,并非欧拉角,代码中调用的eulerAnglesToRotationMatrix以欧拉角为输入做转换,得到的旋转矩阵完全错误,直接导致相机坐标系下的点坐标计算偏差。 - 修复方法:替换旋转矩阵转换逻辑,使用OpenCV官方的罗德里格斯变换接口计算旋转矩阵,修正后两种投影路径结果完全一致,对应修改代码如下:
# 错误写法:将rvec作为欧拉角转换旋转矩阵 # R = eulerAnglesToRotationMatrix(rvec) # 正确写法:通过罗德里格斯公式从旋转向量计算旋转矩阵 R, _ = cv2.Rodrigues(rvec) # 注意维度匹配,tvec转为列向量做广播运算 outer_corners_ccs = (R @ outer_corners_bcs.T + tvec.reshape(3, 1)).T
- 额外注意事项:
- 所有传入
cv2.omnidir.projectPoints的数组统一使用float64类型,避免float32精度截断带来的微小误差 - 矩阵运算时检查数组维度,避免广播逻辑错误导致的计算结果异常
- 所有传入
内容的提问来源于stack exchange,提问作者yonatan
相关产品推荐
相关产品推荐

