点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

小白:师兄,上一次将的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顶点编程套路相关推荐

  1. 从零开始一起学习SLAM | 点云到网格的进化

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 小白:师兄,师兄,你在<从零开始一起学习SLAM | 给点云 ...

  2. 从零开始一起学习SLAM | 三维空间刚体的旋转

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 刚体,顾名思义,是指本身不会在运动过程中产生形变的物体,如相机的运 ...

  3. 从零开始一起学习SLAM | 你好,点云

    本文提纲 先热热身 点云是啥 你知道点云优缺点吗? 点云库PCL:开发者的福音 PCL安装指北 炒鸡简单的PCL实践 留个作业再走 先热热身 小白:hi,师兄,好久不见 师兄:师妹好,上周单应矩阵作业 ...

  4. 从零开始一起学习SLAM | 给点云加个滤网

    对VSLAM和三维重建感兴趣的在计算机视觉life"公众号菜单栏回复"三维视觉"进交流群. 小白:师兄,上次你讲了点云拼接后,我回去费了不少时间研究,终于得到了和你给的参 ...

  5. 从零开始一起学习SLAM | 为啥需要李群与李代数?

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 很多刚刚接触SLAM的小伙伴在看到李群和李代数这部分的时候,都有点 ...

  6. 从零开始一起学习SLAM | 不推公式,如何真正理解对极约束?

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 自从小白向师兄学习了李群李代数和相机成像模型的基本原理后,感觉书上 ...

  7. 从零开始一起学习SLAM(8)相机成像模型

    文章目录 小孔成像 纷繁复杂的坐标系 针孔相机成像原理 相机畸变   此文发于公众号:计算机视觉life.   原文链接:从零开始一起学习SLAM | 相机成像模型 上一篇文章<从零开始一起学习 ...

  8. 从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 小白:师兄师兄,最近我在看SLAM的优化算法,有种方法叫" ...

  9. 从零开始一起学习SLAM | SLAM有什么用?

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 SLAM是 Simultaneous Localization A ...

最新文章

  1. 0x32.数学知识 - 约数
  2. 【科大星云诗社动态20201204
  3. python自动控制生产线输送线_一个关于自动化装配生产线结构组成案例,易懂干货...
  4. php 转换数组的字符集,PHP 自动转换字符集(支持字符串和数组)功能实例
  5. 知识点1: 进度条随数据变化,并添加渐变样式
  6. jquery中的serializeArray方法的使用
  7. 作者:陈波,男,中国科学院计算技术研究所研究实习员。
  8. mysql gui tools ojdbc14.jar_[java]OJDBC版本区别 [ojdbc14.jar,ojdbc5.jar和ojdbc6.jar的区别]
  9. 中国农田生产潜力数据集
  10. 简体,繁体中文互转类
  11. html5+自动更换背景图片,html 随机切换背景图片
  12. 大数据技术如何影响企业决策?
  13. html 在线测试 鱼缸,新做的草缸教程,看1遍你也会做,鱼缸造景其实很简单
  14. 首阴战法胜率不高?应该是忘记加上一个大前提!
  15. SSH免密失败并报错:no mutual signature algorithm
  16. Android wear 睡眠追踪,为什么智能手表还不是最理想的睡眠追踪设备
  17. 关于eclipse无法连接手机调试问题
  18. python pandas 实战 百度音乐歌单 数据分析
  19. 【光线追踪】光线追踪重投影方法(Ray Tracing Reprojection)
  20. 红旗linux分区方案有,红旗Linux分区全攻略

热门文章

  1. AlphaGo之父DeepMind再出神作,PrediNet原理详解
  2. 宝宝都能看懂的机器学习世界
  3. 初学者的机器学习入门实战教程!
  4. 全栈AI工程师指南,DIY一个识别手写数字的web应用
  5. AI一分钟 | 知乎融资2.7亿美元;腾讯投资特斯拉大赚特赚
  6. Hinton胶囊理论代码开源,上线即受热捧
  7. 面试官: 讲讲 Spring 事务有哪些坑?
  8. Spring是如何运用设计模式的?
  9. Java 如何设计 API 接口,实现统一格式返回?
  10. 7个提升PyTorch性能的技巧