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

news/2025/1/2 3:25:10/

问题说明

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_NEWprivate:// 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.ppmy.cn/news/18522.html

相关文章

23种设计模式(十四)——中介者模式【接口隔离】

文章目录 意图什么时候使用中介者真实世界类比中介者模式的实现中介者模式的优缺点亦称:调解人、控制器、Intermediary、Controller、Mediator 意图 用一个中介者对象来封装一系列的对象交互。使得各对象不需要显式地相互引用,从而使其松散耦合,而且可以独立地改变它们之间…

第六层:继承

文章目录前情回顾继承继承的作用继承的基本语法继承方式公共继承保护继承私有继承继承中的对象模型继承中的构造和析构顺序继承中同名成员访问非静态成员静态成员多继承语法注意多继承中的对象模型多继承父类成员名相同菱形继承概念菱形继承出现的问题虚继承步入第七层本章知识…

【论文翻译】ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation

【论文】https://arxiv.org/abs/2204.12484v3 【github】GitHub - ViTAE-Transformer/ViTPose: The official repo for [NeurIPS22] "ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation" and [Arxiv22] "ViTPose: Vision Transformer F…

让交互更加生动!巧用CSS实现鼠标跟随 3D 旋转效果

简单分析一下&#xff0c;这个交互效果主要有两个核心&#xff1a; 借助了 CSS 3D 的能力 元素的旋转需要和鼠标的移动相结合 本人简单的说一下如何使用纯 CSS 实现类似的交互效果&#xff0c;以及&#xff0c;借助 JavaScript 绑定鼠标事件&#xff0c;快速还原上述效果。 …

代码重构之路 --我的2022年总结

2022年是我正式参加工作的第10个年头&#xff0c;也是我在CSDN上写博客的第11个年头。在这10余年的时间里&#xff0c;虽然在工作上遇到了各种情况&#xff0c;但我一直坚持输出、坚持分享&#xff0c;一共在CSDN上发表了530多篇原创博文。在这些文章中&#xff0c;大部分都是与…

2023-1-21 刷题情况

最少侧跳次数 首先祝大家除夕快乐。 题目描述 给你一个长度为 n 的 3 跑道道路 &#xff0c;它总共包含 n 1 个 点 &#xff0c;编号为 0 到 n 。一只青蛙从 0 号点第二条跑道 出发 &#xff0c;它想要跳到点 n 处。然而道路上可能有一些障碍。 给你一个长度为 n 1 的数组…

奶牛大学(2023寒假每日一题 1)

Farmer John 计划为奶牛们新开办一所大学&#xff01;有 NNN 头奶牛可能会入学。 每头奶牛最多愿意支付 cic_ici​ 的学费。 Farmer John 可以设定所有奶牛入学需要支付的学费。 如果这笔学费大于一头奶牛愿意支付的最高金额&#xff0c;那么这头奶牛就不会入学。 Farmer J…

【Java开发】Spring Cloud 06 :分布式配置管理-Nacos Config

在微服务架构中&#xff0c;我们会使用一个分布式的“配置中心”来管理所有的配置文件和配置项&#xff0c;本章节将介绍 Nacos 配置中心的特性&#xff0c;以及这些特性在微服务体系中所发挥的作用。在 Spring Boot 应用中&#xff0c;我们习惯于使用传统的配置管理方式&#…