Ceres 目标函数(pose_graph_3d使用之)构建学习笔记

news2024/11/14 15:14:35

问题说明

ceres-solver库是google的非线性优化库,可以对slam问题,机器人位姿进行优化,使其建图的效果得到改善。pose_graph_3d是官方给出的二维平面上机器人位姿优化问题,需要读取一个g2o文件,运行程序后返回一个poses_original.txt和一个poses_optimized.txt,大家按字面意思理解,内部格式长这样:

pose_id x y z q_x q_y q_z q_w
pose_id x y z q_x q_y q_z q_w
pose_id x y z q_x q_y q_z q_w
...

按examples中pose_graph_3d包内的README操作。)得到这两个文件后,用官方提供的plot_results.py可以画出原始和优化后的位姿地图,类似下图:
在这里插入图片描述

变量说明

重要变量为以下几个:
constraints:vector,例程中起名叫VectorOfConstraints,放入变量的类型为Constraint3d, 如下所示,注意eigen的对齐。

//eigen 对齐,可能是因为Constraint3d里面有eigen的数据类型
typedef std::vector<Constraint3d, Eigen::aligned_allocator<Constraint3d> >
    VectorOfConstraints;

含义为机器人两个pose之间的限制,Constraint3d包括两个pose的id,相对旋转矩阵t_be(含义为从e帧变换到b帧的变换矩阵),和协方差阵。constraints这个变量描述的是观测量测量量measurement,即机器人认为自己感知到的正确的数据。

poses: map类指针,例程起名为MapOfPoses键值对为id和 Pose3d ,后面两个参数为按键从小到大排列和eigen的内部对齐。如下所示。

//map的四个参数 键是整形,值是Pose3d,按key值从小到大排序,(Pose3d含有eigen的数据类型)需要对齐
typedef std::map<int, Pose3d, std::less<int>,
                 Eigen::aligned_allocator<std::pair<const int, Pose3d> > >
    MapOfPoses;

此步搭建了costfunction,详情下面介绍。

//为四元数添加一个LocalParameterization,定义新的求导方式,求解雅可比矩阵
//定义一个父类指针指向子类
  ceres::LocalParameterization* quaternion_local_parameterization =
      new EigenQuaternionParameterization;

此步new了一个LocalParameterization,详情下面介绍。

Costfunction的搭建

使用ceres库的关键是构造 costfunction ,ceres官方搭建的costfunction,同样有一个类表示,名为PoseGraph3dErrorTerm,具体如下所示:

class PoseGraph3dErrorTerm {
 public:
  PoseGraph3dErrorTerm(const Pose3d& t_ab_measured,
                       const Eigen::Matrix<double, 6, 6>& sqrt_information)
      : t_ab_measured_(t_ab_measured), sqrt_information_(sqrt_information) {}

  template <typename T>
  bool operator()(const T* const p_a_ptr, const T* const q_a_ptr,
                  const T* const p_b_ptr, const T* const q_b_ptr,
                  T* residuals_ptr) const {
    Eigen::Map<const Eigen::Matrix<T, 3, 1> > p_a(p_a_ptr);
    Eigen::Map<const Eigen::Quaternion<T> > q_a(q_a_ptr);

    Eigen::Map<const Eigen::Matrix<T, 3, 1> > p_b(p_b_ptr);
    Eigen::Map<const Eigen::Quaternion<T> > q_b(q_b_ptr);

    // Compute the relative transformation between the two frames.
    Eigen::Quaternion<T> q_a_inverse = q_a.conjugate();
    Eigen::Quaternion<T> q_ab_estimated = q_a_inverse * q_b;

    // Represent the displacement between the two frames in the A frame.
    Eigen::Matrix<T, 3, 1> p_ab_estimated = q_a_inverse * (p_b - p_a);

    // Compute the error between the two orientation estimates.
    Eigen::Quaternion<T> delta_q =
        t_ab_measured_.q.template cast<T>() * q_ab_estimated.conjugate();

    // Compute the residuals.
    // [ position         ]   [ delta_p          ]
    // [ orientation (3x1)] = [ 2 * delta_q(0:2) ]
    Eigen::Map<Eigen::Matrix<T, 6, 1> > residuals(residuals_ptr);
    residuals.template block<3, 1>(0, 0) =
        p_ab_estimated - t_ab_measured_.p.template cast<T>();
    residuals.template block<3, 1>(3, 0) = T(2.0) * delta_q.vec();

    // Scale the residuals by the measurement uncertainty.
    residuals.applyOnTheLeft(sqrt_information_.template cast<T>());

    return true;
  }

  static ceres::CostFunction* Create(
      const Pose3d& t_ab_measured,
      const Eigen::Matrix<double, 6, 6>& sqrt_information) {
    return new ceres::AutoDiffCostFunction<PoseGraph3dErrorTerm, 6, 3, 4, 3, 4>(
        new PoseGraph3dErrorTerm(t_ab_measured, sqrt_information));
  }

  EIGEN_MAKE_ALIGNED_OPERATOR_NEW

 private:
  // The measurement for the position of B relative to A in the A frame.
  const Pose3d t_ab_measured_;
  // The square root of the measurement information matrix.
  const Eigen::Matrix<double, 6, 6> sqrt_information_;
};

其中包括:
一个构造函数PoseGraph3dErrorTerm( t_ab_measured,sqrt_information );
一个运算符重载operator()( p_a_ptr, q_a_ptr, p_b_ptr, q_b_ptr, residuals_ptr ),其中residuals_ptr指向的东西是计算出的残差;
一个构造costfunction的函数Create( t_ab_measured, sqrt_information )。

operator()的作用
传入参数计算残差,残差有六维,如下所示:
在这里插入图片描述
其中Vec()函数取四元数的虚部,也就是跟旋转向量相关的那部分,这样的话最小化误差的理想状态最后三维会是0向量。pab 是a坐标系中表示的机器人在b时的位置,qab是b到a的旋转四元数, 由下式计算。带hat的是测量值,是在时刻a时机器人坐标系下观察的测量值。
在这里插入图片描述
pb 和 pa 的定义见pose_graph_2d里的说明。 qab是由 b→world→a 得到。

代码中首先将传入的参数map到eigen的数据类型,p_a和p_b(Eigen::Matrix),q_a和q_b(Eigen::Quaternion),关于eigen的map问题可点击这里,然后将q_a取了其共轭(conjugate),并当做其逆使用,这里有些不懂它为什么这么做,inverse和conjugate相差了个系数即四元数的模,如果旋转向量不是单位向量的话,这个系数并不是1。推测可能对最小化误差没有太大影响,或者后面会在信息矩阵中补上这个系数。接下来计算残差就是按上面的公式按部就班的编写代码即可,最后左乘信息矩阵( applyOnTheLeft() ),函数使用点击这里。

Create函数的作用
用来构造一个costfunction类,与一般不同的是,main函数里调用Create函数来构造costfunction.
定义求导方式,官方例程里定义的是自动求导方式,即ceres::AutoDiffCostFunction,<>里的参数是我们的PoseGraph2dErrorTerm类,和误差,优化变量的维数。

二、构造Problem

当costfunction搭建好后,给每个constraint都加入残差快AddResidualBlock, 官方例程没有用核函数,传入costfunction,传入待优化参数即可。

//传入的待优化参数是两个世界坐标系下的pose,类型为Pose3d,
// p是vector类型,访问数据用.data(),q是四元数类型,访问数据用.coeffs()得到内部的vector,再用.data()得到数据
problem->AddResidualBlock(cost_function, loss_function,
                          pose_begin_iter->second.p.data(),
                          pose_begin_iter->second.q.coeffs().data(),
                          pose_end_iter->second.p.data(),
                          pose_end_iter->second.q.coeffs().data());

四元数访问操作见注释,有关eigen四元数的更多信息,例如存储方式balabala请点击这里。

三、LocalParameterization搭建

在遍历迭代器之前,需给四元数new一个LocalParameterization,如下所示:

  //为四元数添加一个LocalParameterization,定义新的求导方式,求解雅可比矩阵
  //定义一个父类指针指向子类
  ceres::LocalParameterization* quaternion_local_parameterization =
      new EigenQuaternionParameterization;

与pose_graph_2d中不同的是,这里不需要自己定义类,因为EigenQuaternionParameterization是ceres::LocalParameterization的一个子类。遍历迭代器的时候设置了localparameterization,如下所示:

//为四元数设置localparameterization,进而设置新的求导方式,雅可比矩阵等..
    problem->SetParameterization(pose_begin_iter->second.q.coeffs().data(),
                                 quaternion_local_parameterization);
    problem->SetParameterization(pose_end_iter->second.q.coeffs().data(),
                                 quaternion_local_parameterization);

四、固定初始位姿

原因同pose_graph_2d中写的差不多,需要固定一个位姿,这样可以限制 gauge freedom。

problem->SetParameterBlockConstant(pose_start_iter->second.p.data());
problem->SetParameterBlockConstant(pose_start_iter->second.q.coeffs().data());

参考文献: http://ceres-solver.org/nnls_tutorial.html#robust-curve-fitting

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

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

相关文章

Android 课设之个人音乐播放器

第一章 绪论1.1选题背景由于时代快速发展&#xff0c;各种各样的音乐播放器层出不穷&#xff0c;此时需要一个可以根据个人爱好来播放的音乐播放器就尤为重要&#xff0c;因此我特意制作了一个根据自己喜好的音乐播放器&#xff0c;只需要把音乐文件放进制定的目录下即可。1.2开…

C++语法小笔记:内联函数,auto关键字,nullptr

目录 一.内联函数 1.回顾c语言中的“宏函数” 2.内联函数 3.内联函数的特性 二.C auto 关键字 1.auto的基本概念 2.auto使用的注意事项 3.auto不能使用的地方 三. C11中的 nullptr 一.内联函数 1.回顾c语言中的“宏函数” 先给出一段简单的代码&#xff1a; int Add(in…

plt设置柱状图标注

1、plt.text方法 在matplotlib 3.4.0之前的版本中&#xff0c;一般使用plt.text方法绘制数据标签。顾名思义&#xff0c;plt.text可以在图像的任何地方绘制指定的文本。基于此&#xff0c;我们只需要在相应数据点的坐标位置绘制相应的值&#xff0c;即可显示数据标签。 2、plt.…

react初始高阶组件

首先 我们要了解什么是高阶组件 第一 高阶组件必须是一个函数 第二 高阶组件接收一个参数&#xff0c;这个参数也必须是一个组件 第三 他的返回值 也是一个组件 至于高阶组件的作用 我们后续会讲解 本文只是带大家认识一下高阶组件 并手把手带大家创建一个 下面我们来创建一个…

微服务调用组件Feign学习笔记

目录 JAVA 项目中如何实现接口调用&#xff1f; 1. 什么是Feign 2. Spring Cloud Alibaba快速整合OpenFeign 3. Spring Cloud Feign的自定义配置及使用 4.自定义拦截器 5.超时时间配置 JAVA 项目中如何实现接口调用&#xff1f; 1&#xff09;Httpclient HttpClient 是 …

数据结构(模式匹配及相关算法)

目录 模式匹配 BF算法 算法实现 算法分析 KMP算法 问题的引入&#xff08;一&#xff09; 问题的引入&#xff08;二&#xff09; 问题的引入&#xff08;三&#xff09; 相关概念 计算失配函数的算法 算法思路 算法优点 模式匹配 函数int find(const sstring &am…

机器学习(三):人工智能主要分支

文章目录 人工智能主要分支 一、计算机视觉 二、语音识别 三、文本挖掘/分类 四、机器翻译 五、机器人 人工智能主要分支 通讯、感知与行动是现代人工智能的三个关键能力&#xff0c;在这里我们将根据这些能力/应用对这三个技术领域进行介绍&#xff1a; 计算机视觉(CV…

WiFi monitor模式的配置和运行检查(Ubuntu系统)

WiFi monitor模式的配置和运行检查1. WiFi monitor模式介绍2. WiFi monitor模式查看Step1&#xff1a;确保计算机上有安装硬件WiFi无线网卡Step2&#xff1a;安装必要的工具Step 3&#xff1a;iw list查看无线网卡是否支持monitor模式Step 4&#xff1a;配置WiFi monitor模式St…

有了独自开,我们离自己开发一套系统还会远吗

目录 一、结识独自开 二、独自开的介绍 三、独自开的需求 四、独自开注册流程 五、神仙公司独自开 一、结识独自开 算是机缘巧合&#xff0c;我被C站白佬拉入了他的聊天群&#xff0c;群内均是来自于CSDN的不同领域的优质作者&#xff0c;其中不乏相关领域工作多年的老工程…

“任性”华为 | 七十八岁老人的“四渡赤水”(二)

导读华为是一家“任性”的企业——因为任正非将自己的性格赋予了华为——在企业前进的每一个路口&#xff0c;都会看见这种性格的印记。2022年12月&#xff0c;当美国总统拜登出现在凤凰城出席台积电工厂迁机仪式上&#xff0c;苹果公司首席执行官库克等约900名政商界人士前往捧…

c#检测网络连接信息

用手机全屏看B站视频时可以看到右上角标识有WIFI&#xff0c;比较好奇如何检测当前网络连接是wifi还是数据网络什么的。于是百度相关信息&#xff0c;找到参考文献1-2&#xff0c;其中介绍采用Xamarin.Essentials检测网络连接性&#xff0c;其中的Connectivity类可用于监视设备…

【MATLAB】三维旋转的实现

1 三维旋转的表达方式 三维空间中常用的表示旋转的方式有&#xff1a; **[1]旋转矩阵(rotation matrix) [2]旋转向量(rotation vector&#xff09;/角轴&#xff08;轴角&#xff09;(axis angle) [3]欧拉角(euler angles) [4]四元数(quaternion)**主动旋转和被动旋转&#x…

现场工程师出手-PCAPHub与云IP实现异地LAN工业联测

在去年&#xff0c;因为众所周知的因素影响&#xff0c;项目的甲方主动提出延缓设备的交付。作为乙方&#xff0c;尽管项目延缓是甲方提出的&#xff0c;但依旧希望按期交付&#xff0c;这样才能回款&#xff0c;熬过一年。其实&#xff0c;2022年初&#xff0c;几类传感器、压…

Visual Studio 17.5 拼写检查器预览版现已推出,来说说你的看法吧

写在前面&#xff1a; Visual Studio17.5版本已添加拼写检查器功能&#xff0c;Visual Studio 中的许多功能旨在帮助你编写所需的代码。Visual Studio帮助你确保代码的编译&#xff0c;甚至可以帮助代码样式。现在它甚至可以确保您的拼写准确。Visual Studio 17.5 preview 3 引…

Linux常用命令——talk命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) talk 让用户和其他用户聊天 补充说明 talk命令是talk服务器的客户端工具&#xff0c;通过talk命令可以让用户和其他用户聊天。linux中talk命令参数程序的使用很简单&#xff0c;只要知道交谈对象的地址&#x…

plt自定义主要刻度值和次要刻度值

使用set_xticks方法和set_xticklabels方法即可 1、set_xticks方法 参数说明&#xff1a; set_xticks(self, ticks, minorFalse)参数1&#xff1a;ticks&#xff1a;指定刻度出现的位置参数2&#xff1a;minor&#xff1a;指定是否是次要刻度返回值1&#xff1a;包含XTick实例…

【数学建模】华为杯研究生数学建模备赛的一些建议

文章目录前言一、建模题目介绍1.1、题目数量1.2、题目种类1.3、题目难度1.4、题目选择二、笔者的备赛过程2.1.简单的题目&#xff08;本科比赛&#xff0c;学科大作业&#xff09;2.2.真题三、编程的备赛建议3.1.matlab和python的基础语法3.2.数据预处理3.3.常用的机器学习算法…

win10系统新增的几款非常有用的基础快捷键

win就是键盘的这个键winq或wins 打开搜索winw 打开白板、全屏截图wint 配合左右箭头←→&#xff0c;来回切换最小化窗口在任务栏的缩略图winu 设置显示器wini 打开设置winp 屏幕投影、扩展屏幕wina 查看最近消息通知winh 语音输入法wink 查找设备&#xff08;如&#xff1a;打…

线程池默认大小为CPU核数的2倍

1、前言 有位工作5年的小伙伴问我说&#xff0c;为什么Netty线程池默认大小为CPU核数的2倍&#xff0c;今天&#xff0c;我花2分钟时间给大家专门分享一下我对这个问题的理解。 另外&#xff0c;我花了1个多星期把往期的面试题解析配套文档准备好了&#xff0c;想获取的小伙伴…

Introduction to Multi-Armed Bandits——03 Thompson Sampling[1]

Introduction to Multi-Armed Bandits——03 Thompson Sampling[1] 参考资料 Russo D J, Van Roy B, Kazerouni A, et al. A tutorial on thompson sampling[J]. Foundations and Trends in Machine Learning, 2018, 11(1): 1-96. ts_tutorial 在线学习(MAB)与强化学习(RL)[…