(02)Cartographer源码无死角解析-(16) SensorBridge→Rigid3(刚体变换)

讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下:
(02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885
 
文 末 正 下 方 中 心 提 供 了 本 人 联 系 方 式 , 点 击 本 人 照 片 即 可 显 示 W X → 官 方 认 证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} WX
 

一、前言

通过上一篇博客,可以了解到,每条轨迹 (trajectory_id) 都对应一个 SensorBridge 类对象,其被存储于MapBuilderBridge 的成员变量 sensor_bridges_ 之中:

std::unordered_map<int, std::unique_ptr<SensorBridge>> sensor_bridges_;

SensorBridge 的初始化位于 MapBuilderBridge::AddTrajectory() 函数之中,代码如下:

  // Step: 2 为这个新轨迹 添加一个SensorBridge
  sensor_bridges_[trajectory_id] = absl::make_unique<SensorBridge>(
      trajectory_options.num_subdivisions_per_laser_scan,
      trajectory_options.tracking_frame,
      node_options_.lookup_transform_timeout_sec, 
      tf_buffer_,
      map_builder_->GetTrajectoryBuilder(trajectory_id)); // CollatedTrajectoryBuilder

SensorBridge 的实现位于 src/cartographer_ros/cartographer_ros/cartographer_ros/sensor_bridge.cc 文件中,在对齐进行讲解之前,先来看如下两个类:

//src/cartographer_ros/cartographer_ros/cartographer_ros/tf_bridge.cc
class TfBridge

//src/cartographer/cartographer/transform/rigid_transform.cc
class Rigid3

 

二、Rigid3

首先来看看其头文件 rigid_transform.h,该中实现了两个模板类

template <typename FloatType>
class Rigid3 {}

template <typename FloatType>
class Rigid2 {}

先从复杂的 Rigid3 说起。
Rigid3主要实现了如下几个接口(粗略看一下步骤即可,后面有代码注释):
( 1 ) : \color{blue}(1): (1) 共三个构造函数(一个默认,两个重载),默认构造函数平移与旋转设置都为0,重载构造函数可以通过传入平移与旋转进行初始化,旋转可以使用四元数或者轴角表示。但是最终都是以四元数的格式存储的。另外还有4个创建实例化对象的静态重载函数,单独传入平移和旋转都可以生成实例(没有传入的默认为0),另外以 std::array 格式同时传入平移与旋转也可创建实例化对象

( 2 ) : \color{blue}(2): (2) 实现静态函数 Identity(),返回平移与旋转都为0的实例。 实现类中的模板函数 Rigid3<OtherType> cast(),注意调用该函数的时候,需要使用 .template 关键字。

( 3 ) : \color{blue}(3): (3) 欧式变换群求逆函数 Rigid3 inverse() ,推导公式如下所示(代码注解在后面):
T = [ R t 0 1 ]                 设 T − 1 = [ A b c d ]              由 于 :    T T − 1 = E (01) \color{Green} \tag{01} \mathbf T =\begin{bmatrix} \mathbf R& \mathbf t\\ \\ 0 & 1 \end{bmatrix}~~~~~~~~~~~~~~~设 \mathbf T^{-1}=\begin{bmatrix} \mathbf A& \mathbf b\\ \\ c & d \end{bmatrix} ~~~~~~~~~~~~由于:~~\mathbf T \mathbf T^{-1}=\mathbf E T=R0t1               T1=Acbd            :  TT1=E(01) 所 以 { R A + c t = E c = 0 R b + d t = 0 d = 1          得 : { A = R − 1 t = − R − 1 t         所 以 : T − 1 = [ R − 1 − R − 1 t 0 1 ] (02) \color{Green} \tag{02}所以 \begin{cases} \mathbf R \mathbf A + c\mathbf t=\mathbf E\\ c=0\\ \mathbf R \mathbf b+d \mathbf t=0\\ d=1 \end{cases}~~~~~~~~得: \begin{cases} \mathbf A=\mathbf R^{-1} \\ \\ \mathbf t=-\mathbf R^{-1}\mathbf t\\ \end{cases}~~~~~~~所以:\mathbf T^{-1}=\begin{bmatrix} \mathbf R^{-1}& -\mathbf R^{-1}\mathbf t\\ \\ 0 & 1 \end{bmatrix} RA+ct=Ec=0Rb+dt=0d=1        :A=R1t=R1t       T1=R10R1t1(02)

( 4 ) : \color{blue}(4): (4) 另外对模板类 Rigid3<FloatType> 还实现了 ‘ ∗ * ’ 操作函数,即 operator ∗ * 函数,其有两个重载函数,其一:

template <typename FloatType>
Rigid3<FloatType> operator*(const Rigid3<FloatType>& lhs,
                            const Rigid3<FloatType>& rhs) 

该函数主要作用为两个欧式变换群相乘法,推导过程如下:
T a = [ R a t a 0 1 ]         T b = [ R b t b 0 1 ]         T a T b = [ R a R b R a t b + t a 0 1 ] (03) \color{Green} \tag{03} \mathbf T_a =\begin{bmatrix} \mathbf R_a& \mathbf t_a\\ \\ 0 & 1 \end{bmatrix}~~~~~~~\mathbf T_b =\begin{bmatrix} \mathbf R_b& \mathbf t_b\\ \\ 0 & 1 \end{bmatrix}~~~~~~~\mathbf T_a\mathbf T_b=\begin{bmatrix} \mathbf R_a \mathbf R_b& \mathbf R_a \mathbf t_b+\mathbf t_a\\ \\ 0 & 1 \end{bmatrix} Ta=Ra0ta1       Tb=Rb0tb1       TaTb=RaRb0Ratb+ta1(03)

( 5 ) : \color{blue}(5): (5) 另外还有一个 operator ∗ * 函数得重载:

template <typename FloatType>
typename Rigid3<FloatType>::Vector operator*(
    const Rigid3<FloatType>& rigid,
    const typename Rigid3<FloatType>::Vector& point) 

其就是把点 p \mathbf p p 进行坐标变换,即 p n e w = R p + t \mathbf p_{new}=\mathbf R \mathbf p+ \mathbf t pnew=Rp+t

( 6 ) : \color{blue}(6): (6) RollPitchYaw 函数,把欧拉角转换成四元数。
 
代码的注释如下:

template <typename FloatType>
class Rigid3 {
 public:
  using Vector = Eigen::Matrix<FloatType, 3, 1>; //用Vector代替表示Eigen中的旋转矩阵
  using Quaternion = Eigen::Quaternion<FloatType>; //用Quaternion代替表示Eigen中的四元数
  using AngleAxis = Eigen::AngleAxis<FloatType>; //用AngleAxis代替表示Eigen中的轴角
  
  //默认构造函数,对平移translation_与旋转rotation_两个变量通过初始化列表进行初始化,全为0
  Rigid3() : translation_(Vector::Zero()), rotation_(Quaternion::Identity()) {}
  //构造函数重载,传入一个向量表示的平移translation, 与四元数表示的旋转进行初始化
  Rigid3(const Vector& translation, const Quaternion& rotation)
      : translation_(translation), rotation_(rotation) {}
  //构造函数重载,传入一个向量表示的平移translation, 与与轴角表示的旋转
  Rigid3(const Vector& translation, const AngleAxis& rotation)
      : translation_(translation), rotation_(rotation) {}

  //声明该为静态函数,该函数可以通过Rigid3::Rotation()直接进行调用,
  //而非必须创建实例之后才能调用,理解为python中的类函数,注意其没有this指针
  static Rigid3 Rotation(const AngleAxis& angle_axis) {
    return Rigid3(Vector::Zero(), Quaternion(angle_axis));
  }
  //该为重载函数,作用与上一函数一样,就是根据传入的参数创建一个Rigid3实例返回,
  //该实例平移初始值都为0, 旋转使用传入的参数进行表示
  static Rigid3 Rotation(const Quaternion& rotation) {
    return Rigid3(Vector::Zero(), rotation);
  }
  //根据传入的参数创建一个Rigid3实例返回,
  //该实例平移为传入的vector,旋转初始化全为0
  static Rigid3 Translation(const Vector& vector) {
    return Rigid3(vector, Quaternion::Identity());
  }
  //根据以数组形式传入的四元素旋转rotation,以及平移translation构建一个实例
  static Rigid3 FromArrays(const std::array<FloatType, 4>& rotation,
                           const std::array<FloatType, 3>& translation) {
    return Rigid3(Eigen::Map<const Vector>(translation.data()),
                  Eigen::Quaternion<FloatType>(rotation[0], rotation[1],
                                               rotation[2], rotation[3]));
  }
  //创建一个初始化全为0的Rigid3实例
  static Rigid3<FloatType> Identity() { return Rigid3<FloatType>(); }

  //该函数主要实现数据的类型转换,把原来的数据类型转化为OtherType
  template <typename OtherType> 
  Rigid3<OtherType> cast() const {
    //.template的用法比较简单,因为cast<OtherType>() 为 Eigen::Matrix 实例对象的
    //模板函数,所以使用.template声明,告诉编译器,接下来要调用的是一个类中实现的模板函数。
    //如果直接调用 translation_.cast<OtherType>() 会报错如下:
    //error: expected primary-expression before ‘>’ token,
    //简单的说就是编译器弄不清楚translation_.cast后面'<'是解析成模板还是解析成小于符号
    return Rigid3<OtherType>(translation_.template cast<OtherType>(),
                             rotation_.template cast<OtherType>());
  }

  //const修饰返回值,表示返回值不能被修改,只能赋值给其他变量
  //const修饰函数体,或者花括号,表示函数体或者花括号中,都是常量操作,
  //且其中只能调用使用const修饰的函数。另外这里返回的变量为应勇类型
  const Vector& translation() const { return translation_; } //返回平移向量
  const Quaternion& rotation() const { return rotation_; } //返回四元数表示的旋转

  // T = [R t]      T^-1 = [R^-1  -R^-1*t]
  //     [0 1]             [0         1  ] 
  // R是旋转矩阵, 特殊正交群, 所以R^-1 = R^T
  Rigid3 inverse() const {
    const Quaternion rotation = rotation_.conjugate(); //共轭,等价于旋转矩阵求逆
    const Vector translation = -(rotation * translation_);
    return Rigid3(translation, rotation); //返回欧式变换群的逆
  }

  std::string DebugString() const { //absl::Substitute 是一个高效的字符串替换函数,用于调试信息的打印
    return absl::Substitute("{ t: [$0, $1, $2], q: [$3, $4, $5, $6] }",
                            translation().x(), translation().y(),
                            translation().z(), rotation().w(), rotation().x(),
                            rotation().y(), rotation().z());
  }

  bool IsValid() const { //检测这些数据是否有效,如平移的xyz不能为nan,四元数各个元素平方和为1。
    return !std::isnan(translation_.x()) && !std::isnan(translation_.y()) &&
           !std::isnan(translation_.z()) &&
           std::abs(FloatType(1) - rotation_.norm()) < FloatType(1e-3);
  }

 private:
  Vector translation_; //平移私有成员变量
  Quaternion rotation_; //旋转私有成员变量
};


//实现模板类Rigid3的 '*' 操作,该操作为两个 Rigid3 实例进行 '*' 运算
//lhs(Left Hand Side)表示乘法操作的左值,  rhs(Right Hand Side)表示乘法操作的右值
// Tlhs=[Rl tl]   Trhs = [Rr  tr]    Tlhs*Trhs=[Rl*Rr      Rl*tr+tl]
//      [0  1 ]          [0   1 ]              [0            1     ]
// Tlhs 与 Trhs 都是欧式变换群
template <typename FloatType>
Rigid3<FloatType> operator*(const Rigid3<FloatType>& lhs,
                            const Rigid3<FloatType>& rhs) {
  return Rigid3<FloatType>(
      lhs.rotation() * rhs.translation() + lhs.translation(),
      (lhs.rotation() * rhs.rotation()).normalized());
}

//该函数的功能为对一个3维点进行欧式变换
//p_new = R*p + t 
template <typename FloatType>
typename Rigid3<FloatType>::Vector operator*(
    const Rigid3<FloatType>& rigid,
    const typename Rigid3<FloatType>::Vector& point) {
  return rigid.rotation() * point + rigid.translation();
}

// This is needed for gmock.  //实现cout打印与输出功能
template <typename T>
std::ostream& operator<<(std::ostream& os,
                         const cartographer::transform::Rigid3<T>& rigid) {
  os << rigid.DebugString();
  return os;
}

using Rigid3d = Rigid3<double>;  //类似于Eigen中的设计
using Rigid3f = Rigid3<float>;

// Converts (roll, pitch, yaw) to a unit length quaternion. Based on the URDF
// specification http://wiki.ros.org/urdf/XML/joint.
Eigen::Quaterniond RollPitchYaw(double roll, double pitch, double yaw);

// Returns an transform::Rigid3d given a 'dictionary' containing 'translation'
// (x, y, z) and 'rotation' which can either we an array of (roll, pitch, yaw)
// or a dictionary with (w, x, y, z) values as a quaternion.
Rigid3d FromDictionary(common::LuaParameterDictionary* dictionary);

 

三、Rigid2

在了解了Rigid3之后,在来了解Rigid2就比较简单了。class Rigid2 这个模板类主要实现2维的刚性变换。三维空间中表示旋转,使用的是四元数。在2维空间表示旋转只需要一个角度就可以了,变量对应如下代码:

  using Rotation2D = Eigen::Rotation2D<FloatType>;
  Rotation2D rotation_;

另外,对于二变换来说来说,推导公式还是与前面一样的,只是这里的 R \mathbf R R 是 2x2 的矩阵,如下所示
T − 1 = [ R − 1 − R − 1 t 0 1 ] (04) \color{Green} \tag{04} \mathbf T^{-1}=\begin{bmatrix} \mathbf R^{-1}& -\mathbf R^{-1}\mathbf t\\ \\ 0 & 1 \end{bmatrix} T1=R10R1t1(04)又因为在代码中, R \mathbf R R 使用 Rotation2D rotation_表示,其实际就是一个角度,所以对其求逆,就是在该角度的前面加个负号就可以,所以 Rigid2 inverse()::Rigid2 inverse() 的代码实现如下:

  // T = [R t] T^-1 = [R^-1  -R^-1 * t]
  //     [0 1]        [0         1    ] 
  // R是旋转矩阵, 特殊正交群, 所以R^-1 = R^T
  Rigid2 inverse() const {
    const Rotation2D rotation = rotation_.inverse();
    const Vector translation = -(rotation * translation_);
    return Rigid2(translation, rotation);
  }

其他的实现与 Rigid3 基本都比较类似,这里就不进行细致的讲解了。
 

四、结语

对 /src/cartographer/cartographer/transform/rigid_transform.cc 文件中的 Rigid3(刚体变换) 进行了详细的简介,接下来还要对 /src/cartographer_ros/cartographer_ros/cartographer_ros/tf_bridge.cc 中的 class TfBridge 进行讲解。

 
 
 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在cartographer中,使用2D点云进行扫描匹配时,可以使用ceresscanmatch功能。这个功能是基于Ceres Solver库实现的。Ceres Solver是一个非线性优化库,用于解决各种最小化问题。在cartographer中,ceresscanmatch被用于解决2D点云匹配的问题。 具体来说,ceresscanmatch用于匹配两个相邻帧的2D点云。在进行扫描匹配时,需要先对数据进行滤波处理,然后使用ceres进行优化,找到两个点云之间的最佳匹配。在这个过程中,需要使用一种优化算法来最小化匹配误差,这个误差是通过计算点云之间的距离来得到的。 相比于其他扫描匹配方法,ceresscanmatch的优势在于它能够进行非常精准的匹配。这是因为它使用了一个非线性优化算法,能够处理复杂的误差函数和约束条件。此外,ceresscanmatch还支持使用多种不同的误差函数,以适应不同的应用场景。 总之,ceresscanmatch是cartographer中用于2D点云扫描匹配的一个非常重要的功能,它能够让我们更加准确、稳定地进行扫描匹配,并且支持广泛的应用场景。 ### 回答2: 本文将继续介绍cartographer中的ceres扫描匹配部分,ceres扫描匹配是利用Ceres Solver进行的位姿优化,可以准确估计机器人运动的姿态。 ceres扫描匹配部分主要包括ceres_scan_matcher.cc和ceres_scan_matcher.h两个文件。其中ceres_scan_matcher.cc包含了ceres扫描匹配算法的具体实现,而ceres_scan_matcher.h则是相关的头文件。 ceres_scan_matcher.cc中的函数主要有两个,分别是CeresScanMatcher::Match()和CeresScanMatcher::MatchFullSubmap()。其中,CeresScanMatcher::Match()函数用于实现一次扫描匹配,输入参数为当前激光数据和候选的位姿,输出参数为匹配的位姿和评估值。 在CeresScanMatcher::Match()函数中,先通过叶芽上下文来获取轨迹和submap,然后将当前激光数据换为点云,并对点云进行滤波和预处理,接着定义优化问题和相关的参数,其中优化问题使用ceres::Problem类来定义,相关参数则定义在CeresScanMatcherOptions结构体中,最后通过ceres::Solve()函数进行位姿优化。 CeresScanMatcher::MatchFullSubmap()函数则用于在整个submap上进行匹配,并返回匹配的位姿和评估值。它的实现与CeresScanMatcher::Match()函数类似,只是输入参数为整个submap的信息。 综上所述,ceres扫描匹配部分利用Ceres Solver进行位姿优化,可以准确估计机器人运动的姿态,是cartographer中重要的功能之一。 ### 回答3: cartographer是一款开源的SLAM系统,其源代码完整透明,方便研究和理解。其中,2D点云扫描匹配是cartographer中的一个重要功能,而这一功能又是由ceres扫描匹配实现的。 ceresscanmatch是cartographer中的一个重要模块,用于实现2D点云的扫描匹配。在这个模块中,ceres solver被用来进行优化过程。具体来说,ceresscanmatch会将已知位姿下的实测点云与预测的点云进行匹配,得到匹配误差。随后,ceres solver会对这些匹配误差进行非线性优化,最终得到最优位姿。这样,就能够实现快速准确的2D点云扫描匹配,从而提高了SLAM系统的性能和精度。 在详细研究ceresscanmatch之前,首先需要了解一下ceres solver。ceres solver是一个基于C++的非线性优化库,用于解决复杂的数值优化问题。在cartographer中,ceres solver被用来进行扫描匹配的优化过程,应用目标函数和求解器来寻求最优解。其中,目标函数是由误差项和状态变量构成的,求解器则用来解决这个目标函数并确定状态变量的最优化值。 具体而言,在cartographer中,扫描匹配的目标函数是根据传感器数据得出的,其包括一系列误差项和参考帧的相对位姿。在每个迭代步骤中,ceres solver会计算目标函数的梯度和海森矩阵,并利用这些值来更新参考帧的位姿。当误差项最小化时,相对位姿就能够得到最优解。 总之,ceresscanmatch是cartographer中的一个重要模块,用于实现2D点云的扫描匹配。借助ceres solver进行优化,可以实现高效准确的扫描匹配,为SLAM系统的实现提供了重要的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江南才尽,年少无知!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值