您的位置:首页 > 运维架构

OpenCV计算变换与重投影的矩阵说明

2017-06-18 11:40 316 查看
本篇博客主要讨论opencv中两个函数中几何变换(矩阵)的对应关系,以下函数接口摘自opencv-2.4.8官方文档

1.Finds an object pose from 3D-2D point correspondences.

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


2.Projects 3D points to an image plane.

void projectPoints(InputArray objectPoints, InputArray rvec, InputArray tvec, InputArray cameraMatrix, InputArray distCoeffs, OutputArray imagePoints, OutputArray jacobian=noArray(), double aspectRatio=0 )


根据说明,可以看出,函数
solvepnp
接收一组对应的3D坐标和2D坐标,计算得到两组坐标对应的几何变换(旋转矩阵
rvec
,平移矩阵
tvec
);

函数
projectPoints
与之相反,根据所给的3D坐标和已知的几何变换来求解投影后的2D坐标。

在空间几何变换中,很多时候会涉及到由坐标A变换到B或B变换到A类似容易弄混的情况(即一些矩阵和其逆矩阵具有不同应用场景的特性),一旦把变换矩阵的对象弄反,结果将南辕北辙。本文将通过代码实验的方式来验证,上述两个函数在所给变换一致(即
tvec
rvec
相同)的情况下,所关联的空间点2D、3D坐标是否一致。

首先,给出小孔相机模型和一组3D点

camara=⎡⎣⎢⎢⎢fx000fy0cxcy1⎤⎦⎥⎥⎥(1)

这里为了直观说明坐标值,我们取fx、fy、cx、cy均为100

cv::Mat camera = ( cv::Mat_<double>(3,3) << 100 , 0 , 100 , 0 , 100 , 100 , 0 , 0 , 1 );
std::vector<cv::Point3f> totalPre;
totalPre.push_back(cv::Point3f(-1,-1,1));
totalPre.push_back(cv::Point3f(-1,1,1));
totalPre.push_back(cv::Point3f(1,-1,1));
totalPre.push_back(cv::Point3f(1,1,1));


像素坐标系Op和相机坐标系Oc的转换公式如下:

x=(u−cx)∗fx/z,y=(v−cy)∗fy/z(2)

totalPre
中的四个点可以看作是相机光心正前方距离为1处(此处不考虑单位,需要单位时,保证各处尺度一致即可)一个2*2的正方形的四个角点,其在成像平面上的像素坐标为(0,0)、(0,200)、(200,0)、(200,200)

假设这四个3D点在空间中进行了统一的运动M(rvec,tvec),导致其在成像平面上的投影像素坐标发生了变化,变成了如下四个点:

std::vector<cv::Point2f> totalPost;
totalPost.push_back(cv::Point2f(50,50));
totalPost.push_back(cv::Point2f(50,150));
totalPost.push_back(cv::Point2f(150,50));
totalPost.push_back(cv::Point2f(150,150));


对比可以看出,正方形面积缩小到了原来的1/4,长宽变为了原来的一半,可以直观地想象出来,这四个点所进行的运动即垂直远离成像平面,移动到了原来距离二倍的位置。使用本文引用的第一个函数,求出这个过程中的几何变换

cv::Mat rvec,tvec;
cv::solvePnP(totalPre,totalPost,camera,cv::Mat(),rvec,tvec);
//由于这个过程中实际上没有发生旋转,因此输出平移信息观察结果
cout << "total PnP result : " << tvec << endl;


输出结果:

total PnP result : [-7.035697327650722e-17; -7.035697327650722e-17; 1]


结果里
tvec
的前两个维度近似为0,第三个维度为1,表示在z方向(成像平面法向)位移为1,也就是说传给函数的这几个3D坐标向前运动位移为1,和我们的直观认识一致。

然后来看看我们引用的第二个函数,第二个函数涉及到重投影,就是说,假如没有这个大小为1的位移,我们通过公式(2)即可由3D坐标得到2D坐标,但正是由于这个位移,因此需要引入几何变换(‘重’投影),我们要验证的是第一个函数计算得到的运动参数
tvec
rvec
直接代入第二个函数,是否就能得到运动后的2D坐标,代码如下:

vector<cv::Point2f> reProjection;
cv::projectPoints(totalPre,rvec,tvec,camera,cv::Mat(),reProjection);
for (auto i:reProjection)
cout << i.x << " " << i.y << endl;


结果如下:

50 50
50 150
150 50
150 150


和前面所给2D坐标一致,证明这两个函数的运动参数对应即可得到一致的坐标关系。

机器视觉里面,需要计算变换时,常常是相机在运动,而并非观测点在运动,而在文中所讨论的两个函数里,3D空间坐标系实际上是依照相机坐标系建立的,因此将相机视作静止,认为观测点在运动,因而这里得到的几何变换未必能直接拿来使用,相机运动的情况请参考另一篇博文 讨论opencv位姿估计结果与实际运动轨迹的关系(涉及变换矩阵与其逆矩阵)

完整代码如下:

cv::Mat camera = ( cv::Mat_<double>(3,3) << 100 , 0 , 100 , 0 , 100 , 100 , 0 , 0 , 1 );
std::vector<cv::Point2f> totalPost;
std::vector<cv::Point3f> totalPre;
totalPre.push_back(cv::Point3f(-1,-1,1));
totalPre.push_back(cv::Point3f(-1,1,1));
totalPre.push_back(cv::Point3f(1,-1,1));
totalPre.push_back(cv::Point3f(1,1,1));

totalPost.push_back(cv::Point2f(50,50));
totalPost.push_back(cv::Point2f(50,150));
totalPost.push_back(cv::Point2f(150,50));
totalPost.push_back(cv::Point2f(150,150));

cv::Mat rvec,tvec; cv::solvePnP(totalPre,totalPost,camera,cv::Mat(),rvec,tvec); //由于这个过程中实际上没有发生旋转,因此输出平移信息观察结果 cout << "total PnP result : " << tvec << endl;

vector<cv::Point2f> reProjection; cv::projectPoints(totalPre,rvec,tvec,camera,cv::Mat(),reProjection); for (auto i:reProjection) cout << i.x << " " << i.y << endl;


若有内容需要讨论,欢迎发邮件至gxsheen@foxmail.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息