本文作者是计算机视觉life公众号成员蔡量力,由于格式问题部分内容显示可能有问题,更好的阅读体验,请查看原文链接:代码解读 | VINS 视觉前端
在搞清楚VINS前端之前,首先要搞清楚什么是SLAM前端?
SLAM的前端、后端系统本身没有特别明确的划分,但是在实际研究中根据处理的先后顺序一般认为特征点提取和跟踪为前端部分,然后利用前端获取的数据进行优化、回环检测等操作,从而将优化、回环检测等作为后端。
而在VINSMONO中将视觉跟踪模块(featuretrackers)为其前端。在视觉跟踪模块中,首先,对于每一幅新图像,KLT稀疏光流算法对现有特征进行跟踪。然后,检测新的角点特征以保证每个图像特征的最小数目,并设置两个相邻特征之间像素的最小间隔来执行均匀的特征分布。接着,将二维特征点去畸变,然后在通过外点剔除后投影到一个单位球面上。最后,利用基本矩阵模型的RANSAC算法进行外点剔除。
VINS_MONO原文中还将关键帧的选取作为前端分,本文暂不讨论, 后续文章会详细介绍。
VINS-Mono将前端封装为一个ROS节点,该节点的实现在feature_tracker目录下的src中,src里共有3个头文件和3个源文件:
操作 |
话题 |
消息类型 |
功能 |
---|---|---|---|
Subscribe | image |
sensor_msgs::ImageConstPtr | 订阅原始图像,传给回调函数 |
Publish |
feature |
sensor_msgs::PointCloud |
跟踪的特征点,供后端优化使用 |
Publish |
feature_img | sensor_msgs::Image |
跟踪特征点图片,输出给RVIZ,调试用 |
函数 |
功能 |
---|---|
bool inBorder() |
判断跟踪的特征点是否在图像边界内 |
void reduceVector() |
去除无法跟踪的特征点 |
void FeatureTracker::setMask() |
对跟踪点进行排序并去除密集点 |
void FeatureTracker::addPoints() |
添将新检测到的特征点n_pts |
void FeatureTracker::readImage() |
对图像使用光流法进行特征点跟踪 |
void FeatureTracker::rejectWithF() |
利用F矩阵剔除外点 |
bool FeatureTracker::updateID() |
更新特征点id |
void FeatureTracker::readIntrinsicParameter() | 读取相机内参 |
void FeatureTracker::showUndistortion() |
显示去畸变矫正后的特征点 |
void FeatureTracker::undistortedPoints() |
对角点进行去畸变矫正,并计算每个角点的速度 |
//ros初始化和设置句柄
ros::init(argc, argv, "feature_tracker");
ros::NodeHandle n("~");
//设置logger的级别。 只有级别大于或等于level的日志记录消息才会得到处理。
ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Info);
//读取config->euroc->euroc_config.yaml中的一些配置参数
readParameters(n);
for (int i = 0; i < NUM_OF_CAM; i )
trackerData[i].readIntrinsicParameter(CAM_NAMES[i]);
ros::Subscriber sub_img = n.subscribe(IMAGE_TOPIC, 100, img_callback);
pub_img = n.advertise("feature", 1000);
pub_match = n.advertise("feature_img",1000);
pub_restart = n.advertise("restart",1000);
if(first_image_flag)
{
first_image_flag = false;
first_image_time = img_msg->header.stamp.toSec();//记录图像帧的时间
last_image_time = img_msg->header.stamp.toSec();
return;
}
if (img_msg->header.stamp.toSec() - last_image_time > 1.0 || img_msg->header.stamp.toSec() < last_image_time)
if (round(1.0 * pub_count / (img_msg->header.stamp.toSec() - first_image_time)) <= FREQ)
{
PUB_THIS_FRAME = true;
// 时间间隔内的发布频率十分接近设定频率时,更新时间间隔起始时刻,并将数据发布次数置0
if (abs(1.0 * pub_count / (img_msg->header.stamp.toSec() - first_image_time) - FREQ) < 0.01 * FREQ)
{
first_image_time = img_msg->header.stamp.toSec();
pub_count = 0;
}
}
else
PUB_THIS_FRAME = false;
for (unsigned int i = 0;; i )
{
bool completed = false;
for (int j = 0; j < NUM_OF_CAM; j )
if (j != 1 || !STEREO_TRACK)
completed |= trackerData[j].updateID(i);
if (!completed)
break;
}
pubimg.publish(featurepoints)
pub_match.publish(ptr->toImageMsg())
prev_img = cur_img = forw_img = img;//避免后面使用到这些数据时,它们是空的
cv::calcOpticalFlowPyrLK(cur_img, forw_img, cur_pts, forw_pts, status, err, cv::Size(21, 21), 3);
否(PUBTHISFRAME=0):当前帧 forw 的数据赋给上一帧 cur,然后在这一步就结束了。
是(PUBTHISFRAME=0):
void FeatureTracker::rejectWithF()
{
if (forw_pts.size() >= 8)
{
ROS_DEBUG("FM ransac begins");
TicToc t_f;
vector 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 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());
}
}
cv::goodFeaturesToTrack(forw_img, n_pts, MAX_CNT - forw_pts.size(), 0.01, MIN_DIST, mask);
如何从零开始系统化学习视觉SLAM?从零开始一起学习SLAM | 为什么要学SLAM?从零开始一起学习SLAM | 学习SLAM到底需要学什么?从零开始一起学习SLAM | SLAM有什么用?从零开始一起学习SLAM | C 新特性要不要学?从零开始一起学习SLAM | 为什么要用齐次坐标?从零开始一起学习SLAM | 三维空间刚体的旋转从零开始一起学习SLAM | 为啥需要李群与李代数?从零开始一起学习SLAM | 相机成像模型从零开始一起学习SLAM | 不推公式,如何真正理解对极约束?从零开始一起学习SLAM | 神奇的单应矩阵从零开始一起学习SLAM | 你好,点云从零开始一起学习SLAM | 给点云加个滤网从零开始一起学习SLAM | 点云平滑法线估计从零开始一起学习SLAM | 点云到网格的进化从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码从零开始一起学习SLAM | 掌握g2o顶点编程套路从零开始一起学习SLAM | 掌握g2o边的代码套路从零开始一起学习SLAM | 用四元数插值来对齐IMU和图像帧零基础小白,如何入门计算机视觉?SLAM领域牛人、牛实验室、牛研究成果梳理我用MATLAB撸了一个2D LiDAR SLAM可视化理解四元数,愿你不再掉头发最近一年语义SLAM有哪些代表性工作?视觉SLAM技术综述汇总 | VIO、激光SLAM相关论文分类集锦研究SLAM,对编程的要求有多高?2018年SLAM、三维视觉方向求职经验分享2018年SLAM、三维视觉方向求职经验分享深度学习遇到SLAM | 如何评价基于深度学习的DeepVO,VINet,VidLoc?AI资源对接需求汇总:第1期AI资源对接需求汇总:第2期AI资源对接需求汇总:第3期
计算机视觉是人工智能之眼。公众号已原创170篇文章,兼具系统性,严谨性,易读性,菜单栏点击“汇总分类”查看原创系列包括:三维视觉、视觉SLAM、深度学习、机器学习、深度相机、入门科普、CV方向简介、手机双摄、全景相机、相机标定、医学图像、前沿会议、机器人、ARVR、行业趋势等。同时有入门基础、项目实战、面试经验、教学资料等干货。一键关注星标,加技术交流群,一起进步。