SVO学习笔记(二)
SVO学习笔记(二)
- 这篇文章
- 稀疏图像对齐
- 地图点投影(地图与当前帧间的关系)
- reprojectMap
- reprojectPoint
- reprojectCell
- 特征点对齐中的非线性优化
- 结尾
这篇文章
这次仍是关于SVO系统的学习记录(冲冲冲!)。这次的主要内容是sparseimgalign、reproject和featurealign这三个代码文件。首先介绍第一个代码文件----稀疏图像对齐。
稀疏图像对齐
这部分代码是论文中“Sparse Model-based Image Alignment”部分的实现。它通过直接法在前后两帧中寻找匹配点,然后估计出两帧间的相对位姿变换矩阵(粗略估计)。这部分大家可以参考的这个博客。这篇博客对这部分代码讲解的十分详细且容易理解(向前辈学习!)。此处只记录一下run()的部分。
size_t SparseImgAlign::run(FramePtr ref_frame, FramePtr cur_frame)
{reset();if(ref_frame->fts_.empty()){SVO_WARN_STREAM("SparseImgAlign: no features to track!");return 0;}ref_frame_ = ref_frame;cur_frame_ = cur_frame;//参考帧中所有特征点的像素块(refpatches)。每一行对应一个特征点像素块内所有的像素ref_patch_cache_ = cv::Mat(ref_frame_->fts_.size(), patch_area_, CV_32F);//jacobian_cache_保存各像素的雅可比矩阵//使用resize能一次性给容器分配好空间,之后pushback就只是放入元素。这样能节约一定时间jacobian_cache_.resize(Eigen::NoChange, ref_patch_cache_.rows*patch_area_);//记录参考帧中哪些特征点被当前帧观测到了visible_fts_.resize(ref_patch_cache_.rows, false); SE3 T_cur_from_ref(cur_frame_->T_f_w_ * ref_frame_->T_f_w_.inverse());//先使用高层的金字塔图像进行位姿优化(粗略优化),然后逐步使用低层金字塔图像进行位姿优化//(在高层找到匹配点后,再去低层优化匹配点位置)for(level_=max_level_; level_>=min_level_; --level_){mu_ = 0.1;jacobian_cache_.setZero();have_ref_patch_cache_ = false;if(verbose_)printf("\nPYRAMID LEVEL %i\n---------------\n", level_);optimize(T_cur_from_ref);}cur_frame_->T_f_w_ = T_cur_from_ref * ref_frame_->T_f_w_;return n_meas_/patch_area_;
}
地图点投影(地图与当前帧间的关系)
这个文件是将地图中的地图点投影到当前帧中,然后建立这些地图点的最初观测帧(关键帧)与当前帧间的联系,以便实现论文中的特征点对齐部分的内容。主要介绍文件中较为重要的几个函数,首先是reprojectMap函数。该函数将地图中的点投影到当前帧中,帮助建立关键帧和当前帧间的联系。
reprojectMap
//将地图点投影到当前帧中,建立关键帧与当前帧的联系
void Reprojector::reprojectMap(FramePtr frame,//当前帧//容器保存的是关键帧的指针和其与当前帧的共视程度std::vector< std::pair<FramePtr,std::size_t> >& overlap_kfs)//关联上的关键帧
{resetGrid();SVO_START_TIMER("reproject_kfs");list< pair<FramePtr,double> > close_kfs;//获得与当前frame共视的KFsmap_.getCloseKeyframes(frame, close_kfs);//那五个特殊点派上了用场//根据KF与frame的距离对共视的KFs进行排序close_kfs.sort(boost::bind(&std::pair<FramePtr, double>::second, _1) <boost::bind(&std::pair<FramePtr, double>::second, _2));size_t n = 0;//只取最前面几个KFsoverlap_kfs.reserve(options_.max_n_kfs);//统计与当前帧有共视的KFs,以及每个KF与当前frame有多少共视的地图点for(auto it_frame=close_kfs.begin(), ite_frame=close_kfs.end();it_frame!=ite_frame && n<options_.max_n_kfs; ++it_frame, ++n)//遍历距离近的所有KFs{FramePtr ref_frame = it_frame->first;//pair(与当前frame有共视部分的KF,共视的特征点的数量)overlap_kfs.push_back(pair<FramePtr,size_t>(ref_frame,0));for(auto it_ftr=ref_frame->fts_.begin(), ite_ftr=ref_frame->fts_.end();it_ftr!=ite_ftr; ++it_ftr)//遍历KF中所有特征点(主要是看特征点对应的地图点是否在frame上可见){if((*it_ftr)->point == NULL)continue;//防止对同一个地图点进行重复操作if((*it_ftr)->point->last_projected_kf_id_ == frame->id_)continue;(*it_ftr)->point->last_projected_kf_id_ = frame->id_;if(reprojectPoint(frame, (*it_ftr)->point))//投影判断地图点是否在能在当前帧中overlap_kfs.back().second++;//共视点加1}}SVO_STOP_TIMER("reproject_kfs");SVO_START_TIMER("reproject_candidates");{boost::unique_lock<boost::mutex> lock(map_.point_candidates_.mut_);auto it=map_.point_candidates_.candidates_.begin();//遍历地图中所有候选地图点,并统计地图点投影失败的次数。失败多的就从地图中删除。//剔除掉地图中的坏点while(it!=map_.point_candidates_.candidates_.end()){if(!reprojectPoint(frame, it->first))//看候选地图点是否在frame中{it->first->n_failed_reproj_ += 3;if(it->first->n_failed_reproj_ > 30){map_.point_candidates_.deleteCandidate(*it);it = map_.point_candidates_.candidates_.erase(it);continue;}}++it;}} // unlock the mutex when out of scopeSVO_STOP_TIMER("reproject_candidates");SVO_START_TIMER("feature_align");//grid_:在reprojectPoint函数中配置好的,记录某地图点在当前图像中哪一个网格内for(size_t i=0; i<grid_.cells.size(); ++i){// we prefer good quality points over unkown quality (more likely to match)// and unknown quality over candidates (position not optimized//使每个当前帧中的网格内只保留一个3D-2D匹配点对if(reprojectCell(*grid_.cells.at(grid_.cell_order[i]), frame))++n_matches_;if(n_matches_ > (size_t) Config::maxFts())break;}SVO_STOP_TIMER("feature_align");
}
上面的代码将地图点投影到当前帧中,为地图点在当前帧中找到了更好的匹配点,并建立相应关键帧与当前帧之间的关系。代码中要注意的两个函数是:1)reprojectPoint:判断地图点是否在当前帧中,并记录地图点投影在图像的哪个网格内;2)reprojectCell:保证每个网格内只保留一个地图点以及它的匹配点。这写操作能控制匹配点对的分布,同时减少计算量。下面来介绍这两个函数。
reprojectPoint
//判断某一个地图点是否在当前帧中
//如果在,则将该地图点分配到对应的网格中,并保存3D-2D匹配点对。
//此时网格中的所有3D-2D点对都只是最粗略的匹配,需要进一步优化匹配结果
bool Reprojector::reprojectPoint(FramePtr frame, Point* point)
{Vector2d px(frame->w2c(point->pos_));// 保证特征点以及它周围的像素块(搜索匹配时要用)也能够在frame的图像范围内if(frame->cam_->isInFrame(px.cast<int>(), 8)) {const int k = static_cast<int>(px[1]/grid_.cell_size)*grid_.grid_n_cols+ static_cast<int>(px[0]/grid_.cell_size);grid_.cells.at(k)->push_back(Candidate(point, px));return true;}return false;
}
reprojectCell
/*函数所需参数:cell--网格对象frame--当前帧
功能:优化网格中的地图点与像素点(3D-2D点对)的匹配关系,并保证每个网格内只有一对匹配点对。
*/
bool Reprojector::reprojectCell(Cell& cell, FramePtr frame)
{//按照点的好坏程度进行排序cell.sort(boost::bind(&Reprojector::pointQualityComparator, _1, _2));Cell::iterator it=cell.begin();//遍历cell中的Candidates类对象(3D-2D点对)while(it!=cell.end()){++n_trials_;if(it->pt->type_ == Point::TYPE_DELETED)//剔除坏点的匹配{it = cell.erase(it);continue;}bool found_match = true;if(options_.find_match_direct)//为地图点在网格中寻找更准确的匹配点(使用非线性优化的方式)found_match = matcher_.findMatchDirect(*it->pt, *frame, it->px);if(!found_match)//如果没找到合适的匹配,则给该地图点添加一次投影失败的标记{it->pt->n_failed_reproj_++;//地图点投影失败的次数过多时,就会被删除掉if(it->pt->type_ == Point::TYPE_UNKNOWN && it->pt->n_failed_reproj_ > 15)map_.safeDeletePoint(it->pt);if(it->pt->type_ == Point::TYPE_CANDIDATE && it->pt->n_failed_reproj_ > 30)map_.point_candidates_.deleteCandidatePoint(it->pt);it = cell.erase(it);continue;}//更新地图点的状态。可以提高地图中的好点数it->pt->n_succeeded_reproj_++;if(it->pt->type_ == Point::TYPE_UNKNOWN && it->pt->n_succeeded_reproj_ > 10)it->pt->type_ = Point::TYPE_GOOD;//给frame增加新的特征点Feature* new_feature = new Feature(frame.get(), it->px, matcher_.search_level_);frame->addFeature(new_feature);//new_feature->point = it->pt;//如果是边缘类特征,需要进行一些设置if(matcher_.ref_ftr_->type == Feature::EDGELET){new_feature->type = Feature::EDGELET;new_feature->grad = matcher_.A_cur_ref_*matcher_.ref_ftr_->grad;new_feature->grad.normalize();}it = cell.erase(it);//网格中找出一对精确的匹配点对,就不再关心网格中的剩余部分点对return true;}return false;
}
特征点对齐中的非线性优化
这部分被featurematcher文件所调用。这里介绍align2D函数中迭代求解的步骤,它是针对点特征的求解方法。函数中迭代求解的最小二乘问题是根据Inverse Compositional Image Alignment算法(我愿称它为逆向光流法)构建的。对这个算法的介绍可以参考这个博客。
//点特征的非线性优化搜索匹配
bool align2D(const cv::Mat& cur_img,//当前帧图像uint8_t* ref_patch_with_border,//参考帧中的像素块(带边框)uint8_t* ref_patch,//不带边框const int n_iter,//迭代次数Vector2d& cur_px_estimate,bool no_simd)
{
//使用的是8X8的patchconst int halfpatch_size_ = 4;const int patch_size_ = 8;const int patch_area_ = 64;bool converged=false;//__attribute是C语言中的字节对齐操作,用来提高访问数据的效率float __attribute__((__aligned__(16))) ref_patch_dx[patch_area_];//保存patch中每个点在x、y方向上的梯度float __attribute__((__aligned__(16))) ref_patch_dy[patch_area_];Matrix3f H; H.setZero();//加2是因为这里处理的patch是带边框的const int ref_step = patch_size_+2;float* it_dx = ref_patch_dx;float* it_dy = ref_patch_dy;//论文中计算的是特征点那个框框中的光度误差,所以要对patch中每个点都进行误差计算//这里所求解的最小二乘问题由反向LK光流法构建。for(int y=0; y<patch_size_; ++y){//获得带边界Patch中的每行头指针uint8_t* it = ref_patch_with_border + (y+1)*ref_step + 1;for(int x=0; x<patch_size_; ++x, ++it, ++it_dx, ++it_dy)//{Vector3f J;//用差分来计算x、y方向上的光度值的导数(离散)J[0] = 0.5 * (it[1] - it[-1]);//dxJ[1] = 0.5 * (it[ref_step] - it[-ref_step]);//dyJ[2] = 1;*it_dx = J[0];*it_dy = J[1];//组成Hession矩阵H += J*J.transpose();}}Matrix3f Hinv = H.inverse();float mean_diff = 0;float u = cur_px_estimate.x();float v = cur_px_estimate.y();const float min_update_squared = 0.03*0.03;const int cur_step = cur_img.step.p[0];Vector3f update; update.setZero();//使用高斯牛顿法迭代for(int iter = 0; iter<n_iter; ++iter){int u_r = floor(u);int v_r = floor(v);if(u_r < halfpatch_size_ || v_r < halfpatch_size_ || u_r >= cur_img.cols-halfpatch_size_ || v_r >= cur_img.rows-halfpatch_size_)break;if(isnan(u) || isnan(v)) // TODO very rarely this can happen, maybe H is singular? should not be at corner.. checkreturn false;//亚像素float subpix_x = u-u_r;float subpix_y = v-v_r;//用于中间插值。更新时的像素坐标也许不是个整型数,所以需要通过其周围的像素光度来计算该坐标下的光度值float wTL = (1.0-subpix_x)*(1.0-subpix_y);float wTR = subpix_x * (1.0-subpix_y);float wBL = (1.0-subpix_x)*subpix_y;float wBR = subpix_x * subpix_y;uint8_t* it_ref = ref_patch;float* it_ref_dx = ref_patch_dx;float* it_ref_dy = ref_patch_dy;Vector3f Jres; Jres.setZero();for(int y=0; y<patch_size_; ++y){//获得匹配特征点在cur的patch中各元素的光度值uint8_t* it = (uint8_t*) cur_img.data + (v_r+y-halfpatch_size_)*cur_step + u_r-halfpatch_size_;for(int x=0; x<patch_size_; ++x, ++it, ++it_ref, ++it_ref_dx, ++it_ref_dy){float search_pixel = wTL*it[0] + wTR*it[1] + wBL*it[cur_step] + wBR*it[cur_step+1];float res = search_pixel - *it_ref + mean_diff;//雅可比矩阵乘以误差Jres[0] -= res*(*it_ref_dx);Jres[1] -= res*(*it_ref_dy);Jres[2] -= res;}}//计算坐标的更新量update = Hinv * Jres;u += update[0];v += update[1];mean_diff += update[2];//坐标更新量很小时,就不再优化if(update[0]*update[0]+update[1]*update[1] < min_update_squared){converged=true;break;}}cur_px_estimate << u, v;return converged;
}
//省略了代码中的一些输出项
结尾
以上就是整篇博客的全部内容。这篇博客介绍了SVO论文中的“Sparse Model-based Image Alignment”部分、“Relaxation Through Feature Alignment”部分中的重投影操作和迭代优化求解的实现。之后计划写有关SVO的深度滤波等部分内容,也有会写一下自己对于逆向光流法的理解(冲冲冲!)。
相关文章:

用JDBC写一个学生管理系统(添加、删除、修改、查询学生信息)
首先需要用Navicat Premium创建一个student表 用Java连接好MySQL数据库(需要copy一个mysql-connector-java-5.1.44-bin.jar包,该包可在网上找到)后,我们开始用Java写一个学生管理系统: 我们首选需要定义好添加、删除、…

tensorflow在训练和验证时监视不同的summary的操作
如果想在训练和验证时监视不同的summary,将train summary ops和val summary ops放进不同的集合中即可。 train_writer tf.summary.FileWriter(log_dir /train, sess.graph) val_writer tf.summary.FileWriter(log_dir /val, sess.graph)# 假设train_loss和val_l…

Anaconda3 离线安装和配置 Django-3.2.7 使用 MySQL-5.7 数据库
Django文档 Settings / Core Settings / DATABASES 一节阐述了django与数据库交互配置的内容。 先在 MySQL 5.7 版本数据库中创建一个名为 learning_log_db 的数据库,和名为 myuser 的用户,并分配权限。 create databases learning_log_db; create use…

用JDBC写一个学生管理系统(添加、删除、修改、查询学生信息)(二)
本文上接用JDBC写一个学生管理系统(添加、删除、修改、查询学生信息) 这次主要是对上一文中的查询方法做一下调整,用创建内部类的方法来实现学生信息的查询。 我们先要定义一个接口IRowMapper: import java.sql.ResultSet;public…

(原)ubuntu中使用conda安装tensorflow-gpu
转载请注明出处: https://www.cnblogs.com/darkknightzh/p/9834567.html 参考网址: https://www.anaconda.com/blog/developer-blog/tensorflow-in-anaconda/ 之前的一篇,直接安装tensorflow的: https://www.cnblogs.com/darkknig…
SVO中 Inverse Compositional Image Alignment方法的学习笔记
SVO中 Inverse Compositional Image Alignment方法的学习笔记这篇文章光流法简介逆向光流法结尾这篇文章 在SVO系统中的"Relaxation Through Feature Alignment"部分使用的是一种特别的LK光流法:the inverse compositional Lucas-Kanade algorithm&#x…

Head First设计模式之目录
只有沉淀、积累,才能远航;沉沉浮浮,脚踏实地。 这本书已经闲置了好久,心血来潮,决定写个目录,让自己坚持看完这本书 创建型模式 抽象工厂模式(Abstract factory pattern): 提供一个接口, 用于创建相关或依赖…

HANA 数据库备份hang住的解决办法
今天遇到 HANA 数据库备份hang住的情况。经过查 SAP NOTE 解决,记录一下过程。两个NOTE如下: 2452735 - HANA Backup failing with "[447]: backup could not be completed: [110122] A data backup cannot be created because another data backu…

简单DP【p2642】双子序列最大和
Description 给定一个长度为n的整数序列,要求从中选出两个连续子序列,使得这两个连续子序列的序列和之和最大,最终只需输出最大和。一个连续子序列的和为该子序列中所有数之和。每个连续子序列的最小长度为1,并且两个连续子序列之…

JDBC工具类
本文主要是将JDBC最基础的增删改查的工具类的代码详细的罗列出来: 一、我们先来看一看项目结构: 二、配置JDBC工具类 1.我们先处理异常 我们知道几乎不可能一次性就写出完美的代码,都是要经过很多次的调试才行,那在调试过程中就难免会出现…

SVO 学习笔记(三)
SVO 学习笔记(三)这篇博客InitializationFrame_Handler_MonoprocessFirstFrameprocessSecondFrameprocessFramerelocalizeFrame结尾这篇博客 这篇博客将介绍SVO源代码中的frame_handler_mono、initialization两个文件的代码流程。前者是SVO系统类&#x…

CMAKE设置INSTALL工程,分别设置头文件、Lib和DLL的输出路径
使用CMAKE管理工程,可以设置工程中的INSTALL项目运行时安装的路径,使用命令:install。 可以简单的设置安装文件的路径和文件夹: set(head_files//要安装的头文件 ) install(TARGETS ${head_files} DESTINATION ${CMAKE_BINARY_DI…

2021年中国工业互联网安全大赛核能行业赛道writeup之hacker
附加题 hacker,题目描述:hacker,附件下载 hackerhttps://download.csdn.net/download/qpeity/33230528解压缩得到一个EXE文件 ARE_YOU_SDPD.exe,在一个文件夹下运行看一下。 用 IDA 反汇编一下,发现找不到程序入口&am…

利用人工智能(Magpie开源库)给一段中文的文本内容进行分类打标签
当下人工智能是真心的火热呀,各种原来传统的业务也都在尝试用人工智能技术来处理,以此来节省人工成本,提高生产效率。既然有这么火的利器,那么我们就先来简单认识下什么是人工智能吧,人工智能是指利用语音识别、语义理…

动态环境下的SLAM:DynaSLAM 论文学习笔记
动态环境下的SLAM:DynaSLAM 论文学习笔记这篇文章论文摘要系统流程相关环节的实现方法神经网络检测图中动态物体(Mask R-CNN)Low-Cost Tracking使用多视图几何的方法检测图中动态物体(Multi-view Geometry)跟踪与建图&…

用C语言编写:判断一个≥2的整型数是否存在于斐波那契数列中?
自己写的,感觉挺有成就感的,就展示出来吧! 判断一个≥2的整型数是否存在于斐波那契数列中? 若存在,则返回第几项;若不在,则返回-1 #include <stdio.h> long generate(long n);//函数声…

团队作业8——第二次项目冲刺(Beta阶段)--第六天
会议照片: 燃尽图: 项目进展: 练习模式能够给出正确的答案,部分模块正在正在测试。 团队贡献比: 队员 角色 团队贡献比 陈麟凤 PM 17% 张志杰 DEV 18% 黄海鸿 TEST 16% 康建灿 TEST 16% 许明涛 DEV…

2021年中国工业互联网安全大赛核能行业赛道writeup之隐写
附件题:隐写 题目描述:隐写 附件下载: 2021-10-12T15_44_19.17491400_00scene.jpg.zip-网络攻防文档类资源-CSDN下载 先用 010Editor 查看这个图片,能直接看到图片的头部是否完整正常,能直接看到是否隐藏了fla…

SVO 学习笔记(深度滤波)
SVO 学习笔记(深度滤波)这篇博客论文中的深度滤波深度滤波的代码流程更新Seed对象初始化Seed对象结尾这篇博客 这篇博客将介绍SVO论文中的Mapping部分,主要介绍深度滤波器在获取新的图像帧后,更新相应地图点深度的过程。ÿ…

寻找孪生素数(当p为素数时,p+2也为素数)
数学家希尔伯特在1900年国际数学家大会的报告上提出一个“孪生素数猜想”,即: 存在无穷多个素数p,使得p 2是素数。p和p2这一对差为2的素数,被称为“孪生素数”。 看起来,这个猜想是成立的,我们总能找到很多…

C++利用cin输入时检测回车的方法
今天做TJU的OJ ,其中一道题是先读入一个字符串,再读入一个整数,循环往复,直到字符串是空,也就是说回车键结束循环。 但是cin对空格和回车都不敏感,都不影响继续读入数据,所以需要一种新的方式检…

使用grep过滤make的输出内容
make的输出内容其实分为两种,有些是到标准输出,有些是到标准错误,由于标准输出和标准错误默认都是屏幕,所以平时区分不出来, 实际上一般是error和warning信息到标准错误,其余的到标准输出。 如果要过滤erro…

2021年中国工业互联网安全大赛核能行业赛道writeup之机房密码
附件题:机房密码 题目描述: (具体描述忘记了) 经过黑客人员的不屑努力,在上位机上发现了登录密码的一半信息,剩下的一半要靠你们继续努力辣!!! ZmxhZyU3Qmgwd19hX0M 附件…

ORB-SLAM2系统的实时点云地图构建
ORB-SLAM2系统的实时点云地图构建这篇博客点云地图构建的流程代码介绍点云地图构建类对象小调整获取关键帧点云地图构建与叠加在地图中设置当前相机位置点云地图到Octomap的转换地图效果结尾这篇博客 (PS:修改于2020-9-21,添加了关于System和Tracking类…

使用maven导入jar包
我们都经历过自己写代码时有时就要引用一些第三方的jar包,这个我们都会,但在公司里进行团队开发时,是不允许我们自己导入jar包的,是由项目组长之类的统一导入jar包,我们在这里来了解一下这个过程: a、先创建…

Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法...
Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法 www.MyException.Cn 发布于:2012-09-15 19:09:28 浏览:164次0Struts2中action接收参数的三种方法及ModelDriven和Preparable接口结合JAVA反射机制的灵活…

关于CSS的长度单位及颜色表示
长度单位 1.q 1/4mm. 2.px 计算机语言中的像素。大多数网页制作常用图片分辨率为72,即每英寸像素为72,1英寸等于2.54cm。那么通过换算可以得出每厘米等于28像素。 3.em 它是描述相对于应用在当前元素的字体尺寸,所以它也是相对长度单位。一班浏览器字体大…

2021年中国工业互联网安全大赛核能行业赛道writeup之鱿鱼游戏
目录 一、尝试 二、Writeup 附加题 鱿鱼游戏(来自最近一部很火的韩剧) 题目描述: 小王由于操作不规范,误将不明U盘插入到上位机中,导致上位机中的某些关键文件被加密,但攻击者在U盘中还留下了一个可执行…

视觉惯性SLAM: VI ORB-SLAM
视觉惯性SLAM: VI ORB-SLAM这篇博客视觉惯性SLAM预备知识符号说明:相机投影变换矩阵IMU数据更新方程IMU数据的预积分VI ORB-SLAM各环节工作方式InitializationTrackingLocalMappingLoop ClosingFull BAIMU初始化估计bgb_{g}bg估计尺度sss和重力向量gWg_{W}gW&am…

AEC、AGC、ANS在视音频会议中的作用?
AGC是自动增益补偿功能(Automatic Gain Control),AGC可以自动调麦克风的收音量,使与会者收到一定的音量水平,不会因发言者与麦克风的距离改变时,声音有忽大忽小声的缺点。ANS是背景噪音抑制功能(…