ROS-3DSLAM(四)lvi-sam源代码阅读2

2021SC@SDUSC

(四)lvi-sam源代码阅读2

lidar_odometry

imuPreintegration.cpp

本次分析主要是对imuHandler函数的分析。

imu的回调函数 imuHandler

根据上一次的odom和偏差,用gtsam来估计当下的odom作为imu的位姿,通过静态TF变换求得雷达的位姿,将其逐个发布。

在这里插入图片描述

如图所示,由于 IMU 的频率比雷达高得多,因此在下一帧点云即时间戳到来之前,会有大量的IMU数据读入,如上图中紫色线条所示,则可以利用这段时间间隔内的IMU测量量进行预积分操作。

用上一帧激光里程计,加上从上一帧对应时刻到当前时刻 imu 预计分量,得到当前状态,也就是 imu 里程计,再将其转到 lidar 坐标系发布。

//ConstPtr 可被理解为const pointer(常量指针),理解为shared pointer to a constant message(传递常量数据的共享指针)

/*
sensor_msgs::Imu
header中包含三个参数(三个协方差矩阵):
方向协方差,角速度协方差,加速度协方差
*/
void imuHandler(const sensor_msgs::Imu::ConstPtr& imuMsg)
{
    //imuConverter 这个函数只旋转,没有平移,和真正的雷达坐标系之间还是差了一个平移的
    sensor_msgs::Imu thisImu = imuConverter(*imuMsg);

    //后续与thisImu有关的都是在差一个平移的雷达坐标系中

    // publish static tf 广播
    //map_to_odm在构造函数中初始化(全0) 时间戳 父系名字 子系名字
    // // map 和 odom 坐标系完全重合(设置的),发布 map 坐标系,实际中并没有帮助
    tfMap2Odom.sendTransform(tf::StampedTransform(map_to_odom, thisImu.header.stamp, "map", "odom"));

    // 两个双端队列分别是优化前后的 imu 数据,将当前帧 imu 数据添加到队列
    imuQueOpt.push_back(thisImu);
    imuQueImu.push_back(thisImu);

    // 要求上一次imu因子图优化执行成功,确保更新了上一帧(激光里程计帧)的状态、偏置,预积分已经被重新计算
    // 如果还没有执行过 odom 优化,或者上一次优化失败导致系统重置
    // 则等待一次 odom 的优化再继续函数流程
    // 这里需要先在 odomhandler 中优化一次后再进行该函数后续的工作
    if (doneFirstOpt == false)
        return;


    //获取当前imu因子图的时间戳
    double imuTime = ROS_TIME(&thisImu);
    // lastImuT_imu初始化为-1
    // 如果首次优化,则定义初始时间间隔为 1/500 秒,否则是与上一帧作差
    double dt = (lastImuT_imu < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_imu);
    //更新上一帧imu因子图的时间戳
    lastImuT_imu = imuTime;

    // integrate this single imu message
    // imu预积分器添加一帧imu数据,起始时刻是上一帧激光里程计时刻
    // https://bitbucket.org/gtborg/gtsam/src/develop/gtsam/navigation/ImuFactor.cpp
    imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu.linear_acceleration.x, thisImu.linear_acceleration.y, thisImu.linear_acceleration.z),
                                            gtsam::Vector3(thisImu.angular_velocity.x,    thisImu.angular_velocity.y,    thisImu.angular_velocity.z), dt);

    // predict odometry
    // 用上一帧激光里程计时刻对应的状态、偏置,施加从该时刻开始到当前时刻的imu预计分量,得到当前时刻的状态
    /*
    NavState : 
        Rot3 R_;
        Point3 t_;
        Velocity3 v_;
    */
    // 这个函数在wiki中找不到
    //暂时认为这个函数,根据上一个nav state,以及上一个里程计的偏差,得到了一个navigation state
    gtsam::NavState currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);

    // publish odometry
    // 发布imu里程计(转到lidar系,与激光里程计同一个系)
    //*** 是imu的里程计

    //nav_msgs::Odometry 包含位置信息和速度信息
    /*

    */
    nav_msgs::Odometry odometry;
    odometry.header.stamp = thisImu.header.stamp;
    odometry.header.frame_id = "odom";
    odometry.child_frame_id = "odom_imu";

    // transform imu pose to ldiar
    //预测值currentState获得imu位姿, 再由imu到雷达变换, 获得雷达位姿(这里应该是将imu里程计的信息,从本来所在的imu坐标系 转换到 雷达的坐标系下)
    /*
    Pose3 : 
        Rot3 R_;
        Point3 t_;
    */
    gtsam::Pose3 imuPose = gtsam::Pose3(currentState.quaternion(), currentState.position());
    //转换 compose,继承自LieGroup类
    gtsam::Pose3 lidarPose = imuPose.compose(imu2Lidar);

    //在导航的消息中添加对应的信息

    /*
    geometry_msgs/PoseWithCovariance pose //表示位置的point和quaternion,以及对应的协方差矩阵
    geometry_msgs/TwistWithCovariance twist //表示速度的linear和angular,以及对应的协方差矩阵
    */

   //第一个pose:geometry_msgs/PoseWithCovariance;第二个pose:geometry_msgs/Pose,包含一个point和一个quaternion
    odometry.pose.pose.position.x = lidarPose.translation().x();
    odometry.pose.pose.position.y = lidarPose.translation().y();
    odometry.pose.pose.position.z = lidarPose.translation().z();
    odometry.pose.pose.orientation.x = lidarPose.rotation().toQuaternion().x();
    odometry.pose.pose.orientation.y = lidarPose.rotation().toQuaternion().y();
    odometry.pose.pose.orientation.z = lidarPose.rotation().toQuaternion().z();
    odometry.pose.pose.orientation.w = lidarPose.rotation().toQuaternion().w();

    odometry.twist.twist.linear.x = currentState.velocity().x();
    odometry.twist.twist.linear.y = currentState.velocity().y();
    odometry.twist.twist.linear.z = currentState.velocity().z();
    odometry.twist.twist.angular.x = thisImu.angular_velocity.x + prevBiasOdom.gyroscope().x();
    odometry.twist.twist.angular.y = thisImu.angular_velocity.y + prevBiasOdom.gyroscope().y();
    odometry.twist.twist.angular.z = thisImu.angular_velocity.z + prevBiasOdom.gyroscope().z();
    // information for VINS initialization
    odometry.pose.covariance[0] = double(imuPreintegrationResetId);
    odometry.pose.covariance[1] = prevBiasOdom.accelerometer().x();
    odometry.pose.covariance[2] = prevBiasOdom.accelerometer().y();
    odometry.pose.covariance[3] = prevBiasOdom.accelerometer().z();
    odometry.pose.covariance[4] = prevBiasOdom.gyroscope().x();
    odometry.pose.covariance[5] = prevBiasOdom.gyroscope().y();
    odometry.pose.covariance[6] = prevBiasOdom.gyroscope().z();
    odometry.pose.covariance[7] = imuGravity;
    //发布里程计信息
    pubImuOdometry.publish(odometry);

    // publish imu path
    // 发布 imu 轨迹,但只保存 3s
    /*
        PoseStamped[]
        带有时间戳timeStamp的pose
        Pose:
            Point 表示 position位置
            Quaternion 表示 orientation方向

    */
    //imu位姿的数组
    //静态的**** 注意生存周期
    static nav_msgs::Path imuPath;
    static double last_path_time = -1;
    //当前imu因子图的时间戳
    if (imuTime - last_path_time > 0.1)
    {
        //如果imuTime>-1 就一定会进入这个if
        last_path_time = imuTime;
        //定义一个带有时间戳的pose的消息
        geometry_msgs::PoseStamped pose_stamped;
        //把这一帧imu的 时间戳、关联的坐标系id、位置相关信息 赋值给pose_stamped
        //thisImu是在原始的Imu数据旋转到雷达坐标系后的数据
        pose_stamped.header.stamp = thisImu.header.stamp;
        pose_stamped.header.frame_id = "odom";
        pose_stamped.pose = odometry.pose.pose;
        //加入Path
        imuPath.poses.push_back(pose_stamped);
        //当imuPath数组不为空 && 且数组中最前和最后两个pose的时间戳>3秒(可以理解为超时了?太长了?)
        //true: 清除队列最开头的pose,直到间距<=3s
        while(!imuPath.poses.empty() && abs(imuPath.poses.front().header.stamp.toSec() - imuPath.poses.back().header.stamp.toSec()) > 3.0)
            imuPath.poses.erase(imuPath.poses.begin());

        //获取数组中元素个数?
        if (pubImuPath.getNumSubscribers() != 0)
        {
            //数组中存在元素的话 就发布
            //给imuPath消息添加 时间戳stamp和frame_id坐标系id
            imuPath.header.stamp = thisImu.header.stamp;
            imuPath.header.frame_id = "odom";
            //发布消息
            pubImuPath.publish(imuPath);
        }
    }

    // publish transformation
    // 发布 odom 到 base_link 的变换,其实也就是到 imu 的变换
    // 因为作者采用的模型的 base_link 就是 imu
    // 可以考虑改为发布 odom 到 imu 的 TF
    tf::Transform tCur;
    //odometry.pose.pose imu里程计的pose信息(位置 和 方向)
    tf::poseMsgToTF(odometry.pose.pose, tCur);
    //StampedTransform 
    tf::StampedTransform odom_2_baselink = tf::StampedTransform(tCur, thisImu.header.stamp, "odom", "base_link");
    tfOdom2BaseLink.sendTransform(odom_2_baselink);
}
};

总结(整体初步理解):

  • 从imu获取了原始数据(原始数据包括方向四元数、加速度、角速度以及他们的协方差,见:sensor_msgs/Imu.msg)

  • 将imu进行旋转变换到雷达坐标系(没有平移),得到thisImu

  • 将thisImu加入到两个存储imu数据的双端队列中

  • 更新当前imu因子图的时间戳,在imu预积分器中添加当前这一帧imu数据

  • 用上一帧激光里程计时刻对应的状态、偏置,施加从该时刻开始到当前时刻的imu预计分量,得到当前时刻的状态

  • 发布imu里程计信息

  • 将imu位姿的信息数组更新并发布

  • 中间穿插一些tf坐标转换的操作(imu、雷达、baselink(作者设置为imu)等)

前置知识学习

imu因子图优化

https://blog.csdn.net/ljtx200888/article/details/114164685

SLAM问题的贝叶斯网络、因子图

在上一篇博客的学习中,已经了解了关于贝叶斯网络和因子图的有关知识(https://blog.csdn.net/qq_38170211/article/details/120688614)。

在 SLAM 中,由于我们有运动方程和观测方程,它们恰好表示了状态变量之间的条件概率,所以SLAM 可以自然地表达成一个动态贝叶斯网络(Dynamic Bayes Network, DBN).下图是slam问题的贝叶斯网络表示:

在这里插入图片描述

蓝色线表示运动方程,红色线为观测方程。红框表示一次观测,蓝框表示一次运动。

节点说明:

  • 相机位姿形成的节点:x1,x2…
  • 输入量节点:u1,u2…
  • 路标节点:l1,l2…
  • 观测数据节点:z1,z2…

图上的箭头表示依赖关系,而红色和蓝色框说明变量间的条件概率关系:例如红色框表示P (z 1 |x 1 , l 1 );蓝色框表示P (x 3 |x 2 , u 3 );

slam问题通过贝叶斯网络求解:事实上,后端优化的目标,就是在这些既有的约束之上,通过调整贝叶斯网络中随机变量的取值,使整个后验概率达到最大(使用概率图模型中的算法进行求解):

在这里插入图片描述

通过上式,我们发现最大后验概率由许多项因子乘积而成,因此该贝叶斯网络又可以转化成一个因子图(Factor Graph),如下图所示:在这里插入图片描述

对因子图的优化,就是调整各变量的值,使它们的因子之乘积最大化:在通常的做法中,我们把各因子的条件概率取高斯分布的形式,则根据运动方程和观测方程有:

在这里插入图片描述

运动方程和观测方程

https://blog.csdn.net/Robot_Starscream/article/details/83448257

运动方程:
x k = f ( x k _ 1 , u k , w k ) x_k = f(x_k\__1,u_k,w_k) xk=f(xk_1,uk,wk)
观测方程:
z k   ,   j = h ( y j , x k , v k   ,   j ) z_k\,_,\,_j = h(y_j, x_k, v_k\,_,\,_j) zk,j=h(yj,xk,vk,j)
这两个方程描述了最基本的SLAM问题:当我们知道运动测量的读数 u ,以及传感器的读数 z 时,如何求解定位问题(估计 x)和建图问题(估计 y)? 这时,我们把SLAM问题建模成了一个状态估计问题:如何通过带有噪声的测量数据,估计内部的、隐藏着的状态变量?

运动:

通常,机器人会携带一个测量自身运动的传感器,比如说码盘或惯性传感器。这个传感器可以测量有关运动的读数,无论什么传感器,我们都能使用一个通用的、抽象的数学模型:
x k = f ( x k _ 1 , u k , w k ) x_k = f(x_k\__1,u_k,w_k) xk=f(xk_1,uk,wk)

u k 是 运 动 传 感 器 的 参 数 , w k 为 噪 声 u_k是运动传感器的参数,w_k为噪声 ukwk

观测:

假设机器人在K时刻,在xk出探测到了某个路标yj,产生了一个观测数据zk,j

,用抽象的函数h来描述这个关系:
z k   ,   j = h ( y j , x k , v k   ,   j ) z_k\,_,\,_j = h(y_j, x_k, v_k\,_,\,_j) zk,j=h(yj,xk,vk,j)
Vk,j为这次观测的噪声。

参考ros wiki

std/msgs

- std_msgs/Header.msg
通常用于在特定坐标系中传递时间戳数据。
# Standard metadata for higher-level stamped data types. # 高级标记数据类型的标准元数据。
    
    Compact Message Definition:
//# sequence ID: consecutively increasing ID 序列ID(连续的)
uint32 seq
// 时间戳
time stamp
//#Frame this data is associated with 这个数据所关联的坐标系frame
string frame_id

sensor/msgs

此包定义了常用传感器的信息,包括相机和扫描激光测距仪。

- sensor_msgs/Imu.msg
# 这是一条消息,用于保存来自 IMU(惯性测量单元)的数据
#
# 加速度应该以 m/s^2 为单位(不是以 g 为单位),并且旋转速度应该以弧度/秒为单位
#
# 如果测量的协方差已知,则应填写(如果您只知道每个测量的方差,例如从数据表中,只需沿对角线放置)
# 全零的协方差矩阵将被解释为“协方差未知”,并且要使用数据,必须假设协方差或从其他来源获得协方差
#
# 如果您没有对数据元素之一的估计(例如,您的 IMU 不产生方向估计),请将相关协方差矩阵的元素 0 设置为 -1
# 如果您正在解释此消息,请检查每个协方差矩阵的第一个元素中的值 -1,并忽略相关的估计。

	Compact Message Definition:

std_msgs/Header header
    
geometry_msgs/Quaternion orientation // 四元数
float64[9] orientation_covariance //四元数协方差矩阵 (貌似不包括w)
geometry_msgs/Vector3 angular_velocity //角速度(3位的向量)
float64[9] angular_velocity_covariance // 角速度协方差矩阵
geometry_msgs/Vector3 linear_acceleration // 加速度(3位的向量)
float64[9] linear_acceleration_covariance // 线加速度协方差矩阵

nav/msgs

nav_msgs定义用于与导航堆栈交互的公共消息。

- nav_msgs/Odometry.msg
# 这表示对自由空间中的位置和速度的估计。
# pose应该在header.frame_id给定的坐标系中指定。
# twist应该在child_frame_id给定的坐标系中指定

	Compact Message Definition:

std_msgs/Header header
string child_frame_id
//以下两个消息类型在 geometry_msgs中定义
geometry_msgs/PoseWithCovariance pose //表示位置的point和quaternion,以及对应的协方差矩阵
geometry_msgs/TwistWithCovariance twist //表示速度的linear和angular,以及对应的协方差矩阵
- nav_msgs/Path.msg
#An array of poses that represents a Path for a robot to follow //一系列姿势,表示机器人要遵循的路径

	Compact Message Definition
std_msgs/Header header
geometry_msgs/PoseStamped[] poses //带有时间戳得poses

geometry/msgs

为常见的几何原语(如点、向量和姿态)提供消息。这些原语旨在提供公共数据类型,并促进整个系统的互操作性。

- geometry_msgs/Vector3.msg
三维向量

	Compact Message Definition:

float64 x
float64 y
float64 z
- geometry_msgs/Quaternion.msg
四元数
# This represents an orientation in free space in quaternion form.
#以四元数的形式表示自由空间中的方向
    
	Compact Message Definition:
float64 x
float64 y
float64 z
float64 w
- geometry_msgs/Points.msg
# This contains the position of a point in free space // 点在空间中的位置

	Compact Message Definition:
float64 x
float64 y
float64 z
  • points:坐标点;
  • quaternion:表示方向;
  • vector:没什么实际意义,是一个三维向量
- geometry_msgs/Pose.msg
# A representation of pose in free space, composed of position and orientation.  //由位置和方向组成

	Compact Message Definition:
geometry_msgs/Point position
geometry_msgs/Quaternion orientation
- geometry_msgs/Twist.msg
# This expresses velocity in free space broken into its linear and angular parts.// 分为线性部分和角度部分。

		Compact Message Definition:
geometry_msgs/Vector3 linear
geometry_msgs/Vector3 angular

- geometry_msgs/PoseWithCovariance.msg
# This represents a pose in free space with uncertainty. //表示在自由空间中不确定的位置

Pose pose

# Row-major representation of the 6x6 covariance matrix //6*6的协方差矩阵
# The orientation parameters use a fixed-axis representation.
# In order, the parameters are:
# (x, y, z, rotation about X axis, rotation about Y axis, rotation about Z axis)
float64[36] covariance

	Compact Message Definition:
geometry_msgs/Pose pose
float64[36] covariance
- geometry_msgs/TwistWithConvariance.msg
# This expresses velocity in free space with uncertainty. //表示在自由空间中不确定的速度

Twist twist

# Row-major representation of the 6x6 covariance matrix
# The orientation parameters use a fixed-axis representation.
# In order, the parameters are:
# (x, y, z, rotation about X axis, rotation about Y axis, rotation about Z axis)
float64[36] covariance
    
    Cpmpact Message Definition:
geometry_msgs/Twist twist
float64[36] covariance
- geometry_msgs/PosesStamped.msg
# A Pose with reference coordinate frame and timestamp
// 带有参考坐标系和时间戳

    Cpmpact Message Definition:
std_msgs/Header header
geometry_msgs/Pose pose

gtsam库学习

一个类似ros wiki的网站,提供了gtsam库的源码,并可以根据代码或者文件名进行检索,比较方便

https://bitbucket.org/gtborg/gtsam/src/develop/

PreintegratedImuMeasurements

gtsam/gtsam/navigation/ImuFactor.h

//构造函数:
 /**
   *  Constructor, initializes the class with no measurements
   *  @param bias Current estimate of acceleration and rotation rate biases
   *  @param p    Parameters, typically fixed in a single application
   */
  PreintegratedImuMeasurements(const boost::shared_ptr<PreintegrationParams>& p,
      const imuBias::ConstantBias& biasHat = imuBias::ConstantBias()) :
      PreintegrationType(p, biasHat) {
    preintMeasCov_.setZero();
  }

//添加预积分项
  /**
   * Add a single IMU measurement to the preintegration.
   * @param measuredAcc Measured acceleration (in body frame, as given by the sensor)
   * @param measuredOmega Measured angular velocity (as given by the sensor)
   * @param dt Time interval between this and the last IMU measurement
   */
  void integrateMeasurement(const Vector3& measuredAcc,
      const Vector3& measuredOmega, const double dt) override;
imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);

在PreintegratedImuMeasurement类中没有这个函数的定义,该类继承的PreintegrationType类中也没有找到相应的函数;

在PreintegratedImuMeasurement只有一个叫Predict的函数,目前只能根据这个函数推测predict函数的作用:

void ImuFactor::Predict(const Pose3& pose_i, const Vector3& vel_i,
    Pose3& pose_j, Vector3& vel_j, const imuBias::ConstantBias& bias_i,
    PreintegratedImuMeasurements& pim, const Vector3& n_gravity,
    const Vector3& omegaCoriolis, const bool use2ndOrderCoriolis) {
  // use deprecated predict
  PoseVelocityBias pvb = pim.predict(pose_i, vel_i, bias_i, n_gravity,
      omegaCoriolis, use2ndOrderCoriolis);
  pose_j = pvb.pose;
  vel_j = pvb.velocity;
}
NavState

gtsam/gtsam/navigation/NavState.h

private:

  // TODO(frank):
  // - should we rename t_ to p_? if not, we should rename dP do dT
  Rot3 R_; ///< Rotation nRb, rotates points/velocities in body to points/velocities in nav 四元数--方向
  Point3 t_; ///< position n_t, in nav frame 位置
  Velocity3 v_; ///< velocity n_v in nav frame 速度
Pose3

gtsam/gtsam/geometry/Pose3.h

private:

  Rot3 R_; ///< Rotation gRp, between global and pose frame
  Point3 t_; ///< Translation gTp, from global origin to pose frame origin

//Rots3 在项目中是一个四元数,代表方向
// Points3 实际为Vector3,一个三维向量,表示位置

gtsam/gtsam/geometry/Rot3.h

  private://私有变量中,当使用四元数表示方向的时候,会定义一个四元数作为私有变量

#ifdef GTSAM_USE_QUATERNIONS
    /** Internal Eigen Quaternion */
    gtsam::Quaternion quaternion_;
#else
    Matrix3 rot_;
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值