手撕 视觉slam14讲 ch7 / pose_estimation_3d2d.cpp (2)

上一篇文章中: 手撕ch7/pose_estimation_3d2d(1),我们调用了epnp的方法进行位姿估计,这里我们使用非线性优化的方法来求解位姿,使用g2o进行BA优化

首先介绍g2o:可参考:g2o详细介绍

1.构建g2o图优化思路:

  • 步骤一: 创建线性方程求解器,确定分解方法
// 每个误差项优化变量维度为3,误差值维度为1
typedef g2o::BlockSolver< g2o::BlockSolverTraits<3,1> > Block;  
// 创建一个线性求解器 LinearSolver,采用 dense cholesky 分解法
Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>(); 
  • 步骤二: 构造线性方程的矩阵块,并用上面定义的线性求解器初始化
Block* solver_ptr = new Block( linearSolver );

BlockSolver 内部包含 LinearSolver,用上面定义的线性求解器 LinearSolver 来初始化,前面已经给定了优化变量的维度;

  • 步骤三: 创建总求解器 solver,并从 GN, LM, DogLeg 中选一个,再用上述块求解器 BlockSolver 初始化
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg( solver_ptr );
  • 步骤四: 创建稀疏优化器(SparseOptimizer)
g2o::SparseOptimizer optimizer;     // 图模型
optimizer.setAlgorithm( solver );   // 用前面定义好的求解器作为求解方法:
optimizer.setVerbose( true );       // 打开调试输出
  • 步骤五: 定义图的顶点和边,并添加到 SparseOptimizer 优化器中
// 创建一个顶点
CurveFittingVertex* v = new CurveFittingVertex(); 
// 初始化顶点的值
v->setEstimate( Eigen::Vector3d(0,0,0) );
// 设置顶点的编号
v->setId(0);
// 向图中添加顶点
optimizer.addVertex( v );for ( int i=0; i<N; i++ )    // 往图中增加边
{// 创建一条边CurveFittingEdge* edge = new CurveFittingEdge( x_data[i] );// 设置边的 idedge->setId(i);// 设置边连接的顶点edge->setVertex( 0, v );                // 设置观测数值edge->setMeasurement( y_data[i] );      // 设置信息矩阵:协方差矩阵之逆edge->setInformation( Eigen::Matrix<double,1,1>::Identity()*1/(w_sigma*w_sigma) ); // 将边添加到图中optimizer.addEdge( edge );
}
  •  步骤六: 设置优化参数,开始执行优化
optimizer.initializeOptimization();
optimizer.optimize(100); // 迭代次数
对应原文完整代码如下:
void bundleAdjustmentG2O(const VecVector3d &points_3d,const VecVector2d &points_2d,const Mat &K,Sophus::SE3d &pose) {// 步骤一:构建图优化,先设定g2otypedef g2o::BlockSolver<g2o::BlockSolverTraits<6, 3>> BlockSolverType;  // pose is 6, landmark is 3//步骤二: 线性求解器类型typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType; //步骤三: 梯度下降方法,可以从GN, LM, DogLeg 中选auto solver = new g2o::OptimizationAlgorithmGaussNewton(g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>()));//步骤四: 创建稀疏优化器g2o::SparseOptimizer optimizer;     // 图模型optimizer.setAlgorithm(solver);   // 设置求解器optimizer.setVerbose(true);       // 打开调试输出//步骤五: 定义图的顶点和边// vertex 定义顶点VertexPose *vertex_pose = new VertexPose(); // 定义 相机位姿 为顶点vertex_pose->setId(0);// 设置顶点的编号vertex_pose->setEstimate(Sophus::SE3d());// 初始化顶点的值optimizer.addVertex(vertex_pose);// 向图中添加顶点// K 相机内参Eigen::Matrix3d K_eigen;K_eigen <<K.at<double>(0, 0), K.at<double>(0, 1), K.at<double>(0, 2),K.at<double>(1, 0), K.at<double>(1, 1), K.at<double>(1, 2),K.at<double>(2, 0), K.at<double>(2, 1), K.at<double>(2, 2);// edges 定义 边int index = 1;// 往图中增加边for (size_t i = 0; i < points_2d.size(); ++i) {auto p2d = points_2d[i];auto p3d = points_3d[i];EdgeProjection *edge = new EdgeProjection(p3d, K_eigen);// 创建一条边edge->setId(index);// 设置边的 idedge->setVertex(0, vertex_pose);// 设置边连接的顶点edge->setMeasurement(p2d);  // 设置观测数值edge->setInformation(Eigen::Matrix2d::Identity());// 设置信息矩阵:协方差矩阵之逆optimizer.addEdge(edge);// 将边添加到图中index++;}//步骤六:设置优化参数,开始优化chrono::steady_clock::time_point t1 = chrono::steady_clock::now();optimizer.setVerbose(true);optimizer.initializeOptimization();// 设置优化初始值optimizer.optimize(10);// 迭代次数chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);cout << "optimization costs time: " << time_used.count() << " seconds." << endl;cout << "pose estimated by g2o =\n" << vertex_pose->estimate().matrix() << endl;pose = vertex_pose->estimate();
}

其中我们需要关注如何定义顶点(Vertex)和边(edge)

2. 顶点(Vertex):

g2o 提供了一个比较通用的适合大多数情况的模板类 BaseVertex<D, T>,其中D 是 int 类型,表示顶点 Vertex 的最小维度,比如 3D 空间中旋转是 3 维的,那么这里 D=3;
但是在源码注释中说 D 并非是顶点(更确切的说是状态变量)的维度,而是其在流形空间(manifold)的最小表示(SO3->so3,SE3->se3).

T 是待估计 Vertex 的数据类型,比如用四元数表达三维旋转的话,T 就是 Quaternion 类型

我们自定义一个顶点需要重写以下函数

class myVertex: public g2::BaseVertex<Dim, Type>
{
public:// 类成员变量如果是固定大小对象需要加上以下的宏定义EIGEN_MAKE_ALIGNED_OPERATOR_NEW// 构造函数myVertex(){}// 读写函数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*/;}
}
对应原文代码:
class VertexPose : public g2o::BaseVertex<6, Sophus::SE3d> { //优化变量是 6 自由度的李代数
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW;//重置,设定被优化变量的原始值virtual void setToOriginImpl() override {_estimate = Sophus::SE3d();}/// left multiplication on SE3 //更新virtual void oplusImpl(const double *update) override {Eigen::Matrix<double, 6, 1> update_eigen;update_eigen << update[0], update[1], update[2], update[3], update[4], update[5];_estimate = Sophus::SE3d::exp(update_eigen) * _estimate;  // 左乘更新 SE3 - 旋转矩阵R}virtual bool read(istream &in) override {} //存盘virtual bool write(ostream &out) const override {}//读盘
};

3. 边(edge)

我们一般使用的类是 BaseUnaryEdge,BaseBinaryEdge,BaseMultiEdge 分别表示一元边,两元边,多元边(位于 g2o/g2o/core/base_edge.h 中),类似于顶点,他们又继承自OptimizableGraph::Edge (位于 g2o/g2o/core/optimizable_graph.h 中),hyper_graph::Edge(位于g2o/g2o/core/hyper_graph.h 中)

一元边表示只连接一个顶点,二元边表示连接两个顶点,多元边表示连接 3 个或以上顶点。

主要参数有:D, E, VertexXi, VertexXj

  • D 是 int 型,表示测量值的维度;
  • E 表示测量值的数据类型;
  • VertexXi,VertexXj 分别表示不同顶点的类型。

自定义边需要重写以下成员函数:最重要的是误差计算函数computeError(),计算雅克比矩阵linearizeOplus() 两个函数

class myEdge: public g2o::BaseBinaryEdge<errorDim, errorType, Vertex1Type, Vertex2Type>
{public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW      myEdge(){}     // 读写函数virtual bool read(istream& in) {}virtual bool write(ostream& out) const {}  // 误差计算函数virtual void computeError() override{// ..._error = _measurement - Something;}      // 计算雅克比矩阵 virtual void linearizeOplus() override{_jacobianOplusXi(pos, pos) = something;// ...         /*_jocobianOplusXj(pos, pos) = something;...*/}      private:// data
}
对应原文代码:
// 仅估计位姿的一元边
class EdgeProjection : public g2o::BaseUnaryEdge<2, Eigen::Vector2d, VertexPose> {
public:EIGEN_MAKE_ALIGNED_OPERATOR_NEW;//构造函数EdgeProjection(const Eigen::Vector3d &pos, const Eigen::Matrix3d &K) : _pos3d(pos), _K(K) {}// 误差计算函数virtual void computeError() override {const VertexPose *v = static_cast<VertexPose *> (_vertices[0]);Sophus::SE3d T = v->estimate();Eigen::Vector3d pos_pixel = _K * (T * _pos3d);//将3D世界坐标转为相机像素坐标pos_pixel /= pos_pixel[2];_error = _measurement - pos_pixel.head<2>(); //误差=测量值-投影得到的值}// 计算雅克比矩阵 virtual void linearizeOplus() override {const VertexPose *v = static_cast<VertexPose *> (_vertices[0]);Sophus::SE3d T = v->estimate();Eigen::Vector3d pos_cam = T * _pos3d;//相机坐标系下空间点的坐标=相机位姿 * 空间点的坐标double fx = _K(0, 0);double fy = _K(1, 1);double cx = _K(0, 2);double cy = _K(1, 2);double X = pos_cam[0];double Y = pos_cam[1];double Z = pos_cam[2];double Z2 = Z * Z;// 2*6的雅克比矩阵_jacobianOplusXi<< -fx / Z, 0, fx * X / Z2, fx * X * Y / Z2, -fx - fx * X * X / Z2, fx * Y / Z,0, -fy / Z, fy * Y / (Z * Z), fy + fy * Y * Y / Z2, -fy * X * Y / Z2, -fy * X / Z;}//读写操作virtual bool read(istream &in) override {}virtual bool write(ostream &out) const override {}private:Eigen::Vector3d _pos3d;Eigen::Matrix3d _K;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/138690.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQLJDBC入门与SQL注入

MySQL-JDBC入门与SQL注入 一.JDBC概述 1.JDBC 在Java语言中提供对数据库访问的支持Sun公司于1996年提供了一套访问数据库的标准Java类库JDBCJDBC的全称是Java数据库连接(Java Database Connectivity)它是一套用于执行 SQL语句的Java API应用程序可通过这套API连接到关系数据…

“Flex弹性布局、轮播图mock遍历数据和首页布局解析与实践“

目录 引言1. Flex弹性布局介绍及使用什么是Flex弹性布局&#xff1f;Flex容器与Flex项目Flex属性详解 2. 轮播图mock遍历数据简述轮播图的作用和意义处理mock数据的重要性使用Mock模拟数据遍历 3. 首页布局总结 引言 在现代网页开发中&#xff0c;灵活性和响应式布局是至关重要…

【STL】平衡二叉树

前言 对于之前普通的二叉搜索树&#xff0c;其搜索的效率依靠树的形状来决定&#xff0c;如下&#xff1a; 可以看到 A图 中的树比较彭亨&#xff0c;搜索一个元素的效率接近 O(logN) &#xff1b;而 B图 中的形状也符合搜索二叉树&#xff0c;但是很不平衡&#xff0c;这时的…

TCP/IP网络分层模型

TCP/IP当初的设计者真的是非常聪明&#xff0c;创造性地提出了“分层”的概念&#xff0c;把复杂的网络通信划分出多个层次&#xff0c;再给每一个层次分配不同的职责&#xff0c;层次内只专心做自己的事情就好&#xff0c;用“分而治之”的思想把一个“大麻烦”拆分成了数个“…

解决 MyBatis 一对多查询中,出现每组元素只有一个,总组数与元素数总数相等的问题

文章目录 问题简述场景描述问题描述问题原因解决办法 问题简述 笔者在使用 MyBatis 进行一对多查询的时候遇到一个奇怪的问题。对于笔者的一对多的查询结果&#xff0c;出现了这样的一个现象&#xff1a;原来每个组里有多个元素&#xff0c;查询目标是查询所查的组&#xff0c;…

Stable Diffusion绘图,lora选择

best quality, ultra high res, (photorealistic:1.4), 1girl, off-shoulder white shirt, black tight skirt, black choker, (faded ash gray hair:1), looking at viewer, closeup <lora:koreandolllikeness_v20:0.66> 最佳品质&#xff0c;超高分辨率&#xff0c;&am…

Location的匹配

nginx的正则表达式: ^:字符串的起始位置 $:字符窜的结束位置 *:匹配所有 :匹配前面的字符一次或者多次 ?:匹配前面的字符0次或者1次 .:任意单个字符 {n}:连续重复出现n次。 {n,m}:连续重复出现n-m次 [a-Z0-9A-Z] [C]:匹配单个字符c ():分组 |:或 一 Location的分类&#xff1a…

Prompt 驱动架构设计:探索复杂 AIGC 应用的设计之道?

你是否曾经想过&#xff0c;当你在 Intellij IDEA 中输入一个段代码时&#xff0c;GitHub 是如何给你返回相关的结果的&#xff1f;其实&#xff0c;这背后的秘密就是围绕 Prompt 生成而构建的架构设计。 Prompt 是一个输入的文本段落或短语&#xff0c;用于引导 AI 生成模型执…

dockerfile 搭建lnmp+wordpress,docker-compose搭建lnmp+wordpress

目录 dockerfile 搭建lnmpwordpress 部署nginx&#xff08;容器IP 为 172.18.0.10&#xff09; 部署mysql&#xff08;容器IP 为 172.18.0.20&#xff09; 部署php&#xff08;容器IP 为 172.18.0.30&#xff09; docker-compose搭建lnmpwordpress dockerfile 搭建lnmpword…

Linux:mongodb数据逻辑备份与恢复(3.4.5版本)

我在数据库aaa的里创建了一个名为tarro的集合&#xff0c;其中有三条数据 备份语法 mongodump –h server_ip –d database_name –o dbdirectory 恢复语法 mongorestore -d database_name --dirdbdirectory 备份 现在我要将aaa.tarro进行备份 mongodump --host 192.168.254…

基于R语言实现中介效应检验以及sobel检验代码

数据格式 随机数据&#xff0c;不一定好 y是因变量&#xff0c;x是自变量&#xff0c;m是中介变量 基本原理 M ~ X Y ~ X Y ~ X M 直接上代码 library(mediation) library(bda)# 加载readxl包 library(readxl) # 读取Excel表格# 读取数据 我是从剪切板读取的 data read…

大语言模型在推荐系统的实践应用

本文从应用视角出发&#xff0c;尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数)&#xff0c;训练和推理的时间、空间开销较小&#xff0c;也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…