VINS-Mono关键知识点总结——前端详解

  • VINS-Mono关键知识点总结——前端详解
    • 1. VINS-Mono的前端流程概述
    • 2. setMask() 函数的作用
    • 3. rejectWithF() 函数的作用
    • 4. addPoints() 函数和 updataID() 函数的作用

VINS-Mono关键知识点总结——前端详解

VINS-Mono的前端看上去思路是比较简单的,但是如果仔细阅读源码的话还是能看出许多非常有技术性的东西的,下面我就把一些我觉得很有意思的细节码出来,分享一下

1. VINS-Mono的前端流程概述

VINS-Mono的前端整个封装成了一个ROS节点
其订阅的topic是:

  1. 相机或者数据集发来的图片

其发布topic是:

  1. 由pub_img发布的"feature",发布的是当前帧的特征点,特征点分装成了sensor_msgs::PointCloudPtr类型,里面包括了每个特征点的(1)归一化平面上的坐标(2)归一化平面上的速度(3)图像平面上的坐标,这三者中归一化平面坐标是在SFM和后端用到,而归一化平面上的速度以及图像平面上的坐标仅仅在进行时间偏差估计时会用到。
  2. 由pub_match发布的“feature_img”,发布的是用来调试的带特征点的灰度图,图上特征点的颜色越红代表此特征点被跟踪的帧数越多,越蓝表示越少。

在图片的回调函数中会先对时间进行控制,然后会进入 readImage() 函数 进行光流追踪、特征点提取、特征点提取等步骤

2. setMask() 函数的作用

setMask() 函数主要是和特征点提取有关的,当跟踪的特征点数量没有达到设定的值时就会对图片进行特征点提取,提取采取的算法是OpenCV的Harris角点提取函数

cv::goodFeaturesToTrack(forw_img, n_pts, MAX_CNT - forw_pts.size(), 0.01, MIN_DIST, mask);

函数的最后一个参数mask是是设置角点提取的区域,实际上是和输入forw_img尺寸相同的一个Mat数据类型,而这个mask就在这里就是通过setMask()函数生成的,它的作用是使得提取的特征点分布尽可能均匀,setMask() 函数源码如下:

void FeatureTracker::setMask()
{if(FISHEYE)mask = fisheye_mask.clone();elsemask = cv::Mat(ROW, COL, CV_8UC1, cv::Scalar(255));//算法会更加倾向于保留跟踪时间长的特征点vector<pair<int, pair<cv::Point2f, int>>> cnt_pts_id;for (unsigned int i = 0; i < forw_pts.size(); i++)cnt_pts_id.push_back(make_pair(track_cnt[i], make_pair(forw_pts[i], ids[i])));//对光流跟踪到的特征点forw_pts,按照被跟踪到的次数cnt从大到小排序sort(cnt_pts_id.begin(), cnt_pts_id.end(), [](const pair<int, pair<cv::Point2f, int>> &a, const pair<int, pair<cv::Point2f, int>> &b){return a.first > b.first;});//清空cnt,pts,id并重新存入forw_pts.clear();ids.clear();track_cnt.clear();for (auto &it : cnt_pts_id){if (mask.at<uchar>(it.second.first) == 255){//当前特征点位置对应的mask值为255,则保留当前特征点,将对应的特征点位置pts,id,被追踪次数cnt分别存入forw_pts.push_back(it.second.first);ids.push_back(it.second.second);track_cnt.push_back(it.first);//在mask中将当前特征点周围半径为MIN_DIST的区域设置为0,后面不再选取该区域内的点(使跟踪点不集中在一个区域上)cv::circle(mask, it.second.first, MIN_DIST, 0, -1);}}
}

其大概流程是会根据光流法跟踪下一帧图片forw_img获得的forw_pts的被跟踪的时间进行排序,然后按照被跟踪时间从长到段的顺序以半径MIN_DIST在一张白图上画黑圈,如果画出来的mask大概长下面这个样子(有点密集恐惧症了…):

将这个mask图输入到goodFeaturesToTrack函数中,函数就只会在白色区域,也就是没有跟踪时长特别长的地区提取特征点,因此会使得我们提取的关键点相对较为均匀,如下图所示:


如果你将setMask函数中排序的部分注释掉,特征点的分布就会变成如下图所示结果:

我个人觉得上面这种做法应该是有利有弊的,对于纹理特征丰富的环境,这种操作使得特征点分布更加均匀是有利于系统鲁棒性的,在ORB SLAM2和SVO等经典框架中,前端都有相应的均匀分布的操作,但是在上面图片中这种纹理特征稀疏的环境下,这种做法就不一定好用了,因为强行使得特征点分布到一些本来就没什么特征的区域,这样可能(因为这里我还没有做实验验证)会使得系统精度下降。

3. rejectWithF() 函数的作用

rejectWithF() 函数的源码如下,其实比较简单:

void FeatureTracker::rejectWithF()
{if (forw_pts.size() >= 8){ROS_DEBUG("FM ransac begins");//才哟ing的是ransac的方法TicToc t_f;vector<cv::Point2f> un_cur_pts(cur_pts.size()), un_forw_pts(forw_pts.size());for (unsigned int i = 0; i < cur_pts.size(); i++){Eigen::Vector3d tmp_p;//根据不同的相机模型将二维坐标转换到三维坐标m_camera->liftProjective(Eigen::Vector2d(cur_pts[i].x, cur_pts[i].y), tmp_p);//转换为归一化像素坐标tmp_p.x() = FOCAL_LENGTH * tmp_p.x() / tmp_p.z() + COL / 2.0;tmp_p.y() = FOCAL_LENGTH * tmp_p.y() / tmp_p.z() + ROW / 2.0;un_cur_pts[i] = cv::Point2f(tmp_p.x(), tmp_p.y());m_camera->liftProjective(Eigen::Vector2d(forw_pts[i].x, forw_pts[i].y), tmp_p);tmp_p.x() = FOCAL_LENGTH * tmp_p.x() / tmp_p.z() + COL / 2.0;tmp_p.y() = FOCAL_LENGTH * tmp_p.y() / tmp_p.z() + ROW / 2.0;un_forw_pts[i] = cv::Point2f(tmp_p.x(), tmp_p.y());}vector<uchar> status;//调用cv::findFundamentalMat对un_cur_pts和un_forw_pts计算F矩阵cv::findFundamentalMat(un_cur_pts, un_forw_pts, cv::FM_RANSAC, F_THRESHOLD, 0.99, status);int size_a = cur_pts.size();reduceVector(prev_pts, status);reduceVector(cur_pts, status);reduceVector(forw_pts, status);reduceVector(cur_un_pts, status);reduceVector(ids, status);reduceVector(track_cnt, status);ROS_DEBUG("FM ransac: %d -> %lu: %f", size_a, forw_pts.size(), 1.0 * forw_pts.size() / size_a);ROS_DEBUG("FM ransac costs: %fms", t_f.toc());}
}

这里主要是操作是将图像平面上的特征点,投影到归一化平面上然后消除畸变,这一步是通过liftProjective函数实现的,cameral_model功能包通过工厂模式生成了多种相机模型,不同的相机模型的liftProjective函数里面算法不同,但是好像这些相机模型对应的畸变消除算法和《视觉SLAM十四讲》中讲的最基本的那种k2,k4,k6,p1,p2k_2, k_4, k_6, p_1,p_2k2​,k4​,k6​,p1​,p2​五个参数的都不同,具体的相机模型我还没有时间去研究,这里我还有两个问题:

  1. 为什么FOCAL_LENGTH在程序中设置成了460这个数值?
  2. 为什么要将特征带点转化到归一化平面上的像素坐标?

排除这两个问题之外,其他其实也就很简单了,通过findFundamentalMat函数自带的ransac消除外点的功能在光流法之后以及提取新的特征点之前消除光流法更踪错误的点,维持系统精度。

4. addPoints() 函数和 updataID() 函数的作用

addPoints() 函数代码如下:

void FeatureTracker::addPoints()
{for (auto &p : n_pts){forw_pts.push_back(p);ids.push_back(-1);//新提取的特征点id初始化为-1track_cnt.push_back(1);//新提取的特征点被跟踪的次数初始化为1}
}

updataID() 函数代码如下:

bool FeatureTracker::updateID(unsigned int i)
{if (i < ids.size()){if (ids[i] == -1)ids[i] = n_id++;return true;}elsereturn false;
}

这两个函数都很短啊,addPoints() 函数主要是将goodFeaturesToTrack() 函数中提取的新的特征点n_pts加入到下一帧的关键点forw_pts中,注意这里将新添加的特征点的id都设为了-1, 而updataID() 函数在这之后将id从n_id开始赋值,而n_id是一个全局变量,从第一个特征点为1开始来一个特征点加一,因此是一个一直增长且不重复的整数,而我们特征点对应的id如果答应出来可以发现是一个乱序且不连续的数列,为什么呢?这可以结合到我们上面讲的setMask() 函数和 rejectWithF() 函数,因为他们分别对这个数列进行了排序和删除~

VINS的前端其实很简单的,其精华主要是在后端的优化的边缘化,前端就总结到这里啦,欢迎指正。

此外,对其他SLAM算法感兴趣的同学可以看考我的博客SLAM算法总结——经典SLAM算法框架总结

VINS-Mono关键知识点总结——前端详解相关推荐

  1. Pentium 4处理器架构/微架构/流水线 (7) - NetBurst前端详解 - 分支预测

    Branch Predication 对于使用深度指令流水线的处理器,分支预测能力至关重要.分支预测使得处理器可以在分支指令决断之前就开始执行(预测的)分支路径指令.分支延迟是由于分支预测错误导致的性 ...

  2. Pentium 4处理器架构/微架构/流水线 (6) - NetBurst前端详解 - 取指/译码/缓存

    Intel NetBurst Pipeline Details Front End Pipeline Details 本节讲述的关于流水线前端操作的详细信息可能会对软件在指令预取,分支预测以及执行跟踪 ...

  3. VINS-Mono关键知识点总结——预积分和后端优化IMU部分

    VINS-Mono关键知识点总结--预积分和后端优化IMU部分 VINS-Mono关键知识点总结--预积分和后端优化IMU部分 1. 预积分的理论推导 2. 预积分的代码分析 3. 后端优化IMU部分 ...

  4. 视觉SLAM总结——LSD SLAM中关键知识点总结

    视觉SLAM总结--LSD SLAM中关键知识点总结 视觉SLAM总结--LSD SLAM中关键知识点总结 1. LSD SLAM的创新点/关键点是什么? 2. LSD SLAM的整体框架是怎样的? ...

  5. 视觉SLAM总结——SVO中关键知识点总结

    视觉SLAM总结--SVO中关键知识点总结 视觉SLAM总结--SVO中关键知识点总结 1. 为什么叫半直接法视觉里程计? 2. SVO的优缺点是什么? 3. SVO的整体框架是怎样的? 4. 分别介 ...

  6. 跟我学 Java 8 新特性之 Stream 流(二)关键知识点

    转载自   跟我学 Java 8 新特性之 Stream 流(二)关键知识点 我们的第一篇文章,主要是通过一个Demo,让大家体验了一下使用流API的那种酣畅淋漓的感觉.如果你没有实践,我还是再次呼吁 ...

  7. 第一百五十期:Java程序员必备:异常的十个关键知识点

    总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油.异常是指阻止当前方法或作用域继续执行的问题.比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异常.  前言 总结了Jav ...

  8. Java程序员必备:异常的十个关键知识点

    前言 总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油. 一. 异常是什么 异常是指阻止当前方法或作用域继续执行的问题.比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异 ...

  9. 如何开启jvm日志_直通BAT必考题系列:JVM性能调优的6大步骤,及关键调优参数详解...

    JVM系列 直通BAT必考题系列:7种JVM垃圾收集器特点,优劣势.及使用场景 直通BAT必考题系列:JVM的4种垃圾回收算法.垃圾回收机制与总结 直通BAT必考题系列:深入详解JVM内存模型与JVM ...

最新文章

  1. 一文教你如何用Python预测股票价格,程序员学以致用
  2. 编程题:两个数比较大小,之后用变量t互换。
  3. boost::format模块测试格式与枚举一起使用
  4. hibernate配置
  5. 机器学习性能度量(2):错误接受率 (FAR), 错误拒绝率(FRR),EER计算方法,python实现
  6. 如何在SAP CRM WebClient UI里创建HANA Live Report
  7. out参数不用赋值?这么神奇吗!
  8. 单链表的尾插,头插,遍历,查找和插入
  9. RMAN备份学习笔记
  10. 全面升级,淘宝/天猫api接口大全
  11. Java设计文本编辑器
  12. 物联网建设,智慧城市道路智能交通解决方案
  13. 工商数据采集的10个经典方法
  14. 计算机毕业设计SSM大学生学科竞赛管理系统【附源码数据库】
  15. 罗永浩微博发声,锤子或再出新一代坚果手机?
  16. 古有穷书生街中弄笔,今有弄潮儿网上卖字
  17. 聚宝匯wap站测试工具版本号
  18. linux 下载jdk方式
  19. Web安全-口令破解
  20. geoCoordMap数据,全国省市,4个直辖市,用于echart gl 3d地图

热门文章

  1. leetcode的回溯算法题目用这个模板解题,一网打尽,so easy!!!
  2. 百度 71个炸天的开源项目,你知道几个?
  3. 关于本地mysql客户端如何连接阿里云服务器上的MySQL数据库
  4. Java对【JSON数据的解析】--官方解析法
  5. java使用ZipOutputStream对文件进行压缩
  6. 原型模式(ProtoType) - Java里的对象复制
  7. C语言 关于指针的格式化
  8. 计算机数值方法期末考试,《计算机数值方法》测试题二
  9. icem密度盒怎么设置_seo中关键词密度的问题
  10. django restful 请求_利用 Django REST framework 构建 RESTful Web API