LIO-SAM 详读代码笔记 -- 1

LIO-SAM 详读代码笔记

先上论文原著 :T. Shan, B. Englot, D. Meyers, W. Wang, C. Ratti, D. Rus. LIO-SAM: Tightly-coupled Lidar Inertial Odometry via Smoothing and Mapping, IEEE/RSJ International Conference on Intelligent Robots and Systems, 2020.

论文地址:https://arxiv.org/pdf/2007.00258.pdf

源码链接:GitHub - TixiaoShan/LIO-SAM: LIO-SAM: Tightly-coupled Lidar Inertial Odometry via Smoothing and Mapping

简介:作者Tixiao Shan在2018年发表过非常经典的基于地面优化的slam方法:LeGO-LOAM,这一篇论文是在MIT工作时发表的。这两篇文章都是从LOAM 算法衍生而来,这篇文章LIO-SAM实际上是LeGO-LOAM的扩展版本,也是基于因子图优化的多传感器融合方法的经典之作,特此,详细研读其代码,学习经典,备忘。

先看官网给出的代码架构图(或者说是数据流图):

图1.代码架构图(数据流图)

翻阅源代码或者从图1 中可以看到整slam系统,主要由imuPreintegration.cppimagePaProjection.cppfeatureExtraction.cppmapOptimization.cpp 4个文件构成。外部数据主要有IMUPointcloudGPS,论文里说,还有回环检测事件输入。

图2.ros topic 发布与订阅关系图关系图

从图1 系统架构图中明确看到,imuPreintegration.cpp模块除了接收 Liar/odometry和 imu 的原数据之外,不再以来其他模块的数据,相对比较独立。本博文县先从imuPreintegration.cpp下手分析,没有太多环环相扣的依赖关系。再分析其他三个模块。其他三个模块围绕着Lidar 和 imu 的数据进行展开,耦合性比较强。

0. include/utility.h

在分析4大主要模块代码之前,先要看看include/utility.h 文件,该文件中定义了一个基类ParamServer,其他4个源文件都继承了这个基类,公用了该类中定义的系统参数变量。在该类中,读取ROS 参数服务其中的参数,ros参数服务器的参数是从config/params.yaml中间加载的;还定义了一些工具类的方法。

ParamServer 成员变量

变量名类型注释备注
nhros::NodeHandleros 节点句柄,订阅,发布,读取参数等工作-
robot_idstd::string机器人id-
//*消息名称统一定义*
pointCloudTopicstd::string初始lidar点云数据的ros topic 名称,从ros参数服务器中读取,初始化后不再改变-
imuTopicstd::string初始imu数据的ros topic 名称,从ros参数服务器中读取,初始化后不再改变-
odomTopicstd::string从ros参数服务器中读取,初始化值"odometry/imu",该消息从 imuPreintegration.cpp 中发出-
gpsTopicstd::string初始gps数据的ros topic 名称,从ros参数服务器中读取,初始化后不再改变-
//*坐标系*
lidarFramestd::string激光雷达lidar坐标系名称,初始值为"base_link",在该系统中定义成与baselink 相同,在配置文件可以修改,但是需要提供lidar到baselink 的外参-
baselinkFramestd::string机器人坐标系名称,初始值为"base_link"-
odometryFramestd::string里程计坐标系名称,初始值为"odom"-
mapFramestd::string地图坐标系名称,初始值为"map"-
// GPS Settings
useImuHeadingInitializationbool是否采用imu 数据进行初始化
useGpsElevationbool是否使用gps的高度数据
gpsCovThresholdfloat添加gps 因子的协方差阈值
poseCovThresholdfloat添加gps 因子的协方差阈值
// Save pcd 点云地图保存设置
savePCDbool是否保存地图-
savePCDDirectorystring地图保存路径-
// Lidar Sensor Configuration 激光雷达传感器设置
sensorSensorType激光雷达的品牌型号,枚举类型{ VELODYNE, OUSTER, LIVOX }-
N_SCANint激光雷达 线数 如:16线,32线,64线,128线等-
Horizon_SCANint激光雷达 单线 点数 如:1800-
downsampleRateint降采样比列 default =1 这个字段是用来滤波的压缩数据,downsampleRate =2 时 128线 ->64线 在imageProjection模块中使用-
lidarMinRangefloat激光雷达识别最小距离,小于该距离为盲区 默认值为1.0-
lidarMaxRangefloat激光雷达识别最大距离,大于该距离为盲区 默认值为1000.0-
// IMU 参数设置
imuAccNoisefloatimu加速度计的协方差对角值-
imuGyrNoisefloatimu陀螺仪的协方差对角值-
imuAccBiasNfloatimu加速度计bias的协方差对角值-
imuGyrBiasNfloatimu陀螺仪bias的协方差对角值-
imuGravityfloat重力-
imuRPYWeightfloatimu 旋转角加权值,用于里程计修正-
extRotVvectorimu 与激光雷达位姿的外参旋转角-
extRPYVvectorimu 与激光雷达位姿的外参旋转角-
extTransVvectorimu 与激光雷达位姿的外参-
extRotEigen::Matrix3dimu 与激光雷达位姿的外参旋转矩阵-
extRPYEigen::Matrix3dimu 与激光雷达位姿的外参旋转矩阵-
extTransEigen::Vector3dimu 与激光雷达位姿的外参位移向量-
extQRPYEigen::Quaterniondimu 与激光雷达位姿的外参旋转四元素-
// LOAM 参数设置
edgeThresholdfloat边角点曲率阈值-
surfThresholdfloat平面点点曲率阈值-
edgeFeatureMinValidNumint最小处理边角点的阈值

// 进行scan-map 需要点云角点最小特征数,在MapOptimization中用

-
surfFeatureMinValidNumint最小处理平面点的阈值,

进行scan-map 需要点云平面点最小特征数

-
// voxel filter paprams 降采样过滤器参数注意室内和室外是有区别的。
odometrySurfLeafSizefloat里程计点的降采样参数-
mappingCornerLeafSizefloat角点地图的降采样参数-
mappingSurfLeafSizefloat平面点地图的降采样参数-
z_tollerancefloat可以给z一个比较大的限制,如果用的是无人车,那就不可能在z轴变化过大,这里就可以限制它,防止它飘走-
rotation_tollerancefloat待定-
// CPU Params
numberOfCoresintcpu 核数-
mappingProcessIntervaldoublemapOptimization 的执行时间间隔-
// Surrounding map 局部地图参数设置
surroundingkeyframeAddingDistThresholdfloat距离当前帧多远的距离 才添加近局部地图的阈值(添加关键帧的条件)-
surroundingkeyframeAddingAngleThresholdfloat距离当前帧多远的角度 才添加近局部地图的阈值(添加关键帧的条件)-
surroundingKeyframeDensityfloat待定-
surroundingKeyframeSearchRadiusfloat添加关键帧的搜索半径,

局部地图的距离最新帧中心阈值

-
//Loop closure
loopClosureEnableFlagbool是否启动回环检测-
loopClosureFrequencyfloat添加回环检测的因子的频率-
surroundingKeyframeSizeint局部地图的关键帧数-
historyKeyframeSearchRadiusfloat在mapoptimization 中用,取局部地图时的参数-
historyKeyframeSearchTimeDifffloat在mapoptimization 中用,取局部地图时的参数-
historyKeyframeSearchNumint在mapoptimization 中用,取局部地图时的参数-
historyKeyframeFitnessScorefloat在mapoptimization 中用,取局部地图时的参数-
//global map visualization radius
globalMapVisualizationSearchRadiusfloat

在mapOptimization 发布“全局地图”时设定的可视范围

-
globalMapVisualizationPoseDensityfloat

在mapOptimization 发布“全局地图”时设定的关键帧密度

-
globalMapVisualizationLeafSizefloat

在mapOptimization 发布“全局地图”时设定的下采样参数

-

关于IMU和雷达之间的外参。个人感觉params.yaml这里写了一个Extrinsics (lidar -> IMU),有点问题,应该写成IMU->Lidar。因为它其实是​T_{lb},也就是说在IMU坐标系下的一个点,左乘T_{lb},就可以变换到lidar坐标系下。 针对不同的数据集,这个外参设置不一样,正常情况下,我们用的机器人,x射向正前方,y射向左边,z射向头顶。雷达和IMU都是这样摆放,所以extrinsicRot和extrinsicRPY全部弄成单位矩阵就行。差的不太远的话,extrinsicTrans弄成[0,0,0]就行。作者给出的外参可能有些看不懂,可以看下图,他的IMU 跟常规机器人安装方式不太一样。要根据实际情况来。 不过追求精确的话还是标定了一下,就用浙大在2020开源的标定工具,lidar_IMU_calib标定。

 
/*
1. imuConverter函数,这个函数之后会被频繁调用。它主要的作用,是把IMU的信息,
从IMU坐标系,转换到雷达坐标系。
(注意,这个函数只旋转,没有平移,和真正的雷达坐标系之间还是差了一个平移的。
至于为什么没有平移,在imuPreintegration.cpp文件中,
还有两个imu2Lidar,lidar2imu变量,这俩变量只有平移,没有旋转。
2. 这个slam 系统只接收9轴IMU 数据,6轴的imu 数据 将不能启动,
9轴比6轴imu 多了 roll pitch  yaw 的旋转角度数据*/
sensor_msgs::Imu imuConverter(const sensor_msgs::Imu& imu_in)
    {
        sensor_msgs::Imu imu_out = imu_in;
        // rotate acceleration
        Eigen::Vector3d acc(imu_in.linear_acceleration.x, imu_in.linear_acceleration.y, imu_in.linear_acceleration.z);
        acc = extRot * acc;
        imu_out.linear_acceleration.x = acc.x();
        imu_out.linear_acceleration.y = acc.y();
        imu_out.linear_acceleration.z = acc.z();
        // rotate gyroscope
        Eigen::Vector3d gyr(imu_in.angular_velocity.x, imu_in.angular_velocity.y, imu_in.angular_velocity.z);
        gyr = extRot * gyr;
        imu_out.angular_velocity.x = gyr.x();
        imu_out.angular_velocity.y = gyr.y();
        imu_out.angular_velocity.z = gyr.z();
        // rotate roll pitch yaw
        Eigen::Quaterniond q_from(imu_in.orientation.w, imu_in.orientation.x, imu_in.orientation.y, imu_in.orientation.z);
        Eigen::Quaterniond q_final = q_from * extQRPY;
        imu_out.orientation.x = q_final.x();
        imu_out.orientation.y = q_final.y();
        imu_out.orientation.z = q_final.z();
        imu_out.orientation.w = q_final.w();

        if (sqrt(q_final.x()*q_final.x() + q_final.y()*q_final.y() + q_final.z()*q_final.z() + q_final.w()*q_final.w()) < 0.1)
        {
            ROS_ERROR("Invalid quaternion, please use a 9-axis IMU!");
            ros::shutdown();
        }

        return imu_out;
    }

事实上,作者后续是把imu数据做一次修正,先用imuConverter旋转到雷达系同一个方向的坐标系下,这样IMU跟Lidar的外参就是一个平移变换了。然后他把雷达数据又根据lidar2Imu反向平移了一下,和转换以后差了个平移的imu数据在“中间系”对齐,之后算完又从中间系通过imu2Lidar挪回了雷达系进行publish。

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lio-sam是一个开源项目,是LIO(Linux内核iSCSI target)模块的一个分支。它是专门为高性能和可扩展性而设计的iSCSI目标代码lio-sam项目的主要目标是提供一个高性能的iSCSI目标,同时保持Linux kernel的稳定性和可靠性。它在传输层使用Scst(SCSI target实现)和LIO(Linux iSCSI实现)的组合,并有一些优化以提高性能。它还支持各种iSCSI功能,如CHAP认证、数据压缩和IPsec等。 代码阅读lio-sam对Linux内核和iSCSI有一定的了解是很有帮助的。lio-sam使用了一些Linux内核的机制,如工作队列和内存管理。了解这些机制将有助于理解lio-sam的实现原理和性能优化技巧。 在阅读lio-sam代码时,可以关注以下几个方面: 1. LIO模块的初始化和配置:lio-sam在加载模块时进行一些初始化工作,包括创建Scst的实例和配置iSCSI target。了解这些步骤可以帮助理解lio-sam的工作流程和配置方式。 2. iSCSI连接管理:lio-sam负责管理iSCSI连接,包括连接的建立、维护和中断。了解连接管理的实现原理可以帮助理解lio-sam如何处理多个客户端的连接和请求。 3. SCSI命令处理:lio-sam的核心功能是处理SCSI命令。了解lio-sam如何解析SCSI命令、调用底层存储设备和返回响应可以帮助理解其工作原理和性能优化方法。 4. 性能优化技巧:lio-sam的设计目标之一是提高性能。代码中可能包含一些性能优化技巧,如批量处理、IO调度和缓存管理等。了解这些技巧可以帮助优化自己的应用程序。 需要注意的是,代码阅读是一项耗时耗力的工作,需要具备一定的编程和系统知识。在阅读代码时,可以结合官方文档、论坛和社区来获取更多的信息和帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值