全景图像拼接——相机参数评估

一、项目名称:全景图像拼接

二、语言、库和开发环境:C++、opencv249、VS2013

三、参考

1:博客https://blog.csdn.net/zhaocj/article/details/78799194?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

      2:计算机视觉中的多视图几何

四、包含原理:

1、特征点

2、2D射影变换

3、相机参数

4、拼接缝的查找

 

 

4.3、相机参数评估

 

 

 

 

从图6可以看出,真实物体通过小孔映射到成像平面上,小孔到成像平面的距离称为焦距f。在成像平面上的图像是镜像倒立的,所以为了研究方便,在小孔和物体之间定义一个虚拟成像平面(在后面,我们把该平面也称为成像平面),它与小孔的距离也为焦距,则两个成像平面的图像大小是相同的,但虚拟成像平面上的图像与原物体的方向是一样的。

 

我们以小孔为坐标原点建立一个三维直角坐标系XYZ,坐标原点C称为相机的光心。成像平面xy平行于XY,并且距离光心C为f,其中Z轴定义为光轴,它与成像平面xy的交点为P,因此CP=f。设空间中的任一点Q的坐标为(X, Y, Z),该点映射到成像平面的点q的坐标为(x, y,f)。

我们以小孔为坐标原点建立一个三维直角坐标系XYZ(如图7所示),坐标原点C称为相机的光心。成像平面xy平行于XY,并且距离光心C为f,其中Z轴定义为光轴,它与成像平面xy的交点为P,因此CP=f。设空间中的任一点Q的坐标为(X, Y, Z),该点映射到成像平面的点q的坐标为(x, y,f)。

 

考虑人站在原地,没有发生位移,只是旋转相机角度

这部分结果对前两行、前两列调整至系数为1,便于计算。

 

 

当有多幅图像需要拼接为一幅图像时,是要以其中一幅图像为基准,其他图像都要旋转到该基准图像平面上的,所以就需要找到基准平面。这里用到的算法为最大生成树算法。

 

待拼接图像的排列是无序的,而且我们事先是不知道它们之间的关系的,我们只知道它们之间的单应矩阵,而单应矩阵是由图像间的内点计算评估得到的。由此我们可以构造一个无向图G,G的节点为图像,G的边为内点数,然后利用并查集在该G中得到一棵最大生成树。

 

我们把树的中心节点作为基准图像。中心节点的确定方法为:计算每一个节点到所有叶节点的距离,把其中的最大值作为该节点的值;然后选择这些值中最小者作为中心节点。这里的距离指的是节点间的节点数。如图8(c)所示,节点A和C为中心节点。中心节点可能是1个,也可能是2个,如果是2个,则选择其中一个即可。

 

基于以上方法,我们得到了相机的内外参数,但这样得到的参数忽略了多个图像间的约束,而且会产生累计误差。这时,我们就需要用到光束平差法(Bundle Adjustment)来精确化相机参数。光束指的是相机光心“发射”出来的光束(或射线),它透过相片达到物点,因此相片中的点应该和物点处于一个光束线上,但当两者不共线时,我们就需要对结构和视角参数进行调节,以达到最优解甚至共线的目的。最优化一般采用前文介绍过的LM算法。

 

应用于光束平差法的LM算法,误差指标函数可以有两个,一个是重映射误差,另一个是射线发散误差。

 

 

 

 

 

代码:相机参数评估

         HomographyBasedEstimator estimator;    //相机参数评估

         vector<CameraParams> cameras;

         estimator(features, pairwise_matches, cameras);

 

基本公式:

uv1=f0ppx0afppy001R|tXYZ1

 

 

 

结果:当前两张图像对应两个相机,每一相机由10个参数组成

 

 

 

 

 

 

具体代码:

1、输入数据:两张图像得到的features和匹配后的pairwise_matches特征关系,

1)Features的格式为:

图像0中有976个特征点

图像1中有2325个特征点

其中,第0张图片的特征keypoints

其中,第0张图片的第0个特征点内容:

 

其中,第0张图片的特征描述子descriptors:为976*64的矩阵,代表976个特征,每个特征占64个字节。

  

 

  1. pairwise_matches的格式

共两张图像,可以得到4中对应关系,图像i和j的匹配为:

pairwise_matches[i*num_images + j]

 

其中,以i=0,j=1为例,每一匹配信息为:

其中,matches为280表示图像0到图像1之间的映射有280组对应特征点,以第0个match为例:

queryIdx : 查询点的索引,指0图片中特征库的索引

trainIdx : 被查询到的点的索引,指1图片中特征库的索引

 

 

 

2、参数初始化

1)初始化 f0,f1,由H中得到F0、F1

原理:

在计算f时,为减少参数量,不考虑ppx、ppy和a三个参数,

 

这部分结果对前两行、前两列调整至系数为1,便于计算。

 

 

 

 

 

Step 1: 计算和确定f1

 

  d1 = h[6] * h[7];    //式48的分母

  d2 = (h[7] - h[6]) * (h[7] + h[6]);    //式47的分母

  v1 = -(h[0] * h[1] + h[3] * h[4]) / d1;    //式48

  v2 = (h[0] * h[0] + h[3] * h[3] - h[1] * h[1] - h[4] * h[4]) / d2;    //式47

  if (v1 > 0 && v2 > 0) f1 = sqrt(std::abs(d1) > std::abs(d2) ? v1 : v2);

 

在v1和v2中选择分母较大的作为f1

 

Step 2:计算和确定f0

  d1 = h[0] * h[3] + h[1] * h[4];    //式44的分母

  d2 = h[0] * h[0] + h[1] * h[1] - h[3] * h[3] - h[4] * h[4];    //式43的分母

  v1 = -h[2] * h[5] / d1;    //式44

  v2 = (h[5] * h[5] - h[2] * h[2]) / d2;    //式43

  if (v1 < v2) std::swap(v1, v2);    //使v1不小于v2

             if (v1 > 0 && v2 > 0) f0 = sqrt(std::abs(d1) > std::abs(d2) ? v1 : v2);

 在v1和v2中选择分母较大的作为f0

 

Step 3:计算确定f

      取中值

          all_focals.push_back(sqrt(f0 * f1));    //式49,把焦距存入all_focals内

          median = all_focals[all_focals.size() / 2];

 

Step 4:计算最大生成树,确定中心点图像

 

Step 5:计算旋转矩阵

原理:

        cameras[edge.from].R=I

        cameras[edge.to].aspect 为1

             cameras[edge.from].ppx;    //表示式33的cx  为0

                   cameras[edge.from].ppy;    //表示式33的cy   为0

 

                   int pair_idx = edge.from * num_images + edge.to;    //表示匹配点对的索引

                   //构造式51中的参数K0

                   Mat_<double> K_from = Mat::eye(3, 3, CV_64F);    //初始化

                   K_from(0, 0) = cameras[edge.from].focal;    //表示式33的fu

                   //表示式33的fv

                   K_from(1, 1) = cameras[edge.from].focal * cameras[edge.from].aspect;

                   K_from(0, 2) = cameras[edge.from].ppx;    //表示式33的cx  为0

                   K_from(1, 2) = cameras[edge.from].ppy;    //表示式33的cy   为0

                   //构造式51中的参数K1

                   Mat_<double> K_to = Mat::eye(3, 3, CV_64F);    //初始化

                   K_to(0, 0) = cameras[edge.to].focal;    //表示式33的fu

                   K_to(1, 1) = cameras[edge.to].focal * cameras[edge.to].aspect;    //表示式33的fv  

                   K_to(0, 2) = cameras[edge.to].ppx;    //表示式33的cx

                   K_to(1, 2) = cameras[edge.to].ppy;    //表示式33的cy

 

                   Mat R = K_from.inv() * pairwise_matches[pair_idx].H.inv() * K_to;    //式51

 

                   //式52,可见CameraParams变量中R实际存储的是相机旋转矩阵变量的逆

                   cameras[edge.to].R = cameras[edge.from].R * R;

 

Step 6:初始化参数 ppx,ppy

        cameras[i].ppx += 0.5 * features[i].img_size.width;

        cameras[i].ppy += 0.5 * features[i].img_size.height;

 

 

Step 7:BA

原理:

损失:

https://i-blog.csdnimg.cn/blog_migrate/ce0594331e3716dcd537b6b6bdc545f8.png

 

 

  1. 参数初始化

一个相机共7个参数:f、PPX、PPY、a、R(3个)

                   cam_params_.at<double>(i * 7, 0) = cameras[i].focal;    //初始化fu

                   cam_params_.at<double>(i * 7 + 1, 0) = cameras[i].ppx;    //初始化cx

                   cam_params_.at<double>(i * 7 + 2, 0) = cameras[i].ppy;    //初始化cy

                   cam_params_.at<double>(i * 7 + 3, 0) = cameras[i].aspect;    //初始化α

                   svd(cameras[i].R, SVD::FULL_UV);

                   Mat R = svd.u * svd.vt;

                   if (determinant(R) < 0)

                            R *= -1;

 

                   Mat rvec;

                   Rodrigues(R, rvec);    //旋转矩阵R由Rodrigues算法得到旋转向量rvec

                   CV_Assert(rvec.type() == CV_32F);

                   cam_params_.at<double>(i * 7 + 4, 0) = rvec.at<float>(0, 0);    //初始化rx

                   cam_params_.at<double>(i * 7 + 5, 0) = rvec.at<float>(1, 0);    //初始化ry

                   cam_params_.at<double>(i * 7 + 6, 0) = rvec.at<float>(2, 0);    //初始化rz

 

2) 计算迭代损失函数:

 

 

3)得到单应,计算重投影误差:

 

4)LM优化

 

PS:在 opencv实现的BA的 LM算法迭代优化中存在疑惑??

opencv中的原代码:

每次迭代中_jac 和 _err 赋值为0,在算法中是否起到更新和迭代的作用???

 

改正后的代码

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值