当前位置: 首页 > news >正文

Cartographer 位姿推测器pose_extrapolator

目录

1. local_trajectory_builder_2d.cc

2. imu_tracker.h

3. imu_tracker.cc

3.1 构造函数

3.2 ImuTracker::Advance()

3.3 ImuTracker::AddImuAngularVelocityObservation()

3.4 ImuTracker::AddImuLinearAccelerationObservation()

4. pose_extrapolator.h

5. pose_extrapolator.cc

5.1 构造函数

5.2 PoseExtrapolator::InitializeWithImu()

5.3 PoseExtrapolator::AddImuData()

5.4 PoseExtrapolator::TrimImuData()

5.5 AddImuData()在何处调用

5.6 PoseExtrapolator::AddOdometryData()

5.6.1 PoseExtrapolator::ExtrapolateRotation()

5.6.2 PoseExtrapolator::AdvanceImuTracker()

5.6.3 PoseExtrapolator::AddOdometryData()

5.7 PoseExtrapolator::TrimOdometryData()

5.8 AddOdometryData()在何处调用

5.9 PoseExtrapolator::AddPose()

5.10 PoseExtrapolator::ExtrapolatePose()

5.11 PoseExtrapolator::EstimateGravityOrientation()

5.12 PoseExtrapolator::ExtrapolatePosesWithGravity()


1. local_trajectory_builder_2d.cc

        在LocalTrajectoryBuilder2D::AddRangeData()对点云去畸变时:

std::unique_ptr<LocalTrajectoryBuilder2D::MatchingResult>
LocalTrajectoryBuilder2D::AddRangeData(const std::string& sensor_id,const sensor::TimedPointCloudData& unsynchronized_data) {
......range_data_poses.push_back(extrapolator_->ExtrapolatePose(time_point).cast<float>());
......
}

        在LocalTrajectoryBuilder2D::AddAccumulatedRangeData()执行扫描匹配时:

std::unique_ptr<LocalTrajectoryBuilder2D::MatchingResult>
LocalTrajectoryBuilder2D::AddAccumulatedRangeData(const common::Time time,const sensor::RangeData& gravity_aligned_range_data,const transform::Rigid3d& gravity_alignment,const absl::optional<common::Duration>& sensor_duration) {
......// 进行位姿的预测, 获取先验位姿const transform::Rigid3d non_gravity_aligned_pose_prediction =extrapolator_->ExtrapolatePose(time);
......
}

        都要通过位姿推测器获取根据imu和odometry的数据计算得到的机器人的先验位姿,之前笔记中没有说明pose_extrapolator的计算过程,现在通过代码分析一下。

2. imu_tracker.h

        pose_extrapolator中用到了imu_tracker, 计算IMU 的角速度和线加速度数据,实时维护机器人的姿态、重力方向等状态。

namespace cartographer {
namespace mapping {// Keeps track of the orientation using angular velocities and linear
// accelerations from an IMU. Because averaged linear acceleration (assuming
// slow movement) is a direct measurement of gravity, roll/pitch does not drift,
// though yaw does.
// 使用来自IMU的角速度和线性加速度跟踪方向
// 由于平均线性加速度(假设缓慢移动)是重力的直接量度, 因此, 横摆/俯仰不会漂移, 尽管偏航会漂移/*** @brief * ImuTracker 的主要作用是根据 IMU的角速度来预测姿态,* 并根据IMU的线加速度来确定重力的方向, 并使用重力的方向来对姿态进行校准*/
class ImuTracker {public:ImuTracker(double imu_gravity_time_constant, common::Time time);// Advances to the given 'time' and updates the orientation to reflect this.void Advance(common::Time time);// Updates from an IMU reading (in the IMU frame).void AddImuLinearAccelerationObservation(const Eigen::Vector3d& imu_linear_acceleration);void AddImuAngularVelocityObservation(const Eigen::Vector3d& imu_angular_velocity);// Query the current time.// 获取上一次预测位姿的时间戳common::Time time() const { return time_; }// Query the current orientation estimate.// 查询当前估计出的姿态Eigen::Quaterniond orientation() const { return orientation_; }private:const double imu_gravity_time_constant_;common::Time time_;common::Time last_linear_acceleration_time_;Eigen::Quaterniond orientation_;                 // 上一时刻的姿态(朝向/旋转)Eigen::Vector3d gravity_vector_;                  // 重力方向Eigen::Vector3d imu_angular_velocity_;   // 角速度 
};}  // namespace mapping
}  // namespace cartographer

3. imu_tracker.cc

3.1 构造函数

ImuTracker::ImuTracker(const double imu_gravity_time_constant,const common::Time time): imu_gravity_time_constant_(imu_gravity_time_constant),time_(time),last_linear_acceleration_time_(common::Time::min()),orientation_(Eigen::Quaterniond::Identity()), // 初始方向角gravity_vector_(Eigen::Vector3d::UnitZ()),    // 重力方向初始化为[0,0,1]imu_angular_velocity_(Eigen::Vector3d::Zero()) {}

         imu_gravity_time_constant是用于重力向量平滑的时间常数。在ImuTracker::AddImuLinearAccelerationObservation() 中通过指数滑动平均融合重力观测时使用。

        time_是 ImuTracker 当前数据的时间戳。

        last_linear_acceleration_time_记录上一次接收 IMU 线加速度数据的时间

        orientation_初始化值为单位四元数,为后续基于角速度的姿态预测提供起点。

        gravity_vector_初始化值为(0, 0, 1) 向量,是初始重力向量的假设值,默认重力沿世界坐标系 Z 轴向上。后续会通过 IMU 线加速度观测不断校准,逐步逼近真实重力方向,是姿态校准的参考。

        imu_angular_velocity_记录当前 IMU 的角速度观测值,初始化为零。

3.2 ImuTracker::Advance()

        基于当前角速度,将 ImuTracker的状态(姿态、重力向量)从当前时间预测到目标时间time。

void ImuTracker::Advance(const common::Time time) {CHECK_LE(time_, time);// 1. 输入目标时间 time,计算当前跟踪器时间(time_)到目标时间的时间差const double delta_t = common::ToSeconds(time - time_);// 2. 姿态预测// 用当前存储的上一时刻的角速度乘以时间,得到当前时刻相对于上一时刻的预测的// 旋转量(角轴向量),再转换为四元数表示的旋转:const Eigen::Quaterniond rotation =transform::AngleAxisVectorToRotationQuaternion(Eigen::Vector3d(imu_angular_velocity_ * delta_t));// 使用上一时刻的姿态 orientation_ 乘以姿态变化量,得到目标时间的预测姿态// person:( 四元数乘法等价于 “旋转的复合”,即连续两次旋转的叠加。)orientation_ = (orientation_ * rotation).normalized();// 3. 重力向量预测// 重力向量会随姿态旋转而变化,用旋转量的共轭(逆旋转)更新重力向量//(表示姿态旋转后重力向量的新方向):gravity_vector_ = rotation.conjugate() * gravity_vector_;// 4. 更新时间time_ = time;
}

3.3 ImuTracker::AddImuAngularVelocityObservation()

// 更新角速度
void ImuTracker::AddImuAngularVelocityObservation(const Eigen::Vector3d& imu_angular_velocity) {imu_angular_velocity_ = imu_angular_velocity;
}

3.4 ImuTracker::AddImuLinearAccelerationObservation()

        函数的核心功能是利用 IMU 线加速度观测值优化重力向量估计,并校准机器人姿态,确保姿态与重力方向一致。

/*** @brief 更新线性加速度的值,并根据重力的方向对上一时刻的姿态进行校准* * @param[in] imu_linear_acceleration imu的线加速度的大小*/
void ImuTracker::AddImuLinearAccelerationObservation(const Eigen::Vector3d& imu_linear_acceleration) {// 1. 计算时间差const double delta_t =last_linear_acceleration_time_ > common::Time::min()? common::ToSeconds(time_ - last_linear_acceleration_time_): std::numeric_limits<double>::infinity();last_linear_acceleration_time_ = time_;        // 更新时间// 2.计算平滑系数。delta_t越大, alpha越大const double alpha = 1. - std::exp(-delta_t / imu_gravity_time_constant_);// 3. 更新重力向量(gravity_vector_)gravity_vector_ =(1. - alpha) * gravity_vector_ + alpha * imu_linear_acceleration;// Change the 'orientation_' so that it agrees with the current// 'gravity_vector_'.// 4. 计算姿态校准旋转量const Eigen::Quaterniond rotation = FromTwoVectors(gravity_vector_, orientation_.conjugate() * Eigen::Vector3d::UnitZ());// 5. 使用这个旋转量来校准当前的姿态(orientation_)orientation_ = (orientation_ * rotation).normalized();// 6. 验证校准有效性// note: glog CHECK_GT: 第一个参数要大于第二个参数// 通过 CHECK_GT 检查校准后姿态与重力向量的乘积的 Z 分量是否接近 1(>0.99),确保重力方向大致沿 Z 轴,验证姿态校准的合理性。CHECK_GT((orientation_ * gravity_vector_).z(), 0.);CHECK_GT((orientation_ * gravity_vector_).normalized().z(), 0.99);
}

        其中,采用指数滑动平均融合历史重力向量与当前线加速度观测:gravity_vector_ = (1 - alpha) * 历史重力向量 + alpha * 当前线加速度。因为 IMU 线加速度包含重力分量,此操作可得到更稳定的重力方向估计。

        在 “4.计算姿态校准旋转量” 这一步中,核心是找到一个旋转(四元数),让 “更新后的重力向量” 与 “当前姿态所期望的重力方向” 对齐,为后续校准机器人姿态做准备。

        orientation_是 “机器人坐标系到世界坐标系的旋转”,记作R_{world-robot}。其共轭(orientation_.conjugate())相当于逆旋转,是 “世界坐标系到机器人坐标系的旋转”,记作R_{robot-world}

orientation_.conjugate() * Eigen::Vector3d::UnitZ()

        这句运算,相当于将初始重力向量转换到机器人坐标系下,得到 “当前姿态认为的重力方向”。

   FromTwoVectors(a, b)函数的作用是计算一个旋转四元数,使得向量a经过该旋转后与向量b重合。其计算出的rotation用于修正orientation_暗示的重力方向与优化后的实际重力向量完全一致,从而实现姿态的校准:

orientation_ = (orientation_ * rotation).normalized();

4. pose_extrapolator.h

class PoseExtrapolator : public PoseExtrapolatorInterface {
public:explicit PoseExtrapolator(common::Duration pose_queue_duration,double imu_gravity_time_constant);PoseExtrapolator(const PoseExtrapolator&) = delete;PoseExtrapolator& operator=(const PoseExtrapolator&) = delete;// 使用imu数据进行PoseExtrapolator的初始化static std::unique_ptr<PoseExtrapolator> InitializeWithImu(common::Duration pose_queue_duration, double imu_gravity_time_constant,const sensor::ImuData& imu_data);// Returns the time of the last added pose or Time::min() if no pose was added// yet.common::Time GetLastPoseTime() const override;            // 返回上次校准位姿(将扫描匹配pose送入推测器)的时间common::Time GetLastExtrapolatedTime() const override;    // 返回上一次预测位姿的时间// 添加观测数据void AddPose(common::Time time, const transform::Rigid3d& pose) override;void AddImuData(const sensor::ImuData& imu_data) override;void AddOdometryData(const sensor::OdometryData& odometry_data) override;// 预测得到time时刻 tracking frame 在 local 坐标系下的位姿transform::Rigid3d ExtrapolatePose(common::Time time) override;// 推测机器人一系列的位姿ExtrapolationResult ExtrapolatePosesWithGravity(const std::vector<common::Time>& times) override;// Returns the current gravity alignment estimate as a rotation from// the tracking frame into a gravity aligned frame.// 机器人相对重力方向的姿态Eigen::Quaterniond EstimateGravityOrientation(common::Time time) override;private:void UpdateVelocitiesFromPoses();                 // 校准推测器,根据pose队列计算tracking frame 在 local坐标系下的线速度与角速度void TrimImuData();                               // 丢弃过期数据void TrimOdometryData();// 更新imu_tracker的状态, 并根据imu_data队列的数据将imu_tracker的状态预测到time时刻void AdvanceImuTracker(common::Time time, ImuTracker* imu_tracker) const;// 计算从imu_tracker到time时刻的姿态变化量Eigen::Quaterniond ExtrapolateRotation(common::Time time,ImuTracker* imu_tracker) const;Eigen::Vector3d ExtrapolateTranslation(common::Time time);// 保存一定时间内的poseconst common::Duration pose_queue_duration_;struct TimedPose {common::Time time;transform::Rigid3d pose;};std::deque<TimedPose> timed_pose_queue_;              // 保存扫描匹配得到的的位姿// 线速度有2种计算途径  // person: 通过扫描匹配得到的位姿计算速度Eigen::Vector3d linear_velocity_from_poses_ = Eigen::Vector3d::Zero();Eigen::Vector3d angular_velocity_from_poses_ = Eigen::Vector3d::Zero();const double gravity_time_constant_;std::deque<sensor::ImuData> imu_data_;                // 保存odom数据std::unique_ptr<ImuTracker> imu_tracker_;                                 // 保存与预测当前姿态std::unique_ptr<ImuTracker> odometry_imu_tracker_;          // 用于计算里程计的姿态的ImuTrackerstd::unique_ptr<ImuTracker> extrapolation_imu_tracker_;   // 用于预测姿态的ImuTrackerTimedPose cached_extrapolated_pose_;std::deque<sensor::OdometryData> odometry_data_;      // 保存odom数据Eigen::Vector3d linear_velocity_from_odometry_ = Eigen::Vector3d::Zero();Eigen::Vector3d angular_velocity_from_odometry_ = Eigen::Vector3d::Zero();
};

        类PoseExtrapolator继承了接口PoseExtrapolatorInterface,对其中的接口函数进行了实现。接下来一个一个看函数功能。

5. pose_extrapolator.cc

5.1 构造函数

/*** @brief 构造函数* * @param[in] pose_queue_duration 时间差 0.001s* @param[in] imu_gravity_time_constant 10*/
PoseExtrapolator::PoseExtrapolator(const common::Duration pose_queue_duration,double imu_gravity_time_constant): pose_queue_duration_(pose_queue_duration),gravity_time_constant_(imu_gravity_time_constant),cached_extrapolated_pose_{common::Time::min(),transform::Rigid3d::Identity()} {}

        pose_queue_duration代表位姿队列的持续时间:

  // 保持pose队列中第二个pose的时间要大于 time - pose_queue_duration_while (timed_pose_queue_.size() > 2 && // timed_pose_queue_最少是2个数据timed_pose_queue_[1].time <= time - pose_queue_duration_) {timed_pose_queue_.pop_front();}

        time是最新位姿的时间。imu_gravity_time_constant代表 IMU 重力时间常数,后面再看其具体怎么使用。cached_extrapolated_pose_保存着上一次预测得到的位姿。这里的pose都是带时间戳的结构体:

  struct TimedPose {common::Time time;transform::Rigid3d pose;};

5.2 PoseExtrapolator::InitializeWithImu()

        这个函数的作用是使用imu数据进行PoseExtrapolator的初始化,但cartographer中并没有用到,就先跳过。

5.3 PoseExtrapolator::AddImuData()

// 向imu数据队列中添加imu数据,并进行数据队列的修剪
void PoseExtrapolator::AddImuData(const sensor::ImuData& imu_data) {CHECK(timed_pose_queue_.empty() ||imu_data.time >= timed_pose_queue_.back().time);imu_data_.push_back(imu_data);TrimImuData();
}

        timed_pose_queue_保存的是扫描匹配后得到的准确位姿,接下来要对之后的 扫描匹配 和 点云去畸变操作 提供先验的推测位姿,就需要新的imu数据时间戳 大于 队列最后的扫描匹配后得到的准确位姿的时间。

5.4 PoseExtrapolator::TrimImuData()

// 修剪imu的数据队列,丢掉过时的imu数据
void PoseExtrapolator::TrimImuData() {// 保持imu队列中第二个数据的时间要大于最后一个位姿的时间, imu_data_最少是1个while (imu_data_.size() > 1 && !timed_pose_queue_.empty() &&imu_data_[1].time <= timed_pose_queue_.back().time) {imu_data_.pop_front();}
}

        imu提供的是瞬时的线性加速度和角速度,单个 IMU 数据即可提供基础运动信息。当队列中只剩 1 个数据时,循环会终止,不会继续删除。避免将队列清空,确保始终保留 最新的那个 IMU 数据。

5.5 AddImuData()在何处调用

        在local_trajectory_builder_2d.cc的LocalTrajectoryBuilder2D::AddImuData()函数中被调用:

// 将IMU数据加入到Extrapolator中
void LocalTrajectoryBuilder2D::AddImuData(const sensor::ImuData& imu_data) {CHECK(options_.use_imu_data()) << "An unexpected IMU packet was added.";InitializeExtrapolator(imu_data.time);extrapolator_->AddImuData(imu_data);
}

5.6 PoseExtrapolator::AddOdometryData()

// 向odom数据队列中添加odom数据,并进行数据队列的修剪,并计算角速度与线速度
void PoseExtrapolator::AddOdometryData(const sensor::OdometryData& odometry_data) {// 1. 时间顺序检查CHECK(timed_pose_queue_.empty() ||odometry_data.time >= timed_pose_queue_.back().time);odometry_data_.push_back(odometry_data);// 2. 加入队列并修剪过时数据TrimOdometryData();// 3. 检查数据量,确保可计算速度。数据队列中至少有2个数据if (odometry_data_.size() < 2) {return;}// TODO(whess): Improve by using more than just the last two odometry poses.// Compute extrapolation in the tracking frame.// 4. 提取最新与最老的两个里程计数据,计算时间差const sensor::OdometryData& odometry_data_oldest = odometry_data_.front();const sensor::OdometryData& odometry_data_newest = odometry_data_.back();const double odometry_time_delta =common::ToSeconds(odometry_data_oldest.time - odometry_data_newest.time);// 5. 计算两个里程计位姿的相对变换const transform::Rigid3d odometry_pose_delta =odometry_data_newest.pose.inverse() * odometry_data_oldest.pose;        // 两个里程计位姿间的变化量,tracking_frame坐标系// 6. 计算角速度(从里程计)// 两个位姿间的旋转量除以时间得到 tracking frame 的角速度angular_velocity_from_odometry_ =transform::RotationQuaternionToAngleAxisVector(odometry_pose_delta.rotation()) /odometry_time_delta;if (timed_pose_queue_.empty()) {return;}// 7. 计算线速度(从里程计)// (1)计算里程计坐标系下的线速度(平移变化/时间差)const Eigen::Vector3dlinear_velocity_in_tracking_frame_at_newest_odometry_time =odometry_pose_delta.translation() / odometry_time_delta;// (2) 计算最新里程计时刻的姿态(结合外推的旋转)// 根据位姿队列中最后一个位姿 乘以 上次添加位姿时的姿态预测到time时刻的姿态变化量// 得到预测的 最新里程计数据时刻 tracking frame 在 local 坐标系下的姿态const Eigen::Quaterniond orientation_at_newest_odometry_time =timed_pose_queue_.back().pose.rotation() *ExtrapolateRotation(odometry_data_newest.time,odometry_imu_tracker_.get());// (3) 将线速度转换到 tracking frame坐标系下// 将tracking frame的线速度进行旋转, 得到 local 坐标系下 tracking frame 的线速度linear_velocity_from_odometry_ =orientation_at_newest_odometry_time *linear_velocity_in_tracking_frame_at_newest_odometry_time;
}

        需要注意的是,5计算的是里程计位姿(旋转+平移)的变化量。

// 5. 计算两个里程计位姿的相对变换
const transform::Rigid3d odometry_pose_delta =odometry_data_newest.pose.inverse() * odometry_data_oldest.pose;

        pose是当前位置在local坐标系下的位姿,也可以看作是以当前坐标为原点的坐标系与local坐标系之间的变换关系。假设A.pose为T_{local-A},则A.pose.inverse()相当于T_{local-A}^{-1}==T_{A-local} ,B.pose为T_{local-B}。A.pose.inverse() * B.pose相当于T_{A-local}*T_{local-B}=T_{A-B},也就是B在A坐标系下的位姿,相当于B与A的相对位姿,或者说A时刻相对于B时刻的位姿变化量。

        7.(1)根据(平移变化量/时间差)计算得到线速度。

        7.(2)将系统最新的 “基准位姿”的姿态(旋转),更新(预测)到当前odom数据插入位姿推测器时刻。

const Eigen::Quaterniond orientation_at_newest_odometry_time =timed_pose_queue_.back().pose.rotation() *ExtrapolateRotation(odometry_data_newest.time,odometry_imu_tracker_.get());

    timed_pose_queue_.back().pose.rotation() 是系统最新的 “基准位姿”(由扫描匹配得到)的旋转部分。其中调用了PoseExtrapolator::ExtrapolateRotation()函数。

5.6.1 PoseExtrapolator::ExtrapolateRotation()

        函数用于计算imu_tracker到time时刻的姿态变化量:

Eigen::Quaterniond PoseExtrapolator::ExtrapolateRotation(const common::Time time, ImuTracker* const imu_tracker) const {// 检查time要大于等于imu_tracker上次预测位姿的时间CHECK_GE(time, imu_tracker->time());// 更新imu_tracker的状态到time时刻AdvanceImuTracker(time, imu_tracker);// 通过imu_tracker_获取上一次位姿校准时的姿态const Eigen::Quaterniond last_orientation = imu_tracker_->orientation();// 求取上一帧到当前时刻预测出的姿态变化量:上一帧姿态四元数的逆 乘以 当前时刻预测出来的姿态四元数return last_orientation.inverse() * imu_tracker->orientation();
}

        其中又调用PoseExtrapolator::AdvanceImuTracker()函数更新imu_tracker的状态到time时刻。

5.6.2 PoseExtrapolator::AdvanceImuTracker()

void PoseExtrapolator::AdvanceImuTracker(const common::Time time,ImuTracker* const imu_tracker) const {// 1. 检查指定时间是否大于等于 ImuTracker 上一次预测姿态的时间CHECK_GE(time, imu_tracker->time());// 2. 若 imu_data_ 为空,或目标时间早于最早的 IMU 数据时间,则无法使用真实 IMU 数据if (imu_data_.empty() || time < imu_data_.front().time) {// There is no IMU data until 'time', so we advance the ImuTracker and use// the angular velocities from poses and fake gravity to help 2D stability.// 如果在time之前没有IMU数据, 因此我们推进ImuTracker, 并使用姿势和假重力产生的角速度来帮助2D稳定// 预测当前时刻的姿态与重力方向imu_tracker->Advance(time);// 使用 假的重力数据对加速度的测量进行更新imu_tracker->AddImuLinearAccelerationObservation(Eigen::Vector3d::UnitZ());// 只能依靠其他方式得到的角速度进行测量值的更新imu_tracker->AddImuAngularVelocityObservation(odometry_data_.size() < 2 ? angular_velocity_from_poses_: angular_velocity_from_odometry_);return;}// 3. 同步到最早 IMU 数据时间// 若imu_tracker的时间比imu数据队列中第一个数据的时间早, 就先预测到imu数据队列中第一个数据的时间if (imu_tracker->time() < imu_data_.front().time) {// Advance to the beginning of 'imu_data_'.imu_tracker->Advance(imu_data_.front().time);}// 4. 融合 IMU 数据将预测推进到目标时间:// 在imu数据队列中找到第一个时间上 大于等于 imu_tracker->time() 的数据的索引auto it = std::lower_bound(imu_data_.begin(), imu_data_.end(), imu_tracker->time(),[](const sensor::ImuData& imu_data, const common::Time& time) {return imu_data.time < time;});// 然后依次对imu数据进行预测, 以及添加观测, 直到imu_data_的时间大于等于time截止while (it != imu_data_.end() && it->time < time) {// 预测出当前时刻的姿态与重力方向imu_tracker->Advance(it->time);// 根据线加速度的观测,更新重力的方向,并根据重力的方向对上一时刻预测的姿态进行校准imu_tracker->AddImuLinearAccelerationObservation(it->linear_acceleration);// 更新角速度观测imu_tracker->AddImuAngularVelocityObservation(it->angular_velocity);++it;}// 最后将imu_tracker的状态预测到目标time时刻imu_tracker->Advance(time);
}

        因为imu_data队列中有多个数据,其中可能有多个数据的时间戳处于[imu_tracker->time(), time]之间,所以要一个一个根据imu数据,将姿态的预测向目标time推进。

        再回到ExtrapolateRotation()。在AdvanceImuTracker()之后,又执行了:

// 通过imu_tracker_获取上一次位姿校准时的姿态
const Eigen::Quaterniond last_orientation = imu_tracker_->orientation();
// 求取上一帧到当前时刻预测出的姿态变化量:上一帧姿态四元数的逆 乘以 当前时刻预测出来的姿态四元数
return last_orientation.inverse() * imu_tracker->orientation();

        其中imu_tracker是在ExtrapolateRotation(odometry_data_newest.time,

odometry_imu_tracker_.get())函数调用时传入的odometry_imu_tracker_.get()。而odometry_imu_tracker_是在扫描匹配后的位姿插入到位姿推测器中时对imu_tracker_的拷贝:

void PoseExtrapolator::AddPose(const common::Time time,const transform::Rigid3d& pose) {
......odometry_imu_tracker_ = absl::make_unique<ImuTracker>(*imu_tracker_);extrapolation_imu_tracker_ = absl::make_unique<ImuTracker>(*imu_tracker_);
......
}

        所以imu_tracker->orientation()是上一次pose插入到位姿推测器时(t0时刻)的姿态,last_orientation = imu_tracker_->orientation(),是当前odom数据插入位姿推测器时(t1时刻)预测的姿态。

last_orientation.inverse() * imu_tracker->orientation()

        代码的计算结果为t0t1的姿态变化量。

5.6.3 PoseExtrapolator::AddOdometryData()

        接下来再回到AddOdometryData()函数:

const Eigen::Quaterniond orientation_at_newest_odometry_time =timed_pose_queue_.back().pose.rotation() *ExtrapolateRotation(odometry_data_newest.time,odometry_imu_tracker_.get());

        这一步的计算结果,就是将系统最新的 “基准位姿”的姿态(旋转),更新(预测)到当前odom数据插入位姿推测器时刻。

        再下来,7.(3)将里程计计算出的 “tracking_frame坐标系下的线速度” 转换到 “世界坐标系(local frame)” 下

  linear_velocity_from_odometry_ =orientation_at_newest_odometry_time *linear_velocity_in_tracking_frame_at_newest_odometry_time;

    linear_velocity_in_tracking_frame_at_newest_odometry_time:这是从里程计数据直接计算出的线速度,物理意义是 “在tracking frame坐标系下,机器人的运动速度”,没有与系统的世界坐标系对齐。

    orientation_at_newest_odometry_time:是 “最新里程计数据时刻(odometry_data_newest.time),tracking_frame坐标系在世界坐标系中的姿态”。
        两者相乘后,得到 “最新里程计时刻,世界坐标系下的线速度向量

        通过姿态旋转将速度统一到世界坐标系,确保速度与基准位姿在同一坐标系下。    

5.7 PoseExtrapolator::TrimOdometryData()

// 修剪odom的数据队列,丢掉过时的odom数据
void PoseExtrapolator::TrimOdometryData() {// 保持odom队列中第二个数据的时间要大于最后一个位姿的时间, odometry_data_最少是2个while (odometry_data_.size() > 2 && !timed_pose_queue_.empty() &&odometry_data_[1].time <= timed_pose_queue_.back().time) {odometry_data_.pop_front();}
}

        odometry_data_必须通过两个里程计位姿计算速度,与IMU不同。

5.8 AddOdometryData()在何处调用

        在local_trajectory_builder_2d.cc的LocalTrajectoryBuilder2D::AddOdometryData()函数中被调用:

// 将里程计数据加入到Extrapolator中
void LocalTrajectoryBuilder2D::AddOdometryData(const sensor::OdometryData& odometry_data) {if (extrapolator_ == nullptr) {// Until we've initialized the extrapolator we cannot add odometry data.LOG(INFO) << "Extrapolator not yet initialized.";return;}extrapolator_->AddOdometryData(odometry_data);
}

5.9 PoseExtrapolator::AddPose()

        AddPose()的使用在执行完扫描匹配之后,功能是使用scanMatch()匹配得到的精准位姿来校准pose_extrapolator。(实际未校准)

void PoseExtrapolator::AddPose(const common::Time time,const transform::Rigid3d& pose) {// 1. 如果imu_tracker_没有初始化就先进行初始化if (imu_tracker_ == nullptr) {common::Time tracker_start = time;if (!imu_data_.empty()) {tracker_start = std::min(tracker_start, imu_data_.front().time);}// imu_tracker_的初始化imu_tracker_ =absl::make_unique<ImuTracker>(gravity_time_constant_, tracker_start);}// 2. 在timed_pose_queue_中保存posetimed_pose_queue_.push_back(TimedPose{time, pose});// 3. 保持pose队列中第二个pose的时间要大于 time - pose_queue_duration_while (timed_pose_queue_.size() > 2 && // timed_pose_queue_最少是2个数据timed_pose_queue_[1].time <= time - pose_queue_duration_) {timed_pose_queue_.pop_front();}// 4. 根据加入的pose计算线速度与角速度UpdateVelocitiesFromPoses();// 5. 将imu_tracker_更新到time时刻AdvanceImuTracker(time, imu_tracker_.get());// 6. 丢弃1过期数据// pose队列更新了,之前imu及里程计数据已经过时了// 因为pose是匹配的结果,之前的imu及里程计数据是用于预测的,现在结果都有了,之前的用于预测的数据肯定不需要了TrimImuData();TrimOdometryData();// 7. 保存imu_tracker_的副本// 用于根据里程计数据计算线速度时姿态的预测odometry_imu_tracker_ = absl::make_unique<ImuTracker>(*imu_tracker_);// 用于位姿预测时的姿态预测extrapolation_imu_tracker_ = absl::make_unique<ImuTracker>(*imu_tracker_);
}

        其中,将扫描匹配后的pose加入到pose队列中,计算线速度与角速度,并将imu_tracker_的状态更新到time时刻。

        其中调用了PoseExtrapolator::UpdateVelocitiesFromPoses()函数进行基于pose的线速度与角速度计算:

// 根据pose队列计算tracking frame 在 local坐标系下的线速度与角速度
void PoseExtrapolator::UpdateVelocitiesFromPoses() {if (timed_pose_queue_.size() < 2) {// We need two poses to estimate velocities.return;}CHECK(!timed_pose_queue_.empty());// 取出队列最末尾的一个 Pose,也就是最新时间点的 Pose,并记录相应的时间const TimedPose& newest_timed_pose = timed_pose_queue_.back();const auto newest_time = newest_timed_pose.time;// 取出队列最开头的一个 Pose, 也就是最旧时间点的 Pose,并记录相应的时间const TimedPose& oldest_timed_pose = timed_pose_queue_.front();const auto oldest_time = oldest_timed_pose.time;// 计算两者的时间差const double queue_delta = common::ToSeconds(newest_time - oldest_time);// 如果时间差小于pose_queue_duration_(1ms), 不进行计算if (queue_delta < common::ToSeconds(pose_queue_duration_)) {LOG(WARNING) << "Queue too short for velocity estimation. Queue duration: "<< queue_delta << " s";return;}const transform::Rigid3d& newest_pose = newest_timed_pose.pose;const transform::Rigid3d& oldest_pose = oldest_timed_pose.pose;// 平移量除以时间得到 tracking frame 在 local坐标系下的线速度linear_velocity_from_poses_ =(newest_pose.translation() - oldest_pose.translation()) / queue_delta;// 角度变化量除以时间得到角速度得到 tracking frame 在 local坐标系下的角速度angular_velocity_from_poses_ =transform::RotationQuaternionToAngleAxisVector(oldest_pose.rotation().inverse() * newest_pose.rotation()) /queue_delta;
}

        角度变化量由oldest_pose.rotation().inverse() * newest_pose.rotation()计算得到。

5.10 PoseExtrapolator::ExtrapolatePose()

transform::Rigid3d PoseExtrapolator::ExtrapolatePose(const common::Time time) {const TimedPose& newest_timed_pose = timed_pose_queue_.back();CHECK_GE(time, newest_timed_pose.time);// 如果本次预测时间与上次计算的时间相同 就不再重复计算if (cached_extrapolated_pose_.time != time) {// 预测tracking frame在local坐标系下time时刻的位置const Eigen::Vector3d translation =ExtrapolateTranslation(time) + newest_timed_pose.pose.translation();// 预测tracking frame在local坐标系下time时刻的姿态const Eigen::Quaterniond rotation =newest_timed_pose.pose.rotation() *ExtrapolateRotation(time, extrapolation_imu_tracker_.get());cached_extrapolated_pose_ =TimedPose{time, transform::Rigid3d{translation, rotation}};}return cached_extrapolated_pose_.pose;
}

        这个函数预测得到time时刻 tracking frame 在 local 坐标系下的位姿。其中,调用ExtrapolateTranslation(time)预测位移,调用ExtrapolateRotation(time, extrapolation_imu_tracker_.get())预测旋转(姿态)。

Eigen::Vector3d PoseExtrapolator::ExtrapolateTranslation(common::Time time) {const TimedPose& newest_timed_pose = timed_pose_queue_.back();const double extrapolation_delta =common::ToSeconds(time - newest_timed_pose.time);// 使用tracking frame 在 local坐标系下的线速度 乘以时间 得到平移量的预测if (odometry_data_.size() < 2) {return extrapolation_delta * linear_velocity_from_poses_;}// 如果不使用里程计就使用通过pose计算出的线速度return extrapolation_delta * linear_velocity_from_odometry_;
}

        PoseExtrapolator::ExtrapolateTranslation()的内容较简单,其使用 线速度*时间变化量 计算的到位移变化量。

5.11 PoseExtrapolator::EstimateGravityOrientation()

Eigen::Quaterniond PoseExtrapolator::EstimateGravityOrientation(const common::Time time) {ImuTracker imu_tracker = *imu_tracker_;// 使得 imu_tracker 预测到time时刻AdvanceImuTracker(time, &imu_tracker);// 返回 imu_tracker 预测到的time时刻 的姿态return imu_tracker.orientation();
}

        PoseExtrapolator::EstimateGravityOrientation()用于预测得到time时刻 tracking frame 在 local 坐标系下的姿态(旋转)。

5.12 PoseExtrapolator::ExtrapolatePosesWithGravity()

PoseExtrapolator::ExtrapolationResult
PoseExtrapolator::ExtrapolatePosesWithGravity(const std::vector<common::Time>& times) {// 1. 初始化位姿容器,用于存储外推的位姿std::vector<transform::Rigid3f> poses;// 2. 批量外推位姿(除最后一个时间点)for (auto it = times.begin(); it != std::prev(times.end()); ++it) {poses.push_back(ExtrapolatePose(*it).cast<float>());}// 3. 确定当前速度const Eigen::Vector3d current_velocity = odometry_data_.size() < 2? linear_velocity_from_poses_: linear_velocity_from_odometry_;// 4. 构建并返回外推结果return ExtrapolationResult{poses,         // 除最后一个时间点外的所有外推位姿ExtrapolatePose(times.back()),    // 最后一个时间点的外推位姿current_velocity,        // 当前有效的线速度(local坐标系下)EstimateGravityOrientation(times.back())};    // 最后一个时间点的姿态
}

        PoseExtrapolator::ExtrapolatePosesWithGravity 函数根据输入的多个目标时间点,批量外推每个时间点的位姿,返回一个包含完整外推结果的结构体

6.


文章转载自:

http://QoNyZ3C4.pcgmw.cn
http://shJ1N0Hu.pcgmw.cn
http://gYPgxNLR.pcgmw.cn
http://w8j6cY5a.pcgmw.cn
http://36WoKsOf.pcgmw.cn
http://q49J41QI.pcgmw.cn
http://mrW9iHnD.pcgmw.cn
http://pSPQRmoC.pcgmw.cn
http://vFM0eytj.pcgmw.cn
http://8SoU90dA.pcgmw.cn
http://Leyc3AM1.pcgmw.cn
http://mzWbPiH5.pcgmw.cn
http://miow2Ic7.pcgmw.cn
http://iUsG61ln.pcgmw.cn
http://DBtoJ9Ge.pcgmw.cn
http://ZikJqhBu.pcgmw.cn
http://amPzYIFj.pcgmw.cn
http://yVRocNcf.pcgmw.cn
http://oSAKjOFR.pcgmw.cn
http://L1K7dGlN.pcgmw.cn
http://BH3NIp8k.pcgmw.cn
http://t26s6OIh.pcgmw.cn
http://CzbrjBpf.pcgmw.cn
http://CTqjARKw.pcgmw.cn
http://KoYbiA6s.pcgmw.cn
http://01xE9Qgu.pcgmw.cn
http://XGSRvflB.pcgmw.cn
http://u8fxVIl4.pcgmw.cn
http://jG8aWUvU.pcgmw.cn
http://ZYdhxDXP.pcgmw.cn
http://www.dtcms.com/a/375926.html

相关文章:

  • Matlab机器人工具箱使用5 轨迹规划
  • 【git】Git 大文件推送失败问题及解决方案
  • ctfshow-web入门-php特性(二)
  • CSP认证练习题目推荐 (1)
  • MySQL 命令
  • MyBatis操作数据库——进阶
  • huggingFace学习之编码工具
  • 人工智能期末复习(部分)
  • 【Pytorch】2025 Pytorch基础入门教程(完整详细版)
  • Cookie 与 Session 的关系详解
  • Java微服务架构拆分:边界原则的实战破局与多场景案例解析
  • expect脚本详解
  • 交通识别摄像头以及带AI算法
  • SpringMVC通过注解实现全局异常处理
  • Linux基础知识(四)
  • 向量化与嵌入模型:RAG系统背后的隐形英雄
  • 你知道zip()和zip(*)怎么用吗?
  • 工业领域企业CRM常用的有哪些系统?
  • Git cherry-pick 与分支重置技术实现代码健全性保障下的提交记录精简
  • 【Nginx 运维实战】版本替换:强制 vs 平滑升级全解析
  • HTTPS加解密流程解析
  • Android 升级minSdkVersion 导致 包体积变大的处理
  • Linux系统 Python3.12版本连接达梦数据库dmPython和django_dmPython
  • 零知开源——ESP32驱动OV7670摄像头实现简易照相机系统
  • 前端开发工具trae的使用
  • Coze源码分析-资源库-创建插件-前端源码-核心组件
  • 数据集成平台怎么选?从ETL到CDC再到iPaaS的全景对比
  • 【Linux基础】Linux系统配置IP详解:从入门到精通
  • 2025版基于springboot的企业考勤管理系统
  • 【计算机毕业设计选题】2025-2026年计算机毕业设计选题经验与项目推荐