VINS 估计器之结构初始化
为什么要初始化
非线性VINS估计器的性能对于初始的速度,尺度,重力向量,空间点3D位置,以及外参等非常敏感。在很多场合中,能做到相机和IMU即插即用,线上自动校准与初始化,将会给用户带来极大的方便性。VINS里面分四步进行,第一个就是上次讲的旋转外参校准,第二个就是找到某帧作为系统初始化原点,计算3D地图点,第三就是将相机坐标系转到IMU坐标系中,第四就是相机与IMU对齐,包括IMU零偏初始化,速度,重力向量,尺度初始化
初始化系统原点与转换到IMU坐标系
bool Estimator::initialStructure()
{
TicToc t_sfm;
//check imu observibility, 通过协方差检测IMU的可观测性
{
map<double, ImageFrame>::iterator frame_it;
Vector3d sum_g;
for (frame_it = all_image_frame.begin(), frame_it++; frame_it != all_image_frame.end(); frame_it++)
{
double dt = frame_it->second.pre_integration->sum_dt;
Vector3d tmp_g = frame_it->second.pre_integration->delta_v / dt;
sum_g += tmp_g;
}
Vector3d aver_g;
aver_g = sum_g * 1.0 / ((int)all_image_frame.size() - 1);
double var = 0;
for (frame_it = all_image_frame.begin(), frame_it++; frame_it != all_image_frame.end(); frame_it++)
{
double dt = frame_it->second.pre_integration->sum_dt;
Vector3d tmp_g = frame_it->second.pre_integration->delta_v / dt;
var += (tmp_g - aver_g).transpose() * (tmp_g - aver_g);
//cout << "frame g " << tmp_g.transpose() << endl;
}
var = sqrt(var / ((int)all_image_frame.size() - 1));
//ROS_WARN("IMU variation %f!", var);
if(var < 0.25)
{
ROS_INFO("IMU excitation not enouth!");
//return false;
}
}
// global sfm 准备
Quaterniond Q[frame_count + 1];
Vector3d T[frame_count + 1];
map<int, Vector3d> sfm_tracked_points;
vector<SFMFeature> sfm_f;
for (auto &it_per_id : f_manager.feature)
{
int imu_j = it_per_id.start_frame - 1;
SFMFeature tmp_feature;
tmp_feature.state = false;
tmp_feature.id = it_per_id.feature_id;
for (auto &it_per_frame : it_per_id.feature_per_frame)
{
imu_j++;
Vector3d pts_j = it_per_frame.point;
tmp_feature.observation.push_back(make_pair(imu_j, Eigen::Vector2d{pts_j.x(), pts_j.y()}));
}
sfm_f.push_back(tmp_feature);
}
//在窗口内选择跟最后一帧视差最大的帧,利用五点法计算相对旋转和平移量
Matrix3d relative_R;
Vector3d relative_T;
int l;
if (!relativePose(relative_R, relative_T, l))
{
ROS_INFO("Not enough features or parallax; Move device around");
return false;
}
// 为了更方便理解,把relativePose 函数取了出来
| ----------------------------------------------------------------------------------------------------------
| bool Estimator::relativePose(Matrix3d &relative_R, Vector3d &relative_T, int &l)
| {
| // find previous frame which contians enough correspondance and parallex with newest frame,循环找出和最新帧视差最大的历史帧
| for (int i = 0; i < WINDOW_SIZE; i++)
| {
| vector<pair<Vector3d, Vector3d>> corres;
| corres = f_manager.getCorresponding(i, WINDOW_SIZE);
| if (corres.size() > 20)
| {
| double sum_parallax = 0;
| double average_parallax;
| for (int j = 0; j < int(corres.size()); j++)
| {
| Vector2d pts_0(corres[j].first(0), corres[j].first(1));
| Vector2d pts_1(corres[j].second(0), corres[j].second(1));
| double parallax = (pts_0 - pts_1).norm();
| sum_parallax = sum_parallax + parallax;
| }
| average_parallax = 1.0 * sum_parallax / int(corres.size());
| // 找到后就可以利用五点法计算,如果计算失败就继续循环
| if(average_parallax * 460 > 30 && m_estimator.solveRelativeRT(corres, relative_R, relative_T))
| {
| l = i;
| ROS_DEBUG("average_parallax %f choose l %d and newest frame to triangulate the whole structure", average_parallax * 460, l);
| return true;
| }
| }
| }
| return false;
| }
| // 核心的五点法,也是求本质矩阵
| bool MotionEstimator::solveRelativeRT(const vector<pair<Vector3d, Vector3d>> &corres, Matrix3d &Rotation, Vector3d &Translation)
| {
| if (corres.size() >= 15)
| {
| vector<cv::Point2f> ll, rr;
| for (int i = 0; i < int(corres.size()); i++)
| {
| 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 mask;
| cv::Mat E = cv::findFundamentalMat(ll, rr, cv::FM_RANSAC, 0.3 / 460, 0.99, mask);
| cv::Mat cameraMatrix = (cv::Mat_<double>(3, 3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
| cv::Mat rot, trans;
| // recoverPose内用了一些复杂的条件判断如何选取R和T,但是其用了单位矩阵作为cameraMatrix,feature_tracker_node在处理图像的是已经把图像坐标转为归一化的相机坐标了,并且进行了去畸变,所以之后的重投影部分只需要外参,不再需要内参了.
| int inlier_cnt = cv::recoverPose(E, ll, rr, cameraMatrix, rot, trans, mask);
| //cout << "inlier_cnt " << inlier_cnt << endl;
|
| Eigen::Matrix3d R;
| Eigen::Vector3d T;
| for (int i = 0; i < 3; i++)
| {
| T(i) = trans.at<double>(i, 0);
| for (int j = 0; j < 3; j++)
| R(i, j) = rot.at<double>(i, j);
| }
|
| Rotation = R.transpose();
| Translation = -R.transpose() * T;
| if(inlier_cnt > 12)
| return true;
| else
| return false;
| }
| return false;
| }
| ----------------------------------------------------------------------------------------------------------
GlobalSFM sfm;
/* sfm.construct 函数本身就很巨大,里面很多步骤,代码就不贴了,大致如下:
1. 三角化l 帧与frame_num - 1帧,得到一些地图点。
2. 通过这些地图点,利用pnp的方法计算l+1, l+2, l+3, …… frame_num-2 相对于l的位姿。而且每计算一帧,就会与frame_num - 1帧进行三角化,得出更多地图点
3. 三角化l+1, l+2 …… frame_num-2帧与l帧
4. 对l-1, l-2, l-3 等帧与sfm_f的特征点队列进行pnp求解,得出相对于l的位姿,并三角化其与l帧。
5. 三角化剩余的点
6. ceres全局BA优化,最小化3d投影误差
*/
if(!sfm.construct(frame_count + 1, Q, T, l,
relative_R, relative_T,
sfm_f, sfm_tracked_points))
{
ROS_DEBUG("global SFM failed!");
marginalization_flag = MARGIN_OLD;
return false;
}
//solve pnp for all frame,因为有continue存在,怎么会solve all,感觉只是不满足时间戳相等的才pnp求解
map<double, ImageFrame>::iterator frame_it;
map<int, Vector3d>::iterator it;
frame_it = all_image_frame.begin( );
for (int i = 0; frame_it != all_image_frame.end( ); frame_it++)
{
// provide initial guess
cv::Mat r, rvec, t, D, tmp_r;
if((frame_it->first) == Headers[i].stamp.toSec())
{
frame_it->second.is_key_frame = true;
frame_it->second.R = Q[i].toRotationMatrix() * RIC[0].transpose();
frame_it->second.T = T[i];
i++;
continue;
}
if((frame_it->first) > Headers[i].stamp.toSec())
{
i++;
}
Matrix3d R_inital = (Q[i].inverse()).toRotationMatrix();
Vector3d P_inital = - R_inital * T[i];
cv::eigen2cv(R_inital, tmp_r);
cv::Rodrigues(tmp_r, rvec);
cv::eigen2cv(P_inital, t);
frame_it->second.is_key_frame = false;
vector<cv::Point3f> pts_3_vector;
vector<cv::Point2f> pts_2_vector;
for (auto &id_pts : frame_it->second.points)
{
int feature_id = id_pts.first;
for (auto &i_p : id_pts.second)
{
it = sfm_tracked_points.find(feature_id);
if(it != sfm_tracked_points.end())
{
Vector3d world_pts = it->second;
cv::Point3f pts_3(world_pts(0), world_pts(1), world_pts(2));
pts_3_vector.push_back(pts_3);
Vector2d img_pts = i_p.second.head<2>();
cv::Point2f pts_2(img_pts(0), img_pts(1));
pts_2_vector.push_back(pts_2);
}
}
}
cv::Mat K = (cv::Mat_<double>(3, 3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
if(pts_3_vector.size() < 6)
{
cout << "pts_3_vector size " << pts_3_vector.size() << endl;
ROS_DEBUG("Not enough points for solve pnp !");
return false;
}
if (! cv::solvePnP(pts_3_vector, pts_2_vector, K, D, rvec, t, 1))
{
ROS_DEBUG("solve pnp fail!");
return false;
}
cv::Rodrigues(rvec, r);
MatrixXd R_pnp,tmp_R_pnp;
cv::cv2eigen(r, tmp_R_pnp);
R_pnp = tmp_R_pnp.transpose();
MatrixXd T_pnp;
cv::cv2eigen(t, T_pnp);
T_pnp = R_pnp * (-T_pnp);
frame_it->second.R = R_pnp * RIC[0].transpose();
frame_it->second.T = T_pnp;
}
if (visualInitialAlign())
return true;
else
{
ROS_INFO("misalign visual structure with IMU");
return false;
}
}
相机与IMU对齐
bool Estimator::visualInitialAlign()
{
TicToc t_g;
VectorXd x;
/*里面包含
1. solveGyroscopeBias()零偏初始化: 在滑动窗口中,每两帧之间的相对旋转与IMU预积分产生的旋转进行最小二乘法优化,用ldlt求解
2. LinearAlignment() 尺度初始化:由于在视觉初始化SFM的过程中,将其中位姿变化较大的两帧中使用E矩阵求解出来旋转和位移,后续的PnP和三角化都是在这个尺度下完成的。所以当前的尺度与IMU测量出来的真实世界尺度肯定不是一致的,所以需要这里进行对齐。这里对齐的方法主要是通过在滑动窗口中每两帧之间的位置和速度与IMU预积分出来的位置和速度组成一个最小二乘法的形式,然后求解出来
3. RefineGravity()重力向量优化:进一步细化重力加速度,提高估计值的精度,形式与LinearAlignment()是一致的,只是将g改为g⋅ĝ +w1b1+w2b2
2和3的公式相对难懂,请参考下面图片
*/
bool result = VisualIMUAlignment(all_image_frame, Bgs, g, x);
if(!result)
{
ROS_DEBUG("solve g failed!");
return false;
}
// change state
for (int i = 0; i <= frame_count; i++)
{
Matrix3d Ri = all_image_frame[Headers[i].stamp.toSec()].R;
Vector3d Pi = all_image_frame[Headers[i].stamp.toSec()].T;
Ps[i] = Pi;
Rs[i] = Ri;
all_image_frame[Headers[i].stamp.toSec()].is_key_frame = true;
}
VectorXd dep = f_manager.getDepthVector();
for (int i = 0; i < dep.size(); i++)
dep[i] = -1;
f_manager.clearDepth(dep);
//triangulat on cam pose , no tic三角化特征点,
Vector3d TIC_TMP[NUM_OF_CAM];
for(int i = 0; i < NUM_OF_CAM; i++)
TIC_TMP[i].setZero();
ric[0] = RIC[0];
f_manager.setRic(ric);
f_manager.triangulate(Ps, &(TIC_TMP[0]), &(RIC[0]));
//更新相机速度,位置和旋转量(通过精确求解的尺度,重力向量)
double s = (x.tail<1>())(0);
for (int i = 0; i <= WINDOW_SIZE; i++)
{
pre_integrations[i]->repropagate(Vector3d::Zero(), Bgs[i]);
}
for (int i = frame_count; i >= 0; i--)
Ps[i] = s * Ps[i] - Rs[i] * TIC[0] - (s * Ps[0] - Rs[0] * TIC[0]);
int kv = -1;
map<double, ImageFrame>::iterator frame_i;
for (frame_i = all_image_frame.begin(); frame_i != all_image_frame.end(); frame_i++)
{
if(frame_i->second.is_key_frame)
{
kv++;
Vs[kv] = frame_i->second.R * x.segment<3>(kv * 3);
}
}
for (auto &it_per_id : f_manager.feature)
{
it_per_id.used_num = it_per_id.feature_per_frame.size();
if (!(it_per_id.used_num >= 2 && it_per_id.start_frame < WINDOW_SIZE - 2))
continue;
it_per_id.estimated_depth *= s;
}
Matrix3d R0 = Utility::g2R(g);
double yaw = Utility::R2ypr(R0 * Rs[0]).x();
R0 = Utility::ypr2R(Eigen::Vector3d{-yaw, 0, 0}) * R0;
g = R0 * g;
//Matrix3d rot_diff = R0 * Rs[0].transpose();
Matrix3d rot_diff = R0;
for (int i = 0; i <= frame_count; i++)
{
Ps[i] = rot_diff * Ps[i];
Rs[i] = rot_diff * Rs[i];
Vs[i] = rot_diff * Vs[i];
}
ROS_DEBUG_STREAM("g0 " << g.transpose());
ROS_DEBUG_STREAM("my R0 " << Utility::R2ypr(Rs[0]).transpose());
return true;
}
VINS 估计器之结构初始化的更多相关文章
- VINS 估计器之优化与边缘化
VINS的优化除了添加了投影残差,回环检测残差,还有IMU的残差,边缘化产生的先验信息残差等.有些比较难理解,可参考此博客和知乎回答. void Estimator::optimization() { ...
- VINS 估计器之检查视差
为什么检查视差 VINS里为了控制优化计算量,在实时情况下,只对当前帧之前某一部分帧进行优化,而不是全部历史帧.局部优化帧的数量就是窗口大小.为了维持窗口大小,需要去除旧的帧添加新的帧,也就是边缘化 ...
- C语言标记化结构初始化语法
C语言标记化结构初始化语法 (designated initializer),而且还是一个ISO标准. #include <stdio.h> #include <stdlib.h&g ...
- 标记化结构初始化语法 在结构体成员前加上小数点 如 “.open .write .close ”C99编译器 .
今天在看串口驱动(四)的时候 有这样一个结构体初始化 我很不理解 如下: static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] ...
- VINS 估计器之外参初始化
为何初始化外参 当外参完全不知道的时候,VINS也可以在线对其进行估计(rotation),先在processImage内进行初步估计,然后在后续优化时,会在optimize函数中再次优化. 如何初始 ...
- VINS(四)初始化与相机IMU外参标定
和单目纯视觉的初始化只需要获取R,t和feature的深度不同,VIO的初始化话通常需要标定出所有的关键参数,包括速度,重力方向,feature深度,以及相机IMU外参$R_{c}^{b}$和$p_{ ...
- VINS(八)初始化
首先通过imu预积分陀螺仪和视觉特征匹配分解Fundamental矩阵获取rotationMatrix之间的约束关系,联立方程组可以求得外参旋转矩阵: 接下来会检测当前frame_count是否达到W ...
- php-5.6.26源代码 - hash存储结构 - 初始化
初始化 有指定析构函数,在销毁hash的时候会调用,如:“类似extension=test.so扩展”也是存放在HashTable中的,“类似extension=test.so扩展”的module_s ...
- 标准C的标记化结构初始化语法
1 struct file_operations { 2 struct module *owner; 3 loff_t (*llseek) (struct file * ...
随机推荐
- div内文字显示两行,多出的文字用省略号显示
用-webkit-私有属性,代码如下:text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;disp ...
- 有序的map LinkedHashMap
HashMap是无序的,HashMap在put的时候是根据key的hashcode进行hash然后放入对应的地方.所以在按照一定顺序put进HashMap中,然后遍历出HashMap的顺序跟put的顺 ...
- c语言程序设计第6周编程作业一(分解质因数)
分解质因数 题目内容: 每个非素数(合数)都可以写成几个素数(也可称为质数)相乘的形式,这几个素数就都叫做这个合数的质因数.比如,6可以被分解为2x3,而24可以被分解为2x2x2x3. 现在,你的程 ...
- 【Spring系列】Spring mvc整合druid
一.pom.xml中添加druid依赖 <!-- druid --> <dependency> <groupId>com.alibaba</groupId&g ...
- C语言程序设计(基础)最后一次作业-- 总结报告
本次作业是本学期的最后一次作业,有始有终,本次作业回顾下本学期的第0次作业,回答下面几个问题. 注意:在回答问题时请不要简单的回答 "不是","是".请把这当成 ...
- Linux系统安装gcc/g++详细过程
下载: http://ftp.gnu.org/gnu/gcc/gcc-4.5.1/gcc-4.5.1.tar.bz2 浏览: http://ftp.gnu.org/gnu/gcc/gcc-4.5.1/ ...
- python第三方库requests详解
Requests 是用Python语言编写,基于 urllib,采用 Apache2 Licensed 开源协议的 HTTP 库.它比 urllib 更加方便,可以节约我们大量的工作,完全满足 HTT ...
- python 3.x 爬虫基础---常用第三方库(requests,BeautifulSoup4,selenium,lxml )
python 3.x 爬虫基础 python 3.x 爬虫基础---http headers详解 python 3.x 爬虫基础---Urllib详解 python 3.x 爬虫基础---常用第三方库 ...
- OO第一次作业总结
OO第一次学习总结 1.第一次作业:多项式加法 从未接触过java的我,在从输入输出开始学了几天后,按照C语言的思路,写出了一个与面向过程极其接近的程序. 在这个程序中,存在两个类:一个是Comput ...
- sql 用临时表时报错 "Chinese_PRC_90_CI_AI" 和 "Chinese_PRC_CI_AS" 之间的排序规则冲突
在用临时表关联数据库中的表做关联查询时,如果报这种情况的话,就要把临时表和关联的表的排序规则统一掉. LEFT JOIN #tsub ON #tsub.joinjarno collate Chines ...