VINS-MONO代码解读----vins_estimator(鲁棒初始化部分)

0. 前言

整个初始化部分的pipeline如下所示,参照之前的博客,接下来根据代码一步步讲解。
在这里插入图片描述

1. 旋转约束标定旋转外参Rbc

上回讲了processImageaddFeatureCheckParallax完成了对KF的筛选,我们知道了2nd是否为KF,接下来是初始化和后端求解部分。对于初始化,先标定Ric外参:

//不知道关于外参的任何info,需要标定
if(ESTIMATE_EXTRINSIC == 2)
{ROS_INFO("calibrating extrinsic param, rotation movement is needed");if (frame_count != 0){// 找相邻两帧(bk, bk+1)之间的tracking上的点,构建一个pair,所有pair是一个vector,即corres(pondents),// 要求it.start_frame <= frame_count_l && it.endFrame() >= frame_count_rvector<pair<Vector3d, Vector3d>> corres = f_manager.getCorresponding(frame_count - 1, frame_count);Matrix3d calib_ric;//旋转约束+SVD分解求取Ric旋转外参if (initial_ex_rotation.CalibrationExRotation(corres, pre_integrations[frame_count]->delta_q, calib_ric)){ROS_WARN("initial extrinsic rotation calib success");ROS_WARN_STREAM("initial extrinsic rotation: " << endl << calib_ric);ric[0] = calib_ric;RIC[0] = calib_ric;ESTIMATE_EXTRINSIC = 1;}}
}

CalibrationExRotation是主要函数,用于标定旋转外参Ric,理论依据是旋转约束(下面会讲)。

下面依次讲解各部分代码。

1.1 SfM求取relative pose

重点函数solveRelativeR
其中八点法求取Rt,correspondents不小于9时,1.求取E,2.反解并test出4组Rt,3.使用三角化出的深度来判断正确的那组Rt,这里 求E(看14讲),由E反解出4组Rt(看之前SLAM博客),三角化(看VIO博客),带点进Rt判断正深度来确定正确的Rt(看代码注释) 基本上都是调用的cv库函数,原理之前的SLAM和VIO课程中都有学过,相应回顾即可。

Matrix3d InitialEXRotation::solveRelativeR(const vector<pair<Vector3d, Vector3d>> &corres)
{//大于9个点才进行求解,否则直接返回Identityif (corres.size() >= 9){vector<cv::Point2f> ll, rr;for (int i = 0; i < int(corres.size()); i++){//将找到的这两帧间的所有corr点收集起来,用于后面的 对极约束求本质矩阵E 和 三角化求深度ll.push_back(cv::Point2f(corres[i].first(0), corres[i].first(1)));rr.push_back(cv::Point2f(corres[i].second(0), corres[i].second(1)));}cv::Mat E = cv::findFundamentalMat(ll, rr);//对极约束求本质矩阵E=t^Rcv::Mat_<double> R1, R2, t1, t2;decomposeE(E, R1, R2, t1, t2);//这应该是什么判断if (determinant(R1) + 1.0 < 1e-09){E = -E;decomposeE(E, R1, R2, t1, t2);}//用四个解分别进行Triangulation,带入points,求深度为正的比例,取比例最大的作为正确的Rt解double ratio1 = max(testTriangulation(ll, rr, R1, t1), testTriangulation(ll, rr, R1, t2));double ratio2 = max(testTriangulation(ll, rr, R2, t1), testTriangulation(ll, rr, R2, t2));cv::Mat_<double> ans_R_cv = ratio1 > ratio2 ? R1 : R2;Matrix3d ans_R_eigen;for (int i = 0; i < 3; i++)for (int j = 0; j < 3; j++)ans_R_eigen(j, i) = ans_R_cv(i, j);return ans_R_eigen;}return Matrix3d::Identity();
}

三角化以及反解E

//将一个点
double InitialEXRotation::testTriangulation(const vector<cv::Point2f> &l,const vector<cv::Point2f> &r,cv::Mat_<double> R, cv::Mat_<double> t)
{cv::Mat pointcloud;//因为triangulatePoints函数要传入分别两帧的pose,所以通常假设第一帧的外参pose是Identity,这样由relative Rt即可得第二帧的pose,即P1 = relative Rtcv::Matx34f P = cv::Matx34f(1, 0, 0, 0,0, 1, 0, 0,0, 0, 1, 0);cv::Matx34f P1 = cv::Matx34f(R(0, 0), R(0, 1), R(0, 2), t(0),R(1, 0), R(1, 1), R(1, 2), t(1),R(2, 0), R(2, 1), R(2, 2), t(2));//pointcloud是三角化的结果,是(4*m)维,其中m是传入的correspondents的对数,pointcloud每一列都是相应的三角化之后的为归一化的齐次坐标cv::triangulatePoints(P, P1, l, r, pointcloud);int front_count = 0;for (int i = 0; i < pointcloud.cols; i++){//用于归一化,使最后一维为1,这样前三维就是world系下的3D点了,乘以相应的pose就能转化为每个camera系下的3D点(非归一化平面,// 如果再 /p_3d_r(2) 则可转化为归一化平面下的3D点,但是要使用深度是否>0来筛选出正确的Rt的解,所以就不归一化,见《14讲》P169)double normal_factor = pointcloud.col(i).at<float>(3);cv::Mat_<double> p_3d_l = cv::Mat(P) * (pointcloud.col(i) / normal_factor);cv::Mat_<double> p_3d_r = cv::Mat(P1) * (pointcloud.col(i) / normal_factor);if (p_3d_l(2) > 0 && p_3d_r(2) > 0)front_count++;//统计正深度点的个数}ROS_DEBUG("MotionEstimator: %f", 1.0 * front_count / pointcloud.cols);return 1.0 * front_count / pointcloud.cols;//统计所有pointcloud中正深度点的比率(不是很明白,不应该都是正的吗?直接取ratio=1的不行吗?)
}//从E中分解出Rt
void InitialEXRotation::decomposeE(cv::Mat E,cv::Mat_<double> &R1, cv::Mat_<double> &R2,cv::Mat_<double> &t1, cv::Mat_<double> &t2)
{cv::SVD svd(E, cv::SVD::MODIFY_A);//绕z顺时针90°cv::Matx33d W(0, -1, 0,1, 0, 0,0, 0, 1);//绕z逆时针90°cv::Matx33d Wt(0, 1, 0,-1, 0, 0,0, 0, 1);R1 = svd.u * cv::Mat(W) * svd.vt;R2 = svd.u * cv::Mat(Wt) * svd.vt;t1 = svd.u.col(2);//3*3取最后一个是待求量(TODO:为什么t2可以直接取负号?)t2 = -svd.u.col(2);
}

1.2 旋转约束和旋转residual的构建

在这里插入图片描述
两个路径求取的 q b k c k + 1 q_{b_kc_{k+1}} qbkck+1理想状态下相等,详见VIO Ch7博客2.2节
q b k c k + 1 = q b k b k + 1 ∗ q b c = q b c ∗ q c k c k + 1 \begin{align} q_{b_kc_{k+1}} = q_{b_kb_{k+1}} * q_{bc} = q_{bc} * q_{c_kc_{k+1}} \end{align} qbkck+1=qbkbk+1qbc=qbcqckck+1
但是实际情况,二者之间存在误差,即为residual,移项(左移右)即得residual:
r e s i d u a l = q b c − 1 ∗ q b k b k + 1 − 1 ∗ q b c ∗ q c k c k + 1 = q c b ∗ q b k + 1 b k ∗ q c b − 1 ∗ q c k c k + 1 \begin{align} residual &=q_{bc}^{-1}*q_{b_kb_{k+1}}^{-1} * q_{bc} * q_{c_kc_{k+1}} \tag{1.1} \\ &=q_{cb}*q_{b_{k+1}b_k} * q_{cb}^{-1} * q_{c_kc_{k+1}}\tag{1.2} \\ \end{align} residual=qbc1qbkbk+11qbcqckck+1=qcbqbk+1bkqcb1qckck+1(1.1)(1.2)
上式(1.1)和(1.2)两种形式分别对应旋转外参 q b c q_{bc} qbc q c b q_{cb} qcb,跟后面左右乘的定义方式有关,代码中使用的(1.2)形式的定义,左乘L定义为 相机旋转四元数的左乘矩阵,右乘R定义为 IMU旋转四元数的右乘矩阵,下面会讲。

对应到CalibrationExRotation代码中:
Rc q c k c k + 1 q_{c_kc_{k+1}} qckck+1,由SfM获得,从第 k + 1 k+1 k+1帧到第 k k k帧的rotation
Rimudelta_q q b k + 1 b k q_{b_{k+1}b_k} qbk+1bk:由IMU预积分获得,从第 k k k帧积分到第 k + 1 k+1 k+1
所以式(1.2)中剩下的左边部分 q c b ∗ q b k + 1 b k ∗ q c b − 1 q_{cb}*q_{b_{k+1}b_k} * q_{cb}^{-1} qcbqbk+1bkqcb1对应代码中的Rc_g

1.3 左乘右乘的构建

由于使用了式(1.2)形式来构建左右乘,所以式(1.2)整理为
( [ q c k c k + 1 ] L − [ q b k + 1 b k ] R ) q b c = 0 \begin{align} ([\bm q_{c_kc_{k+1}}]_{\bm L} - [q_{b_{k+1}b_k}]_{\bm R})\bm{q}_{bc} = 0 \end{align} ([qckck+1]L[qbk+1bk]R)qbc=0

崔华坤PDF中的
在这里插入图片描述


文献中的
在这里插入图片描述

二者形式上有差别,但是本质上应该是相同的,崔华坤PDF中的应该是根据代码推出来的,我们暂且使用这一套,对应到代码中:

//L为相机旋转四元数的左乘矩阵,记四元数为(w,x,y,z)
//实际构建的是qc_b
//构建结果为
//w -z  y  x
//z  w -x  y
//-y x  w  z
//-z -y -z  w
double w = Quaterniond(Rc[i]).w();
Vector3d q = Quaterniond(Rc[i]).vec();
L.block<3, 3>(0, 0) = w * Matrix3d::Identity() + Utility::skewSymmetric(q);//构建A左上角3*3
L.block<3, 1>(0, 3) = q;
L.block<1, 3>(3, 0) = -q.transpose();
L(3, 3) = w;//R为IMU旋转四元数的右乘矩阵,同样记四元数为(w,x,y,z)
//构建结果为
//w  z  -y  x
//-z  w  x  y
//y  -x  w  z
//-x -y -z  w
Quaterniond R_ij(Rimu[i]);
w = R_ij.w();
q = R_ij.vec();
R.block<3, 3>(0, 0) = w * Matrix3d::Identity() - Utility::skewSymmetric(q);
R.block<3, 1>(0, 3) = q;
R.block<1, 3>(3, 0) = -q.transpose();
R(3, 3) = w;

1.4 系数矩阵A的构建

如上构建出来的四元数左右乘矩阵均为(4*4)维,每组可构建一个式(2)所示的方程,多组correspondents构成多个方程,组成超定方程组,于是就需要构建超定方程组的系数矩阵 A A A,维度为(frame_count * 4, 4),如下所示:
在这里插入图片描述

使用 SVD分解求解超定方程组,取最后一个特征值对应的特征向量即为我们所求的 q c b \bm q_{cb} qcb

1.5 鲁棒核函数

为了使方程具有更好的数值稳定性,在构建系数矩阵 A A A时根据每项的residual为相应的(4*4)块添加了权重 ω \omega ω,权重由Huber损失函数计算而得:
在这里插入图片描述
对应代码:

Quaterniond r1(Rc[i]);
Quaterniond r2(Rc_g[i]);// 这里的angular_distance是角度误差,5是Huber核函数中的threshold,
// angularDistance: returns the angle (in radian) between two rotations返回两个旋转之间的相对旋转(弧度表示),再换算为角度
// 旋转矩阵R和轴角theta之间的关系:tr(R)=1+2cos(theta),所以theta=arccos( (tr(R)-1)/2 ),就是angularDistance的返回值
double angular_distance = 180 / M_PI * r1.angularDistance(r2);
ROS_DEBUG("%d %f", i, angular_distance);//Huber鲁棒核函数
double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0;

其中angularDistance函数应该对应着上图的式(9),输出rad,代码中又转化为angle。式(8)中的threshold设为5。如此,方程的构建部分就全部完成。

1.6 q c b q_{cb} qcb 的求解

使用SVD分解求解超定方程组,根据之前Triangulation的经验(博客第3.2节),需要对特征值 σ 4 / σ 3 \sigma_4/\sigma_3 σ4/σ3的比值进行判断,但是此处发现并没有满足远小于的条件,后面再探究吧。

关于特征值的判断条件
在这里插入图片描述
至此已完成旋转外参标定的所有理论代码部分的对应,附上该部分完整代码注释。

// 旋转约束标定Ric,两条路径求取的旋转应相同,所以移项构建方程,并根据误差项r的鲁棒核函数值w(r)对每一项的系数进行加权,将大于threshold的部分的权值设的较小,
// 最终使用SVD分解求取特征值最小的特征向量
// 详见第7章博客:https://blog.csdn.net/qq_37746927/article/details/133782580#t4
// 旋转约束:qbk_ck+1=qbk_bk+1*qbc=qbc*qck_ck+1
bool InitialEXRotation::CalibrationExRotation(vector<pair<Vector3d, Vector3d>> corres, Quaterniond delta_q_imu, Matrix3d &calib_ric_result)
{frame_count++;//correspondents对数不小于9时求出Rt: 1.求取E,2.反解并test出4组Rt,3.使用三角化出的深度来判断正确的那组Rt//Rc即Rck_ck+1Rc.push_back(solveRelativeR(corres));//Rimu即delta_q即qbk_bk+1即Rbk+1_bkRimu.push_back(delta_q_imu.toRotationMatrix());//旋转约束:qbk_ck+1 = qbk_bk+1 * qbc = qbc * qck_ck+1,移项(左移右)即得residual=qbc^(-1)*qbk_bk+1^(-1) * qbc * qck_ck+1,//其中Rc=qck_ck+1,剩下的项就是qbc^(-1) * qbk_bk+1^(-1) * qbc = qcb * qbk_bk+1^(-1) * qcb^(-1),记为Rc_g//上式左边是rbc版本构建A时L为IMU,右边是rcb版本,构建A时L为camera,这里采用了后者rcb,L为cameraRc_g.push_back(ric.inverse() * delta_q_imu * ric);//Rc_g * Rc就是整个左移右的residual//构建A矩阵Eigen::MatrixXd A(frame_count * 4, 4);//这个维度是什么意思?从第[1]帧开始A.setZero();int sum_ok = 0;for (int i = 1; i <= frame_count; i++){Quaterniond r1(Rc[i]);Quaterniond r2(Rc_g[i]);// 这里的angular_distance是角度误差,5是Huber核函数中的threshold,// angularDistance: returns the angle (in radian) between two rotations返回两个旋转之间的相对旋转(弧度表示),再换算为角度// 旋转矩阵R和轴角theta之间的关系:tr(R)=1+2cos(theta),所以theta=arccos( (tr(R)-1)/2 ),就是angularDistance的返回值double angular_distance = 180 / M_PI * r1.angularDistance(r2);ROS_DEBUG("%d %f", i, angular_distance);//Huber鲁棒核函数double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0;++sum_ok;Matrix4d L, R;//L为相机旋转四元数的左乘矩阵,记四元数为(w,x,y,z)//实际构建的是qc_b//构建结果为//w -z  y  x//z  w -x  y//-y x  w  z//-z -y -z  wdouble w = Quaterniond(Rc[i]).w();Vector3d q = Quaterniond(Rc[i]).vec();L.block<3, 3>(0, 0) = w * Matrix3d::Identity() + Utility::skewSymmetric(q);//构建A左上角3*3L.block<3, 1>(0, 3) = q;L.block<1, 3>(3, 0) = -q.transpose();L(3, 3) = w;//R为IMU旋转四元数的右乘矩阵,同样记四元数为(w,x,y,z)//构建结果为//w  z  -y  x//-z  w  x  y//y  -x  w  z//-x -y -z  wQuaterniond R_ij(Rimu[i]);w = R_ij.w();q = R_ij.vec();R.block<3, 3>(0, 0) = w * Matrix3d::Identity() - Utility::skewSymmetric(q);R.block<3, 1>(0, 3) = q;R.block<1, 3>(3, 0) = -q.transpose();R(3, 3) = w;A.block<4, 4>((i - 1) * 4, 0) = huber * (L - R);}JacobiSVD<MatrixXd> svd(A, ComputeFullU | ComputeFullV);Matrix<double, 4, 1> x = svd.matrixV().col(3);Quaterniond estimated_R(x);ric = estimated_R.toRotationMatrix().inverse();//这里可以看到求得ric实际上是rci=rcb//cout << svd.singularValues().transpose() << endl;//cout << ric << endl;Vector3d ric_cov;ric_cov = svd.singularValues().tail<3>();ROS_DEBUG_STREAM("svd.singularValues():" << svd.singularValues().transpose() << "  ric_cov: " << ric_cov.transpose() << "  ric_cov(1): " << ric_cov(1));//这个取ric_cov相当于取所有特征值里面的第三个,要求它大于某个阈值,最后一个理论上来说应该是特别小的,//根据SVD有效性判断方法,σ4 << σ3才算解有效,σ4/σ3比值的阈值一般取1e-2~1e-4算远小于,//但是实际Debug发现SVD解出来的一半也不满足这个条件,所以ESTIMATE_EXTRINSIC直接config为0,不标了?if (frame_count >= WINDOW_SIZE && ric_cov(1) > 0.25){calib_ric_result = ric;return true;}elsereturn false;
}

2. 旋转约束标定gyro bias

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/216472.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一键填充字幕——Arctime pro

之前的博客中&#xff0c;我们聊到了PR这款专业的视频制作软件&#xff0c;但是pr有许多的功能需要搭配使用&#xff0c;相信不少小伙伴在剪辑视频时会发现一个致命的问题&#xff0c;就是字幕编写。伴随着人们对字幕需求的逐渐增加&#xff0c;这款软件便应运而生~ 相信应该有…

实时LCM的ImgPilot搭建部署

ImgPilot是具有实时潜在一致性模型&#xff08;LCM&#xff09;功能的图像试点 下载源码 GitHub - leptonai/imgpilot: Image pilot with the power of Real-Time Latent Consistency Modelhttps://github.com/leptonai/imgpilot安装前端web cd imgpilot npm install 安装…

UML建模图文详解教程01——Enterprise Architect的安装与使用

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Enterprise Architect概述 官方网站&#xff1a;https://www.sparxsystems.cn/products/ea/&#xff1b;图示如下&#xff1a; Enterprise Architect是一个全功能的、基于…

AI动画制作 StableDiffusion

1.brew -v 2.安装爬虫项目包所必需的python和git等系列系统支持部件 brew install cmake protobuf rust python3.10 git wget pod --version brew link --overwrite cocoapods 3.从github网站克隆stable-diffusion-webui爬虫项目包至本地 ssh-add /Users/haijunyan/.ssh/id_rs…

Spring Boot Actuator 2.2.5 基本使用

1. pom文件 &#xff0c;添加 Actuator 依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 2.application.properties 文件中添加以下配置 …

深度学习第1天:深度学习入门-Keras与典型神经网络结构

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 文章目录 神经网络 介绍 结构 基本要素 Keras 介绍 导入 定义网络 模型训练 前馈神经网络 特点 常见类型 代码示例 反馈神经网络 特点 …

【matlab程序】matlab利用工具包nctool读取grib2、nc、opendaf、hdf5、hdf4等格式数据

【matlab程序】matlab利用工具包nctool读取grib2、nc、opendaf、hdf5、hdf4等格式数据 引用&#xff1a; B. Schlining, R. Signell, A. Crosby, nctoolbox (2009), Github repository, https://github.com/nctoolbox/nctoolbox Brief summary: nctoolbox is a Matlab toolbox…

远程安全访问JumpServer:使用cpolar内网穿透搭建固定公网地址

文章目录 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5. 公网远程访问Jump server6. 固定Jump server公网地址 前言 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。JumpS…

Ubuntu18.04安装A-Loam保姆级教程

系统环境&#xff1a;Ubuntu18.04.6 LTS 1.A-Loam的安装前要求&#xff1a; 1.1 ROS安装&#xff1a;参考我的另一篇博客 Ubuntu18.04安装ROS-melodic保姆级教程_灬杨三岁灬的博客-CSDN博客还是那句话&#xff0c;有时候加了这行也不好使&#xff0c;我是疯狂试了20次&#…

测试工具JMeter的使用

目录 JMeter的安装配置 测试的性能指标 TPS 响应时长 并发连接 和 并发用户 CPU/内存/磁盘/网络 负载 性能测试实战流程 JMeter JMeter快速上手 GUI模式 运行 HTTP请求默认值 录制网站流量 模拟间隔时间 Cookie管理器 消息数据关联 变量 后置处理器 CSV 数据文…

互联网+智慧工地系统源码

智慧工地以施工现场风险预知和联动预控为目标&#xff0c;将智能AI、传感技术、人像识别、监控、虚拟现实、物联网、5G、大数据、互联网等新一代科技信息技术植入到建筑、机械、人员穿戴设施、场地进出关口等各类设备中&#xff0c;实现工程管理与工程施工现场的整合&#xff0…

【vue_1】console.log没有反应

1、打印不出来&#xff1f;2、警告也会出现问题3、插播&#xff1a;如何使用if-else 语句来处理逻辑 1、打印不出来&#xff1f; 要做一个权限不够的弹出消息框 const authority_message () > {ElMessage({type: warrnings,message: 当前用户的权限不够});console.log(he…