手撕 视觉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.mzph.cn/news/110481.shtml

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

相关文章

加权平均、EMD、小波等方法去噪效果对比

加权平均、EMD、小波等方法去噪效果对比 代码 整体代码如下 %% clear all; clc;load(data_filter120Hz.mat); %可自己生成随机噪声 fs1000;%采样频率是1000Hz %% %生成正弦波信号 tlinspace(0, length(data)/fs-1/fs, length(data)); y1 15*sin(2*pi* 2.8 *t);%生成频率为2.…

Android之使用QBadgeView给TabLayout顶部栏设置数量角标,数值可更新

TabLayout搭配ViewPager、Fragement使用可看另一篇文章&#xff1a; Android中TabLayoutViewPagerFragment实现顶部导航栏 本文主要描述给TabLayout的某一栏添加角标&#xff0c;数值可更新&#xff1a; 一、效果 二、TabLayout使用 1、xml文件中 <com.google.android.m…

通讯协议学习之路:QSPI协议理论

通讯协议之路主要分为两部分&#xff0c;第一部分从理论上面讲解各类协议的通讯原理以及通讯格式&#xff0c;第二部分从具体运用上讲解各类通讯协议的具体应用方法。 后续文章会同时发表在个人博客(jason1016.club)、CSDN&#xff1b;视频会发布在bilibili(UID:399951374) 一、…

【Django 01】环境搭配与项目配置

1. 介绍 https://github.com/Joe-2002/sweettalk-django4.2#readme Django 是一个使用 Python 编写的开源 Web 应用程序框架&#xff0c;它提供了一套用于快速开发安全、 可扩展和高效的 Web 应用程序的工具和功能。Django 基于 MVC&#xff08;Model-View-Controller&#xf…

Windows11家庭版没有本地组策略编辑器解决

1. 新建一个文本文件将下面代码粘到里面&#xff0c;保存后修改后缀为.cmd或者.bat echo off pushd "%~dp0"dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt dir /b C:\Windows\servicing\Packa…

redis(普通连接和连接池、字符串类型、hash类型、列表类型)

1 redis普通连接和连接池 1.1 普通连接 1.2 连接池 2 redis字符串类型 3 redis hash类型 4 redis列表类型 1 redis普通连接和连接池 #1 python 代码作为客户端---》连接# 2 安装模块&#xff1a;pip install redis1.1 普通连接 from redis import Redisconn Redis(host&qu…

Selenium浏览器自动化怎么上传文件

Selenium 封装了现成的文件上传操作。但是随着现代前端框架的发展&#xff0c;文件上传的方式越来越多样。而有一些文件上传的控件&#xff0c;要做自动化控制会更复杂一些&#xff0c;这篇文章主要讨论在复杂情况下&#xff0c;如何通过自动化完成文件上传。 1. input 元素上传…

蓝桥杯每日一题2023.10.17

迷宫 - 蓝桥云课 (lanqiao.cn) 题目描述 样例&#xff1a; 01010101001011001001010110010110100100001000101010 00001000100000101010010000100000001001100110100101 01111011010010001000001101001011100011000000010000 0100000000101010001101000010100000101010101100…

Ubuntu18中的连接网络图标恢复

上图的图标不存在&#xff0c;也连不上网。 输入命令停止网络管理 service NetworkManager stop删除网络管理缓存文件 sudo rm /var/lib/NetworkManager/NetworkManager.state重启网络管理 service NetworkManager start修改网络管理文件 将‘managedfalse’修改为‘man…

性能测试-JMeter分布式测试及其详细步骤

性能测试概要 性能测试是软件测试中的一种&#xff0c;它可以衡量系统的稳定性、扩展性、可靠性、速度和资源使用。它可以发现性能瓶颈&#xff0c;确保能满足业务需求。很多系统都需要做性能测试&#xff0c;如Web应用、数据库和操作系统等。 性能测试种类非常多&#xff0c…

官媒代运营:如何将内容营销做到深入人心

生活中&#xff0c;信息传递和有效的沟通是我们与世界互动的重要方式&#xff0c;而语言是这种互动的关键媒介。然而&#xff0c;在营销界&#xff0c;我们已经迈出了更深一步&#xff0c;将语言与内容相结合&#xff0c;以创造内容营销这一强大的战略工具。内容&#xff0c;作…

小程序入门及案例展示

目录 一、小程序简介 1.1 为什么要使用小程序 1.2 小程序可以干什么 二、前期准备 2.1 申请账号 2.2 开发工具下载与安装 三、电商案例演示 四、入门案例 4.1 项目结构解析 4.2 基础操作及语法 4.3 模拟器 4.4 案例演示 4.4.1 新建页面 4.4.2 头部样式设置 4.4.…

linux 防火墙介绍以及iptables的使用

背景介绍 在前几天&#xff0c;于工发现我们内部的150服务器7554端口被外网访问了。该应用提供着内部的摄像头资源。为了避免被入侵&#xff0c;于是我添加了一些iptables规则&#xff0c;防止外网的访问。 解决方式 解决方式有两种&#xff1a; 关闭公司公网路由器对150服务…

神经网络量化----为了部署而特别设计

引言&#xff1a;一般神经网络量化有两个目的&#xff1a; 为了加速&#xff0c;在某些平台上浮点数计算比较耗费时间&#xff0c;替换为整形可以加快运算为了部署&#xff0c;某些平台上只支持整形运算&#xff0c;比如在芯片中 如果是第1个目的&#xff0c;则使用常规的量化手…

【财政金融】全国各地区财政收入与支出面板数据合集(2000-2022年)

数据简介&#xff1a;2000年到2022年中国经历了快速的经济发展和城市化进程&#xff0c;各地区的财政收入和支出也呈现出显著的增长和变化&#xff0c;全国各地区财政收入与支出的面板数据可用于评估经济发展水平和区域差距&#xff0c;通过比较不同地区之间的财政收入和支出水…

来来来,降温啦给女儿这么穿,好看又保暖

家人们谁懂啊&#xff01;&#xff01;&#xff01; 时尚百搭有气质的羽绒服 小编强烈推荐哦&#xff01; 防风保暖设计加三防工艺 简直不要太哇塞 怎么穿都好看哦

机器人制作开源方案 | 行星探测车概述

1. 功能描述 行星探测车&#xff08;Planetary Rover&#xff09;是一种用于进行科学探索和勘测任务的无人车辆&#xff0c;它们被设计成能够适应各种复杂的地形条件和极端环境&#xff0c;以便收集数据、拍摄照片、采集样本等。行星探测车通常包含以下主要组件和功能&#xff…

抖音小程序制作源码系统 带完整搭建教程

在当今社交媒体时代&#xff0c;抖音作为一款备受欢迎的短视频应用&#xff0c;已经拥有了庞大的用户群体。与此同时&#xff0c;抖音小程序也成为了商家和开发者们关注的热点。今天小编就来给大家介绍一款抖音小程序制作源码系统&#xff0c;五分钟确实创建一个小程序。 系统特…

音乐播放器VHDL蜂鸣器数码管显示简谱,视频/代码

名称&#xff1a;音乐播放器数码管显示简谱蜂鸣器 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 设计音乐播放器&#xff0c;播放一首歌&#xff0c;使用开发板的蜂鸣器播放音乐&#xff0c;使用Quartus内的ROM IP核存储音乐文件&#xff0c;使用数…

《golang设计模式》第三部分·行为型模式-01-责任链模式(Chain of Responsibility)

文章目录 1 概念1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1 概念 责任链&#xff08;Chain of Responsibility&#xff09;是指将客户端请求处理的不同职责对象组成请求处理链。 客户端只需要将请求交付到该链上&#xff0c;而不需要关心链上含有哪些对象。请求…