VINS 中的旋转外参初始化

​ 为了使这个两个传感器融合,我们首先需要做的事情是将两个传感器的数据对齐,除了时间上的对齐,还有空间上的对齐。空间上的对齐通俗的讲就是将一个传感器获取的数据统一到另一个传感器的坐标系中,其关键在于确定这两个传感器之前的外参,本文将详细介绍 VINS_Monocamera-imu 的旋转外参标定算法原理并对其代码进行解读(VINS-Fusion中也是一样的)。

​ 相机与 IMU 之间的相对旋转如下图所示:

​ 上图表示相机和 IMU 集成的系统从到的运动,其中视觉可以通过特征匹配求得到时刻的旋转增量,同时 IMU 也可以通过积分得到到时刻的旋转增量。两个求得的旋转增量应当是相同的,因此分别将在自己坐标系下求得的增量转移到对方的坐标系下,即经过 \(\mathbf{q}_{bc}\) 变换后应当还是相同的。

​ 相邻两时刻 \(k\) , \(k + 1\) 之间有: IMU 旋转积分 \(\mathbf{q}_{b_{k} b_{k+1}}\) ,视觉测量 \(\mathbf{q}_{c_{k} c_{k+1}}\)。则有:

\[\mathbf{q}_{b_{k} b_{k+1}} \otimes \mathbf{q}_{b c}=\mathbf{q}_{b c} \otimes \mathbf{q}_{c_{k} c_{k+1}}
\]

​ 上面的式子可以写成:

\[\left(\left[\mathbf{q}_{b_{k} b_{k+1}}\right]_{L}-\left[\mathbf{q}_{c_{k} c_{k+1}}\right]_{R}\right) \mathbf{q}_{b c}=\mathbf{Q}_{k+1}^{k} \cdot \mathbf{q}_{b c}=\mathbf{0}
\]

​ 其中 \(\left[ \dot{\ \ \ } \right]_L, \left[ \dot{\ \ \ } \right]_R\) 表示 left and right quaternion multiplication,不清楚可以看附录,其实就是四元数乘法换了个表达形式。

​ 将多个时刻线性方程累计起来,并加上鲁棒核权重得到:

\[\begin{array}{c}
{\left[\begin{array}{c}
w_{1}^{0} \cdot \mathbf{Q}_{1}^{0} \\
w_{2}^{1} \cdot \mathbf{Q}_{2}^{1} \\
\vdots \\
w_{N}^{N-1} \cdot \mathbf{Q}_{N}^{N-1}
\end{array}\right] \mathbf{q}_{b c}=\mathbf{Q}_{N} \cdot \mathbf{q}_{b c}=\mathbf{0}} \\
w_{k+1}^{k}=\left\{\begin{array}{ll}
1, & r_{k+1}^{k}<\text { threshold } \\
\frac{\text { threshold }}{r_{k+1}^{k}}, & \text { otherwise }
\end{array}\right.
\end{array}
\]

该线性方程为超定方程,有最小二乘解。采用 SVD 进行求解。

  • 代码
bool InitialEXRotation::CalibrationExRotation(vector<pair<Vector3d, Vector3d>> corres, Quaterniond delta_q_imu, Matrix3d &calib_ric_result)
{
frame_count++;
Rc.push_back(solveRelativeR(corres)); // 对极几何计算出的 R,t 约束
Rimu.push_back(delta_q_imu.toRotationMatrix()); // IMU 预积分的得到的 R,t 约束
Rc_g.push_back(ric.inverse() * delta_q_imu * ric); // 将imu预积分转换到相机坐标系 Eigen::MatrixXd A(frame_count * 4, 4);
A.setZero();
int sum_ok = 0;
for (int i = 1; i <= frame_count; i++)
{
Quaterniond r1(Rc[i]);
Quaterniond r2(Rc_g[i]); // A.angularDistance(B):是求两个旋转之间的角度差,用弧度表示
double angular_distance = 180 / M_PI * r1.angularDistance(r2);
ROS_DEBUG(
"%d %f", i, angular_distance); double huber = angular_distance > 5.0 ? 5.0 / angular_distance : 1.0; // huber 核函数作加权
++sum_ok;
// L R 分别为左乘和右乘矩阵
Matrix4d L, R; double w = Quaterniond(Rc[i]).w();
Vector3d q = Quaterniond(Rc[i]).vec();
L.block<3, 3>(0, 0) = w * Matrix3d::Identity() + Utility::skewSymmetric(q);
L.block<3, 1>(0, 3) = q;
L.block<1, 3>(3, 0) = -q.transpose();
L(3, 3) = 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; A.block<4, 4>((i - 1) * 4, 0) = huber * (L - R);
} // svd 分解中最小奇异值对应的右奇异向量作为旋转四元数
JacobiSVD<MatrixXd> svd(A, ComputeFullU | ComputeFullV);
Matrix<double, 4, 1> x = svd.matrixV().col(3);
Quaterniond estimated_R(x);
// 所求的 x 是q^b_c,在最后需要转换成旋转矩阵并求逆。
ric = estimated_R.toRotationMatrix().inverse();
//cout << svd.singularValues().transpose() << endl;
//cout << ric << endl;
Vector3d ric_cov;
ric_cov = svd.singularValues().tail<3>();
// 至少迭代计算了WINDOW_SIZE次,且R的奇异值大于0.25才认为标定成功
if (frame_count >= WINDOW_SIZE && ric_cov(1) > 0.25)
{
calib_ric_result = ric;
return true;
}
else
return false;
}

Appendix

  • The product of two quaternions is bi-linear and can be expressed as two equivalent matrix products, namely
\[\mathbf{q}_{1} \otimes \mathbf{q}_{2}=\left[\mathbf{q}_{1}\right]_{L} \mathbf{q}_{2} \quad \text { and } \quad \mathbf{q}_{1} \otimes \mathbf{q}_{2}=\left[\mathbf{q}_{2}\right]_{R} \mathbf{q}_{1},
\]

\(\quad\)where \([\mathbf{q}]_{L}\) and \([\mathbf{q}]_{R}\) are respectively the left- and right- quaternion-product matrices

\[[\mathbf{q}]_{L}=\left[\begin{array}{cccc}
q_{w} & -q_{x} & -q_{y} & -q_{z} \\
q_{x} & q_{w} & -q_{z} & q_{y} \\
q_{y} & q_{z} & q_{w} & -q_{x} \\
q_{z} & -q_{y} & q_{x} & q_{w}
\end{array}\right], \quad[\mathbf{q}]_{R}=\left[\begin{array}{cccc}
q_{w} & -q_{x} & -q_{y} & -q_{z} \\
q_{x} & q_{w} & q_{z} & -q_{y} \\
q_{y} & -q_{z} & q_{w} & q_{x} \\
q_{z} & q_{y} & -q_{x} & q_{w}
\end{array}\right],
\]

\(\quad\)then

\[[\mathbf{q}]_{L}=q_{w} \mathbf{I}+\left[\begin{array}{cc}
0 & -\mathbf{q}_{v}^{\top} \\
\mathbf{q}_{v} & {\left[\mathbf{q}_{v}\right]_{\times}}
\end{array}\right], \quad[\mathbf{q}]_{R}=q_{w} \mathbf{I}+\left[\begin{array}{cc}
0 & -\mathbf{q}_{v}^{\top} \\
\mathbf{q}_{v} & -\left[\mathbf{q}_{v}\right]_{\times}
\end{array}\right]
\]

VINS中旋转外参初始化的更多相关文章

  1. VINS(四)初始化与相机IMU外参标定

    和单目纯视觉的初始化只需要获取R,t和feature的深度不同,VIO的初始化话通常需要标定出所有的关键参数,包括速度,重力方向,feature深度,以及相机IMU外参$R_{c}^{b}$和$p_{ ...

  2. VI ORB-SLAM初始化与VINS初始化对比(将vi orb-slam初始化方法移植到vins中)

    初始化时需要求出的变量:相机和imu外参r t.重力g.尺度s.陀螺仪和加速度计偏置ba bg. 下面对两种算法初始化的详细步骤进行对比: 求陀螺仪偏置bg 求解公式相同,求解方法不同.公式如下,VI ...

  3. VINS 估计器之外参初始化

    为何初始化外参 当外参完全不知道的时候,VINS也可以在线对其进行估计(rotation),先在processImage内进行初步估计,然后在后续优化时,会在optimize函数中再次优化. 如何初始 ...

  4. 相机-imu外参校准总结

    1. 研究背景及相关工作 1)研究背景 单目视觉惯性slam是一种旨在跟踪移动平台的增量运动并使用来自单个车载摄像头和imu传感器的测量结果同时构建周围环境地图的技术.视觉相机和惯性测量单元(imu) ...

  5. 相机imu外参标定

    1. 第一步初始化imu外参(可以从参数文档中读取,也可以计算出),VINS中处理如下: # Extrinsic parameter between IMU and Camera. estimate_ ...

  6. 解放双手——相机与IMU外参的在线标定

    本文作者 沈玥伶,公众号:计算机视觉life,编辑部成员 一.相机与IMU的融合 在SLAM的众多传感器解决方案中,相机与IMU的融合被认为具有很大的潜力实现低成本且高精度的定位与建图.这是因为这两个 ...

  7. C++类中成员变量的初始化总结(转帖)

    本文转自:C++类中成员变量的初始化总结 1. 普通的变量:      一般不考虑啥效率的情况下 可以在构造函数中进行赋值.考虑一下效率的可以再构造函数的初始化列表中进行.  1 class CA  ...

  8. 关于map容器的元素被无参初始化

    使用C++中的map容器定义一个mp,当你执行if语句判断mp[3]是否为1时,那么如果mp[3]以前不存在,此时mp[3]就会被无参初始化,second赋值为0. 以下的程序可以证明这一点.执行了第 ...

  9. C++类中成员变量的初始化总结

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  10. 虚机中访问外网;NAT中的POSTROUTING是怎么搞的?

    看下docker中是怎么配置的网络 在虚机中访问外网:设定了qemu,在主机上添加路由:sudo iptables -t nat -I POSTROUTING -s 192.168.1.110 -j ...

随机推荐

  1. SpringBoot配置与打包基础

    本篇主要记录SpringBoot使用的基础配置 SpringBoot Maven配置 SpringBoot maven依赖关系 我们创建springboot项目后,会发现项目的pom文件都会继承自sp ...

  2. 【深入浅出 Yarn 架构与实现】6-4 Container 生命周期源码分析

    本文将深入探讨 AM 向 RM 申请并获得 Container 资源后,在 NM 节点上如何启动和清理 Container.将详细分析整个过程的源码实现. 一.Container 生命周期介绍 Con ...

  3. Github Copilot Chat 初体验

    最近因为阳了的缘故一直躺在床上.今天终于从床上爬起来了.不是因为好透了,而是因为我收到了申请Copilot Chat preview 权限通过的邮件.实在忍不住,于是起床开电脑在咳嗽声中进行了一番体验 ...

  4. 关于JavaBean和vo的解释

    前景提要 最近在学JavaWeb,接触到了很多java后端的概念,其中JavaBean和vo的概念一直让我模糊不清,查询众多资料后写个博客记录一下. 首先先贴一下两者的概念: JavaBean Jav ...

  5. Java笔试真题及参考答案

    题目 使用Swing实现一个窗口程序,窗口包括一个菜单栏,请按以下要求实现相应功能. (1)窗口标题为"GUI程序",大小为400X300, 居中显示:窗口上有一个面板,面板背景色 ...

  6. 脱离 Spring 苦海,Solon v2.3.0 发布

    Solon 是什么框架? 一个,Java 新的生态型应用开发框架.它从零开始构建,有自己的标准规范与开放生态(全球第二级别的生态).与其他框架相比,它解决了两个重要的痛点:启动慢,费资源. 解决痛点? ...

  7. CompTIA Pentest+

    关于学习后CompTIA Pentest+笔记 渗透测试工具 讲述了nmap,burp Suite,Metasploit,Nessus,hydra的入门使用 nmap:https://www.cnbl ...

  8. HttpURLConnection调用webservice,c#、java、python等HTTP调用webservice,简单的webservice调用

    以前调用webservice一般使用axis.axis2先生成java类后,直接引用,多方便.但是有的webservice接口非常的函数,生成的java类非常多,有没有一种非常简化的方法. axis2 ...

  9. 18.详解AQS家族的成员:Semaphore

    关注:王有志,一个分享硬核Java技术的互金摸鱼侠. 欢迎你加入Java人的提桶跑路群:共同富裕的Java人 今天我们来聊一聊AQS家族中另一个重要成员Semaphore,我只收集到了一道关于Sema ...

  10. 原生AJAX的学习

    基础知识 知识点梳理见图: 自己动手实践案例 案例1: 访问本地文件 <!DOCTYPE html> <html> <body> <div id=" ...