PnP 单目相机位姿估计(二):solvePnP利用二维码求解相机世界坐标

本文介绍利用单目相机结合二维码求解相机世界坐标的方法。先阐述原理,通过solvePnP函数,获取特征点世界坐标、2D坐标、相机内参矩阵和畸变参数矩阵来解得外参。接着说明输入参数准备过程,最后介绍相机世界坐标求解,包括世界坐标点在相机坐标系下坐标及相机在世界坐标中的坐标求解。

https://blog.youkuaiyun.com/cocoaqin/article/details/77848588

IDE:visual studio 2013 
使用库:Eigen opencv2.4.9 
文档版本:1.1


1.前言

  很多时候,当我们利用单目相机获取图像时,往往只能获取图像中特征物体的像素坐标。而在空间三维点计算时,我们也第一时间会想到深度相机(Kinect、Realsense等)、双目相机等。 
  但是在实际中,我们亦可以通过单目相机来求得空间三维点,但这个往往有一个条件,已知特征物体的三个以上特征点参数。

在本章中,我们将使用二维码作为标志物,通过二维码四个角点,通过P4P求解相机的位姿及空间坐标。 
特别说明:在本章以及未来的几篇博文中都将以右手坐标系为基准,Z轴垂直于二维码平面往里。

这里写图片描述 
   
               二维码参数:长140mm,宽140mm

在求解时不一定要使用二维码,也可以用其它标志物代替,使用二维码的好处是,通过识别二维码内部的信息可以更精准的匹配以及识别,同时也能更容易的确定四个角点的顺序。 
  


2.原理简介

bool solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int flags=ITERATIVE )

  objectPoints:特征点的世界坐标,坐标值需为float型,不能为double型,可以为mat类型,也可以直接输入vector 
  imagePoints:特征点在图像中的像素坐标,可以输入mat类型,也可以直接输入vector,注意输入点的顺序要与前面的特征点的世界坐标一一对应 
  cameraMatrix:相机内参矩阵 
  distCoeffs:相机的畸变参数【Mat_(5, 1)】 
  rvec:输出的旋转向量 
  tvec:输出的平移矩阵 
  最后的输入参数有三个可选项: 
  CV_ITERATIVE,默认值,它通过迭代求出重投影误差最小的解作为问题的最优解。 
  CV_P3P则是使用非常经典的Gao的P3P问题求解算法。 
  CV_EPNP使用文章《EPnP: Efficient Perspective-n-Point Camera Pose Estimation》中的方法求解。 
  详见 solvepnp三维位姿估算

  因此我们只要获得 特征点的世界坐标(三维坐标)、2D坐标(像素坐标)、相机内参矩阵相机畸变参数矩阵 以上四个参数即可以解得相机与标志物之间的外参(R、T),并以此求得相机的世界坐标(以标志物为世界坐标平面,且原点为标志物已知某一点)。


3.输入参数准备

在上一节中我们已知solvePnP需要输入四个参数,在这一节中,将介绍如何获得这四个参数。

3.1 objectPoints特征点世界坐标

  以特征点所在平面为世界坐标XY平面,并在该平面中确定世界坐标的原点,以我设计的二维码为例,我设定二维码的中心为世界坐标的原点,并按照顺时针方向逐个输入四个角点的世界坐标。

注意,输入一定要按照顺序输入

    m_markerCorners3d.push_back(cv::Point3f(-70.0f, -70.0f, 0));
    m_markerCorners3d.push_back(cv::Point3f(+70.0f, -70.0f, 0));    
    m_markerCorners3d.push_back(cv::Point3f(+70.0f, +70.0f, 0));
    m_markerCorners3d.push_back(cv::Point3f(-70.0f, +70.0f, 0));

3.2 imagePoints特征点在摄像头下的像素点坐标

在这儿将获得四个特征点对应2D的像素点坐标,而这个过程你可以人为的从图像中逐个点获得,也可以通过二维码检测程序检测出,详见 PnP 单目相机位姿估计(三):二维码四个角点的检测

注意,这儿检测到的四个像素点的输入顺序要和输入的世界坐标的顺序相同

3.3cameraMatrix,distCoeffs内参矩阵和畸变矩阵

我们这儿采用matlab的标定工具标定,并形成以下矩阵输入solvePnP

camMatrix = (Mat_<double>(3, 3) << 598.29493, 0, 304.76898, 0, 597.56086, 233.34673, 0, 0, 1);
distCoeff = (Mat_<double>(5, 1) << -0.53572,1.35993,-0.00244,0.00620,0.00000);
  •  

4.相机世界坐标的求解

在上一节中,我们准备好了四个参数,把这四个参数输入solvePnP中,即可获得求得的R旋转矩阵、T平移矩阵,拿到这两个矩阵的值,我们就可以获得许多我们想要的空间信息啦~

已知 Pc=R*Po+T 
定义Pc为相机坐标系的点值,Po为世界坐标系的点值,R、T为世界坐标系和相机坐标系的相对外参。

而我们利用solvePnP解得的R和T正是相机坐标系和世界坐标系的相对外参。

注意,solvePnP返回的raux是旋转向量,可通过罗德里格斯变换成旋转矩阵R。

4.1求世界坐标中的点在相机坐标系下的坐标

当Po=[0;0;0;]时,Pc=T即世界坐标原点(二维码的中心)在相机坐标系下的坐标就为T 
同样的道理,当我们已知一点的世界坐标Po,我们就可以求得Pc

4.2求相机在世界坐标中的坐标

因为相机在相机坐标中相当于Pc=[0;0;0],故Po=-R’*T 即可解得相机在世界坐标系下的坐标 
其中R’为R的逆或者转置矩阵(R是正交矩阵,R的逆等于R的转置)

4.3效果(求解相机在世界坐标系下的坐标点):

这里写图片描述

4.4求解代码

        Marker& m = detectedMarkers[i];
        cv::Mat Rvec;
        cv::Mat_<float> Tvec;
        cv::Mat raux, taux;

        cv::solvePnP(m_markerCorners3d, m.points, camMatrix, distCoeff, raux, taux, false, CV_P3P);

        raux.convertTo(Rvec, CV_32F);    //旋转向量
        taux.convertTo(Tvec, CV_32F);   //平移向量

        cv::Mat_<float> rotMat(3, 3);
        cv::Rodrigues(Rvec, rotMat);  //由于solvePnP返回的是旋转向量,故用罗德里格斯变换变成旋转矩阵

        //格式转换
        Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> R_n;
        Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> T_n;
        cv2eigen(rotMat, R_n);
        cv2eigen(Tvec, T_n);
        Eigen::Vector3f P_oc;

        P_oc = -R_n.inverse()*T_n;
  •  
<think> 我们正在处理世界坐标系、相机坐标系、图像坐标系和像素坐标系之间的转换。根据引用资料,转换过程主要分为两个步骤: 1. 世界坐标系到相机坐标系(通过相机外参) 2. 相机坐标系到图像坐标系(通过相机内参,其中图像坐标系通常指物理单位表示的图像平面坐标,然后还需要转换到像素坐标系) 具体步骤: - 世界坐标系(World Coordinate System, WCS)是物体在真实世界中的坐标,用三维点表示。 - 相机坐标系(Camera Coordinate System, CCS)是以相机光心为原点的坐标系,Z轴沿光轴方向。 - 图像坐标系(Image Coordinate System)是在成像平面上的维坐标系,通常以光心在图像平面的投影为原点(一般用物理单位,如毫米)。 - 像素坐标系(Pixel Coordinate System)是图像在计算机中存储的坐标系,原点在图像的左上角,单位为像素。 转换关系: 1. 世界坐标系 -> 相机坐标系:通过刚体变换(旋转矩阵R和平移向量t)。若有一个点P在世界坐标系中的坐标为$P_w = (X_w, Y_w, Z_w)^T$,在相机坐标系中的坐标为$P_c = (X_c, Y_c, Z_c)^T$,则: $$ P_c = R \cdot P_w + t $$ 或者写成齐次坐标形式: $$ \begin{bmatrix} X_c \\ Y_c \\ Z_c \\ 1 \end{bmatrix} = \begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} \begin{bmatrix} X_w \\ Y_w \\ Z_w \\ 1 \end{bmatrix} $$ 2. 相机坐标系 -> 图像坐标系(物理单位):利用小孔成像模型。设相机焦距为f,则: $$ x = f \frac{X_c}{Z_c}, \quad y = f \frac{Y_c}{Z_c} $$ 这里得到的(x,y)是物理单位(例如毫米)表示的图像平面坐标。 3. 图像坐标系(物理单位)-> 像素坐标系:需要知道每个像素在x和y方向的物理尺寸(dx, dy,单位:毫米/像素)以及图像中心(即主点)在像素坐标系中的坐标(u0, v0)。则: $$ u = \frac{x}{dx} + u_0, \quad v = \frac{y}{dy} + v_0 $$ 通常,我们用一个内参矩阵K来表示这个变换: $$ K = \begin{bmatrix} f_x & 0 & u_0 \\ 0 & f_y & v_0 \\ 0 & 0 & 1 \end{bmatrix} $$ 其中,$f_x = f / dx$, $f_y = f / dy$。 因此,从相机坐标系到像素坐标系的齐次坐标变换可以写为: $$ \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \frac{1}{Z_c} K \begin{bmatrix} X_c \\ Y_c \\ Z_c \end{bmatrix} $$ 注意:这里我们取齐次坐标,所以实际计算时,需要将得到的(u,v)除以第三项(即深度Z_c)来得到归一化的像素坐标。 综合起来,从世界坐标到像素坐标的变换为: $$ \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \frac{1}{Z_c} K \begin{bmatrix} R & t \end{bmatrix} \begin{bmatrix} X_w \\ Y_w \\ Z_w \\ 1 \end{bmatrix} $$ 其中,$\begin{bmatrix} R & t \end{bmatrix}$是外参矩阵(通常为3×4矩阵,或者使用4×4齐次矩阵取前3行),K是内参矩阵(3×3)。 注意:在实际应用中,我们通常将世界坐标点表示为齐次坐标(4维向量,最后一项为1),然后左乘外参矩阵(3×4)得到相机坐标(3维向量,非齐次),再左乘内参矩阵(3×3)得到齐次像素坐标,最后除以深度(即Z_c)得到像素坐标。 代码示例(参考引用[2]): 假设我们有: - 内参矩阵K - 外参矩阵(这里外参矩阵可以用4×4的变换矩阵T表示,或者用旋转矩阵R和平移向量t表示) 以下是一个转换函数的示例: ```python import numpy as np def world_to_pixel(point_world, R, t, K): """将世界坐标系中的点转换到像素坐标系 参数: point_world: 世界坐标系中的点,形状(3,)(4,),如果是3维则自动转换为齐次坐标 R: 旋转矩阵,形状(3,3) t: 平移向量,形状(3,) K: 相机内参矩阵,形状(3,3) 返回: 像素坐标(u, v) """ # 确保point_world是齐次坐标形式(4维) if len(point_world) == 3: point_world = np.append(point_world, 1.0) # 将世界坐标点转换为相机坐标系 point_camera = R @ point_world[:3] + t # 这里point_world[:3]是前三项,即非齐次部分 # 或者使用齐次变换矩阵(这里用分步计算) # 注意:上面这种写法等价于:point_camera = R @ point_world[:3] + t.reshape(3,1) 但如果t是行向量则需要转置 # 更通用的做法是构建一个3x4的变换矩阵 T = np.hstack((R, t.reshape(3,1))) point_camera = T @ point_world # 此时point_camera是3维向量 # 从相机坐标系到像素坐标系 # 先进行投影(得到归一化坐标) if point_camera[2] == 0: # 避免除以0 return None x = point_camera[0] / point_camera[2] y = point_camera[1] / point_camera[2] # 转换为齐次坐标 point_image = np.array([x, y, 1]) point_pixel = K @ point_image u = point_pixel[0] v = point_pixel[1] return u, v ``` 注意:上面的代码中,我们假设输入的t是列向量,或者能够与R水平拼接(因此t应该是3×1的列向量)。在实际使用中,需要根据输入进行调整。另外,上面的转换没有考虑畸变。 此外,引用[2]中提到外参有时用四元数(qw,qx,qy,qz)和平移向量(tx,ty,tz)表示,我们需要将四元数转换为旋转矩阵。可以使用以下函数转换: ```python import numpy as np from scipy.spatial.transform import Rotation as R def quaternion_to_rotation_matrix(q): """将四元数转换为旋转矩阵 参数: q: 四元数,格式为(qw, qx, qy, qz) 返回: 旋转矩阵R,形状(3,3) """ # 使用scipy的Rotation r = R.from_quat([q[1], q[2], q[3], q[0]]) # 注意:scipy要求输入为(x,y,z,w),所以调整顺序 return r.as_matrix() ``` 这样,如果给定了四元数和平移向量,我们可以先得到旋转矩阵R,然后使用上面的转换函数。 总结: 1. 世界坐标系到相机坐标系:通过外参(旋转矩阵R和平移向量t)进行刚体变换。 2. 相机坐标系到图像坐标系:利用透视投影(小孔成像模型),除以Z_c得到归一化坐标。 3. 图像坐标系(归一化坐标)到像素坐标系:通过内参矩阵K进行线性变换。 注意:在实际应用中,我们通常将内参矩阵K和外参矩阵[R|t]合并成一个投影矩阵P=K[R|t],然后直接使用P将世界坐标点(齐次坐标)变换到像素坐标(齐次坐标),然后除以第三个分量得到像素坐标。 参考文献: [^1]: 计算机视觉:相机成像原理:世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换 [^2]: 世界坐标系,相机坐标系,像素坐标系转换 详细说明(附代码) §§相关问题§§ 1. 相机内参矩阵包含哪些参数?如何标定? 2. 相机外参矩阵的作用是什么?如何求解? 3. 在相机坐标系转换到像素坐标系的过程中,为什么需要除以Z_c? 4. 如何使用OpenCV实现世界坐标系到像素坐标系的转换? </think>### 坐标系转换详解 在计算机视觉中,世界坐标系、相机坐标系、图像坐标系和像素坐标系之间的转换是核心基础。转换过程涉及相机内参和外参,以下是完整的转换流程: #### **1. 世界坐标系 → 相机坐标系** - **转换公式**: $$ \begin{bmatrix} X_c \\ Y_c \\ Z_c \\ 1 \end{bmatrix} = \begin{bmatrix} R & t \\ 0 & 1 \end{bmatrix} \begin{bmatrix} X_w \\ Y_w \\ Z_w \\ 1 \end{bmatrix} $$ 其中: - $(X_w, Y_w, Z_w)$:世界坐标系坐标 - $(X_c, Y_c, Z_c)$:相机坐标系坐标 - $R$:旋转矩阵(3×3) - $t$:平移向量(3×1) - **示例代码**: ```python import numpy as np def world_to_camera(point_world, R, t): point_homogeneous = np.append(point_world, 1) # 齐次坐标 T = np.vstack([np.hstack([R, t.reshape(3,1)]), [0,0,0,1]]) # 外参矩阵 point_camera = T @ point_homogeneous return point_camera[:3] # 返回非齐次坐标 ``` #### **2. 相机坐标系 → 图像坐标系** - **转换公式**(透视投影): $$ \begin{cases} x = f_x \frac{X_c}{Z_c} \\ y = f_y \frac{Y_c}{Z_c} \end{cases} $$ 其中: - $(x, y)$:图像坐标系坐标(物理单位,如毫米) - $f_x, f_y$:焦距参数(内参) #### **3. 图像坐标系 → 像素坐标系** - **转换公式**: $$ \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} \frac{1}{dx} & 0 & u_0 \\ 0 & \frac{1}{dy} & v_0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$ 其中: - $(u, v)$:像素坐标(像素单位) - $(u_0, v_0)$:主点坐标(图像中心) - $(dx, dy)$:单个像素的物理尺寸 #### **4. 完整转换(世界→像素)** - **内参矩阵 $K$**: $$ K = \begin{bmatrix} f_x & 0 & u_0 \\ 0 & f_y & v_0 \\ 0 & 0 & 1 \end{bmatrix} $$ - **转换公式**: $$ Z_c \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = K \begin{bmatrix} R & t \end{bmatrix} \begin{bmatrix} X_w \\ Y_w \\ Z_w \\ 1 \end{bmatrix} $$ - **Python实现**: ```python def world_to_pixel(point_world, K, R, t): point_cam = world_to_camera(point_world, R, t) x, y, z = point_cam # 相机坐标系→归一化坐标 x_norm = x / z y_norm = y / z # 像素坐标 uv_homogeneous = K @ np.array([x_norm, y_norm, 1]) return uv_homogeneous[:2] # 返回(u,v) ``` #### **关键参数说明** | 参数类型 | 参数示例 | 物理意义 | |----------|----------------|------------------------------| | **内参** | $f_x, f_y$ | 焦距(像素单位) | | | $u_0, v_0$ | 主点(图像中心像素坐标) | | | $k_1, k_2$ | 畸变系数(未展示) | | **外参** | $R$(旋转矩阵)| 世界坐标系到相机坐标系的旋转 | | | $t$(平移向量)| 世界坐标系到相机坐标系的平移 | > 注:实际应用中需先进行**相机标定**获取内参(如棋盘格标定法),外参可通过PnP算法求解[^1][^2]。 --- ### 应用示例 假设: - 内参 $K = \begin{bmatrix} 800 & 0 & 320 \\ 0 & 800 & 240 \\ 0 & 0 & 1 \end{bmatrix}$ - 外参 $R = I$(单位矩阵),$t = [0, 0, 1]^T$ - 世界点 $P_w = [0.5, 0.5, 0]^T$ 转换过程: 1. $P_c = [0.5, 0.5, 1]^T$(相机坐标) 2. $x = 800 \times 0.5/1 = 400$, $y = 800 \times 0.5/1 = 400$(图像坐标) 3. $u = 400 + 320 = 720$, $v = 400 + 240 = 640$(像素坐标) ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值