如何通过相机内外参获取真实世界点?OpenCV实现疑问
问题概述
我正在用OpenCV通过相机测量平面物体长度,用棋盘格完成相机标定,伪代码如下:
# 棋盘格角点的世界坐标数组 3d_checker_position # 棋盘格角点的图像坐标数组 2d_checker_position # 求解相机内参、畸变系数、旋转向量、平移向量 camera_mat, dist_coeffs, rvec, tvec = cv.calibrateCamera(3d_checker_position, 2d_checker_position) # 3D转2D验证 test_2d_checker_position = cv.projectPoints(3d_checker_position, rvec, tvec, camera_mat, dist_coeffs) # test_2d_checker_position 应与 2d_checker_position 一致 # 2D转3D需求 test_3d_checker_position = what_function_should_I_use(2d_checker_position, rvec, tvec, camera_mat, dist_coeffs)
想知道what_function_should_I_use如何实现,OpenCV是否有内置函数?
另外,我手动实现了3D转2D的针孔模型(公式:sP = I * E * W,其中P是2D图像点,I是内参矩阵,E是外参矩阵,W是3D世界点),代码如下:
// 假设内参、外参(旋转/平移向量)、畸变参数已通过cv::calibrateCamera得到 cv::Mat intrinsic; // 3x3 cv::Mat rvec; // 3x1 cv::Mat tvec; // 3x1 std::vector<float> distortion; // ... // 构造外参矩阵 [R | t] cv::Mat extrinsic= cv::Mat(cv::Size(4, 3), CV_64F); cv::Mat rot_mat; cv::Rodrigues(rvec, rot_mat); rot_mat.copyTo(extrinsic(cv::Rect(0, 0, 3, 3))); tvec.copyTo(extrinsic(cv::Rect(3, 0, 1, 3))); // 合并内参与外参得到针孔模型矩阵 cv::Mat pinhole_model = intrinsic * extrinsic; // 3D点转2D点 cv::Mat world_point_homogeneous = cv::Mat(cv::Size(1, 4), CV_64F); world_point_homogeneous.at<double>(0, 0) = world_point.x; world_point_homogeneous.at<double>(1, 0) = world_point.y; world_point_homogeneous.at<double>(2, 0) = world_point.z; world_point_homogeneous.at<double>(3, 0) = 1; cv::Mat projected_homogeneous = pinhole_model * world_point_homogeneous; // 齐次坐标转图像坐标(对应公式中除以s) const double w = projected_homogeneous.at<double>(2, 0); cv::Point2d image_point; image_point.x = projected_homogeneous.at<double>(0, 0) / w; image_point.y = projected_homogeneous.at<double>(1, 0) / w;
该实现和cv::projectPoints()结果接近,但有两个疑问:
- 畸变参数
distortion何时及如何应用到转换流程中? - 实现2D转3D需要获取公式中的
s(即代码中的w)因子,该如何获取?
解答
一、2D转3D的实现与OpenCV内置函数
OpenCV没有直接的2D转3D内置函数——单张图像的2D点无法唯一确定3D点(存在深度歧义),但针对平面物体(比如你的棋盘格),可以利用平面约束推导逆变换:
核心实现思路(基于平面约束)
假设棋盘格在世界坐标系下的平面为z=0(标定阶段通常这么设置),步骤如下:
- 对图像点去畸变:用
cv::undistortPoints把畸变的2D像素点转换为相机归一化平面(z=1)的点。 - 外参逆变换:利用标定得到的外参(旋转/平移向量),将相机坐标系下的点转换回世界坐标系。
具体代码示例:
// 1. 对2D图像点去畸变,得到归一化平面点(相机坐标系z=1) std::vector<cv::Point2f> undistorted_points; cv::undistortPoints(2d_checker_position, undistorted_points, intrinsic, distortion); // 2. 将旋转向量转为旋转矩阵,并计算其转置(旋转矩阵的逆等于转置) cv::Mat rot_mat; cv::Rodrigues(rvec, rot_mat); cv::Mat rot_mat_t = rot_mat.t(); cv::Mat t_vec_mat = tvec.clone(); // 3. 逐个计算世界坐标点 std::vector<cv::Point3d> world_points; for (const auto& pt : undistorted_points) { // 相机归一化点转为相机坐标系3D点(z=1) cv::Mat camera_point = (cv::Mat_<double>(3,1) << pt.x, pt.y, 1.0); // 逆变换:世界点 = 旋转矩阵转置 * (相机点 - 平移向量) cv::Mat temp = rot_mat_t * (camera_point - t_vec_mat); // 平面约束:世界点z=0 world_points.emplace_back(temp.at<double>(0), temp.at<double>(1), 0.0); }
二、畸变参数的应用时机与方式
3D转2D的完整流程里,畸变参数是在针孔投影得到归一化平面点之后应用的,具体步骤:
- 3D世界点 → 相机坐标系点(外参变换:
C = R*W + t) - 相机坐标系点 → 归一化平面点(除以z坐标:
(x/z, y/z, 1)) - 对归一化点应用畸变:用OpenCV的径向+切向畸变模型计算畸变后的点
- 畸变点 → 图像像素点(内参映射:
u = fx*x_distorted + cx, v = fy*y_distorted + cy)
你的手动实现缺少了第3步,所以和cv::projectPoints结果有差异。手动添加畸变的代码示例:
// 假设已得到归一化平面点(x_normalized, y_normalized) double x = undistorted_point.x; double y = undistorted_point.y; // 提取畸变参数:k1,k2,p1,p2,k3(OpenCV标定输出的顺序) double k1 = distortion[0], k2 = distortion[1], p1 = distortion[2], p2 = distortion[3], k3 = distortion[4]; double r2 = x*x + y*y; // 计算畸变后的点 double x_distorted = x*(1 + k1*r2 + k2*r2*r2 + k3*r2*r2*r2) + 2*p1*x*y + p2*(r2 + 2*x*x); double y_distorted = y*(1 + k1*r2 + k2*r2*r2 + k3*r2*r2*r2) + p1*(r2 + 2*y*y) + 2*p2*x*y; // 映射到图像像素点 double u = intrinsic.at<double>(0,0)*x_distorted + intrinsic.at<double>(0,2); double v = intrinsic.at<double>(1,1)*y_distorted + intrinsic.at<double>(1,2);
三、2D转3D中s因子的获取
公式中的s(即你代码里的w)是相机坐标系下点的z坐标(深度值)。单张图像无法直接获取,但结合平面约束可以解出:
以棋盘格z=0为例,推导过程:
相机坐标系点C = (s*u', s*v', s)(u',v'是去畸变后的归一化点),通过外参变换到世界坐标系后满足W.z=0,代入逆变换方程可解出s:
// 去畸变后的归一化点(u', v') double u_prime = undistorted_point.x; double v_prime = undistorted_point.y; // 提取外参矩阵元素 cv::Mat rot_mat; cv::Rodrigues(rvec, rot_mat); double r31 = rot_mat.at<double>(2,0), r32 = rot_mat.at<double>(2,1), r33 = rot_mat.at<double>(2,2); double tx = tvec.at<double>(0), ty = tvec.at<double>(1), tz = tvec.at<double>(2); // 计算s值 double numerator = r31*tx + r32*ty + r33*tz; double denominator = r31*u_prime + r32*v_prime + r33; double s = numerator / denominator;
得到s后,相机坐标系点为(s*u', s*v', s),再通过外参逆变换即可得到世界坐标点。
内容的提问来源于stack exchange,提问作者wsys
相关产品推荐
相关产品推荐

