You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过相机内外参获取真实世界点?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()结果接近,但有两个疑问:

  1. 畸变参数distortion何时及如何应用到转换流程中?
  2. 实现2D转3D需要获取公式中的s(即代码中的w)因子,该如何获取?

解答

一、2D转3D的实现与OpenCV内置函数

OpenCV没有直接的2D转3D内置函数——单张图像的2D点无法唯一确定3D点(存在深度歧义),但针对平面物体(比如你的棋盘格),可以利用平面约束推导逆变换:

核心实现思路(基于平面约束)

假设棋盘格在世界坐标系下的平面为z=0(标定阶段通常这么设置),步骤如下:

  1. 对图像点去畸变:用cv::undistortPoints把畸变的2D像素点转换为相机归一化平面(z=1)的点。
  2. 外参逆变换:利用标定得到的外参(旋转/平移向量),将相机坐标系下的点转换回世界坐标系。

具体代码示例:

// 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的完整流程里,畸变参数是在针孔投影得到归一化平面点之后应用的,具体步骤:

  1. 3D世界点 → 相机坐标系点(外参变换:C = R*W + t)
  2. 相机坐标系点 → 归一化平面点(除以z坐标:(x/z, y/z, 1))
  3. 对归一化点应用畸变:用OpenCV的径向+切向畸变模型计算畸变后的点
  4. 畸变点 → 图像像素点(内参映射: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 12:30:03