├── imgs ├── H1.png ├── H2.png ├── H3.png ├── JI.png ├── RK4.png ├── StereoFeatures.gif ├── addNewFeatures&pruneGridFeatures.svg ├── comparation.png ├── coordinate.png ├── covariance_augmentation.png ├── eular.png ├── imu_propagate.png ├── midpoint.png ├── msckf_F_G_1.png ├── msckf_F_G_2.png ├── msckf_F_G_3.png ├── msckf_vio.jpg ├── q_note1.png ├── q_note2.png ├── q_note3.png ├── q_note4.png ├── tracking_whole_picture.png ├── triangulation.png ├── twoPointRansac.png ├── update1.png ├── update2.png ├── 前端-初始化部分.svg └── 前端-跟踪部分.svg ├── index.html ├── notes.md └── notes ├── msckf_notes.emmx ├── notes.dia ├── notes.html ├── notes.md └── notes.pdf /imgs/H1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/H1.png -------------------------------------------------------------------------------- /imgs/H2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/H2.png -------------------------------------------------------------------------------- /imgs/H3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/H3.png -------------------------------------------------------------------------------- /imgs/JI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/JI.png -------------------------------------------------------------------------------- /imgs/RK4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/RK4.png -------------------------------------------------------------------------------- /imgs/StereoFeatures.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/StereoFeatures.gif -------------------------------------------------------------------------------- /imgs/addNewFeatures&pruneGridFeatures.svg: -------------------------------------------------------------------------------- 1 | addNewFeatures()1.建立mask,防止重复检测特征点2.检测新的特征点,并将新特征点分配到每个网格,然后按照每个网格特征点的响应排序,使得每个网格特征点的数量不超网格特征点最大数量限制并且不超过本身包含的特征点数量限制,其实这部分已经删除了很多响应值比较小的特征了3.对新检测到的特征做双目匹配,详细方法见之间介绍4.双目匹配之后会有一些outliers,这时候再次依据响应值对每个网格将特征点排序,但是这次包含的内容包括左右特征点位置等5.对每个网格进行增加feature操作,增加之后个数为必须小于网格特征点数量最小值,并且肯定要小于本身该网格新增加的特征点数目结束pruneGridFeatures()1.对每个网格的特征点进行数量检数量大于网格特征点数量最大限制?2.删除到数量为网格特征点数量最大数目结束 -------------------------------------------------------------------------------- /imgs/comparation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/comparation.png -------------------------------------------------------------------------------- /imgs/coordinate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/coordinate.png -------------------------------------------------------------------------------- /imgs/covariance_augmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/covariance_augmentation.png -------------------------------------------------------------------------------- /imgs/eular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/eular.png -------------------------------------------------------------------------------- /imgs/imu_propagate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/imu_propagate.png -------------------------------------------------------------------------------- /imgs/midpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/midpoint.png -------------------------------------------------------------------------------- /imgs/msckf_F_G_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/msckf_F_G_1.png -------------------------------------------------------------------------------- /imgs/msckf_F_G_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/msckf_F_G_2.png -------------------------------------------------------------------------------- /imgs/msckf_F_G_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/msckf_F_G_3.png -------------------------------------------------------------------------------- /imgs/msckf_vio.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/msckf_vio.jpg -------------------------------------------------------------------------------- /imgs/q_note1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/q_note1.png -------------------------------------------------------------------------------- /imgs/q_note2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/q_note2.png -------------------------------------------------------------------------------- /imgs/q_note3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/q_note3.png -------------------------------------------------------------------------------- /imgs/q_note4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/q_note4.png -------------------------------------------------------------------------------- /imgs/tracking_whole_picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/tracking_whole_picture.png -------------------------------------------------------------------------------- /imgs/triangulation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/triangulation.png -------------------------------------------------------------------------------- /imgs/twoPointRansac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/twoPointRansac.png -------------------------------------------------------------------------------- /imgs/update1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/update1.png -------------------------------------------------------------------------------- /imgs/update2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/imgs/update2.png -------------------------------------------------------------------------------- /imgs/前端-初始化部分.svg: -------------------------------------------------------------------------------- 1 | initializeFirstFrame()1.提取FAST特征: detector_ptr->detect(img, new_features);2.stereoMatch(cam0_points, cam1_points, inlier_markers);stereoMatch(cam0_points, cam1_points, inlier_markers);1.预测右相机特征点的位置.根据离线的标定结果R_cam0_cam1将cam0的图像坐标投影到cam1相机坐标系下,其结果为相机坐标系下的归一化坐标;根据右相机的内参和畸变系数,将畸变矫正后的点投影回到未矫正的cam1中,坐标变成普通uv坐标.2.calcOpticalFlowPyrLK()函数vector<unsigned char>& inlier_markers 特征点是否有效的标志位3.剔除那些超出图像边界的cam1_points,将对应的inlier_markers标记为04.根据外参计算本质矩阵E=[t]xR(注对极几何x2^t*E*x1=0)5.E*cam0计算极线,再计算cam1到极线的距离(通过点积算投影),将阈值大于5个像素的点剔除(当然这里要除以fx,因为之前cam0,cam1已经是相机坐标系下归一化坐标了)3.左右匹配数量<5,或者匹配数量小于0.1*提取数,则可能图像不同步.4.将特征分配到Grid内.Grid特征数据结构为:std::map<int, std::vector<FeatureMetaData> > GridFeatures;key为网格ID编号, value为一个FeatureMetaData的vector struct FeatureMetaData { FeatureIDType id; //每个特征都有单独的ID float response; //Feature的响应值 int lifetime; //Feature的生存周期cv::Point2f cam0_point; //Feature在左相机的图像坐cv::Point2f cam1_point; //Feature在右相机的图像坐 };5.对每个网格内的特征按照FAST特征点的响应值排序6.按照响应值从大到下取特征值,每个网格的特征点数目为grid_min_feature_num,当然必须要小于等于本身这个网格所拥有的特征数目,并且将生命周期设置为1.对应的ID相应增加7.将is_first_img标志位设置成false结束 -------------------------------------------------------------------------------- /imgs/前端-跟踪部分.svg: -------------------------------------------------------------------------------- 1 | trackFeatures()1.根据imu的信息对前后时刻的图像的旋转计算得到一个初值integrateImuData(cam0_R_p_c, cam1_R_p_c);integrateImuData(cam0_R_p_c, cam1_R_p_c)1.找到prev和curr帧之间imu的数据,其实按照200hz的IMU和20hz的图像来说,两帧图像之间大约有10帧.2.根据IMU的w数据计算imu系下的平均角速度w~3.平均旋转向量为w~*dt4.罗德里格斯变换将旋转向量转换为旋转矩阵cam0_R_p_c, cam1_R_p_c -> pre to curr.5.清除imu_msg_buffer中已经使用过的imu_data2.根据imu得到的前后两帧图像之间的旋转预测当前帧特征点的位predictFeatureTracking(prev_cam0_points, cam0_R_p_c, cam0_intrinsics, curr_cam0_points);3.LK光流跟踪 calcOpticalFlowPyrLK(*)track_inliers为标记,将超出图像边界的点标记为0template <typename T>void removeUnmarkedElements( const std::vector<T>& raw_vec, const std::vector<unsigned char>& markers, std::vector<T>& refined_vec)分别需要根据tracking的状态移除以下vector状态:A.prev_ids; B.prev_lifetime C.prev_cam0_pointsD.prev_cam1_points E.curr_cam0_points4.移除所有track_inliers值为0的关键点5.前后两帧双目之间特征点关系的选择同一时刻的左右图像特征点用stereoMatch()函数,参考初始化部分对该函数的详细介绍;前后帧采用twoPointRansac()方法对outliers剔除imuCallback(const sensor_msgs::ImuConstPtr& msg)predictFeatureTracking(prev_cam0_points, cam0_R_p_c, cam0_intrinsics, curr_cam0_points);原理:根据单应性原理:已知一个平面的关键点可以得到另一个平面的关键点// 单应性矩阵的计算,公式推到: // x1 = K * X1_c; x2 = K * X2_c // x2_c = H * x1_c; X1_C = R_2_1 * X2_c// x1 = K * R_1_2 * K^inv * x2 --> H = K * R_1_2 * K^invx2 = H*x1note:这个推导忽略了平移,因为只是一个预测 prev frames cam0 ----------> cam1 | | |ransac |ransac | stereo match | curr frames cam0 ----------> cam11) Stereo matching between current images of cam0 and cam1. 2) RANSAC between previous and current images of cam0. 3) RANSAC between previous and current images of cam1.6.步骤5的三个步骤都会有标记位,在这之后都会剔除不满足要求的特征点.结束 -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | ##
一步步深入了解S-MSCKF
2 |

作者:钟心亮

3 |

联系方式:xinliangzhong@foxmail.com

4 |

Github:https://github.com/TurtleZhong

5 | 6 | [TOC] 7 | 8 | ### 0.前言 9 | 10 |   #### 0.1 我为什么写这个? 11 | 12 |   一方面是对自己看过的东西做一个简单的总结,之前也看过一些其他的,但都没有系统的成文,因为时间比较紧张, 科研压力比较大.另外一方面是向各位大佬们学习,希望能得到大佬们的指导, 同时希望对刚入门SLAM和已经入门一段时间的SLAM小伙伴一点干货. 结交一些志同道合的小伙伴! 13 | 14 | #### 0.2 可爱的您能得到什么? 15 | 16 |   如果您正在研究MSCKF或者对VIO很感兴趣,那么您将得到以下几件东西: 17 | 18 | (1) [一份看了不会亏的MSCKF的入门指南](http://www.xinliang-zhong.vip/msckf_notes/) 19 | 20 | (2) 基本我自己推导过的一本书(或者叫比较长的论文) [Quaternion kinematics for the error-state Kalman filter](https://github.com/TurtleZhong/msckf_mono) 21 | 22 | (3) [一份注释过的开源版本的代码](https://github.com/TurtleZhong/msckf_mono) 23 | 24 |   如果您是已经最MSCKF及其熟悉的大佬,那么您将得到: 25 | 26 | 其实我也还没想好您能得到什么,我倒是很想从您那得到一些东西... 27 | 28 | #### 0.3 我希望可爱的您反馈什么? 29 | 30 | (1) 全文一些描述有误的地方,因为自己写东西比较随意,难免有大家理解起来有问题或者本身就描述不对的地方. 31 | 32 | (2) 反馈网址戳 [这里](https://github.com/TurtleZhong/msckf_mono/issues), 可爱的您一定会随手在github留下小星星对不对? 不方便公共留言的话可以邮箱私聊我. 33 | 34 | (3) 交个朋友? 江湖有缘相见. 祝可爱的您科研顺利,一夜暴富!!! 35 | 36 | ### 1.简介 37 | 38 |   MSCKF (Multi-State Constraint Kalman Filter),从2007年提出至今,一直是filter-based SLAM比较经典的实现.据说这也是谷歌tango里面的算法,这要感谢Mingyang Li博士在MSCKF的不懈工作。在传统的EKF-SLAM框架中,特征点的信息会加入到特征向量和协方差矩阵里,这种方法的缺点是特征点的信息会给一个初始深度和初始协方差,如果不正确的话,极容易导致后面不收敛,出现inconsistent的情况。MSCKF维护一个pose的FIFO,按照时间顺序排列,可以称为滑动窗口,一个特征点在滑动窗口的几个位姿都被观察到的话,就会在这几个位姿间建立约束,从而进行KF的更新。如下图所示, 左边代表的是传统EKF SLAM, 红色五角星是old feature,这个也是保存在状态向量中的,另外状态向量中只保存最新的相机姿态; 中间这张可以表示的是keyframe-based SLAM, 它会保存稀疏的关键帧和它们之间相关联的地图点; 最右边这张则可以代表MSCKF的一个基本结构, MSCKF中老的地图点和滑窗之外的相机姿态是被丢弃的, 它只存了滑窗内部的相机姿态和它们共享的地图点. 39 | 40 | ![comparation](imgs/comparation.png) 41 | 42 | #### 符号说明 43 | $\dot{\textbf{x}}$ 表示微分 44 | 45 | $\tilde{\textbf{x}}$ 表示误差 46 | 47 | $\hat{\textbf{x}}$ 表示估计值 48 | 49 | $\textbf{x}_{m}$ 表示测量值 50 | 51 | $G$ 惯性系 52 | 53 | $C$ 相机坐标系 54 | 55 | $I$ IMU坐标系 56 | 57 | $^{G}\textbf{p}$ $G$系下的一个点 58 | 59 | $^{B}_{A}\textbf{R}$ 从$A$系到$B$系的旋转矩阵 60 | 61 | ### 2.前端 62 |   本文主要针对2017年Kumar实验室开源的S-MSCKF进行详细分析,其实这篇文章整体思路与07年提出的基本上是一脉相承的.作为一个VIO的前端,MSCKF采用的是光流跟踪特征点的方法,特征点使用的是FAST特征,另外这是MSCKF双目的一个实现,双目之间的特征点匹配采用的也是光流,这与传统的基于descriptor匹配的方法不同.前端部分其实相对简单,整个前端部分基本在 **[image_processor.cpp](https://github.com/KumarRobotics/msckf_vio/blob/master/src/image_processor.cpp)**中实现. 63 | 64 | #### 2.1 基本数据结构说明 65 | 66 | ##### A.特征点检测和跟踪的参数 67 | 68 | ```c++ 69 | struct ProcessorConfig { 70 | int grid_row; //划分图像网格的行数 71 | int grid_col; //划分图像网格的列数 72 | int grid_min_feature_num; //每个网格特征点的最小个数 73 | int grid_max_feature_num; //每个网格特征点的最大个数 74 | 75 | int pyramid_levels; //金字塔层数 76 | int patch_size; 77 | int fast_threshold; 78 | int max_iteration; 79 | double track_precision; 80 | double ransac_threshold; 81 | double stereo_threshold; 82 | }; 83 | ``` 84 | 85 | ##### B. 特征点数据 86 | 87 | ```c++ 88 | struct FeatureMetaData { 89 | FeatureIDType id; //unsigned long long int 每个特征都有单独的ID 90 | float response; //Feature的响应值 91 | int lifetime; //Feature的生存周期 92 | cv::Point2f cam0_point; //Feature在左相机的图像坐标 93 | cv::Point2f cam1_point; //Feature在右相机的图像坐标 94 | }; 95 | ``` 96 | 97 | #### 2.2 跟踪流程 98 | 99 |   整体框架如下面的流程图所示: 100 | 101 | ![tracking_whole_picture](imgs/tracking_whole_picture.png) 102 | 103 | ##### 2.2.1 Initialization 104 | ![initialization](imgs/前端-初始化部分.svg) 105 | ##### 2.2.2 trackFeatures 106 | 107 |   当第一帧初始化完成之后,后面帧则只需要进行跟踪第一帧的特征点,并且提取新的特征点,整个流程如下: 108 | 109 | ![前端-跟踪部分](imgs/前端-跟踪部分.svg) 110 | 111 |   整个流程还算比较清晰,但是有一个步骤需要单独说明一下,也就是作者在论文中提到的twoPointRansac的方法.我们先来看一下函数原型: 112 | 113 | ```C++ 114 | /** 115 | * @brief 计算原图像帧关键点对应的矫正位置 116 | * @param pts1:上一时刻的关键点位置 117 | * @param pts2:当前时刻跟踪匹配到的关键点位置 118 | * @param R_p_c:根据imu信息计算得到的两个时刻相机的相对旋转信息 119 | * @param distortion_model,intrinsics:相机内参和畸变模型 120 | * @param inlier_error:内点可接受的阈值(关键点距离差) 121 | * @param success_probability:成功的概率 122 | * @return inlier_markers:内点标志位 123 | */ 124 | void ImageProcessor::twoPointRansac( 125 | const vector& pts1, const vector& pts2, 126 | const cv::Matx33f& R_p_c, const cv::Vec4d& intrinsics, 127 | const std::string& distortion_model, 128 | const cv::Vec4d& distortion_coeffs, 129 | const double& inlier_error, 130 | const double& success_probability, 131 | vector& inlier_markers) 132 | ``` 133 | 整个函数的基本流程如下: 134 | ![twoPointRansac](imgs/twoPointRansac.png) 135 | 下面我们来详细讲解一下RANSAC模型及原理依据: 136 | 我们由对极几何可以知道有以下约束: 137 | $$ 138 | p_{2}^{T}\cdot [t]_{x}\cdot R\cdot p_{1}=0 139 | \tag{2.1} 140 | $$ 141 | 我们假设前后帧对应点归一化坐标分别为, 142 | $$ 143 | R\cdot p_{1}=\begin{bmatrix} 144 | x{1} & y{1} & 1 145 | \end{bmatrix}^{T}, 146 | p_{2}=\begin{bmatrix} 147 | x{2} & y{2} & 1 148 | \end{bmatrix}^{T} 149 | \tag{2.2} 150 | $$ 151 | 其中R为根据IMU的平均角速度得到的,此时坐标系都统一到一个坐标系下. 152 | $$ 153 | \begin{bmatrix} 154 | x_{2}& y_{2} & 1 155 | \end{bmatrix} 156 | \cdot \begin{bmatrix} 157 | 0 & -t_{z} & t_{y}\\ 158 | t_{z} & 0 & -t_{x}\\ 159 | -t_{y} & t_{x} & 0 160 | \end{bmatrix} 161 | \cdot \begin{bmatrix} 162 | x_{1}\\ 163 | y_{1}\\ 164 | 1 165 | \end{bmatrix}=0 166 | \tag{2.3} 167 | $$ 168 | 169 | 将式子(2.3)展开之后我们可以得到: 170 | $$ 171 | {\color{Green} \begin{bmatrix} 172 | y_{1}-y_{2} & -(x_{1}-x_{2}) & x_{1}y_{2}-x_{2}y_{2} 173 | \end{bmatrix}}\cdot 174 | \begin{bmatrix} 175 | t_{x}\\ 176 | t_{y}\\ 177 | t_{z} 178 | \end{bmatrix}=0 179 | \tag{2.4} 180 | $$ 181 | 其中绿色部分在代码中对应这一块: 182 | 183 | ```c++ 184 | vector pts_diff(pts1_undistorted.size()); 185 | for (int i = 0; i < pts1_undistorted.size(); ++i) 186 | pts_diff[i] = pts1_undistorted[i] - pts2_undistorted[i]; 187 | ... 188 | ... 189 | MatrixXd coeff_t(pts_diff.size(), 3); 190 | for (int i = 0; i < pts_diff.size(); ++i) { 191 | coeff_t(i, 0) = pts_diff[i].y; 192 | coeff_t(i, 1) = -pts_diff[i].x; 193 | coeff_t(i, 2) = pts1_undistorted[i].x*pts2_undistorted[i].y - 194 | pts1_undistorted[i].y*pts2_undistorted[i].x; 195 | } 196 | ``` 197 | 198 | 至于这个模型是怎么选出来的呢? 假设一共有N个inliers点对,那么根据式(2.4)势必会得到一个N*3 * 3*1 = N(0)的等式.但事实上由于误差和outliers的存在,最终结果不可能为零,我们取两个点将式子分块,并且只考虑两个点的情况,那么将会有: 199 | $$ 200 | {\color{Green} \begin{bmatrix} 201 | y_{1}-y_{2} & -(x_{1}-x_{2}) & x_{1}y_{2}-x_{2}y_{2}\\ 202 | y_{3}-y_{4} & -(x_{3}-x_{4}) & x_{3}y_{4}-x_{4}y_{3} 203 | \end{bmatrix}} 204 | \cdot 205 | \begin{bmatrix} 206 | t_{x}\\ 207 | t_{y}\\ 208 | t_{z} 209 | \end{bmatrix}= 210 | {\color{Green} \begin{bmatrix} 211 | A_{x} \\ A_{y} \\ A_{z} 212 | \end{bmatrix}^{T}} 213 | \cdot 214 | \begin{bmatrix} 215 | t_{x}\\ 216 | t_{y}\\ 217 | t_{z} 218 | \end{bmatrix}\approx 219 | {\color{Red} 220 | \begin{bmatrix} 221 | 0\\ 222 | 0 223 | \end{bmatrix}} 224 | \tag{2.5} 225 | $$ 226 | 那我们可以分别得到以下三个式子: 227 | $$ 228 | \begin{split} 229 | \begin{bmatrix} 230 | A_{x} \\ 231 | A_{y} 232 | \end{bmatrix}^{T} 233 | \cdot 234 | \begin{bmatrix} 235 | t_{x} \\ 236 | t_{y} 237 | \end{bmatrix} 238 | \approx {\color{Green}A_{z}}\cdot t_{z} \\ 239 | \begin{bmatrix} 240 | A_{x} \\ 241 | A_{z} 242 | \end{bmatrix}^{T} 243 | \cdot 244 | \begin{bmatrix} 245 | t_{x} \\ 246 | t_{z} 247 | \end{bmatrix} 248 | \approx {\color{Green}A_{y}}\cdot t_{y} \\ 249 | 250 | \begin{bmatrix} 251 | A_{y} \\ 252 | A_{z} 253 | \end{bmatrix}^{T} 254 | \cdot 255 | \begin{bmatrix} 256 | t_{y} \\ 257 | t_{z} 258 | \end{bmatrix} 259 | \approx {\color{Green}A_{x}}\cdot t_{x} 260 | \end{split}\tag{2.6} 261 | $$ 262 |   我们的目标当然是使得误差最小,所以作者的做法是比较式子(2.6)绿色部分的大小,取最小的并令模型的平移为1,进而直接求逆然后得到最初的模型假设,之后要做的步骤跟常规RANSAC就十分接近了,找出适应当前模型的所有inliers,然后计算误差并不断迭代找到最好的模型. 263 |   至此我们已经完成了整个feature的tracking过程! 264 | 265 | ##### 2.2.3 addNewFeatures & pruneGridFeatures 266 | 267 |   如果一直tracking的话那么随着时间流逝,有些特征会消失,有些可能也会有累计误差,所以我们势必要添加一些新的特征,这个步骤正是在跟踪上一帧特征之后要做的,因为stereoMatching 和twoPointRansac都会剔除一些outliers,那我们需要提取新的特征保证能一直运行下去. 268 | 269 | ![addNewFeatures&pruneGridFeatures](imgs/addNewFeatures&pruneGridFeatures.svg) 270 | 271 | ##### 2.2.4 publish 272 | 273 |   主要发布了当前帧特征点的数据,每个特征的数据结构为FeasureMeasurement如下: 274 | 275 | ```c++ 276 | uint64 id 277 | # Normalized feature coordinates (with identity intrinsic matrix) 278 | float64 u0 # horizontal coordinate in cam0 279 | float64 v0 # vertical coordinate in cam0 280 | float64 u1 # horizontal coordinate in cam0 281 | float64 v1 # vertical coordinate in cam0 282 | ``` 283 | 284 | 发布的其实是CameraMeasurement,那其实就是一个包含上面特征数据FeasureMeasurement的一个数组. 285 | 286 | #### 2.3 显示 287 | 其实前端基本上可以说是非常简单了,也没有太多的trick,最后我们来看一下前端的跟踪效果的动图: 288 | 289 | 290 | 291 | ### 3.基于四元数的 error-state Kalman Filter 292 | 293 | ​ 其实原理部分相当重要,包括你对error-state Kalman Filter的理解,但是如果要从头讲起的话可以说篇幅太长,考虑到做SLAM的同学们基本上都应该知道这本书 [Quaternion kinematics for the error-state Kalman filter](https://arxiv.org/abs/1711.02508), 这本书会让你在四元数,SO3,IMU的模型以及基于IMU的ESKF(error-state Kalman Filter)都会有比较深刻的理解,对应这本书我也做了很多注释,关于基于四元数原理部分由于篇幅限制,我这里不想做太多的说明,但是接下来在讲解S-MSCKF原理部分我们会将参考公式附上.下面是一些我在这本参考资料中的注释图. 294 | 295 | | | | 296 | | :----------------------------------------------------------: | :----------------------------------------------------------: | 297 | | | | 298 | 299 | 当然重要的事情需要说三遍,那就是[Quaternion kinematics for the error-state Kalman filter](https://arxiv.org/abs/1711.02508)四元数是Hamilton表示方法,而MSCKF中采用的是JPL表示方法,关于这两者的不同,可以参考书中34页,Table2. 300 | 301 |   看过论文代码的同学可能会说,MSCKF这一部分代码参考的是MARS实验室[Indirect Kalman Filter for 3D Attitude Estimation-A Tutorial for Quaternion Algebra](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.304.6207&rep=rep1&type=pdf),答案是肯定的.但是我的建议是以Hamilton那本书为基础,然后自行再去推导MARS出品那一本,那样你的体验会更加深刻. 302 | 303 | ### 4.S-MSCKF 304 | 305 | #### 4.1 基本原理 306 | 307 |   在讲解之前,我们先来定义一下坐标系,如下图所示: 308 | ![coordinate](imgs/coordinate.png) 309 | 310 | 其中$I$表示IMU机体坐标系(Body Frame),$G$表示的是惯性系(Inertial Frame),$C$表示的是相机坐标系(Camera Frame). 311 | 312 | ​ 作为一个滤波器,我们首先来看滤波器的状态向量,它包含两个部分,IMU状态和Camera状态: 313 | $$ 314 | \textbf{x}_{I}=\begin{bmatrix} 315 | _{G}^{I}\textbf{q}^{T} & \textbf{b}_{g}^{T} & ^{G}\textbf{v}_{I}^{T} & \textbf{b}_{a}^{T} & 316 | ^{G}\textbf{p}_{I}^{T} & _{C}^{I}\textbf{q}^{T} & ^{I}\textbf{p}_{C}^{T} 317 | \end{bmatrix}^{T} 318 | \tag{4.1} 319 | $$ 320 | 其中$_{G}^{I}\textbf{q}​$表示的是从惯性系到机体IMU系的旋转变换, $^{G}\textbf{v}_{I}​$和$^{G}\textbf{p}_{I}​$分别表示机体在惯性系下的速度和位置,$\textbf{b}_{g}​$和$\textbf{b}_{a}​$分别代表IMU的bias,而$_{C}^{I}\textbf{q}​$ 和$ ^{I}\textbf{p}_{C}​$分别代表从相机坐标系到IMU坐标系的旋转与平移,其中以左相机为准. 但是我们注意到旋转实际上只有三个自由度,而且四元数必须是单位单位四元数,那这样额外的约束会使得协方差矩阵奇异,所以我们定义error IMU状态如下: 321 | $$ 322 | \tilde{\textbf{x}}_{I}=\begin{bmatrix} 323 | _{G}^{I}\tilde{\theta }^{T} & \tilde{\textbf{b}}_{g}^{T} & ^{G}\tilde{\textbf{v}}_{I}^{T} & \tilde{\textbf{b}}_{a}^{T} & 324 | ^{G}\tilde{\textbf{p}}_{I}^{T} & _{C}^{I}\tilde{\textbf{q}}^{T} & ^{I}\tilde{\textbf{p}}_{C}^{T} 325 | \end{bmatrix}^{T} 326 | \tag{4.2} 327 | $$ 328 | 329 | 所以IMU的状态一共是$3\cdot7=21​$维向量,后6维度其实是相机与IMU的外参,这6个维度在MSCKF1.0版本是不在状态向量里边的. MSCKF的状态向量还包含另外一个组成部分,那就是N个相机的姿态,那么每个相机的姿态误差向量定义为 $\tilde{\textbf{x}}_{C_{i}}=\left ( \begin{matrix}_{G}^{C_{i}}\tilde{\theta }^{T} & ^{G}\tilde{\textbf{p}}_{C_{i}}^{T}\end{matrix} \right )^T​$,所以当滑窗里边有N个相机姿态的时候,整个误差状态向量为: 330 | $$ 331 | \tilde{\textbf{x}} = \begin{pmatrix} 332 | \tilde{\textbf{x}}_{I}^{T} & \tilde{\textbf{x}}_{C_{1}}^{T} & ... & \tilde{\textbf{x}}_{C_{N}}^{T} 333 | \end{pmatrix} 334 | \tag{4.3} 335 | $$ 336 | 我们以标准error-state KF为准,其过程包含运动模型和观测模型. 337 | 338 |   ##### A.运动模型 339 | 340 | 我们知道对于IMU的状态连续时间的运动学有: 341 | $$ 342 | \begin{split} 343 | _{G}^{I}\dot{\hat{\textbf{q}}}=\frac{1}{2}\cdot_{G}^{I}\hat{\textbf{q}}\otimes\hat{w} =\frac{1}{2}\Omega (\hat{w})_{G}^{I}\hat{\textbf{q}},\\ 344 | \dot{\hat{\textbf{b}}}_{g}=\textbf{0}_{3\times 1},\\ 345 | ^{G}\dot{\hat{\textbf{v}}}=C(_{G}^{I}\hat{\textbf{q}})^{T}\hat{\textbf{a}}+^{G}\textbf{g},\\ 346 | \dot{\hat{\textbf{b}}}_{a}=\textbf{0}_{3\times 1},\\ 347 | ^{G}\dot{\hat{\textbf{p}}_{I}}=^{G}{\hat{\textbf{v}}},\\ 348 | _{C}^{I}\dot{\hat{\textbf{q}}}=\textbf{0}_{3\times 1},\\ 349 | ^{I}\dot{\hat{\textbf{p}}_{C}}=\textbf{0}_{3\times 1} 350 | \end{split}\tag{4.4} 351 | $$ 352 | 其中 $\hat{w}​$ 和 $\hat{a}​$ 分别为角速度和加速度的估计值(测量值减去bias),即有: 353 | $$ 354 | \hat{w}=w_{m}-\hat{b}_{g},\hat{a}=a_{m}-\hat{b}_{a}\tag{4.5} 355 | $$ 356 | 其中有几点要说明,其中, 357 | $$ 358 | \Omega (\hat{w})=\Omega (\hat{w})=\begin{pmatrix} 359 | -[\hat{w}_{\times }] & w\\ 360 | -w^{T} & 0 361 | \end{pmatrix} 362 | \tag{4.6} 363 | $$ 364 | 这个直接参考四元数乘法就可以了,然后 $[\hat{w}_{\times }]$ 是 $\hat{w}$的反对称矩阵; $C(\cdot)$表示四元数到旋转矩阵的转换,这个可以参照[1]和[2]. 那按照S-MSCKF的论文所述,我们可以得到以下式子, 365 | $$ 366 | \dot{\tilde{\textbf{x}}}_{I}=\textbf{F}\tilde{\textbf{x}}_{I}+ 367 | \textbf{G} \textbf{n}_{I} 368 | \tag{4.7} 369 | $$ 370 | 其中 371 | $$ 372 | \textbf{n}_{I}^{T}=\begin{pmatrix} 373 | \textbf{n}_{g}^{T}& \textbf{n}_{wg}^{T} & \textbf{n}_{a}^{T} & \textbf{n}_{wa}^{T} 374 | \end{pmatrix}^{T} 375 | \tag{4.8} 376 | $$ 377 | $\textbf{n}_{g}$ 和 $\textbf{n}_{a}$ 分别代表角速度和加速度的测量噪声,服从高斯分布; $\textbf{n}_{wg}$ 和 $\textbf{n}_{wa}$ 分别代表角速度和加速度的bias的随机游走噪声. $\textbf{F} $ 是 $21\times21$ 大小矩阵, $\textbf{G}$ 是 $21\times12$ 大小的矩阵,其详细推到见附录A. 378 | 379 |   对于IMU的状态来说,我们可以采用RK4的积分方法根据(4.4)求得IMU的状态. 那么对于IMU的协方差矩阵呢,我们需要事先求取状态转移矩阵和离散的运动噪声协方差矩阵,如下: 380 | $$ 381 | \Phi_{k}=\Phi(t_{k+1},t_{k})=\textbf{exp}(\int_{t_{k}}^{t_{k+1}} \textbf{F}(\tau)d\tau) \\ 382 | \textbf{Q}_{k}=\int_{t_{k}}^{t_{k+1}}\Phi({t_{k+1},\tau})\textbf{GQG}\Phi({t_{k+1},\tau})^{T}d\tau 383 | \tag{4.9} 384 | $$ 385 | 386 | 关于这个状态转移矩阵 $\Phi_{k}$ 的求法, 其实式子4.9是一个指数,指数项是一个积分项,当 $t_{k+1}$ 与 $t_{k}$ 间 $\Delta t$ 较小时候,可以得到这样的式子: 387 | $$ 388 | \Phi_{k}=\Phi(t_{k+1},t_{k})=\textbf{exp}(\int_{t_{k}}^{t_{k+1}} \textbf{F}(\tau)d\tau) = \textbf{exp}(\textbf{F}\Delta t)= I+\textbf{F}\Delta t + \frac{1}{2!}(\textbf{F}\Delta t)^{2}+\frac{1}{3!}(\textbf{F}\Delta t)^{3}+... 389 | \tag{4.10} 390 | $$ 391 | 整个状态(IMU+Camera)的covariance传播过程如图所示: 392 | 393 | ![imu_propagate](imgs/imu_propagate.png) 394 | 395 | 那么对于左上角IMU的corvariance的传播有: 396 | $$ 397 | \textbf{P}_{II_{k+1|k}}=\Phi_{k}\textbf{P}_{II_{k|k}}\Phi_{k}^{T}+\textbf{Q}_{k} 398 | \tag{4.11} 399 | $$ 400 | 其中Camera的covariance暂时还没有变化是因为这个时间段图像还没有到来,只有IMU的影响,但是会影响到IMU与Camera协方差,即上图灰色矩形块. 401 | 402 | ##### B. 增广 403 | 404 |   当图像到来时,需要对当前相机姿态做增广,这个时刻的相机姿态是由上一时刻的IMU propagate的结果结合外参得到的: 405 | 406 | $$ 407 | _{G}^{C}\hat{\textbf{q}}=_{I}^{C}\hat{\textbf{q}}\otimes _{G}^{I}\hat{\textbf{q}} \\ 408 | ^{G}\hat{\textbf{p}}_{C} = ^{G}\hat{\textbf{p}}_{I} + C(_{G}^{I}\hat{\textbf{q}})^{T} \cdot{^{I}\hat{\textbf{p}}_{C}} 409 | \tag{4.12} 410 | $$ 411 | 假设上一时刻共有N个相机姿态在状态向量中,那么当新一帧图像到来时,这个时候整个滤波器的状态变成了$21+6(N+1)$的向量, 那么它对应的covariance维度变为 $(21+6(N+1))\times(21+6(N+1))$ .其数学表达式为: 412 | $$ 413 | \textbf{P}_{k|k}=\begin{pmatrix} 414 | \textbf{I}_{21+6N}\\ 415 | \textbf{J} 416 | \end{pmatrix}\textbf{P}_{k|k}\begin{pmatrix} 417 | \textbf{I}_{21+6N}\\ 418 | \textbf{J} 419 | \end{pmatrix}^{T} 420 | \tag{4.13} 421 | $$ 422 | 这个过程对应如下图过程: 423 | 424 | ![covariance_propagate](imgs/covariance_augmentation.png) 425 | 426 | 其中$J$ 的详细推导过程见附录B. 427 | 428 | ##### C.观测模型 429 | 430 |   MSCKF的观测模型是以特征点为分组的,我们可以知道一个特征(之前一直处于跟踪成功状态)会拥有多个Camera State.所有这些对于同一个3D点的Camera State都会去约束观测模型. 那这样其实隐式的将特征点位置从状态向量中移除,取而代之的便是Camera State. 我们考虑单个feture $f_{j}$, 假设它所对应到 $M_{j}$ 个相机姿态 $(_{G}^{C_{i}}\textbf{q}, ^{G}\textbf{p}_{C_{i}}), i \in j$. 当然双目版本的包含左目和右目两个相机姿态, $(_{G}^{C_{i,1}}\textbf{q}, ^{G}\textbf{p}_{C_{i,1}})$ 和 $(_{G}^{C_{i,2}}\textbf{q}, ^{G}\textbf{p}_{C_{i,2}})$ 右相机很容易能通过外参得到. 其中双目的观测值可以表示如下: 431 | $$ 432 | \textbf{z}^{j}_{i}=\begin{pmatrix} 433 | u^{j}_{i,1}\\ 434 | v^{j}_{i,1}\\ 435 | u^{j}_{i,2}\\ 436 | v^{j}_{i,2} 437 | \end{pmatrix} 438 | = \begin{pmatrix} 439 | \frac{1}{^{C_{i,1}}Z_{j}} & \textbf{0}_{2\times 2} \\ 440 | \textbf{0}_{2\times 2} & \frac{1}{^{C_{i,2}}Z_{j}} 441 | \end{pmatrix} 442 | \begin{pmatrix} 443 | \frac{1}{^{C_{i,1}}X_{j}}\\ 444 | \frac{1}{^{C_{i,1}}Y_{j}}\\ 445 | \frac{1}{^{C_{i,2}}X_{j}}\\ 446 | \frac{1}{^{C_{i,2}}Y_{j}} 447 | \end{pmatrix} 448 | \tag{4.14} 449 | $$ 450 | 而特征点在两个相机坐标系下可以分别表示为: 451 | $$ 452 | \begin{split} 453 | ^{C_{i,1}}\textbf{p}_{j}=\begin{pmatrix} 454 | ^{C_{i,1}}X_{j}\\ 455 | ^{C_{i,1}}Y_{j}\\ 456 | ^{C_{i,1}}Z_{j} 457 | \end{pmatrix} 458 | = C(^{C_{i,1}}_{G} \textbf{q})(^{G}\textbf{p}_{j}-^{G}\textbf{p}_{C_{i,1}})\\ 459 | ^{C_{i,2}}\textbf{p}_{j}=\begin{pmatrix} 460 | ^{C_{i,2}}X_{j}\\ 461 | ^{C_{i,2}}Y_{j}\\ 462 | ^{C_{i,2}}Z_{j} 463 | \end{pmatrix} 464 | = C(^{C_{i,2}}_{G} \textbf{q})(^{G}\textbf{p}_{j}-^{G}\textbf{p}_{C_{i,2}})\\ 465 | =C(^{C_{i,2}}_{C_{i,1}} \textbf{q})(^{C_{i,1}}\textbf{p}_{j}-^{C_{i,1}}\textbf{p}_{C_{i,2}}) 466 | \end{split} 467 | \tag{4.15} 468 | $$ 469 | 其中 $^{G}\textbf{p}_{j}$ 是特征点在惯性系下的坐标,这个是通过这个特征点的对应的所有camera state三角化得到的结果. 将观测模型在当前状态线性化可以得到如下式子: 470 | $$ 471 | \textbf{r}^{j}_{i}=\textbf{z}^{j}_{i}-\hat{\textbf{z}}^{j}_{i}= 472 | \textbf{H}^{j}_{C_{i}}\tilde{\textbf{x}}_{C_{i}}+ \textbf{H}^{j}_{f_{i}}\tilde{\textbf{p}}_{j}+\textbf{n}^{j}_{i} 473 | \tag{4.16} 474 | $$ 475 | 其中 $\textbf{n}^{j}_{i}$是观测噪声, $\textbf{H}^{j}_{C_{i}}$和 $\textbf{H}^{j}_{f_{i}}$ 是对应的雅克比矩阵.其详细推导和解析见附录C. 式子(4.16)对应到的是单个特征点对应的其中某一个相机姿态, 但是这个特征点会对应到很多相机姿态,我们直接将它贴在后边可以得到一个特征点的残差模型为: 476 | $$ 477 | \textbf{r}^{j}= 478 | \textbf{H}^{j}_{\textbf{x}}\tilde{\textbf{x}}+ {\color{Red} \textbf{H}^{j}_{f}\tilde{\textbf{p}}_{j}}+\textbf{n}^{j} 479 | \tag{4.17} 480 | $$ 481 | 但是这个其实并不是一个标准的EKF观测模型,因为我们知道 $\tilde{\textbf{p}}_{j}$ 并不在我们的状态向量里边,所以做法是将式子(4.17)中红色部分投影到零空间, 假设 $\textbf{H}^{j}_{f}$ 的[left null space](https://blog.csdn.net/liuheng0111/article/details/52522845) 为$\textbf{V}^{T}$, 即有$\textbf{V}^{T}\textbf{H}^{j}_{f}=\textbf{0}$, 所以式(4.17)可有写成: 482 | $$ 483 | \textbf{r}_{o}^{j}= 484 | \textbf{V}^{T}\textbf{H}^{j}_{\textbf{x}}\tilde{\textbf{x}}+\textbf{V}^{T}\textbf{n}^{j}=\\ 485 | \textbf{H}^{j}_{\textbf{x,o}}\tilde{\textbf{x}}+\textbf{n}^{j}_{o} 486 | \tag{4.18} 487 | $$ 488 | 式(4.18)则是一个标准的EKF观测模型了,下面简单分析一下维度.分析时针对单个特征点, 我们知道 $\textbf{H}^{j}_{f}$ 的维度是 $4M_{j}\times 3 $, 那么它的left null space的维度即$\textbf{V}^{T}$ 的维度为 $(4\textbf{M}_{j} - 3)\times 4\textbf{M}_{j}$, 则最终 $\textbf{H}^{j}_{\textbf{x,o}}\tilde{\textbf{x}}$的维度变为 $(4\textbf{M}_{j} - 3)\times 6$, 残差的维度变为 $(4\textbf{M}_{j} - 3)\times 1$, 假设一共有L个特征的话,那最终残差的维度会是 $L(4\textbf{M}_{j} - 3)\times 1$. 更多详细的代码细节见给到的注释版代码,然后H矩阵的详细推导见附录C. 489 | 490 | #### 4.2 三角化 491 | 492 |   三角化是通过多帧相机对同一个点的观测计算出特征点在世界坐标系下的绝对3D坐标,或者你可以认为是恢复出一个比较可靠的3D点. 在讲这个之前,我们先来简单过一下相机的投影模型, 假设相机图像已经去畸变了,那么我们很容易得到这样一个模型: 493 | $$ 494 | \begin{bmatrix} 495 | u\\ 496 | v 497 | \end{bmatrix} 498 | =h\begin{pmatrix} 499 | X/Z\\ 500 | Y/Z 501 | \end{pmatrix}= 502 | \begin{bmatrix} 503 | f_{x} & 0\\ 504 | 0 & f_{y} 505 | \end{bmatrix} 506 | \begin{bmatrix} 507 | X/Z\\ 508 | Y/Z 509 | \end{bmatrix} 510 | +\begin{bmatrix} 511 | c_{x}\\ 512 | c_{y} 513 | \end{bmatrix} 514 | \tag{4.19} 515 | $$ 516 | 其中(X,Y,Z)为相机坐标系下的一个点. 我们再来看下图: 517 | 518 | ![triangulation](imgs/triangulation.png) 519 | 520 | 其中在惯性系下 $^{G}\hat{\textbf{p}}_{f}$ 被多帧相机观测到,其中在每个相机下的坐标表示为 $^{C_{i}}\textbf{p}_{f} = (^{C_{i}}X, ^{C_{i}}Y, ^{C_{i}}Z)^{T}$, 假设该特征第一个观测为 $^{C_{0}}\textbf{p}$, 余数在第 $i$个相机帧中可以表示为: 521 | $$ 522 | ^{C_{i}}\textbf{p}_{f} = ^{C_{i}}_{C_{0}}\textbf{R}^{C_{0}}\textbf{p}_{f} + ^{C_{i}}\textbf{p}_{C_{0}} 523 | \tag{4.20} 524 | $$ 525 | 我们将这个转换为逆深度的表达形式,可以得到下面一系列式子: 526 | $$ 527 | ^{C_{i}}\textbf{p}_{f} = ^{C_{i}}_{C_{0}}\textbf{R} 528 | \begin{bmatrix} 529 | ^{C_{0}}X\\ 530 | ^{C_{0}}Y\\ 531 | ^{C_{0}}Z 532 | \end{bmatrix} 533 | + ^{C_{i}}\textbf{p}_{C_{0}}\\ 534 | =^{C_{0}}Z(^{C_{i}}_{C_{0}}\textbf{R} 535 | \begin{bmatrix} 536 | ^{C_{0}}X / ^{C_{0}}Z\\ 537 | ^{C_{0}}Y/ ^{C_{0}}Z \\ 538 | 1 539 | \end{bmatrix}+ 540 | \frac{1}{^{C_{0}}Z} \cdot ^{C_{i}}\textbf{p}_{C_{0}} 541 | )\\ 542 | =\frac{1}{\rho } \textbf{g}_{i}(^{C_{i}}_{C_{0}}\textbf{R} 543 | \begin{bmatrix} 544 | \alpha \\ 545 | \beta \\ 546 | 1 547 | \end{bmatrix}+\rho ^{C_{i}}\textbf{p}_{C_{0}} 548 | )\\ 549 | =\frac{1}{\rho }\textbf{g}_{i}(\begin{bmatrix} 550 | \alpha \\ 551 | \beta \\ 552 | \rho 553 | \end{bmatrix}) 554 | =\frac{1}{\rho }\textbf{g}_{i}(\theta) 555 | \tag{4.21} 556 | $$ 557 | 其中$\theta$ 为参数, $\alpha=^{C_{0}}X/^{C_{0}}Z$, $\beta=^{C_{0}}Y/^{C_{0}}Z$, $\rho=1/^{C_{0}}Z$. 558 | 559 | 并且这个假设我们的$^{C_{i}}\textbf{p}_{f}$ 为$(^{C_{i}}X / ^{C_{i}}Z, ^{C_{i}}Y/^{C_{i}}Z)^{T})$, 那么就是说$g_{i}(\theta)$是一个3维输入,二维输出的函数. 所以误差函数可以写成: 560 | $$ 561 | f_{i}(\theta)=\textbf{z}_{i}-h(g_{i}(\theta)) 562 | \tag{4.22} 563 | $$ 564 | 假设一共共有N个相机观测,那么我们可以构建一个最小二乘问题,形如下式: 565 | $$ 566 | arg\: min\sum_{i=1}^{n}\left \| f_{i}(\theta) \right \|_{2} 567 | \tag{4.23} 568 | $$ 569 | 其中对应于单个特征点的$Jocabian$形式如下: 570 | $$ 571 | J_{f}=\frac{\partial f }{\partial \theta}=\frac{\partial h }{\partial g}\frac{\partial g }{\partial \theta} 572 | \tag{4.24} 573 | $$ 574 | 其中第一项 $\frac{\partial f }{\partial g}$非常简单,就是参考式(4,19), 得到的结果第比较简单,第二部分$\frac{\partial g}{\partial \theta}$ 根据式子(4.21)可以很容易得到 575 | $$ 576 | \frac{\partial g}{\partial \theta}=\begin{bmatrix} 577 | \frac{\partial g_{i}}{\partial \alpha} & \frac{\partial g_{i}}{\partial \beta} & \frac{\partial g_{i}}{\partial \rho} 578 | \end{bmatrix}\\ 579 | = 580 | [^{C_{i}}_{C_{0}}R\begin{bmatrix} 581 | 1\\ 582 | 0\\ 583 | 0 584 | \end{bmatrix},\: ^{C_{i}}_{C_{0}}R\begin{bmatrix} 585 | 0\\ 586 | 1\\ 587 | 0 588 | \end{bmatrix},\; ^{C_{i}}\textbf{p}_{C_{0}}] 589 | \tag{4.25} 590 | $$ 591 | 然后用高斯-牛顿法就可以红容易解决这个最小二乘问题. 最后能得到$\hat{\theta}=[\hat{\alpha}, \hat{\beta}, \hat\rho]$, 那么其实也就是特征点在首个观测到它的相机帧下的坐标,根据下面的式子则很容易恢复出惯性系下的特征点的位置: 592 | $$ 593 | ^{G}\hat{\textbf{p}}_{f}=\frac{1}{\hat{\rho}} \: ^{G}_{C_{0}}\textbf{R}\begin{bmatrix} 594 | \hat{\alpha}\\ 595 | \hat{\beta}\\ 596 | 1 597 | \end{bmatrix} 598 | + ^{G}\textbf{p}_{C_{0}} 599 | \tag{4.26} 600 | $$ 601 | 注意,代码中的实现和现在这个推到稍微有点出入,它的实现主要参考的是文献7,不过基本大同小异,大家阅读起来应该也不会有太大的阻碍. 602 | 603 | 604 | 605 | #### 4.3 能观测性分析 606 | 607 |   关于能观性分析,我个人感觉公式太多了,并且没有想到一个很好的描述方式,理解的也不算太透彻,所以这里还希望有大佬能把这部分写一下,或者单独写一个专题,我觉得那是极好的. 608 | 609 |   另外开源版本的S-MSCKF用的是OC-EKF, 这个主要参考了这两篇论文 [Consistency Analysis and IMprovement of Vision-aided Inertial Navigation](http://xueshu.baidu.com/s?wd=paperuri:(f308abb33c5a80cd81f674dff09513a2)&filter=sc_long_sign&sc_ks_para=q%3DConsistency+Analysis+and+Improvement+of+Vision-aided+Inertial+Navigation&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&sc_us=13835246309820327221) 和 [On the consistency of Vision-aided Inertial Navigation](http://xueshu.baidu.com/s?wd=paperuri:(c1627c8837b2165d335c77f39de88f7d)&filter=sc_long_sign&sc_ks_para=q%3DOn+the+Consistency+of+Vision-Aided+Inertial+Navigation&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&sc_us=7890470351829265780). 代码中的实现主要参考了第二篇的给出的公式,我在注释里应该都有注明. 610 | 611 | #### 4.4 滤波器更新机制 612 | 613 |   大致是有两种更新策略,假设新进来一帧图像,这个时候会丢失一些特征点,这个时候丢失的特征点(且三角化成功)用于滤波器更新,如下图所示: 614 | 615 | ![update1](imgs/update1.png) 616 | 617 |   那当然随着时间的推移,相机状态会越来越多,这个时候呢, 相机状态会有一个阈值,也就是滑窗的上限, S-MSCKF与msckf1.0有稍微不同,它是当满了之后每次迭代的清除两个,最新的这个状态肯定保持, 清除依据就是帧间的旋转跟位移大小,如下图所示,假设Slide Window的大小正好为7,且已经经过了上面的update过程,那么这个时候还会再update一次,这个时候它的所有特征都会用于更新.因为要移除两个camera state,所以对应的状态和covariance也都需要剔除掉.所以删掉的两个状态其实肯定处于紫色框其中的两个. 618 | 619 | ![update2](imgs/update2.png) 620 | 621 | ### 5.S-MSCKF代码流程 622 | 623 |   这里放一张之前做的图,清晰图片从这里[下载](https://github.com/TurtleZhong/msckf_mono/blob/master/msckf_vio.jpg).另外中文注释版本的代码在[这里](https://github.com/TurtleZhong/msckf_mono) 624 | 625 | ![msckf_vio](imgs/msckf_vio.jpg) 626 | 627 | ### 6.总结 628 | 629 | ​ 全文以S-MSCKF为依托, 主要对MSCKF的理论基础和实现原理及细节进行了讲述, 并且对论文公式进行了详细的推导(很多手写的地方实在是不想敲了,太费时间了), 然后还对[Quaternion kinematics for the error-state Kalman filter](https://arxiv.org/abs/1711.02508) 这本书进行了详细的注释, 同时对开源版本的S-MSCKF的代码进行了注释. 由于笔者水平有限,有些地方理解难免不到位,其中就包括能观性分析这部分还没有做比较到位的解释,最后希望望读者批评指正文中不足的地方. 630 | 631 | ### 附录 632 | 633 | #### A. F矩阵和G矩阵的推导 634 | 635 | ![msckf](imgs/msckf_F_G_1.png) 636 | 637 | ![msckf](imgs/msckf_F_G_2.png) 638 | 639 | ![msckf](imgs/msckf_F_G_3.png) 640 | 641 | #### B. $J_{I}$的计算 642 | 643 | $J_{I}$ 的计算与正文有点出入,但还是先贴上来了,希望哪位大佬能推导得到论文的结果并告知我一下. 644 | 645 | ![JI](imgs/JI.png) 646 | 647 | #### C. H矩阵 648 | 649 | ![H1](imgs/H1.png) 650 | 651 | ![H2](imgs/H2.png) 652 | 653 | ![H3](imgs/H3.png) 654 | 655 | #### D. 三种常用数值积分方式:欧拉,中值,RK4积分 656 | 657 |   这部分其实比较简单, 大家也可以参考 参考文献1中附录A部分,也讲的很详细.这里简单附上三张图,分别对应三种积分方式. 658 | 659 | ![eular](imgs/eular.png) 660 | 661 | ![midpoint](imgs/midpoint.png) 662 | 663 | ![RK4](imgs/RK4.png) 664 | 665 | ### 7.参考文献 666 | 667 | (1) [Quaternion kinematics for the error-state Kalman filter](https://arxiv.org/abs/1711.02508) 668 | 669 | (2) [Indirect Kalman Filter for 3D Attitude Estimation-A Tutorial for Quaternion Algebra](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.304.6207&rep=rep1&type=pdf) 670 | 671 | (3) [Consistency Analysis and IMprovement of Vision-aided Inertial Navigation](http://xueshu.baidu.com/s?wd=paperuri:(f308abb33c5a80cd81f674dff09513a2)&filter=sc_long_sign&sc_ks_para=q%3DConsistency+Analysis+and+Improvement+of+Vision-aided+Inertial+Navigation&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&sc_us=13835246309820327221) 672 | 673 | (4) [On the consistency of Vision-aided Inertial Navigation](http://xueshu.baidu.com/s?wd=paperuri:(c1627c8837b2165d335c77f39de88f7d)&filter=sc_long_sign&sc_ks_para=q%3DOn+the+Consistency+of+Vision-Aided+Inertial+Navigation&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&sc_us=7890470351829265780). 674 | 675 | (5) [Robust Stereo Visual Inertial Odometry for Fast Autonomous Flight](https://arxiv.org/abs/1712.00036) 676 | 677 | (6) [Monocular Visual Inertial Odometry on a Mobile Device](http://xueshu.baidu.com/s?wd=paperuri:(68dda4a4688ea361c151eab22dedbe17)&filter=sc_long_sign&sc_ks_para=q%3DMonocular+Visual+Inertial+Odometry+on+a+Mobile+Device&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&sc_us=7517713721102109000) 678 | 679 | (7) [A multi-state constraint Kalman filter for vision-aided inertial navigation](http://xueshu.baidu.com/s?wd=paperuri:(fc81955af6e11c8b7d738d8ef94b512d)&filter=sc_long_sign&sc_ks_para=q%3DA+Multi-State+Constraint+Kalman+Filter+for+Vision-aided+Inertial+Navigation&tn=SE_baiduxueshu_c1gjeupa&ie=utf-8&sc_us=10867325448677835818) 680 | 681 | (8) [视觉SLAM十四讲](https://baike.baidu.com/item/%E8%A7%86%E8%A7%89SLAM%E5%8D%81%E5%9B%9B%E8%AE%B2/20786239?fr=aladdin) 682 | 683 | (9) [卡尔曼滤波与组合导航原理](https://baike.baidu.com/item/%E5%8D%A1%E5%B0%94%E6%9B%BC%E6%BB%A4%E6%B3%A2%E4%B8%8E%E7%BB%84%E5%90%88%E5%AF%BC%E8%88%AA%E5%8E%9F%E7%90%86/12130448?fr=aladdin) 684 | 685 | 686 | 687 | 688 | 689 | -------------------------------------------------------------------------------- /notes/msckf_notes.emmx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/notes/msckf_notes.emmx -------------------------------------------------------------------------------- /notes/notes.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/notes/notes.dia -------------------------------------------------------------------------------- /notes/notes.md: -------------------------------------------------------------------------------- 1 | ##
一步步深入了解S-MSCKF
2 |

作者:钟心亮

3 |

联系方式:xinliangzhong@foxmail.com

4 | 5 | [TOC] 6 | 7 | ### 符号说明 8 | 9 | ### 0.前言 10 | 11 | ### 1.简介 12 | 13 |   MSCKF (Multi-State Constraint Kalman Filter),从2007年提出至今,一直是filter-based SLAM比较经典的实现.据说这也是谷歌tango里面的算法,这要感觉Mingyang Li博士在MSCKF的不懈工作。在传统的EKF-SLAM框架中,特征点的信息会加入到特征向量和协方差矩阵里,这种方法的缺点是特征点的信息会给一个初始深度和初始协方差,如果不正确的话,极容易导致后面不收敛,出现inconsistent的情况。MSCKF维护一个pose的FIFO,按照时间顺序排列,可以称为滑动窗口,一个特征点在滑动窗口的几个位姿都被观察到的话,就会在这几个位姿间建立约束,从而进行KF的更新。 14 | 15 | ### 2.前端 16 |   本文主要针对2017年Kumar实验室开源的S-MSCKF进行详细分析,其实这篇文章整体思路与07年提出的基本上是一脉相承的.作为一个VIO的前端,MSCKF采用的是光流跟踪特征点的方法,特征点使用的是FAST特征,另外这是MSCKF双目的一个实现,双目之间的特征点匹配采用的也是光流,这与传统的基于descriptor匹配的方法不同.前端部分其实相对简单,整个前端部分基本在 **image_processor.cpp**中实现. 17 | 18 | #### 2.1 基本数据结构说明 19 | 20 | ##### A.特征点检测和跟踪的参数 21 | 22 | ```c++ 23 | struct ProcessorConfig { 24 | int grid_row; //划分图像网格的行数 25 | int grid_col; //划分图像网格的列数 26 | int grid_min_feature_num; //每个网格特征点的最小个数 27 | int grid_max_feature_num; //每个网格特征点的最大个数 28 | 29 | int pyramid_levels; //金字塔层数 30 | int patch_size; 31 | int fast_threshold; 32 | int max_iteration; 33 | double track_precision; 34 | double ransac_threshold; 35 | double stereo_threshold; 36 | }; 37 | ``` 38 | 39 | ##### B. 特征点数据 40 | 41 | ```c++ 42 | struct FeatureMetaData { 43 | FeatureIDType id; //unsigned long long int 每个特征都有单独的ID 44 | float response; //Feature的响应值 45 | int lifetime; //Feature的生存周期 46 | cv::Point2f cam0_point; //Feature在左相机的图像坐标 47 | cv::Point2f cam1_point; //Feature在右相机的图像坐标 48 | }; 49 | ``` 50 | 51 | #### 2.2 跟踪流程 52 | 53 |   整体框架如下面的流程图所示: 54 | 55 | ![tracking_whole_picture](../imgs/tracking_whole_picture.png) 56 | 57 | ##### 2.2.1 Initialization 58 | ![initialization](../imgs/前端-初始化部分.svg) 59 | ##### 2.2.2 trackFeatures 60 | 61 |   当第一帧初始化完成之后,后面帧则只需要进行跟踪第一帧的特征点,并且提取新的特征点,整个流程如下: 62 | 63 | ![前端-跟踪部分](../imgs/前端-跟踪部分.svg) 64 | 65 |   整个流程还算比较清晰,但是有一个步骤需要单独说明一下,也就是作者在论文中提到的twoPointRansac的方法.我们先来看一下函数原型: 66 | 67 | ```C++ 68 | /** 69 | * @brief 计算原图像帧关键点对应的矫正位置 70 | * @param pts1:上一时刻的关键点位置 71 | * @param pts2:当前时刻跟踪匹配到的关键点位置 72 | * @param R_p_c:根据imu信息计算得到的两个时刻相机的相对旋转信息 73 | * @param distortion_model,intrinsics:相机内参和畸变模型 74 | * @param inlier_error:内点可接受的阈值(关键点距离差) 75 | * @param success_probability:成功的概率 76 | * @return inlier_markers:内点标志位 77 | */ 78 | void ImageProcessor::twoPointRansac( 79 | const vector& pts1, const vector& pts2, 80 | const cv::Matx33f& R_p_c, const cv::Vec4d& intrinsics, 81 | const std::string& distortion_model, 82 | const cv::Vec4d& distortion_coeffs, 83 | const double& inlier_error, 84 | const double& success_probability, 85 | vector& inlier_markers) 86 | ``` 87 | 整个函数的基本流程如下: 88 | ![twoPointRansac](../imgs/twoPointRansac.png) 89 | 下面我们来详细讲解一下RANSAC模型及原理依据: 90 | 我们由对极几何可以知道有以下约束: 91 | $$ 92 | p_{2}^{T}\cdot [t]_{x}\cdot R\cdot p_{1}=0 93 | \tag{2.1} 94 | $$ 95 | 我们假设前后帧对应点归一化坐标分别为, 96 | $$ 97 | R\cdot p_{1}=\begin{bmatrix} 98 | x{1} & y{1} & 1 99 | \end{bmatrix}^{T}, 100 | p_{2}=\begin{bmatrix} 101 | x{2} & y{2} & 1 102 | \end{bmatrix}^{T} 103 | \tag{2.2} 104 | $$ 105 | 其中R为根据IMU的平均角速度得到的,此时坐标系都统一到一个坐标系下. 106 | $$ 107 | \begin{bmatrix} 108 | x_{2}& y_{2} & 1 109 | \end{bmatrix} 110 | \cdot \begin{bmatrix} 111 | 0 & -t_{z} & t_{y}\\ 112 | t_{z} & 0 & -t_{x}\\ 113 | -t_{y} & t_{x} & 0 114 | \end{bmatrix} 115 | \cdot \begin{bmatrix} 116 | x_{1}\\ 117 | y_{1}\\ 118 | 1 119 | \end{bmatrix}=0 120 | \tag{2.3} 121 | $$ 122 | 123 | 将式子(2.3)展开之后我们可以得到: 124 | $$ 125 | {\color{Green} \begin{bmatrix} 126 | y_{1}-y_{2} & -(x_{1}-x_{2}) & x_{1}y_{2}-x_{2}y_{2} 127 | \end{bmatrix}}\cdot 128 | \begin{bmatrix} 129 | t_{x}\\ 130 | t_{y}\\ 131 | t_{z} 132 | \end{bmatrix}=0 133 | \tag{2.4} 134 | $$ 135 | 其中绿色部分在代码中对应这一块: 136 | 137 | ```c++ 138 | vector pts_diff(pts1_undistorted.size()); 139 | for (int i = 0; i < pts1_undistorted.size(); ++i) 140 | pts_diff[i] = pts1_undistorted[i] - pts2_undistorted[i]; 141 | ... 142 | ... 143 | MatrixXd coeff_t(pts_diff.size(), 3); 144 | for (int i = 0; i < pts_diff.size(); ++i) { 145 | coeff_t(i, 0) = pts_diff[i].y; 146 | coeff_t(i, 1) = -pts_diff[i].x; 147 | coeff_t(i, 2) = pts1_undistorted[i].x*pts2_undistorted[i].y - 148 | pts1_undistorted[i].y*pts2_undistorted[i].x; 149 | } 150 | ``` 151 | 152 | 至于这个模型是怎么选出来的呢? 假设一共有N个inliers点对,那么根据式(2.4)势必会得到一个N*3 * 3*1 = N(0)的等式.但事实上由于误差和outliers的存在,最终结果不可能为零,我们取两个点将式子分块,并且只考虑两个点的情况,那么将会有: 153 | $$ 154 | {\color{Green} \begin{bmatrix} 155 | y_{1}-y_{2} & -(x_{1}-x_{2}) & x_{1}y_{2}-x_{2}y_{2}\\ 156 | y_{3}-y_{4} & -(x_{3}-x_{4}) & x_{3}y_{4}-x_{4}y_{3} 157 | \end{bmatrix}} 158 | \cdot 159 | \begin{bmatrix} 160 | t_{x}\\ 161 | t_{y}\\ 162 | t_{z} 163 | \end{bmatrix}= 164 | {\color{Green} \begin{bmatrix} 165 | A_{x} \\ A_{y} \\ A_{z} 166 | \end{bmatrix}^{T}} 167 | \cdot 168 | \begin{bmatrix} 169 | t_{x}\\ 170 | t_{y}\\ 171 | t_{z} 172 | \end{bmatrix}\approx 173 | {\color{Red} 174 | \begin{bmatrix} 175 | 0\\ 176 | 0 177 | \end{bmatrix}} 178 | \tag{2.5} 179 | $$ 180 | 那我们可以分别得到以下三个式子: 181 | $$ 182 | \begin{split} 183 | \begin{bmatrix} 184 | A_{x} \\ 185 | A_{y} 186 | \end{bmatrix}^{T} 187 | \cdot 188 | \begin{bmatrix} 189 | t_{x} \\ 190 | t_{y} 191 | \end{bmatrix} 192 | \approx {\color{Green}A_{z}}\cdot t_{z} \\ 193 | \begin{bmatrix} 194 | A_{x} \\ 195 | A_{z} 196 | \end{bmatrix}^{T} 197 | \cdot 198 | \begin{bmatrix} 199 | t_{x} \\ 200 | t_{z} 201 | \end{bmatrix} 202 | \approx {\color{Green}A_{y}}\cdot t_{y} \\ 203 | 204 | \begin{bmatrix} 205 | A_{y} \\ 206 | A_{z} 207 | \end{bmatrix}^{T} 208 | \cdot 209 | \begin{bmatrix} 210 | t_{y} \\ 211 | t_{z} 212 | \end{bmatrix} 213 | \approx {\color{Green}A_{x}}\cdot t_{x} 214 | \end{split}\tag{2.6} 215 | $$ 216 |   我们的目标当然是使得误差最小,所以作者的做法是比较式子(2.6)绿色部分的大小,取最小的并令模型的平移为1,进而直接求逆然后得到最初的模型假设,之后要做的步骤跟常规RANSAC就十分接近了,找出适应当前模型的所有inliers,然后计算误差并不断迭代找到最好的模型. 217 |   至此我们已经完成了整个feature的tracking过程! 218 | 219 | ##### 2.2.3 addNewFeatures & pruneGridFeatures 220 | 221 |   如果一直tracking的话那么随着时间流逝,有些特征会消失,有些可能也会有累计误差,所以我们势必要添加一些新的特征,这个步骤正是在跟踪上一帧特征之后要做的,因为stereoMatching 和twoPointRansac都会剔除一些outliers,那我们需要提取新的特征保证能一直运行下去. 222 | 223 | ![addNewFeatures&pruneGridFeatures](../imgs/addNewFeatures&pruneGridFeatures.svg) 224 | 225 | ##### 2.2.4 publish 226 | 227 |   主要发布了当前帧特征点的数据,每个特征的数据结构为FeasureMeasurement如下: 228 | 229 | ```c++ 230 | uint64 id 231 | # Normalized feature coordinates (with identity intrinsic matrix) 232 | float64 u0 # horizontal coordinate in cam0 233 | float64 v0 # vertical coordinate in cam0 234 | float64 u1 # horizontal coordinate in cam0 235 | float64 v1 # vertical coordinate in cam0 236 | ``` 237 | 238 | 发布的其实是CameraMeasurement,那其实就是一个包含上面特征数据FeasureMeasurement的一个数组. 239 | 240 | #### 2.3 显示 241 | 其实前端基本上可以说是非常简单了,也没有太多的trick,最后我们来看一下前端的跟踪效果的动图: 242 | 243 | 244 | 245 | ### 3.基于四元数的 error-state Kalman Filter 246 | 247 | ​ 其实原理部分相当重要,包括你对error-state Kalman Filter的理解,但是如果要从头讲起的话可以说篇幅太长,考虑到做SLAM的同学们基本上都应该知道这本书 [Quaternion kinematics for the error-state Kalman filter](https://arxiv.org/abs/1711.02508), 这本书会让你在四元数,SO3,IMU的模型以及基于IMU的ESKF(error-state Kalman Filter)都会有比较深刻的理解,对应这本书我也做了很多注释,关于基于四元数原理部分由于篇幅限制,我这里不想做太多的说明,但是接下来在讲解S-MSCKF原理部分我们会将参考公式附上.下面是一些我在这本参考资料中的注释图. 248 | 249 | ||| 250 | |:----------------------------------------------------------:|:----------------------------------------------------------:| 251 | ||| 252 | 253 | 当然重要的事情需要说三遍,那就是[Quaternion kinematics for the error-state Kalman filter](https://arxiv.org/abs/1711.02508)四元数是Hamilton表示方法,而MSCKF中采用的是JPL表示方法,关于这两者的不同,可以参考书中34页,Table2. 254 | 255 |   看过论文代码的同学可能会说,MSCKF这一部分代码参考的是MARS实验室[Indirect Kalman Filter for 3D Attitude Estimation-A Tutorial for Quaternion Algebra](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.304.6207&rep=rep1&type=pdf),答案是肯定的.但是我的建议是以Hamilton那本书为基础,然后自行再去推导MARS出品那一本,那样你的体验会更加深刻. 256 | 257 | ### 4.S-MSCKF 258 | 259 |   巴拉巴拉这里放一点大致性的简介 260 | 261 | #### 4.1 基本原理 262 | 263 |   在讲解之前,我们先来定义一下坐标系,如下图所示: 264 | ![coordinate](../imgs/coordinate.png) 265 | 其中$I$表示IMU机体坐标系(Body Frame),$G$表示的是惯性系(Inertial Frame),$C$表示的是相机坐标系(Camera Frame). 266 | 267 | ​ 作为一个滤波器,我们首先来看滤波器的状态向量,它包含两个部分,IMU状态和Camera状态: 268 | $$ 269 | \textbf{x}_{I}=\begin{bmatrix} 270 | _{G}^{I}\textbf{q}^{T} & \textbf{b}_{g}^{T} & ^{G}\textbf{v}_{I}^{T} & \textbf{b}_{a}^{T} & 271 | ^{G}\textbf{p}_{I}^{T} & _{C}^{I}\textbf{q}^{T} & ^{I}\textbf{p}_{C}^{T} 272 | \end{bmatrix}^{T} 273 | \tag{4.1} 274 | $$ 275 | 其中$_{G}^{I}\textbf{q}​$表示的是从惯性系到机体IMU系的旋转变换, $^{G}\textbf{v}_{I}​$和$^{G}\textbf{p}_{I}​$分别表示机体在惯性系下的速度和位置,$\textbf{b}_{g}​$和$\textbf{b}_{a}​$分别代表IMU的bias,而$_{C}^{I}\textbf{q}​$ 和$ ^{I}\textbf{p}_{C}​$分别代表从相机坐标系到IMU坐标系的旋转与平移,其中以左相机为准. 但是我们注意到旋转实际上只有三个自由度,而且四元数必须是单位单位四元数,那这样额外的约束会使得协方差矩阵奇异,所以我们定义error IMU状态如下: 276 | $$ 277 | \tilde{\textbf{x}}_{I}=\begin{bmatrix} 278 | _{G}^{I}\tilde{\theta }^{T} & \tilde{\textbf{b}}_{g}^{T} & ^{G}\tilde{\textbf{v}}_{I}^{T} & \tilde{\textbf{b}}_{a}^{T} & 279 | ^{G}\tilde{\textbf{p}}_{I}^{T} & _{C}^{I}\tilde{\textbf{q}}^{T} & ^{I}\tilde{\textbf{p}}_{C}^{T} 280 | \end{bmatrix}^{T} 281 | \tag{4.2} 282 | $$ 283 | 284 | 所以IMU的状态一共是$3\cdot7=21​$维向量,后6维度其实是相机与IMU的外参,这6个维度在MSCKF1.0版本是不在状态向量里边的. MSCKF的状态向量还包含另外一个组成部分,那就是N个相机的姿态,那么每个相机的姿态误差向量定义为 $\tilde{\textbf{x}}_{C_{i}}=\left ( \begin{matrix}_{G}^{C_{i}}\tilde{\theta }^{T} & ^{G}\tilde{\textbf{p}}_{C_{i}}^{T}\end{matrix} \right )^T​$,所以当滑窗里边有N个相机姿态的时候,整个误差状态向量为: 285 | $$ 286 | \tilde{\textbf{x}} = \begin{pmatrix} 287 | \tilde{\textbf{x}}_{I}^{T} & \tilde{\textbf{x}}_{C_{1}}^{T} & ... & \tilde{\textbf{x}}_{C_{N}}^{T} 288 | \end{pmatrix} 289 | \tag{4.3} 290 | $$ 291 | 我们以标准error-state KF为准,其过程包含运动模型和观测模型. 292 | 293 |   ##### A.运动模型 294 | 295 | 我们知道对于IMU的状态连续时间的运动学有: 296 | $$ 297 | \begin{split} 298 | _{G}^{I}\dot{\hat{\textbf{q}}}=\frac{1}{2}\cdot_{G}^{I}\hat{\textbf{q}}\otimes\hat{w} =\frac{1}{2}\Omega (\hat{w})_{G}^{I}\hat{\textbf{q}},\\ 299 | \dot{\hat{\textbf{b}}}_{g}=\textbf{0}_{3\times 1},\\ 300 | ^{G}\dot{\hat{\textbf{v}}}=C(_{G}^{I}\hat{\textbf{q}})^{T}\hat{\textbf{a}}+^{G}\textbf{g},\\ 301 | \dot{\hat{\textbf{b}}}_{a}=\textbf{0}_{3\times 1},\\ 302 | ^{G}\dot{\hat{\textbf{p}}_{I}}=^{G}{\hat{\textbf{v}}},\\ 303 | _{C}^{I}\dot{\hat{\textbf{q}}}=\textbf{0}_{3\times 1},\\ 304 | ^{I}\dot{\hat{\textbf{p}}_{C}}=\textbf{0}_{3\times 1} 305 | \end{split}\tag{4.4} 306 | $$ 307 | 其中 $\hat{w}​$ 和 $\hat{a}​$ 分别为角速度和加速度的估计值(测量值减去bias),即有: 308 | $$ 309 | \hat{w}=w_{m}-\hat{b}_{g},\hat{a}=a_{m}-\hat{b}_{a}\tag{4.5} 310 | $$ 311 | 其中有几点要说明,其中, 312 | $$ 313 | \Omega (\hat{w})=\Omega (\hat{w})=\begin{pmatrix} 314 | -[\hat{w}_{\times }] & w\\ 315 | -w^{T} & 0 316 | \end{pmatrix} 317 | \tag{4.6} 318 | $$ 319 | 这个直接参考四元数乘法就可以了,然后 $[\hat{w}_{\times }]​$ 是 $\hat{w}​$的反对称矩阵; $C(\cdot)​$表示四元数到旋转矩阵的转换,这个可以参照[1]和[2]. 那按照S-MSCKF的论文所述,我们可以得到以下式子, 320 | $$ 321 | \dot{\tilde{\textbf{x}}}_{I}=\textbf{F}\tilde{\textbf{x}}_{I}+ 322 | \textbf{G} \textbf{n}_{I} 323 | \tag{4.7} 324 | $$ 325 | 其中 326 | $$ 327 | \textbf{n}_{I}^{T}=\begin{pmatrix} 328 | \textbf{n}_{g}^{T}& \textbf{n}_{wg}^{T} & \textbf{n}_{a}^{T} & \textbf{n}_{wa}^{T} 329 | \end{pmatrix}^{T} 330 | \tag{4.8} 331 | $$ 332 | $\textbf{n}_{g}$ 和 $\textbf{n}_{a}$ 分别代表角速度和加速度的测量噪声,服从高斯分布; $\textbf{n}_{wg}$ 和 $\textbf{n}_{wa}$ 分别代表角速度和加速度的bias的随机游走噪声. $\textbf{F} $ 是 $21\times21$ 大小矩阵, $\textbf{G}$ 是 $21\times12$ 大小的矩阵,其详细推到见附录A. 333 | 334 |   对于IMU的状态来说,我们可以采用RK4的积分方法根据(4.4)求得IMU的状态. 那么对于IMU的协方差矩阵呢,我们需要事先求取状态转移矩阵和离散的运动噪声协方差矩阵,如下: 335 | $$ 336 | \Phi_{k}=\Phi(t_{k+1},t_{k})=\textbf{exp}(\int_{t_{k}}^{t_{k+1}} \textbf{F}(\tau)d\tau) \\ 337 | \textbf{Q}_{k}=\int_{t_{k}}^{t_{k+1}}\Phi({t_{k+1},\tau})\textbf{GQG}\Phi({t_{k+1},\tau})^{T}d\tau 338 | $$ 339 | 340 | 341 | #### 4.2三角化 342 | 343 | #### 4.2 能观测性分析 344 | 345 | ### 5.S-MSCKF代码流程 346 | 347 | ### 6.总结 348 | 349 | ### 附录 350 | 351 | #### A. F矩阵和G矩阵的推导 352 | 353 | #### B. H矩阵 354 | 355 | #### C. 三种常用积分方式:欧拉,中值,RK4积分 356 | 357 | ### 7.参考文献 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | -------------------------------------------------------------------------------- /notes/notes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TurtleZhong/msckf_notes/e70b4df5ebfb6b47b7769f010516f3ba309b0022/notes/notes.pdf --------------------------------------------------------------------------------