SVO 学习笔记(三)
SVO 学习笔记(三)
- 这篇博客
- Initialization
- Frame_Handler_Mono
- processFirstFrame
- processSecondFrame
- processFrame
- relocalizeFrame
- 结尾
这篇博客
这篇博客将介绍SVO源代码中的frame_handler_mono、initialization两个文件的代码流程。前者是SVO系统类,展示了整个系统的执行过程;后者则是用于系统初始化的文件。首先介绍initialization部分代码。
Initialization
初始化的主要任务是使用光流法完成前几帧的跟踪工作,设置第一、第二关键帧。addFirstFrame函数处理获得的第一帧,并提取其中特征点。如果特征点数量足够,就将这一帧看作第一关键帧。
addSecondFrame函数用于判断能否构建第二个关键帧。它的步骤为:
1、获得当前帧,对当前帧使用光流法:
trackKlt(frame_ref_, frame_cur, px_ref_, px_cur_, f_ref_, f_cur_, disparities_);
完成和参考帧的匹配(因为这时还没有地图点,所以那些图像对齐之类的方法还不能用)。其中disparities_保存的是各匹配点所对应的视差。
2、double disparity = vk::getMedian(disparities_);
if(disparity < Config::initMinDisparity())
return NO_KEYFRAME;
判断视差大小是否满足要求(视差足够大时才会把当前帧设为第二关键帧)。
3、若当前帧被选为第二关键帧,则用computeHomography计算两关键帧的单应矩阵,根据内点数量判断是否跟踪成功。然后构建内点对应的3D地图点。
4、跟踪成功后,计算地图点在当前帧中的深度中值。用这个中值表示场景的尺度(因为是单目,所以需要计算尺度)。然后设置当前帧的位姿,要考虑尺度的影响:
frame_cur->T_f_w_ = T_cur_from_ref_ * frame_ref_->T_f_w_;
旋转不用考虑地图尺度问题,平移需要考虑:
frame_cur->T_f_w_.translation() =-frame_cur->T_f_w_.rotation_matrix()*(frame_ref_->pos() + scale*(frame_cur->pos() - frame_ref_->pos()));
5、最后将当前帧看到的地图点转换到世界坐标系中,作为第一代地图点,并将前两个关键帧与这些地图点关联起来。
Frame_Handler_Mono
主要介绍这个文件中的processFirstFrame、processSecondFrame、 processFrame和relocalizeFrame这四个函数。它们代表了这样的流程:
(获取第一关键帧) > (获取第二关键帧,完成地图初始化) > (处理之后关键帧,完成跟踪任务) > (跟踪丢失后进行重定位)
这个流程在该文件中的addImage()函数(处理每次获得的图像)中进行。下面介绍这四个函数代码。
processFirstFrame
这个函数用来处理最开始的图像,直到获得第一个关键帧为止。
//处理获得的第一个帧,并判断该帧能否成为关键帧
//这个会一直使用到第一个关键帧构建了为止(即函数中klt...中addFirstFrame成功)
//返回结果有三种:当前帧是KF,不是KF,结果无效
FrameHandlerMono::UpdateResult FrameHandlerMono::processFirstFrame()
{//设置当前帧的初始位姿为单位变换矩阵new_frame_->T_f_w_ = SE3(Matrix3d::Identity(), Vector3d::Zero());//将当前帧添加到初始化类中,看其是否能够作为第一关键帧if(klt_homography_init_.addFirstFrame(new_frame_) == initialization::FAILURE)return RESULT_NO_KEYFRAME;new_frame_->setKeyframe();//将当前帧设置为关键帧map_.addKeyframe(new_frame_);//更新系统的状态,以便下次使用其他帧处理函数stage_ = STAGE_SECOND_FRAME;SVO_INFO_STREAM("Init: Selected first frame.");return RESULT_IS_KEYFRAME;
}
processSecondFrame
这个函数在第一关键帧出现后开始使用,一直用到出现第二关键帧为止
//返回结果有三种:结果是KF,不是KF,结果无效
FrameHandlerBase::UpdateResult FrameHandlerMono::processSecondFrame()
{
//klt...是用于系统初始化的类对象initialization::InitResult res = klt_homography_init_.addSecondFrame(new_frame_);if(res == initialization::FAILURE)//使用两帧初始化失败return RESULT_FAILURE;else if(res == initialization::NO_KEYFRAME)//第二帧不能作为关键帧return RESULT_NO_KEYFRAME;//条件编译:如果需要使用BA,就用BA优化头两个关键帧的位姿#ifdef USE_BUNDLE_ADJUSTMENTba::twoViewBA(new_frame_.get(), map_.lastKeyframe().get(), Config::lobaThresh(), &map_);#endifnew_frame_->setKeyframe();double depth_mean, depth_min;//此时第一代地图点以由前两个关键帧构建了//获得所有地图点在当前帧中的平均、最小深度值frame_utils::getSceneDepth(*new_frame_, depth_mean, depth_min);//将当前这个新的关键帧添加到深度滤波器中depth_filter_->addKeyframe(new_frame_, depth_mean, 0.5*depth_min);// add frame to mapmap_.addKeyframe(new_frame_);//更新系统状态stage_ = STAGE_DEFAULT_FRAME;klt_homography_init_.reset();SVO_INFO_STREAM("Init: Selected second frame, triangulated initial map.");return RESULT_IS_KEYFRAME;
}
processFrame
在成功添加了两个关键帧之后,使用该函数处理后续的帧,也就是论文中描述的那几个部分。它的大致步骤为:
1、先用上一帧的位姿作为当前帧的初始位姿:
new_frame_->T_f_w_ = last_frame_->T_f_w_;
2、进行稀疏图像对齐:
size_t img_align_n_tracked = img_align.run(last_frame_, new_frame_);
。
3、将地图点重投影到当前帧中,获得更加精确的特征匹配(关键帧和当前帧的特征匹配)。
reprojector_.reprojectMap(new_frame_, overlap_kfs_);
如果匹配特征的数量较少,那么就将当前帧的位姿设置为上一帧的位姿,并设置跟踪失败,直接结束函数。
(PS:上面这两步的代码解释可参考笔记(二))
4、如果第3步成了,就对当前帧的估计位姿进行优化,通过最小化重投影误差实现。先是只优化位姿,也就是所谓的“motion-only BA“。
pose_optimizer::optimizeGaussNewton(Config::poseOptimThresh(), Config::poseOptimNumIter(), false,new_frame_, sfba_thresh, sfba_error_init, sfba_error_final, sfba_n_edges_final);
5、之后就是结构优化,即优化地图点的坐标(structure-only BA),也是根据最小化重投影误差实现。这部分只优化地图点。
optimizeStructure(new_frame_, Config::structureOptimMaxPts(), Config::structureOptimNumIter());
(PS:对位姿和地图点分别执行优化,能很大地减少优化时间。)
6、完成位姿和结构性优化后,开始选择关键帧。先进行一个跟踪效果判断:
core_kfs_.insert(new_frame_);setTrackingQuality(sfba_n_edges_final);//跟踪效果判断if(tracking_quality_ == TRACKING_INSUFFICIENT){//跟踪得不好,就返回“跟踪失败”//个人认为这样设置是为了方便在重定位的时候找参考关键帧,因为之后newframe的成员会给到lastframe中//然后使用与lastframe距离最近的一部分关键帧来实现重定位new_frame_->T_f_w_ = last_frame_->T_f_w_; // reset to avoid crazy pose jumpsreturn RESULT_FAILURE;}
跟踪效果不错时,对当前帧进行关键帧的判断:
//关键帧的判断double depth_mean, depth_min;//获得当前帧中所有地图点深度的平均和最小值frame_utils::getSceneDepth(*new_frame_, depth_mean, depth_min);//根据两个条件判断当前帧能否做关键帧://1、跟踪的质量好//2、当前帧距离其他共视帧的距离远//第二条由neddNewKF决定。这个函数由将共视关键帧的位置坐标投影到当前帧坐标系,并通过距离判断是否加入需要关键帧if(!needNewKf(depth_mean) || tracking_quality_ == TRACKING_BAD){//虽然不能做关键帧,但还是能用于更新深度滤波器中点的深度depth_filter_->addFrame(new_frame_);return RESULT_NO_KEYFRAME;}
7、如果当前帧被设置为关键帧,则增加相应地图点的观测次数和观测对象。
for(Features::iterator it=new_frame_->fts_.begin(); it!=new_frame_->fts_.end(); ++it)if((*it)->point != NULL)(*it)->point->addFrameRef(*it);map_.point_candidates_.addCandidatePointToFrame(new_frame_);
8、最后将当前帧作为关键帧添加到深度滤波器中。如果地图和深度滤波器中的关键帧数量过多,则剔除掉离新关键帧最远的关键帧(这一步之前有个条件编译:如果定义了USE_BUNDLE_ADJUSTMENT,则使用BA优化当前帧位姿):
// init new depth-filters.添加新的关键帧能帮助滤波器产生新的地图点,以及优化已有地图点的深度信息depth_filter_->addKeyframe(new_frame_, depth_mean, 0.5*depth_min);// if limited number of keyframes, remove the one furthest apartif(Config::maxNKfs() > 2 && map_.size() >= Config::maxNKfs()){//获得离新关键帧最远的关键帧,并将其从深度滤波器和地图中剔除FramePtr furthest_frame = map_.getFurthestKeyframe(new_frame_->pos());depth_filter_->removeKeyframe(furthest_frame); map_.safeDeleteFrame(furthest_frame);}map_.addKeyframe(new_frame_);
至此整个函数结束,当前帧处理完毕,继续等待下一个图像的到来。
relocalizeFrame
//以关键帧作为参考,完成重定位操作
//使用的是与lastframe距离最近的关键帧最为参考关键帧
FrameHandlerMono::UpdateResult FrameHandlerMono::relocalizeFrame(const SE3& T_cur_ref,FramePtr ref_keyframe)
{SVO_WARN_STREAM_THROTTLE(1.0, "Relocalizing frame");if(ref_keyframe == nullptr){SVO_INFO_STREAM("No reference keyframe.");return RESULT_FAILURE;}SparseImgAlign img_align(Config::kltMaxLevel(), Config::kltMinLevel(),30, SparseImgAlign::GaussNewton, false, false);size_t img_align_n_tracked = img_align.run(ref_keyframe, new_frame_);//若匹配上的特征个数超过30则认为重定位成功if(img_align_n_tracked > 30){SE3 T_f_w_last = last_frame_->T_f_w_;//用参考关键帧作为上一帧,以便进行processFrame()last_frame_ = ref_keyframe;//用关键帧做参考,进行定位操作FrameHandlerMono::UpdateResult res = processFrame();if(res != RESULT_FAILURE){stage_ = STAGE_DEFAULT_FRAME;SVO_INFO_STREAM("Relocalization successful.");}else//重定位失败,用上一帧位姿作为当前帧位姿,以便继续重定位new_frame_->T_f_w_ = T_f_w_last; // reset to last well localized pose//(其实之后很大概率还是使用这个参考关键帧作为重定位的参考)return res;}return RESULT_FAILURE;
}
结尾
总算是将SVO的代码笔记延续到了第三期,可喜可贺X3。感谢屏幕前的你耐心地看完,也感谢自己坚持了下来。如果文中有什么不足,劳烦各位博友指出,十分感谢。
相关文章:

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是背景噪音抑制功能(…

Java中的拆箱与装箱
我们先来了解一下拆箱与装箱的概念: 装箱:将基本数据类型转换为包装类; 拆箱:将包装类转换为基本数据类型 我们来看两串代码: Integer b1 127;Integer b2 127;System.out.println(b1b2);//trueInteger b3 128;Inte…

WannaCry的UWP版,哈哈哈
转载于:https://www.cnblogs.com/R00R/p/6916731.html

2021年中国工业互联网安全大赛核能行业赛道writeup之数据库登录
附件题:数据库登录(一道MISC、流量分析类型题目) 题目描述: 具体描述已经忘记o(╯□╰)o 大概意思就是分析附件里的.pcapng包,找到flag。流量涉及到 MySQL 数据库了。 附件下载: https://download.csdn.n…

学生管理系统(用maven来导入jar包)
不废话,直接上 先看一下项目列表: 首先创建一个maven工程,然后导入相应的jar包,请参考:使用maven导入jar包 接着在Source Folder创建具体的项目: Main类(客户端) package com.z…

视觉惯性SLAM:VINS-Mono
视觉惯性SLAM:VINS-Mono这篇博客一些符号说明IV 测量数据的预处理A.视觉处理前端B.IMU预积分V. 初始化A.Vision-Only SfM in Sliding WindowB.Visual-Inertial AlignmentVI.TIGHTLY COUPLED MONOCULAR VIOA.公式介绍B.IMU误差C.视觉误差D.边缘化E.位姿优化F.以IMU采…

mysql 0x80004005 unable to connect to any of the specified mysql hosts
语言:c# 问题:偶尔会出现连不上mysql 报标题的这个错误。 解决方法:把server localhost 改为 127.0.0.1 或者静态IP ,按着改暂时没出现了,继续观望! 转载于:https://www.cnblogs.com/wdw31210/p/9857514…
iOS开发-自己定义重用机制给ScrollerView加入子视图
iOS开发-自己定义重用机制给ScrollerView加入子视图 事实上这个问题我非常早就想过,仅仅是没有通过去写程序实现,昨天有人提起,我就巧了一下 不知道大家打印郭tableview:cellforrow中cell初始的次数,也就是重用池中的c…

2021年中国工业互联网安全大赛核能行业赛道writeup之Webshell密码
附件题:Webshell密码 题目描述: 某次攻防演练中,抓到了一个webshell的流量,请分析出密码,flag形式:flag{密码} 附件下载: https://download.csdn.net/download/qpeity/33675356https://downlo…

关于python3与python2同时存在情况下导入pyqt失败解决记录
最近感觉tkinter功能还是比较不适合新手做出高大上的界面,故开始使用pyqt,通过pip安装好了之后,利用qt设计师设计好界面之后,cmd运行之,报错提示没有找到pyqt5模块,IDE运行能正常加载 查找资料后发现&#…

ORBSLAM-Altas:多地图SLAM
ORBSLAM-Atlas:多地图SLAM这篇博客ORBSLAM-Altas这个系统系统方法两类子地图新地图的构建相机位姿的可观测性子地图融合系统线程结尾这篇博客 最近ORB-SLAM3横空出世,马上跑去GitHub膜拜。然后在项目的相关工作中看到了ORB-SLAM3使用了一个多地图方法。这…

Android驱动学习-内部机制_回顾binder框架关键点
内部机制_回顾binder框架关键点server注册服务时, 对每个服务都提供不同的ptr/cookie,在驱动程序里对每个服务都构造一个binder_node, 它也含有ptr/cookie client使用服务前要先getService:会在驱动程序里对该服务构造一个binder_ref, binder_ref含有desc, node成员…