从零开始一起学习SLAM | 掌握g2o顶点编程套路
点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
小白:师兄,上一次将的g2o框架《从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码》真的很清晰,我现在再去看g2o的那些优化的部分,基本都能看懂了呢!
师兄:那太好啦,以后多练习练习,加深理解
小白:嗯,我开始编程时,发现g2o的顶点和边的定义也非常复杂,光看十四讲里面,就有好几种不同的定义,完全懵圈状态。。。师兄,能否帮我捋捋思路啊
师兄:嗯,你说的没错,入门的时候确实感觉很乱,我最初也是花了些时间才搞懂的,下面分享一下。
g2o的顶点(Vertex) 从哪里来的?
师兄:在《g2o: A general Framework for (Hyper) Graph Optimization》这篇文档里,我们找到那张经典的类结构图。也就是上次讲框架用到的那张结构图。其中涉及到顶点 (vertex) 的就是下面 加了序号的3个东东了。
小白:记得呢,这个图很关键,帮助我理清了很多思路,原来来自这篇文章啊
师兄:对,下面我们一步步来看吧。先来看看上图中和vertex有关的第①个类:HyperGraph::Vertex,在g2o的GitHub上(https://github.com/RainerKuemmerle/g2o),它在这个路径
g2o/core/hyper_graph.h
这个 HyperGraph::Vertex 是个abstract vertex,必须通过派生来使用。如下图所示
然后我们看g2o 类结构图中第②个类,我们看到HyperGraph::Vertex 是通过类OptimizableGraph 来继承的, 而OptimizableGraph的定义在
g2o/core/optimizable_graph.h
我们找到vertex定义,发现果然,OptimizableGraph 继承自 HyperGraph,如下图所示
不过,这个OptimizableGraph::Vertex 也非常底层,具体使用时一般都会进行扩展,因此g2o中提供了一个比较通用的适合大部分情况的模板。就是g2o 类结构图中 对应的第③个类:
BaseVertex
那么它在哪里呢?在这个路径:
g2o/core/base_vertex.h
小白:哇塞,原来是这样抽丝剥茧的呀,学习了,授人以鱼不如授人以渔啊!
师兄:嗯,其实就是根据那张图结合g2o GitHub代码就行了
g2o的顶点(Vertex) 参数如何理解?
小白:那是不是就可以开始用了?
师兄:别急,我们来看看参数吧,这个很关键。
我们来看一下模板参数 D 和 T,翻译一下上图红框:
D是int 类型的,表示vertex的最小维度,比如3D空间中旋转是3维的,那么这里 D = 3
T是待估计vertex的数据类型,比如用四元数表达三维旋转的话,T就是Quaternion 类型
小白:哦哦,大概理解了,但还是有点模糊
师兄:我们进一步来细看一下D, T。这里的D 在源码里面是这样注释的
static const int Dimension = D; ///< dimension of the estimate (minimal) in the manifold space
可以看到这个D并非是顶点(更确切的说是状态变量)的维度,而是其在流形空间(manifold)的最小表示,这里一定要区别开,另外,源码里面也给出了T的作用
typedef T EstimateType;
EstimateType _estimate;
可以看到,这里T就是顶点(状态变量)的类型,跟前面一样。
小白:Got it!
如何自己定义顶点?
小白:师兄,我们是不是可以开始写顶点定义了?
师兄:嗯,我们知道了顶点的基本类型是 BaseVertex,那么下一步关心的就是如何使用了,因为在不同的应用场景(二维空间,三维空间),有不同的待优化变量(位姿,空间点),还涉及不同的优化类型(李代数位姿、李群位姿)
小白:这么多啊,那要自己根据 BaseVertex 一个个实现吗?
师兄:那不需要!g2o本身内部定义了一些常用的顶点类型,我给找出来了,大概这些:
VertexSE2 : public BaseVertex<3, SE2> //2D pose Vertex, (x,y,theta)
VertexSE3 : public BaseVertex<6, Isometry3> //6d vector (x,y,z,qx,qy,qz) (note that we leave out the w part of the quaternion)
VertexPointXY : public BaseVertex<2, Vector2>
VertexPointXYZ : public BaseVertex<3, Vector3>
VertexSBAPointXYZ : public BaseVertex<3, Vector3>// SE3 Vertex parameterized internally with a transformation matrix and externally with its exponential map
VertexSE3Expmap : public BaseVertex<6, SE3Quat>// SBACam Vertex, (x,y,z,qw,qx,qy,qz),(x,y,z,qx,qy,qz) (note that we leave out the w part of the quaternion.
// qw is assumed to be positive, otherwise there is an ambiguity in qx,qy,qz as a rotation
VertexCam : public BaseVertex<6, SBACam>// Sim3 Vertex, (x,y,z,qw,qx,qy,qz),7d vector,(x,y,z,qx,qy,qz) (note that we leave out the w part of the quaternion.
VertexSim3Expmap : public BaseVertex<7, Sim3>
小白:好全啊,我们可以直接用啦!
师兄:当然我们可以直接用这些,但是有时候我们需要的顶点类型这里面没有,就得自己定义了。
重新定义顶点一般需要考虑重写如下函数:
virtual bool read(std::istream& is);
virtual bool write(std::ostream& os) const;
virtual void oplusImpl(const number_t* update);
virtual void setToOriginImpl();
小白:这些函数啥意思啊,我也就能看懂 read 和 write(/尴尬脸),还有每次定义都要重新写这几个函数吗?
师兄:是的,这几个是主要要改的地方。我们来看一下他们都是什么意义:
read,write:分别是读盘、存盘函数,一般情况下不需要进行读/写操作的话,仅仅声明一下就可以
setToOriginImpl:顶点重置函数,设定被优化变量的原始值。
oplusImpl:顶点更新函数。非常重要的一个函数,主要用于优化过程中增量△x 的计算。我们根据增量方程计算出增量之后,就是通过这个函数对估计值进行调整的,因此这个函数的内容一定要重视。
自己定义 顶点一般是下面的格式:
class myVertex: public g2::BaseVertex<Dim, Type>{public:EIGEN_MAKE_ALIGNED_OPERATOR_NEWmyVertex(){}virtual void read(std::istream& is) {}virtual void write(std::ostream& os) const {}virtual void setOriginImpl(){_estimate = Type();}virtual void oplusImpl(const double* update) override{_estimate += /*update*/;}}
小白:看不太懂啊,师兄
师兄:没事,我们看例子就知道了,先看一个简单例子,来自十四讲中的曲线拟合,来源如下
ch6/g2o_curve_fitting/main.cpp
// 曲线模型的顶点,模板参数:优化变量维度和数据类型
class CurveFittingVertex: public g2o::BaseVertex<3, Eigen::Vector3d>
{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEWvirtual void setToOriginImpl() // 重置{_estimate << 0,0,0;}virtual void oplusImpl( const double* update ) // 更新{_estimate += Eigen::Vector3d(update);}// 存盘和读盘:留空virtual bool read( istream& in ) {}virtual bool write( ostream& out ) const {}
};
我们可以看到下面代码中顶点初值设置为0,更新时也是直接把更新量 update 加上去的,知道为什么吗?
小白:更新不就是 x + △x 吗,这是定义吧
师兄:嗯,对于这个例子是可以直接加,因为顶点类型是Eigen::Vector3d,属于向量,是可以通过加法来更新的。但是但是有些例子就不行,比如下面这个复杂点例子:李代数表示位姿VertexSE3Expmap
来自g2o官网,在这里
g2o/types/sba/types_six_dof_expmap.h
/**\* \brief SE3 Vertex parameterized internally with a transformation matrixand externally with its exponential map*/class G2O_TYPES_SBA_API VertexSE3Expmap : public BaseVertex<6, SE3Quat>{
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEWVertexSE3Expmap();bool read(std::istream& is);bool write(std::ostream& os) const;virtual void setToOriginImpl() {_estimate = SE3Quat();}virtual void oplusImpl(const number_t* update_) {Eigen::Map<const Vector6> update(update_);setEstimate(SE3Quat::exp(update)*estimate()); //更新方式}
};
小白:师兄,这个里面的6, SE3Quat 分别是什么意思?
师兄:书中都写了,以下来自十四讲的介绍:
第一个参数6 表示内部存储的优化变量维度,这是个6维的李代数
第二个参数是优化变量的类型,这里使用了g2o定义的相机位姿类型:SE3Quat。
在这里可以具体查看g2o/types/slam3d/se3quat.h
它内部使用了四元数表达旋转,然后加上位移来存储位姿,同时支持李代数上的运算,比如对数映射(log函数)、李代数上增量(update函数)等操作
说完了,那我现在问你个问题,为啥这里更新时没有像上面那样直接加上去?
小白:这个表示位姿,好像是不能直接加的我记得,原因有点忘了
师兄:嗯,是不能直接加,原因是变换矩阵不满足加法封闭。那我再问你,为什么相机位姿顶点类VertexSE3Expmap使用了李代数表示相机位姿,而不是使用旋转矩阵和平移矩阵?
小白:不造啊。。
师兄:其实也是上述原因的拓展:这是因为旋转矩阵是有约束的矩阵,它必须是正交矩阵且行列式为1。使用它作为优化变量就会引入额外的约束条件,从而增大优化的复杂度。而将旋转矩阵通过李群-李代数之间的转换关系转换为李代数表示,就可以把位姿估计变成无约束的优化问题,求解难度降低。
小白:原来如此啊,以前学的东西都忘了。。
师兄:以前学的要多看,温故而知新。我们继续看例子,刚才是位姿的例子,下面是三维点的例子,空间点位置 VertexPointXYZ,维度为3,类型是Eigen的Vector3,比较简单,就不解释了
class G2O_TYPES_SBA_API VertexSBAPointXYZ : public BaseVertex<3, Vector3>
{public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW VertexSBAPointXYZ();virtual bool read(std::istream& is);virtual bool write(std::ostream& os) const;virtual void setToOriginImpl() {_estimate.fill(0);}virtual void oplusImpl(const number_t* update){Eigen::Map<const Vector3> v(update);_estimate += v;}
};
如何向图中添加顶点?
师兄:往图中增加顶点比较简单,我们还是先看看第一个曲线拟合的例子,setEstimate(type) 函数来设定初始值;setId(int) 定义节点编号
// 往图中增加顶点CurveFittingVertex* v = new CurveFittingVertex();v->setEstimate( Eigen::Vector3d(0,0,0) );v->setId(0);optimizer.addVertex( v );
这个是添加 VertexSBAPointXYZ 的例子,都很容易看懂
/ch7/pose_estimation_3d2d.cpp
int index = 1;for ( const Point3f p:points_3d ) // landmarks{g2o::VertexSBAPointXYZ* point = new g2o::VertexSBAPointXYZ();point->setId ( index++ );point->setEstimate ( Eigen::Vector3d ( p.x, p.y, p.z ) );point->setMarginalized ( true ); optimizer.addVertex ( point );}
至此,我们讲完了g2o 的顶点的来源,定义,自定义方法,添加方法,基本上你以后再看到顶点就不会陌生啦!
小白:太感谢啦!
下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。
下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。
下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。
交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
从零开始一起学习SLAM | 掌握g2o顶点编程套路相关推荐
- 从零开始一起学习SLAM | 点云到网格的进化
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 小白:师兄,师兄,你在<从零开始一起学习SLAM | 给点云 ...
- 从零开始一起学习SLAM | 三维空间刚体的旋转
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 刚体,顾名思义,是指本身不会在运动过程中产生形变的物体,如相机的运 ...
- 从零开始一起学习SLAM | 你好,点云
本文提纲 先热热身 点云是啥 你知道点云优缺点吗? 点云库PCL:开发者的福音 PCL安装指北 炒鸡简单的PCL实践 留个作业再走 先热热身 小白:hi,师兄,好久不见 师兄:师妹好,上周单应矩阵作业 ...
- 从零开始一起学习SLAM | 给点云加个滤网
对VSLAM和三维重建感兴趣的在计算机视觉life"公众号菜单栏回复"三维视觉"进交流群. 小白:师兄,上次你讲了点云拼接后,我回去费了不少时间研究,终于得到了和你给的参 ...
- 从零开始一起学习SLAM | 为啥需要李群与李代数?
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 很多刚刚接触SLAM的小伙伴在看到李群和李代数这部分的时候,都有点 ...
- 从零开始一起学习SLAM | 不推公式,如何真正理解对极约束?
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 自从小白向师兄学习了李群李代数和相机成像模型的基本原理后,感觉书上 ...
- 从零开始一起学习SLAM(8)相机成像模型
文章目录 小孔成像 纷繁复杂的坐标系 针孔相机成像原理 相机畸变 此文发于公众号:计算机视觉life. 原文链接:从零开始一起学习SLAM | 相机成像模型 上一篇文章<从零开始一起学习 ...
- 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 小白:师兄师兄,最近我在看SLAM的优化算法,有种方法叫" ...
- 从零开始一起学习SLAM | SLAM有什么用?
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 SLAM是 Simultaneous Localization A ...
最新文章
- 0x32.数学知识 - 约数
- 【科大星云诗社动态20201204
- python自动控制生产线输送线_一个关于自动化装配生产线结构组成案例,易懂干货...
- php 转换数组的字符集,PHP 自动转换字符集(支持字符串和数组)功能实例
- 知识点1: 进度条随数据变化,并添加渐变样式
- jquery中的serializeArray方法的使用
- 作者:陈波,男,中国科学院计算技术研究所研究实习员。
- mysql gui tools ojdbc14.jar_[java]OJDBC版本区别 [ojdbc14.jar,ojdbc5.jar和ojdbc6.jar的区别]
- 中国农田生产潜力数据集
- 简体,繁体中文互转类
- html5+自动更换背景图片,html 随机切换背景图片
- 大数据技术如何影响企业决策?
- html 在线测试 鱼缸,新做的草缸教程,看1遍你也会做,鱼缸造景其实很简单
- 首阴战法胜率不高?应该是忘记加上一个大前提!
- SSH免密失败并报错:no mutual signature algorithm
- Android wear 睡眠追踪,为什么智能手表还不是最理想的睡眠追踪设备
- 关于eclipse无法连接手机调试问题
- python pandas 实战 百度音乐歌单 数据分析
- 【光线追踪】光线追踪重投影方法(Ray Tracing Reprojection)
- 红旗linux分区方案有,红旗Linux分区全攻略