当前位置:   article > 正文

LIO_SAM实测运行,论文学习及代码注释[附对应google driver数据]_lio sam

lio sam

LIO-SAM: Tightly-coupled Lidar Inertial Odometry via Smoothing and Mapping

作者Tixiao Shan在2018年发表过LeGO-LOAM,当时他还在史蒂文斯理工学院读博士,19年毕业之后去了MIT做助理研究员(羡慕.jpg)。。。这篇文章LIO-SAM实际上是LeGO-LOAM的扩展版本,添加了IMU预积分因子和GPS因子,去除了帧帧匹配部分,然后更详细地描述了LeGO-LOAM帧图匹配部分的设计动机和细节。(引用于知乎大佬文章【论文阅读38】LIO-SAM)现在论文已经被IROS2020录用,作为高精度,imu,雷达,gps结合,程序还少的开源slam,非常值得学习。

目录

原文代码和数据

安装和运行

论文分析

程序注释

程序ros节点图

全部函数说明

imageProjection.cpp

featureExtraction.cpp

imuPreintegration.cpp

mapOptmization.cpp


原文代码和数据

原文:https://github.com/TixiaoShan/LIO-SAM/blob/master/config/doc/paper.pdf

代码:https://github.com/TixiaoShan/LIO-SAM

数据:链接: https://pan.baidu.com/s/1-sAB_cNlYPqTjDuaFgz9pg 提取码: ejmu (walk不需要改配置文件,其他两个需要下文有)

安装和运行

ros

  1. sudo apt-get install -y ros-kinetic-navigation
  2. sudo apt-get install -y ros-kinetic-robot-localization
  3. sudo apt-get install -y ros-kinetic-robot-state-publisher

gtsam4.0.2(网速慢用国内的码云https://gitee.com/eminbogen/gtsam/tags

  1. wget -O ~/Downloads/gtsam.zip https://github.com/borglab/gtsam/archive/4.0.2.zip
  2. cd ~/Downloads/ && unzip gtsam.zip -d ~/Downloads/
  3. cd ~/Downloads/gtsam-4.0.2/
  4. mkdir build && cd build
  5. cmake -DGTSAM_BUILD_WITH_MARCH_NATIVE=OFF ..
  6. sudo make install -j8

install(同上https://gitee.com/eminbogen/LIOSAM

  1. cd ~/catkin_ws/src
  2. git clone https://github.com/TixiaoShan/LIO-SAM.git
  3. cd ..
  4. catkin_make

运行walk数据包不需要改params.yaml文件。其他两个数据包运行要修改topics和extrinsicRPY,extrinsicRot。需要保存pcd请修改保存true和路径。之后

sudo gedit /opt/ros/kinetic/lib/python2.7/dist-packages/roslaunch/nodeprocess.py 

调大_TIMEOUT_SIGINT

具体params.yaml配置修改:

  1. lio_sam:
  2. # Topics
  3. pointCloudTopic: "points_raw" # Point cloud data
  4. imuTopic: "imu_correct" # IMU data
  5. odomTopic: "odometry/imu" # IMU pre-preintegration odometry, same frequency as IMU
  6. gpsTopic: "odometry/gpsz" # GPS odometry topic from navsat, see module_navsat.launch file
  7. # GPS Settings
  8. useImuHeadingInitialization: false # if using GPS data, set to "true"
  9. useGpsElevation: false # if GPS elevation is bad, set to "false"
  10. gpsCovThreshold: 2.0 # m^2, threshold for using GPS data
  11. poseCovThreshold: 25.0 # m^2, threshold for using GPS data
  12. # Export settings
  13. savePCD: true # https://github.com/TixiaoShan/LIO-SAM/issues/3
  14. savePCDDirectory: "/data/lio/" # in your home folder, starts and ends with "/". Warning: the code deletes "LOAM" folder then recreates it. See "mapOptimization" for implementation
  15. # Sensor Settings
  16. N_SCAN: 16 # number of lidar channel (i.e., 16, 32, 64, 128)
  17. Horizon_SCAN: 1800 # lidar horizontal resolution (Velodyne:1800, Ouster:512,1024,2048)
  18. timeField: "time" # point timestamp field, Velodyne - "time", Ouster - "t"
  19. downsampleRate: 1 # default: 1. Downsample your data if too many points. i.e., 16 = 64 / 4, 16 = 16 / 1
  20. # IMU Settings
  21. imuAccNoise: 3.9939570888238808e-03
  22. imuGyrNoise: 1.5636343949698187e-03
  23. imuAccBiasN: 6.4356659353532566e-05
  24. imuGyrBiasN: 3.5640318696367613e-05
  25. imuGravity: 9.80511
  26. # Extrinsics (lidar -> IMU)
  27. extrinsicTrans: [0.0, 0.0, 0.0]
  28. extrinsicRPY: [1, 0, 0,
  29. 0, 1, 0,
  30. 0, 0, 1]
  31. extrinsicRot: [1, 0, 0,
  32. 0, 1, 0,
  33. 0, 0, 1]
  34. # extrinsicRPY: [1, 0, 0,
  35. # 0, 1, 0,
  36. # 0, 0, 1]
  37. # LOAM feature threshold
  38. edgeThreshold: 1.0
  39. surfThreshold: 0.1
  40. edgeFeatureMinValidNum: 10
  41. surfFeatureMinValidNum: 100
  42. # voxel filter paprams
  43. odometrySurfLeafSize: 0.4 # default: 0.4
  44. mappingCornerLeafSize: 0.2 # default: 0.2
  45. mappingSurfLeafSize: 0.4 # default: 0.4
  46. # robot motion constraint (in case you are using a 2D robot)
  47. z_tollerance: 1000 # meters
  48. rotation_tollerance: 1000 # radians
  49. # CPU Params
  50. numberOfCores: 4 # number of cores for mapping optimization
  51. mappingProcessInterval: 0.15 # seconds, regulate mapping frequency
  52. # Surrounding map
  53. surroundingkeyframeAddingDistThreshold: 1.0 # meters, regulate keyframe adding threshold
  54. surroundingkeyframeAddingAngleThreshold: 0.2 # radians, regulate keyframe adding threshold
  55. surroundingKeyframeDensity: 2.0 # meters, downsample surrounding keyframe poses
  56. surroundingKeyframeSearchRadius: 50.0 # meters, within n meters scan-to-map optimization (when loop closure disabled)
  57. # Loop closure
  58. loopClosureEnableFlag: false
  59. surroundingKeyframeSize: 25 # submap size (when loop closure enabled)
  60. historyKeyframeSearchRadius: 15.0 # meters, key frame that is within n meters from current pose will be considerd for loop closure
  61. historyKeyframeSearchTimeDiff: 30.0 # seconds, key frame that is n seconds older will be considered for loop closure
  62. historyKeyframeSearchNum: 25 # number of hostory key frames will be fused into a submap for loop closure
  63. historyKeyframeFitnessScore: 0.3 # icp threshold, the smaller the better alignment
  64. # Visualization
  65. globalMapVisualizationSearchRadius: 1000.0 # meters, global map visualization radius
  66. globalMapVisualizationPoseDensity: 10.0 # meters, global map visualization keyframe density
  67. globalMapVisualizationLeafSize: 1.0 # meters, global map visualization cloud density
  68. # Navsat (convert GPS coordinates to Cartesian)
  69. navsat:
  70. frequency: 50
  71. wait_for_datum: false
  72. delay: 0.0
  73. magnetic_declination_radians: 0
  74. yaw_offset: 0
  75. zero_altitude: true
  76. broadcast_utm_transform: false
  77. broadcast_utm_transform_as_parent_frame: false
  78. publish_filtered_gps: false
  79. # EKF for Navsat
  80. ekf_gps:
  81. publish_tf: false
  82. map_frame: map
  83. odom_frame: odom
  84. base_link_frame: base_link
  85. world_frame: odom
  86. frequency: 50
  87. two_d_mode: false
  88. sensor_timeout: 0.01
  89. # -------------------------------------
  90. # External IMU:
  91. # -------------------------------------
  92. imu0: imu_correct
  93. # make sure the input is aligned with ROS REP105. "imu_correct" is manually transformed by myself. EKF can also transform the data using tf between your imu and base_link
  94. imu0_config: [false, false, false,
  95. true, true, true,
  96. false, false, false,
  97. false, false, true,
  98. true, true, true]
  99. imu0_differential: false
  100. imu0_queue_size: 50
  101. imu0_remove_gravitational_acceleration: true
  102. # -------------------------------------
  103. # Odometry (From Navsat):
  104. # -------------------------------------
  105. odom0: odometry/gps
  106. odom0_config: [true, true, true,
  107. false, false, false,
  108. false, false, false,
  109. false, false, false,
  110. false, false, false]
  111. odom0_differential: false
  112. odom0_queue_size: 10
  113. # x y z r p y x_dot y_dot z_dot r_dot p_dot y_dot x_ddot y_ddot z_ddot
  114. process_noise_covariance: [ 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  115. 0, 1.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  116. 0, 0, 10.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  117. 0, 0, 0, 0.03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  118. 0, 0, 0, 0, 0.03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  119. 0, 0, 0, 0, 0, 0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  120. 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0, 0,
  121. 0, 0, 0, 0, 0, 0, 0, 0.25, 0, 0, 0, 0, 0, 0, 0,
  122. 0, 0, 0, 0, 0, 0, 0, 0, 0.04, 0, 0, 0, 0, 0, 0,
  123. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0, 0,
  124. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0, 0, 0,
  125. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0, 0, 0,
  126. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0, 0,
  127. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.01, 0,
  128. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.015]

效果从地面平整,墙面贴合,墙体厚薄看还是非常好的。

论文分析

论文认为loam系列文章存在一些问题。

1.将其数据保存在全局体素地图中
2.难以执行闭环检测
3.没有结合其他绝对测量(GPS,指南针等)
4.当该体素地图变得密集时,在线优化过程的效率降低

决定使用因子图的思想优化激光SLAM,引入四种因子。

1.IMU预积分因子
2.激光雷达里程因子
3.GPS因子
4.闭环因子

对于预积分因子。计算采用常见的角速度,加速度,线速度,位置的公式推导方式即s=vt+1/2at^2

对于里程计因子。采用三步。

1.粗计算新帧与前关键帧的相对位姿变换,按阈值提取关键帧,五个关键帧合成一次区域的体素化点云地图。e,p代表edge和plane。M代表合成点云地图,F代表单关键帧点云,取∪集是合成。

2.edge和plane进行匹配。

3.优化也是常规优化。点对线和面对面来优化变换矩阵。

对于GPS因子。作者提出。

1.GPS测量值的时间戳根据里程计时间戳进行线性插值。
2.无需不断添加GPS因子。
3.当估计的位置协方差大于接收的GPS位置协方差时,添加GPS因子。
4.GPS在z方向可信度较低。

这里计算协方差是提取0,7,14号位置,根据ros手册和协方差知识,只是指xyz的测量方差。因为常见GPS为50HZ,所以短时间内能测多次,可以求方差。

对于闭环因子。作者提出。

1.使用的是一种简单但有效的基于欧氏距离的闭环检测方法。
2.闭环系数对于校正机器人高度的漂移特别有用,因为GPS的海拔高度测量非常不准确。

成果

1.精度对比

2.时间上特征多的区域即使scan少也更耗时,park时间少于Amsterdam。左图park

3.程序上作者注释,如果运动慢,点云由于运动导致的矫正不如没有

程序注释

程序ros节点图

全部函数说明

imageProjection.cpp:

PointXYZIPT 类型,点和强度,线数,时间。可以进pcl库使用。

Main ImageProjection():接收rosbag和lidar信息,imu的数值,来自imuPreintegration的odom数据;内存+参数初始化重置

void allocateMemory():点云输入,全部点云,提取点云空间重置(pointColInd与pointRange的定义问题)

void resetParameters():输入点云,提取点云,各点与光心距,imu数据清零

void imuHandler(const sensor_msgs::Imu::ConstPtr &imuMsg):仅接收放队列

void odometryHandler(const nav_msgs::Odometry::ConstPtr &odometryMsg):仅接收放队列

void cloudHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg):深度图生成,去畸变;深度图中提取点云,发布,重置参数

bool cachePointCloud(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg):清除临时点云,并检查点云帧里面是否有ring和time通道

bool deskewInfo():检测队列数量,

void imuDeskewInfo():计算imu转角

void odomDeskewInfo():读取odom数据,并根据协方差判断是否相信

void findRotation(double pointTime, float *rotXCur, float *rotYCur, float *rotZCur):将imuDeskewInfo数值用于每个点云点在一条线中的位置

void findPosition(double relTime, float *posXCur, float *posYCur, float *posZCur):高速时按位移百分比平移点云

PointType deskewPoint(PointType *point, double relTime):根据时间戳,对每个点去畸变

void projectPointCloud():将点云投影成深度图

void cloudExtraction():去边缘与过远点

void publishClouds():ros输出

featureExtraction.cpp:

struct smoothness_t 曲率与序号

struct by_value 大小排序函数

Main FeatureExtraction():获取imageProjection修正后输入点云,输出特征点和输入点云;参数初始化

void initializationValue():曲率储存,降采样,特征点,临近标签初始化

void laserCloudInfoHandler(const lio_sam::cloud_infoConstPtr &msgIn):点云句柄,包括下面所有内容

void calculateSmoothness():计算每个点的曲率

void markOccludedPoints():边界过远一侧去除,上下三线变化过大去除

void extractFeatures():特征点提取

void publishFeatureCloud():ros输出

imuPreintegration.cpp:

Main IMUPreintegration(): 接收imu数据,map来的odom数据,输出imu的odom,path;预积分参数设置,gtsam参数设置

void resetOptimization():gtsam相关优化参数重置

void resetParams():初始参数设定;是否第一帧;是否进行过优化;系统初始化

void odometryHandler(const nav_msgs::Odometry::ConstPtr &odomMsg):接收map里的odom数据

  1. 获取map里的odom位置和四元数数据,转换到imu坐标系
  2. 初始化优化参数,引入PVB数据,优化,重置优化器
  3. 更新bias重新预积分

bool failureDetection(const gtsam::Vector3 &velCur, const gtsam::imuBias::ConstantBias &biasCur):判断优化后速度和bias是否计算过大

void imuHandler(const sensor_msgs::Imu::ConstPtr &imu_raw):接收imu原始数据,使用gtsam短暂预积分,计算PVB,输出暂时轨迹

mapOptmization.cpp:

struct PointXYZIRPYT 位置欧拉角时间

Main mapOptimization():接收imageProjection的lidar,IMU信息;数据集的GPS信息,发布位姿,周围点,路径,历史帧,icp帧,局部帧;定义滤波参数与内存;

void allocateMemory():历史,局部位姿,棱面点,特征值,协方差,协方差添加标志,降采样点云,KD树,最近关键帧点云,临近历史帧点云

void pointAssociateToMap(PointType const *const pi, PointType *const po):点云点到世界坐标系转换

gtsam::Pose3 pclPointTogtsamPose3(PointTypePose thisPoint):pcl到gtsam格式

gtsam::Pose3 trans2gtsamPose(float transformIn[]):数组到gtsam格式

Eigen::Affine3f pclPointToAffine3f(PointTypePose thisPoint):pcl到仿射矩阵格式(实际欧拉角)

Eigen::Affine3f trans2Affine3f(float transformIn[]):矩阵到仿射矩阵格式

PointTypePose trans2PointTypePose(float transformIn[]):数组到pcl格式

三个线程:

1.接收数据

void gpsHandler(const nav_msgs::Odometry::ConstPtr &gpsMsg):GPS数据进队列

void laserCloudInfoHandler(const lio_sam::cloud_infoConstPtr &msgIn):

void updateInitialGuess():更新初始位姿

void extractSurroundingKeyFrames():从关键帧里面提取附近回环候选帧

void downsampleCurrentScan():进行下采样

void scan2MapOptimization(): 构建点到平面、点到直线的残差, 用高斯牛顿法进行优化

void saveKeyFramesAndFactor():添加factor到gtsam

void correctPoses():更新位姿

void publishOdometry():ros输出

void publishFrames():ros输出

2.闭环检测

void loopClosureThread():定期启动performLoopClosure()进行闭环检测

void performLoopClosure():根据两帧计算icp引入到gtsam优化中

bool detectLoopClosure(int *latestID, int *closestID):寻找临近历史帧和关键帧

3.显示

void visualizeGlobalMapThread():循环调用publishGlobalMap();ros环境关闭时退出循环并生成pcd

void publishGlobalMap():输出临近点云

imageProjection.cpp

  1. #include "utility.h"
  2. #include "lio_sam/cloud_info.h"
  3. // Velodyne点云点结构体构造,point4d是xyz和强度intensity.ring是线数,EIGEN_MAKE_ALIGNED_OPERATOR_NEW字符对齐
  4. struct PointXYZIRT
  5. {
  6. PCL_ADD_POINT4D
  7. PCL_ADD_INTENSITY;
  8. uint16_t ring;
  9. float time;
  10. EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  11. } EIGEN_ALIGN16;
  12. POINT_CLOUD_REGISTER_POINT_STRUCT (PointXYZIRT,
  13. (float, x, x) (float, y, y) (float, z, z) (float, intensity, intensity)
  14. (uint16_t, ring, ring) (float, time, time)
  15. )
  16. // Ouster
  17. // struct PointXYZIRT {
  18. // PCL_ADD_POINT4D;
  19. // float intensity;
  20. // uint32_t t;
  21. // uint16_t reflectivity;
  22. // uint8_t ring;
  23. // uint16_t noise;
  24. // uint32_t range;
  25. // EIGEN_MAKE_ALIGNED_OPERATOR_NEW
  26. // }EIGEN_ALIGN16;
  27. // POINT_CLOUD_REGISTER_POINT_STRUCT(PointXYZIRT,
  28. // (float, x, x) (float, y, y) (float, z, z) (float, intensity, intensity)
  29. // (uint32_t, t, t) (uint16_t, reflectivity, reflectivity)
  30. // (uint8_t, ring, ring) (uint16_t, noise, noise) (uint32_t, range, range)
  31. // )
  32. //循环imu长度,作者imu Microstrain 3DM-GX5-25,为500hz,使用loam数据集时要修改
  33. const int queueLength = 500;
  34. //引用参数
  35. class ImageProjection : public ParamServer
  36. {
  37. private:
  38. std::mutex imuLock;
  39. std::mutex odoLock;
  40. ros::Subscriber subLaserCloud;
  41. ros::Publisher pubLaserCloud;
  42. //输出距离不过近的点云点
  43. ros::Publisher pubExtractedCloud;
  44. //输出下文的cloudInfo
  45. ros::Publisher pubLaserCloudInfo;
  46. ros::Subscriber subImu;
  47. std::deque<sensor_msgs::Imu> imuQueue;
  48. ros::Subscriber subOdom;
  49. std::deque<nav_msgs::Odometry> odomQueue;
  50. std::deque<sensor_msgs::PointCloud2> cloudQueue;
  51. sensor_msgs::PointCloud2 currentCloudMsg;
  52. double *imuTime = new double[queueLength];
  53. double *imuRotX = new double[queueLength];
  54. double *imuRotY = new double[queueLength];
  55. double *imuRotZ = new double[queueLength];
  56. int imuPointerCur;
  57. bool firstPointFlag;
  58. Eigen::Affine3f transStartInverse;
  59. pcl::PointCloud<PointXYZIRT>::Ptr laserCloudIn;
  60. pcl::PointCloud<PointType>::Ptr fullCloud;
  61. pcl::PointCloud<PointType>::Ptr extractedCloud;
  62. int deskewFlag;
  63. cv::Mat rangeMat;
  64. bool odomDeskewFlag;
  65. float odomIncreX;
  66. float odomIncreY;
  67. float odomIncreZ;
  68. //这里面有imu是否可用,odo是否可用,lidar的位姿,scan中各点对应线号,各点与光心距离的信息
  69. lio_sam::cloud_info cloudInfo;
  70. double timeScanCur;
  71. double timeScanNext;
  72. std_msgs::Header cloudHeader;
  73. public:
  74. ImageProjection():
  75. deskewFlag(0)
  76. {
  77. subImu = nh.subscribe<sensor_msgs::Imu>(imuTopic, 2000, &ImageProjection::imuHandler, this, ros::TransportHints().tcpNoDelay());
  78. subOdom = nh.subscribe<nav_msgs::Odometry>(odomTopic, 2000, &ImageProjection::odometryHandler, this, ros::TransportHints().tcpNoDelay());
  79. subLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>(pointCloudTopic, 5, &ImageProjection::cloudHandler, this, ros::TransportHints().tcpNoDelay());
  80. pubExtractedCloud = nh.advertise<sensor_msgs::PointCloud2> ("lio_sam/deskew/cloud_deskewed", 1);
  81. pubLaserCloudInfo = nh.advertise<lio_sam::cloud_info> ("lio_sam/deskew/cloud_info", 10);
  82. //配置内存,重置参数
  83. allocateMemory();
  84. resetParameters();
  85. //pcl控制台输出error信息
  86. pcl::console::setVerbosityLevel(pcl::console::L_ERROR);
  87. }
  88. void allocateMemory()
  89. {
  90. laserCloudIn.reset(new pcl::PointCloud<PointXYZIRT>());
  91. fullCloud.reset(new pcl::PointCloud<PointType>());
  92. extractedCloud.reset(new pcl::PointCloud<PointType>());
  93. fullCloud->points.resize(N_SCAN*Horizon_SCAN);
  94. cloudInfo.startRingIndex.assign(N_SCAN, 0);
  95. cloudInfo.endRingIndex.assign(N_SCAN, 0);
  96. //从h文件看是16*1800
  97. cloudInfo.pointColInd.assign(N_SCAN*Horizon_SCAN, 0);
  98. cloudInfo.pointRange.assign(N_SCAN*Horizon_SCAN, 0);
  99. resetParameters();
  100. }
  101. void resetParameters()
  102. {
  103. laserCloudIn->clear();
  104. extractedCloud->clear();
  105. // reset range matrix for range image projection 全部默认初值为最大浮点数
  106. rangeMat = cv::Mat(N_SCAN, Horizon_SCAN, CV_32F, cv::Scalar::all(FLT_MAX));
  107. imuPointerCur = 0;
  108. firstPointFlag = true;
  109. odomDeskewFlag = false;
  110. for (int i = 0; i < queueLength; ++i)
  111. {
  112. imuTime[i] = 0;
  113. imuRotX[i] = 0;
  114. imuRotY[i] = 0;
  115. imuRotZ[i] = 0;
  116. }
  117. }
  118. ~ImageProjection(){}
  119. void imuHandler(const sensor_msgs::Imu::ConstPtr& imuMsg)
  120. {
  121. sensor_msgs::Imu thisImu = imuConverter(*imuMsg);
  122. std::lock_guard<std::mutex> lock1(imuLock);
  123. imuQueue.push_back(thisImu);
  124. // debug IMU data
  125. // cout << std::setprecision(6);
  126. // cout << "IMU acc: " << endl;
  127. // cout << "x: " << thisImu.linear_acceleration.x <<
  128. // ", y: " << thisImu.linear_acceleration.y <<
  129. // ", z: " << thisImu.linear_acceleration.z << endl;
  130. // cout << "IMU gyro: " << endl;
  131. // cout << "x: " << thisImu.angular_velocity.x <<
  132. // ", y: " << thisImu.angular_velocity.y <<
  133. // ", z: " << thisImu.angular_velocity.z << endl;
  134. // double imuRoll, imuPitch, imuYaw;
  135. // tf::Quaternion orientation;
  136. // tf::quaternionMsgToTF(thisImu.orientation, orientation);
  137. // tf::Matrix3x3(orientation).getRPY(imuRoll, imuPitch, imuYaw);
  138. // cout << "IMU roll pitch yaw: " << endl;
  139. // cout << "roll: " << imuRoll << ", pitch: " << imuPitch << ", yaw: " << imuYaw << endl << endl;
  140. }
  141. void odometryHandler(const nav_msgs::Odometry::ConstPtr& odometryMsg)
  142. {
  143. std::lock_guard<std::mutex> lock2(odoLock);
  144. odomQueue.push_back(*odometryMsg);
  145. }
  146. void cloudHandler(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg)
  147. {
  148. //检测一下点数量是否够2个,线数通道和时间通道是否存在
  149. if (!cachePointCloud(laserCloudMsg))
  150. return;
  151. //IMU不是空,imu序列的第一个时间戳大于当前帧雷达时间戳,IMU最后一个时间戳小于下一帧雷达时间戳
  152. if (!deskewInfo())
  153. return;
  154. projectPointCloud();
  155. cloudExtraction();
  156. publishClouds();
  157. resetParameters();
  158. }
  159. //检测一下点数量是否够2个,线数通道和时间通道是否存在
  160. bool cachePointCloud(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg)
  161. {
  162. // cache point cloud
  163. cloudQueue.push_back(*laserCloudMsg);
  164. if (cloudQueue.size() <= 2)
  165. return false;
  166. else
  167. {
  168. //提取队列第一个做timeScanCur,之后pop出第一个,选第二个做timeScanNext。后面与imu对比时间戳。
  169. currentCloudMsg = cloudQueue.front();
  170. cloudQueue.pop_front();
  171. cloudHeader = currentCloudMsg.header;
  172. timeScanCur = cloudHeader.stamp.toSec();
  173. timeScanNext = cloudQueue.front().header.stamp.toSec();
  174. }
  175. // convert cloud
  176. pcl::fromROSMsg(currentCloudMsg, *laserCloudIn);
  177. // check dense flag
  178. if (laserCloudIn->is_dense == false)
  179. {
  180. ROS_ERROR("Point cloud is not in dense format, please remove NaN points first!");
  181. ros::shutdown();
  182. }
  183. // check ring channel
  184. static int ringFlag = 0;
  185. if (ringFlag == 0)
  186. {
  187. ringFlag = -1;
  188. for (int i = 0; i < (int)currentCloudMsg.fields.size(); ++i)
  189. {
  190. if (currentCloudMsg.fields[i].name == "ring")
  191. {
  192. ringFlag = 1;
  193. break;
  194. }
  195. }
  196. if (ringFlag == -1)
  197. {
  198. ROS_ERROR("Point cloud ring channel not available, please configure your point cloud data!");
  199. ros::shutdown();
  200. }
  201. }
  202. // check point time
  203. if (deskewFlag == 0)
  204. {
  205. deskewFlag = -1;
  206. for (int i = 0; i < (int)currentCloudMsg.fields.size(); ++i)
  207. {
  208. if (currentCloudMsg.fields[i].name == timeField)
  209. {
  210. deskewFlag = 1;
  211. break;
  212. }
  213. }
  214. if (deskewFlag == -1)
  215. ROS_WARN("Point cloud timestamp not available, deskew function disabled, system will drift significantly!");
  216. }
  217. return true;
  218. }
  219. //IMU不是空,imu序列的第一个时间戳大于当前帧雷达时间戳,IMU最后一个时间戳小于下一帧雷达时间戳
  220. bool deskewInfo()
  221. {
  222. std::lock_guard<std::mutex> lock1(imuLock);
  223. std::lock_guard<std::mutex> lock2(odoLock);
  224. // make sure IMU data available for the scan
  225. if (imuQueue.empty() || imuQueue.front().header.stamp.toSec() > timeScanCur || imuQueue.back().header.stamp.toSec() < timeScanNext)
  226. {
  227. ROS_DEBUG("Waiting for IMU data ...");
  228. return false;
  229. }
  230. imuDeskewInfo();
  231. odomDeskewInfo();
  232. return true;
  233. }
  234. //计算当前激光时间戳前,每个时刻imu累计出的角速度,并判定imu数据是否可用(数量够)
  235. void imuDeskewInfo()
  236. {
  237. cloudInfo.imuAvailable = false;
  238. //直到imu的时间戳到当前scan时间戳前0.01s以内
  239. while (!imuQueue.empty())
  240. {
  241. if (imuQueue.front().header.stamp.toSec() < timeScanCur - 0.01)
  242. imuQueue.pop_front();
  243. else
  244. break;
  245. }
  246. if (imuQueue.empty())
  247. return;
  248. imuPointerCur = 0;
  249. for (int i = 0; i < (int)imuQueue.size(); ++i)
  250. {
  251. sensor_msgs::Imu thisImuMsg = imuQueue[i];
  252. double currentImuTime = thisImuMsg.header.stamp.toSec();
  253. //当前imu时间戳小于当前帧点云时间戳转换,大于下一帧+0.01s退出
  254. // get roll, pitch, and yaw estimation for this scan
  255. if (currentImuTime <= timeScanCur)
  256. imuRPY2rosRPY(&thisImuMsg, &cloudInfo.imuRollInit, &cloudInfo.imuPitchInit, &cloudInfo.imuYawInit);
  257. if (currentImuTime > timeScanNext + 0.01)
  258. break;
  259. //赋值0
  260. if (imuPointerCur == 0){
  261. imuRotX[0] = 0;
  262. imuRotY[0] = 0;
  263. imuRotZ[0] = 0;
  264. imuTime[0] = currentImuTime;
  265. ++imuPointerCur;
  266. continue;
  267. }
  268. // get angular velocity
  269. double angular_x, angular_y, angular_z;
  270. imuAngular2rosAngular(&thisImuMsg, &angular_x, &angular_y, &angular_z);
  271. // integrate rotation 用a×t累加
  272. double timeDiff = currentImuTime - imuTime[imuPointerCur-1];
  273. imuRotX[imuPointerCur] = imuRotX[imuPointerCur-1] + angular_x * timeDiff;
  274. imuRotY[imuPointerCur] = imuRotY[imuPointerCur-1] + angular_y * timeDiff;
  275. imuRotZ[imuPointerCur] = imuRotZ[imuPointerCur-1] + angular_z * timeDiff;
  276. imuTime[imuPointerCur] = currentImuTime;
  277. ++imuPointerCur;
  278. }
  279. //把赋值0后面的自加减回去
  280. --imuPointerCur;
  281. //如果数量为0直接返回,不为0,该imu属性为可用
  282. if (imuPointerCur <= 0)
  283. return;
  284. cloudInfo.imuAvailable = true;
  285. }
  286. //应该是为了groundtruth对比用的,pose信息保存在cloudInfo里
  287. void odomDeskewInfo()
  288. {
  289. cloudInfo.odomAvailable = false;
  290. while (!odomQueue.empty())
  291. {
  292. if (odomQueue.front().header.stamp.toSec() < timeScanCur - 0.01)
  293. odomQueue.pop_front();
  294. else
  295. break;
  296. }
  297. if (odomQueue.empty())
  298. return;
  299. if (odomQueue.front().header.stamp.toSec() > timeScanCur)
  300. return;
  301. // get start odometry at the beinning of the scan
  302. nav_msgs::Odometry startOdomMsg;
  303. for (int i = 0; i < (int)odomQueue.size(); ++i)
  304. {
  305. startOdomMsg = odomQueue[i];
  306. if (ROS_TIME(&startOdomMsg) < timeScanCur)
  307. continue;
  308. else
  309. break;
  310. }
  311. tf::Quaternion orientation;
  312. tf::quaternionMsgToTF(startOdomMsg.pose.pose.orientation, orientation);
  313. double roll, pitch, yaw;
  314. tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw);
  315. // Initial guess used in mapOptimization
  316. cloudInfo.initialGuessX = startOdomMsg.pose.pose.position.x;
  317. cloudInfo.initialGuessY = startOdomMsg.pose.pose.position.y;
  318. cloudInfo.initialGuessZ = startOdomMsg.pose.pose.position.z;
  319. cloudInfo.initialGuessRoll = roll;
  320. cloudInfo.initialGuessPitch = pitch;
  321. cloudInfo.initialGuessYaw = yaw;
  322. cloudInfo.imuPreintegrationResetId = round(startOdomMsg.pose.covariance[0]);
  323. cloudInfo.odomAvailable = true;
  324. // get end odometry at the end of the scan
  325. odomDeskewFlag = false;
  326. if (odomQueue.back().header.stamp.toSec() < timeScanNext)
  327. return;
  328. nav_msgs::Odometry endOdomMsg;
  329. for (int i = 0; i < (int)odomQueue.size(); ++i)
  330. {
  331. endOdomMsg = odomQueue[i];
  332. if (ROS_TIME(&endOdomMsg) < timeScanNext)
  333. continue;
  334. else
  335. break;
  336. }
  337. if (int(round(startOdomMsg.pose.covariance[0])) != int(round(endOdomMsg.pose.covariance[0])))
  338. return;
  339. Eigen::Affine3f transBegin = pcl::getTransformation(startOdomMsg.pose.pose.position.x, startOdomMsg.pose.pose.position.y, startOdomMsg.pose.pose.position.z, roll, pitch, yaw);
  340. tf::quaternionMsgToTF(endOdomMsg.pose.pose.orientation, orientation);
  341. tf::Matrix3x3(orientation).getRPY(roll, pitch, yaw);
  342. Eigen::Affine3f transEnd = pcl::getTransformation(endOdomMsg.pose.pose.position.x, endOdomMsg.pose.pose.position.y, endOdomMsg.pose.pose.position.z, roll, pitch, yaw);
  343. Eigen::Affine3f transBt = transBegin.inverse() * transEnd;
  344. float rollIncre, pitchIncre, yawIncre;
  345. pcl::getTranslationAndEulerAngles(transBt, odomIncreX, odomIncreY, odomIncreZ, rollIncre, pitchIncre, yawIncre);
  346. odomDeskewFlag = true;
  347. }
  348. //根据点云中某点的时间戳赋予其对应插值得到的旋转量
  349. void findRotation(double pointTime, float *rotXCur, float *rotYCur, float *rotZCur)
  350. {
  351. *rotXCur = 0; *rotYCur = 0; *rotZCur = 0;
  352. int imuPointerFront = 0;
  353. //要么imuPointerFront计数大于了imu一组的数量imuPointerCur,(异常跳出)
  354. //要么该次imu时间戳大于了该点时间戳(话说这函数一个点调用一次,是不是静态变量好一些)
  355. while (imuPointerFront < imuPointerCur)
  356. {
  357. if (pointTime < imuTime[imuPointerFront])
  358. break;
  359. ++imuPointerFront;
  360. }
  361. //如果计数为0或该次imu时间戳小于了该点时间戳(异常退出)
  362. if (pointTime > imuTime[imuPointerFront] || imuPointerFront == 0)
  363. {
  364. *rotXCur = imuRotX[imuPointerFront];
  365. *rotYCur = imuRotY[imuPointerFront];
  366. *rotZCur = imuRotZ[imuPointerFront];
  367. }
  368. else {
  369. int imuPointerBack = imuPointerFront - 1;
  370. //算一下该点时间戳在本组imu中的位置
  371. double ratioFront = (pointTime - imuTime[imuPointerBack]) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);
  372. double ratioBack = (imuTime[imuPointerFront] - pointTime) / (imuTime[imuPointerFront] - imuTime[imuPointerBack]);
  373. //按前后百分比赋予旋转量
  374. *rotXCur = imuRotX[imuPointerFront] * ratioFront + imuRotX[imuPointerBack] * ratioBack;
  375. *rotYCur = imuRotY[imuPointerFront] * ratioFront + imuRotY[imuPointerBack] * ratioBack;
  376. *rotZCur = imuRotZ[imuPointerFront] * ratioFront + imuRotZ[imuPointerBack] * ratioBack;
  377. }
  378. }
  379. void findPosition(double relTime, float *posXCur, float *posYCur, float *posZCur)
  380. {
  381. *posXCur = 0; *posYCur = 0; *posZCur = 0;
  382. // If the sensor moves relatively slow, like walking speed, positional deskew seems to have little benefits. Thus code below is commented.
  383. // if (cloudInfo.odomAvailable == false || odomDeskewFlag == false)
  384. // return;
  385. //
  386. // float ratio = relTime / (timeScanNext - timeScanCur);
  387. //
  388. // *posXCur = ratio * odomIncreX;
  389. // *posYCur = ratio * odomIncreY;
  390. // *posZCur = ratio * odomIncreZ;
  391. }
  392. //这个类型是pcl::PointXYZI
  393. PointType deskewPoint(PointType *point, double relTime)
  394. {
  395. //这个来源于上文的时间戳通道和imu可用判断,没有或是不可用则返回点
  396. if (deskewFlag == -1 || cloudInfo.imuAvailable == false)
  397. return *point;
  398. //点的时间等于scan时间加relTime(后文的laserCloudIn->points[i].time)
  399. double pointTime = timeScanCur + relTime;
  400. //根据时间戳插值获取imu计算的旋转量与位置量(注意imu计算的)
  401. float rotXCur, rotYCur, rotZCur;
  402. findRotation(pointTime, &rotXCur, &rotYCur, &rotZCur);
  403. float posXCur, posYCur, posZCur;
  404. findPosition(relTime, &posXCur, &posYCur, &posZCur);
  405. //这里的firstPointFlag来源于resetParameters函数,而resetParameters函数每次ros调用cloudHandler都会启动
  406. //也就是求扫描第一个点时lidar的世界坐标系下变换矩阵的逆
  407. if (firstPointFlag == true)
  408. {
  409. transStartInverse = (pcl::getTransformation(posXCur, posYCur, posZCur, rotXCur, rotYCur, rotZCur)).inverse();
  410. firstPointFlag = false;
  411. }
  412. // transform points to start
  413. //扫描当前点时lidar的世界坐标系下变换矩阵
  414. Eigen::Affine3f transFinal = pcl::getTransformation(posXCur, posYCur, posZCur, rotXCur, rotYCur, rotZCur);
  415. //扫描该点相对扫描本次scan第一个点的lidar变换矩阵=第一个点时lidar世界坐标系下变换矩阵的逆×当前点时lidar世界坐标系下变换矩阵
  416. Eigen::Affine3f transBt = transStartInverse * transFinal;
  417. //根据lidar位姿变换,修正点云位置
  418. PointType newPoint;
  419. newPoint.x = transBt(0,0) * point->x + transBt(0,1) * point->y + transBt(0,2) * point->z + transBt(0,3);
  420. newPoint.y = transBt(1,0) * point->x + transBt(1,1) * point->y + transBt(1,2) * point->z + transBt(1,3);
  421. newPoint.z = transBt(2,0) * point->x + transBt(2,1) * point->y + transBt(2,2) * point->z + transBt(2,3);
  422. newPoint.intensity = point->intensity;
  423. return newPoint;
  424. }
  425. void projectPointCloud()
  426. {
  427. int cloudSize = laserCloudIn->points.size();
  428. // range image projection
  429. for (int i = 0; i < cloudSize; ++i)
  430. {
  431. PointType thisPoint;
  432. thisPoint.x = laserCloudIn->points[i].x;
  433. thisPoint.y = laserCloudIn->points[i].y;
  434. thisPoint.z = laserCloudIn->points[i].z;
  435. thisPoint.intensity = laserCloudIn->points[i].intensity;
  436. int rowIdn = laserCloudIn->points[i].ring;
  437. //0--N_SCAN内,且是整数
  438. if (rowIdn < 0 || rowIdn >= N_SCAN)
  439. continue;
  440. if (rowIdn % downsampleRate != 0)
  441. continue;
  442. //仰角
  443. float horizonAngle = atan2(thisPoint.x, thisPoint.y) * 180 / M_PI;
  444. //每线Horizon_SCAN(1800)的点,占据360度除一下
  445. static float ang_res_x = 360.0/float(Horizon_SCAN);
  446. int columnIdn = -round((horizonAngle-90.0)/ang_res_x) + Horizon_SCAN/2;
  447. if (columnIdn >= Horizon_SCAN)
  448. columnIdn -= Horizon_SCAN;
  449. //如果线数不正确
  450. if (columnIdn < 0 || columnIdn >= Horizon_SCAN)
  451. continue;
  452. //该点云点距离光心距离
  453. float range = pointDistance(thisPoint);
  454. if (range < 1.0)
  455. continue;
  456. //如果没有初始赋值
  457. if (rangeMat.at<float>(rowIdn, columnIdn) != FLT_MAX)
  458. continue;
  459. // for the amsterdam dataset
  460. // if (range < 6.0 && rowIdn <= 7 && (columnIdn >= 1600 || columnIdn <= 200))
  461. // continue;
  462. // if (thisPoint.z < -2.0)
  463. // continue;
  464. thisPoint = deskewPoint(&thisPoint, laserCloudIn->points[i].time); // Velodyne
  465. // thisPoint = deskewPoint(&thisPoint, (float)laserCloudIn->points[i].t / 1000000000.0); // Ouster
  466. //计算各点距离
  467. rangeMat.at<float>(rowIdn, columnIdn) = pointDistance(thisPoint);
  468. int index = columnIdn + rowIdn * Horizon_SCAN;
  469. fullCloud->points[index] = thisPoint;
  470. }
  471. }
  472. void cloudExtraction()
  473. {
  474. int count = 0;
  475. // extract segmented cloud for lidar odometry
  476. for (int i = 0; i < N_SCAN; ++i)
  477. {
  478. cloudInfo.startRingIndex[i] = count - 1 + 5;
  479. for (int j = 0; j < Horizon_SCAN; ++j)
  480. {
  481. if (rangeMat.at<float>(i,j) != FLT_MAX)
  482. {
  483. // mark the points' column index for marking occlusion later
  484. cloudInfo.pointColInd[count] = j;
  485. // save range info
  486. cloudInfo.pointRange[count] = rangeMat.at<float>(i,j);
  487. // save extracted cloud
  488. extractedCloud->push_back(fullCloud->points[j + i*Horizon_SCAN]);
  489. // size of extracted cloud
  490. ++count;
  491. }
  492. }
  493. cloudInfo.endRingIndex[i] = count -1 - 5;
  494. }
  495. }
  496. void publishClouds()
  497. {
  498. cloudInfo.header = cloudHeader;
  499. //publishCloud在h文件里
  500. cloudInfo.cloud_deskewed = publishCloud(&pubExtractedCloud, extractedCloud, cloudHeader.stamp, "base_link");
  501. pubLaserCloudInfo.publish(cloudInfo);
  502. }
  503. };
  504. int main(int argc, char** argv)
  505. {
  506. ros::init(argc, argv, "lio_sam");
  507. ImageProjection IP;
  508. ROS_INFO("\033[1;32m----> Image Projection Started.\033[0m");
  509. ros::MultiThreadedSpinner spinner(3);
  510. spinner.spin();
  511. return 0;
  512. }

featureExtraction.cpp

  1. #include "utility.h"
  2. #include "lio_sam/cloud_info.h"
  3. //曲率值和序号
  4. struct smoothness_t{
  5. float value;
  6. size_t ind;
  7. };
  8. //曲率值对比
  9. struct by_value{
  10. bool operator()(smoothness_t const &left, smoothness_t const &right) {
  11. return left.value < right.value;
  12. }
  13. };
  14. class FeatureExtraction : public ParamServer
  15. {
  16. public:
  17. ros::Subscriber subLaserCloudInfo;
  18. ros::Publisher pubLaserCloudInfo;
  19. ros::Publisher pubCornerPoints;
  20. ros::Publisher pubSurfacePoints;
  21. pcl::PointCloud<PointType>::Ptr extractedCloud;
  22. pcl::PointCloud<PointType>::Ptr cornerCloud;
  23. pcl::PointCloud<PointType>::Ptr surfaceCloud;
  24. pcl::VoxelGrid<PointType> downSizeFilter;
  25. lio_sam::cloud_info cloudInfo;
  26. std_msgs::Header cloudHeader;
  27. //曲率存放
  28. std::vector<smoothness_t> cloudSmoothness;
  29. float *cloudCurvature;
  30. int *cloudNeighborPicked;
  31. int *cloudLabel;
  32. FeatureExtraction()
  33. {
  34. subLaserCloudInfo = nh.subscribe<lio_sam::cloud_info>("lio_sam/deskew/cloud_info", 10, &FeatureExtraction::laserCloudInfoHandler, this, ros::TransportHints().tcpNoDelay());
  35. pubLaserCloudInfo = nh.advertise<lio_sam::cloud_info> ("lio_sam/feature/cloud_info", 10);
  36. pubCornerPoints = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/feature/cloud_corner", 1);
  37. pubSurfacePoints = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/feature/cloud_surface", 1);
  38. initializationValue();
  39. }
  40. void initializationValue()
  41. {
  42. cloudSmoothness.resize(N_SCAN*Horizon_SCAN);
  43. downSizeFilter.setLeafSize(odometrySurfLeafSize, odometrySurfLeafSize, odometrySurfLeafSize);
  44. extractedCloud.reset(new pcl::PointCloud<PointType>());
  45. cornerCloud.reset(new pcl::PointCloud<PointType>());
  46. surfaceCloud.reset(new pcl::PointCloud<PointType>());
  47. cloudCurvature = new float[N_SCAN*Horizon_SCAN];
  48. cloudNeighborPicked = new int[N_SCAN*Horizon_SCAN];
  49. cloudLabel = new int[N_SCAN*Horizon_SCAN];
  50. }
  51. void laserCloudInfoHandler(const lio_sam::cloud_infoConstPtr& msgIn)
  52. {
  53. cloudInfo = *msgIn; // new cloud info
  54. cloudHeader = msgIn->header; // new cloud header
  55. pcl::fromROSMsg(msgIn->cloud_deskewed, *extractedCloud); // new cloud for extraction
  56. //10点算曲率
  57. calculateSmoothness();
  58. //相邻两点距光心差距大则设为不进行特征提取的状态,差距过大,周围点也设置为不进行特征提取的状态
  59. markOccludedPoints();
  60. //对于scan中的棱点和面点进行提取
  61. extractFeatures();
  62. publishFeatureCloud();
  63. }
  64. //10点算曲率
  65. void calculateSmoothness()
  66. {
  67. int cloudSize = extractedCloud->points.size();
  68. for (int i = 5; i < cloudSize - 5; i++)
  69. {
  70. float diffRange = cloudInfo.pointRange[i-5] + cloudInfo.pointRange[i-4]
  71. + cloudInfo.pointRange[i-3] + cloudInfo.pointRange[i-2]
  72. + cloudInfo.pointRange[i-1] - cloudInfo.pointRange[i] * 10
  73. + cloudInfo.pointRange[i+1] + cloudInfo.pointRange[i+2]
  74. + cloudInfo.pointRange[i+3] + cloudInfo.pointRange[i+4]
  75. + cloudInfo.pointRange[i+5];
  76. cloudCurvature[i] = diffRange*diffRange;//diffX * diffX + diffY * diffY + diffZ * diffZ;
  77. cloudNeighborPicked[i] = 0;
  78. cloudLabel[i] = 0;
  79. // cloudSmoothness for sorting
  80. cloudSmoothness[i].value = cloudCurvature[i];
  81. cloudSmoothness[i].ind = i;
  82. }
  83. }
  84. //相邻两点距光心差距大则设为不进行特征提取的状态,差距过大,周围点也设置为不进行特征提取的状态
  85. void markOccludedPoints()
  86. {
  87. int cloudSize = extractedCloud->points.size();
  88. // mark occluded points and parallel beam points
  89. for (int i = 5; i < cloudSize - 6; ++i)
  90. {
  91. // occluded points
  92. float depth1 = cloudInfo.pointRange[i];
  93. float depth2 = cloudInfo.pointRange[i+1];
  94. int columnDiff = std::abs(int(cloudInfo.pointColInd[i+1] - cloudInfo.pointColInd[i]));
  95. if (columnDiff < 10){
  96. // 10 pixel diff in range image
  97. if (depth1 - depth2 > 0.3){
  98. cloudNeighborPicked[i - 5] = 1;
  99. cloudNeighborPicked[i - 4] = 1;
  100. cloudNeighborPicked[i - 3] = 1;
  101. cloudNeighborPicked[i - 2] = 1;
  102. cloudNeighborPicked[i - 1] = 1;
  103. cloudNeighborPicked[i] = 1;
  104. }else if (depth2 - depth1 > 0.3){
  105. cloudNeighborPicked[i + 1] = 1;
  106. cloudNeighborPicked[i + 2] = 1;
  107. cloudNeighborPicked[i + 3] = 1;
  108. cloudNeighborPicked[i + 4] = 1;
  109. cloudNeighborPicked[i + 5] = 1;
  110. cloudNeighborPicked[i + 6] = 1;
  111. }
  112. }
  113. // parallel beam
  114. float diff1 = std::abs(float(cloudInfo.pointRange[i-1] - cloudInfo.pointRange[i]));
  115. float diff2 = std::abs(float(cloudInfo.pointRange[i+1] - cloudInfo.pointRange[i]));
  116. if (diff1 > 0.02 * cloudInfo.pointRange[i] && diff2 > 0.02 * cloudInfo.pointRange[i])
  117. cloudNeighborPicked[i] = 1;
  118. }
  119. }
  120. //对于scan中的棱点和面点进行提取
  121. void extractFeatures()
  122. {
  123. cornerCloud->clear();
  124. surfaceCloud->clear();
  125. pcl::PointCloud<PointType>::Ptr surfaceCloudScan(new pcl::PointCloud<PointType>());
  126. pcl::PointCloud<PointType>::Ptr surfaceCloudScanDS(new pcl::PointCloud<PointType>());
  127. //和loam一样,对于每条线分为六部分,各自取20棱点和全部面点
  128. for (int i = 0; i < N_SCAN; i++)
  129. {
  130. surfaceCloudScan->clear();
  131. for (int j = 0; j < 6; j++)
  132. {
  133. //获取六段的初始截止位置
  134. int sp = (cloudInfo.startRingIndex[i] * (6 - j) + cloudInfo.endRingIndex[i] * j) / 6;
  135. int ep = (cloudInfo.startRingIndex[i] * (5 - j) + cloudInfo.endRingIndex[i] * (j + 1)) / 6 - 1;
  136. if (sp >= ep)
  137. continue;
  138. //按曲率排序
  139. std::sort(cloudSmoothness.begin()+sp, cloudSmoothness.begin()+ep, by_value());
  140. int largestPickedNum = 0;
  141. for (int k = ep; k >= sp; k--)
  142. {
  143. int ind = cloudSmoothness[k].ind;
  144. //可特征提取的状态+曲率大于0.1,保存20个棱点,周围点除非光心距差距极大,不然都设置为可特征提取的状态
  145. //达到均匀分布的目的
  146. if (cloudNeighborPicked[ind] == 0 && cloudCurvature[ind] > edgeThreshold)
  147. {
  148. largestPickedNum++;
  149. if (largestPickedNum <= 20){
  150. cloudLabel[ind] = 1;
  151. cornerCloud->push_back(extractedCloud->points[ind]);
  152. } else {
  153. break;
  154. }
  155. cloudNeighborPicked[ind] = 1;
  156. for (int l = 1; l <= 5; l++)
  157. {
  158. int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l - 1]));
  159. if (columnDiff > 10)
  160. break;
  161. cloudNeighborPicked[ind + l] = 1;
  162. }
  163. for (int l = -1; l >= -5; l--)
  164. {
  165. int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l + 1]));
  166. if (columnDiff > 10)
  167. break;
  168. cloudNeighborPicked[ind + l] = 1;
  169. }
  170. }
  171. }
  172. //可特征提取的状态+曲率小于0.1,保存全部面点,周围点除非光心距差距极大,不然都设置为可特征提取的状态
  173. //达到均匀分布的目的
  174. for (int k = sp; k <= ep; k++)
  175. {
  176. int ind = cloudSmoothness[k].ind;
  177. if (cloudNeighborPicked[ind] == 0 && cloudCurvature[ind] < surfThreshold)
  178. {
  179. cloudLabel[ind] = -1;
  180. cloudNeighborPicked[ind] = 1;
  181. for (int l = 1; l <= 5; l++) {
  182. int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l - 1]));
  183. if (columnDiff > 10)
  184. break;
  185. cloudNeighborPicked[ind + l] = 1;
  186. }
  187. for (int l = -1; l >= -5; l--) {
  188. int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l + 1]));
  189. if (columnDiff > 10)
  190. break;
  191. cloudNeighborPicked[ind + l] = 1;
  192. }
  193. }
  194. }
  195. for (int k = sp; k <= ep; k++)
  196. {
  197. if (cloudLabel[k] <= 0){
  198. surfaceCloudScan->push_back(extractedCloud->points[k]);
  199. }
  200. }
  201. }
  202. surfaceCloudScanDS->clear();
  203. downSizeFilter.setInputCloud(surfaceCloudScan);
  204. downSizeFilter.filter(*surfaceCloudScanDS);
  205. *surfaceCloud += *surfaceCloudScanDS;
  206. }
  207. }
  208. void freeCloudInfoMemory()
  209. {
  210. cloudInfo.startRingIndex.clear();
  211. cloudInfo.endRingIndex.clear();
  212. cloudInfo.pointColInd.clear();
  213. cloudInfo.pointRange.clear();
  214. }
  215. void publishFeatureCloud()
  216. {
  217. // free cloud info memory
  218. freeCloudInfoMemory();
  219. // save newly extracted features
  220. cloudInfo.cloud_corner = publishCloud(&pubCornerPoints, cornerCloud, cloudHeader.stamp, "base_link");
  221. cloudInfo.cloud_surface = publishCloud(&pubSurfacePoints, surfaceCloud, cloudHeader.stamp, "base_link");
  222. // publish to mapOptimization
  223. pubLaserCloudInfo.publish(cloudInfo);
  224. }
  225. };
  226. int main(int argc, char** argv)
  227. {
  228. ros::init(argc, argv, "lio_sam");
  229. FeatureExtraction FE;
  230. ROS_INFO("\033[1;32m----> Feature Extraction Started.\033[0m");
  231. ros::spin();
  232. return 0;
  233. }

imuPreintegration.cpp

发现已经有写的更好的博客了,引用一下他的注释

  1. #include "utility.h"
  2. #include <gtsam/geometry/Rot3.h>
  3. #include <gtsam/geometry/Pose3.h>
  4. #include <gtsam/slam/PriorFactor.h>
  5. #include <gtsam/slam/BetweenFactor.h>
  6. #include <gtsam/navigation/GPSFactor.h>
  7. #include <gtsam/navigation/ImuFactor.h>
  8. #include <gtsam/navigation/CombinedImuFactor.h>
  9. #include <gtsam/nonlinear/NonlinearFactorGraph.h>
  10. #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
  11. #include <gtsam/nonlinear/Marginals.h>
  12. #include <gtsam/nonlinear/Values.h>
  13. #include <gtsam/inference/Symbol.h>
  14. #include <gtsam/nonlinear/ISAM2.h>
  15. #include <gtsam_unstable/nonlinear/IncrementalFixedLagSmoother.h>
  16. using gtsam::symbol_shorthand::X; // Pose3 (x,y,z,r,p,y)
  17. using gtsam::symbol_shorthand::V; // Vel (xdot,ydot,zdot)
  18. using gtsam::symbol_shorthand::B; // Bias (ax,ay,az,gx,gy,gz)
  19. class IMUPreintegration : public ParamServer {
  20. public:
  21. IMUPreintegration() {
  22. // subscriber 订阅imu数据和激光Odom
  23. // 业务逻辑都在callback里面写, 两个数据是耦合关系, imu通过激光odom给出优化后的预积分预测
  24. // odom根据预测的位姿优化、融合出新的odom
  25. subImu = nh.subscribe<sensor_msgs::Imu>(imuTopic, 2000, &IMUPreintegration::imuHandler, this,
  26. ros::TransportHints().tcpNoDelay());
  27. subOdometry = nh.subscribe<nav_msgs::Odometry>("lio_sam/mapping/odometry", 5,
  28. &IMUPreintegration::odometryHandler, this,
  29. ros::TransportHints().tcpNoDelay());
  30. // publisher 发布融合后的imu path和预积分完成优化后预测的odom
  31. pubImuOdometry = nh.advertise<nav_msgs::Odometry>(odomTopic, 2000);
  32. pubImuPath = nh.advertise<nav_msgs::Path>("lio_sam/imu/path", 1);
  33. map_to_odom = tf::Transform(tf::createQuaternionFromRPY(0, 0, 0), tf::Vector3(0, 0, 0));
  34. // 下面是预积分使用到的gtsam的一些参数配置
  35. boost::shared_ptr<gtsam::PreintegrationParams> p = gtsam::PreintegrationParams::MakeSharedU(imuGravity);
  36. p->accelerometerCovariance = gtsam::Matrix33::Identity(3, 3) * pow(imuAccNoise, 2); // acc white noise in continuous
  37. p->gyroscopeCovariance = gtsam::Matrix33::Identity(3, 3) * pow(imuGyrNoise, 2); // gyro white noise in continuous
  38. p->integrationCovariance =
  39. gtsam::Matrix33::Identity(3, 3) * pow(1e-4, 2); // error committed in integrating position from velocities
  40. gtsam::imuBias::ConstantBias
  41. prior_imu_bias((gtsam::Vector(6) << 0, 0, 0, 0, 0, 0).finished());; // assume zero initial bias
  42. priorPoseNoise = gtsam::noiseModel::Diagonal::Sigmas((gtsam::Vector(6)
  43. << 1e-2, 1e-2, 1e-2, 1e-2, 1e-2, 1e-2).finished()); // rad,rad,rad,m, m, m
  44. priorVelNoise = gtsam::noiseModel::Isotropic::Sigma(3, 1e2); // m/s
  45. priorBiasNoise = gtsam::noiseModel::Isotropic::Sigma(6, 1e-3); // 1e-2 ~ 1e-3 seems to be good
  46. correctionNoise = gtsam::noiseModel::Isotropic::Sigma(6, 1e-2); // meter
  47. noiseModelBetweenBias =
  48. (gtsam::Vector(6) << imuAccBiasN, imuAccBiasN, imuAccBiasN, imuGyrBiasN, imuGyrBiasN, imuGyrBiasN).finished();
  49. // 优化前后的imu
  50. imuIntegratorImu_ = new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for IMU message thread
  51. imuIntegratorOpt_ =new gtsam::PreintegratedImuMeasurements(p, prior_imu_bias); // setting up the IMU integration for optimization
  52. }
  53. void resetOptimization() {
  54. // gtsam相关优化参数重置
  55. gtsam::ISAM2Params optParameters;
  56. optParameters.relinearizeThreshold = 0.1;
  57. optParameters.relinearizeSkip = 1;
  58. optimizer = gtsam::ISAM2(optParameters);
  59. gtsam::NonlinearFactorGraph newGraphFactors;
  60. graphFactors = newGraphFactors;
  61. gtsam::Values NewGraphValues;
  62. graphValues = NewGraphValues;
  63. }
  64. void resetParams() {
  65. lastImuT_imu = -1;
  66. doneFirstOpt = false;
  67. systemInitialized = false;
  68. }
  69. void odometryHandler(const nav_msgs::Odometry::ConstPtr &odomMsg) {
  70. double currentCorrectionTime = ROS_TIME(odomMsg);
  71. // make sure we have imu data to integrate
  72. // 保证有imu数据,两个回调函数是互有联系的,
  73. // 在imu的回调里就强调要完成一次优化才往下执行
  74. if (imuQueOpt.empty())
  75. return;
  76. // 从雷达odom中取出位姿数据
  77. float p_x = odomMsg->pose.pose.position.x;
  78. float p_y = odomMsg->pose.pose.position.y;
  79. float p_z = odomMsg->pose.pose.position.z;
  80. float r_x = odomMsg->pose.pose.orientation.x;
  81. float r_y = odomMsg->pose.pose.orientation.y;
  82. float r_z = odomMsg->pose.pose.orientation.z;
  83. float r_w = odomMsg->pose.pose.orientation.w;
  84. int currentResetId = round(odomMsg->pose.covariance[0]);
  85. gtsam::Pose3 lidarPose = gtsam::Pose3(gtsam::Rot3::Quaternion(r_w, r_x, r_y, r_z),
  86. gtsam::Point3(p_x, p_y, p_z));
  87. // correction pose jumped, reset imu pre-integration
  88. if (currentResetId != imuPreintegrationResetId) {
  89. resetParams();
  90. imuPreintegrationResetId = currentResetId;
  91. }
  92. // 0. initialize system
  93. // 第一帧进来初始化系统
  94. if (systemInitialized == false) {
  95. resetOptimization(); // 重置优化参数
  96. // pop old IMU message
  97. // 去掉一些比较旧的imu数据, 只需要保证雷达odom时间戳在imu队列中间
  98. // 因为imu是高频数据, 这里是有效的
  99. while (!imuQueOpt.empty()) {
  100. if (ROS_TIME(&imuQueOpt.front()) < currentCorrectionTime - delta_t) {
  101. lastImuT_opt = ROS_TIME(&imuQueOpt.front());
  102. imuQueOpt.pop_front();
  103. } else
  104. break;
  105. }
  106. // initial pose
  107. prevPose_ = lidarPose.compose(lidar2Imu); // 雷达odom转到imu系下
  108. //PriorFactor,包括了位姿、速度和bias
  109. //加入PriorFactor在图优化中基本都是必需的前提
  110. gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, priorPoseNoise);
  111. graphFactors.add(priorPose);
  112. // initial velocity
  113. prevVel_ = gtsam::Vector3(0, 0, 0);
  114. gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, priorVelNoise);
  115. graphFactors.add(priorVel);
  116. // initial bias
  117. prevBias_ = gtsam::imuBias::ConstantBias();
  118. gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, priorBiasNoise);
  119. graphFactors.add(priorBias);
  120. // add values、
  121. // 除了因子外, 还要有节点value
  122. graphValues.insert(X(0), prevPose_);
  123. graphValues.insert(V(0), prevVel_);
  124. graphValues.insert(B(0), prevBias_);
  125. // optimize once
  126. // 进行一次优化
  127. optimizer.update(graphFactors, graphValues);
  128. graphFactors.resize(0);
  129. graphValues.clear(); //图和节点均清零, 为什么要清零?
  130. // 重置积分器
  131. imuIntegratorImu_->resetIntegrationAndSetBias(prevBias_);
  132. imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
  133. key = 1; // 计数
  134. systemInitialized = true;
  135. return;
  136. }
  137. // reset graph for speed
  138. // key超过设定的100则重置整个图
  139. // 减小计算压力,保存最后的噪声值
  140. if (key == 100) {
  141. // get updated noise before reset
  142. gtsam::noiseModel::Gaussian::shared_ptr updatedPoseNoise = gtsam::noiseModel::Gaussian::Covariance(
  143. optimizer.marginalCovariance(X(key - 1)));
  144. gtsam::noiseModel::Gaussian::shared_ptr updatedVelNoise = gtsam::noiseModel::Gaussian::Covariance(
  145. optimizer.marginalCovariance(V(key - 1)));
  146. gtsam::noiseModel::Gaussian::shared_ptr updatedBiasNoise = gtsam::noiseModel::Gaussian::Covariance(
  147. optimizer.marginalCovariance(B(key - 1)));
  148. // reset graph 重置参数
  149. resetOptimization();
  150. // 重置之后还有类似与初始化的过程 区别在于噪声值不同
  151. // add pose
  152. gtsam::PriorFactor<gtsam::Pose3> priorPose(X(0), prevPose_, updatedPoseNoise);
  153. graphFactors.add(priorPose);
  154. // add velocity
  155. gtsam::PriorFactor<gtsam::Vector3> priorVel(V(0), prevVel_, updatedVelNoise);
  156. graphFactors.add(priorVel);
  157. // add bias
  158. gtsam::PriorFactor<gtsam::imuBias::ConstantBias> priorBias(B(0), prevBias_, updatedBiasNoise);
  159. graphFactors.add(priorBias);
  160. // add values
  161. graphValues.insert(X(0), prevPose_);
  162. graphValues.insert(V(0), prevVel_);
  163. graphValues.insert(B(0), prevBias_);
  164. // optimize once
  165. // 优化一次, 相当于初始化
  166. optimizer.update(graphFactors, graphValues);
  167. graphFactors.resize(0);
  168. graphValues.clear();
  169. key = 1;
  170. }
  171. // 1. integrate imu data and optimize
  172. // 这里才开始主要的优化流程
  173. while (!imuQueOpt.empty()) {
  174. // pop and integrate imu data that is between two optimizations
  175. // 对两次优化的之间的imu数据进行优化
  176. sensor_msgs::Imu *thisImu = &imuQueOpt.front(); // 最新的imu数据帧
  177. double imuTime = ROS_TIME(thisImu);
  178. if (imuTime < currentCorrectionTime - delta_t) {
  179. // 求dt,初始是1/500,后续是两帧imu数据的时间差
  180. double dt = (lastImuT_opt < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_opt);
  181. // 进行预积分得到新的状态值,注意用到的是imu数据的加速度和角速度
  182. // 作者要求的9轴imu数据中欧拉角在本程序中没有任何用到, 全在地图优化里用到的
  183. imuIntegratorOpt_->integrateMeasurement(
  184. gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y,
  185. thisImu->linear_acceleration.z),
  186. gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y,
  187. thisImu->angular_velocity.z), dt);
  188. //在pop出一次数据前保存上一个数据的时间戳
  189. lastImuT_opt = imuTime;
  190. imuQueOpt.pop_front();
  191. } else
  192. break;
  193. }
  194. // add imu factor to graph
  195. // 利用两帧之间的IMU数据完成了预积分后增加imu因子到因子图中
  196. // add imu factor to graph
  197. const gtsam::PreintegratedImuMeasurements
  198. &preint_imu = dynamic_cast<const gtsam::PreintegratedImuMeasurements &>(*imuIntegratorOpt_);
  199. gtsam::ImuFactor imu_factor(X(key - 1), V(key - 1), X(key), V(key), B(key - 1), preint_imu);
  200. graphFactors.add(imu_factor);
  201. // add imu bias between factor
  202. graphFactors.add(
  203. gtsam::BetweenFactor<gtsam::imuBias::ConstantBias>(B(key - 1), B(key), gtsam::imuBias::ConstantBias(),
  204. gtsam::noiseModel::Diagonal::Sigmas(
  205. sqrt(imuIntegratorOpt_->deltaTij()) *
  206. noiseModelBetweenBias)));
  207. // add pose factor
  208. // 还加入了pose factor,其实对应于作者论文中的因子图结构
  209. // 就是与imu因子一起的 Lidar odometry factor
  210. gtsam::Pose3 curPose = lidarPose.compose(lidar2Imu);
  211. gtsam::PriorFactor<gtsam::Pose3> pose_factor(X(key), curPose, correctionNoise);
  212. graphFactors.add(pose_factor);
  213. // insert predicted values
  214. // 插入预测的值 即因子图中x0 x1 x2……节点
  215. gtsam::NavState propState_ = imuIntegratorOpt_->predict(prevState_, prevBias_);
  216. graphValues.insert(X(key), propState_.pose());
  217. graphValues.insert(V(key), propState_.v());
  218. graphValues.insert(B(key), prevBias_);
  219. // optimize 优化后重置
  220. optimizer.update(graphFactors, graphValues);
  221. optimizer.update();
  222. graphFactors.resize(0);
  223. graphValues.clear();
  224. // Overwrite the beginning of the preintegration for the next step.
  225. // 用这次的优化结果重写或者说是覆盖相关初始值, 为下一次优化准备
  226. gtsam::Values result = optimizer.calculateEstimate();
  227. prevPose_ = result.at<gtsam::Pose3>(X(key));
  228. prevVel_ = result.at<gtsam::Vector3>(V(key));
  229. prevState_ = gtsam::NavState(prevPose_, prevVel_);
  230. prevBias_ = result.at<gtsam::imuBias::ConstantBias>(B(key));
  231. // Reset the optimization preintegration object.
  232. imuIntegratorOpt_->resetIntegrationAndSetBias(prevBias_);
  233. // check optimization
  234. // 检查是否有失败情况,如有则重置参数
  235. if (failureDetection(prevVel_, prevBias_)) {
  236. resetParams();
  237. return;
  238. }
  239. // 2. after optiization, re-propagate imu odometry preintegration
  240. // 为了维持实时性imuIntegrateImu就得在每次odom触发优化后立刻获取最新的bias,
  241. // 同时对imu测量值imuQueImu执行bias改变的状态重传播处理, 这样可以最大限度的保证实时性和准确性?
  242. prevStateOdom = prevState_;
  243. prevBiasOdom = prevBias_;
  244. // first pop imu message older than current correction data
  245. // 去除旧的imu数据
  246. double lastImuQT = -1;
  247. while (!imuQueImu.empty() && ROS_TIME(&imuQueImu.front()) < currentCorrectionTime - delta_t) {
  248. lastImuQT = ROS_TIME(&imuQueImu.front());
  249. imuQueImu.pop_front();
  250. }
  251. // repropogate
  252. // 重传播?
  253. if (!imuQueImu.empty()) {
  254. // reset bias use the newly optimized bias
  255. // 使用最新的优化后的bias更新bias值
  256. imuIntegratorImu_->resetIntegrationAndSetBias(prevBiasOdom);
  257. // integrate imu message from the beginning of this optimization
  258. // 利用imuQueImu中的数据进行预积分,主要区别旧在于上一行的更新了bias
  259. for (int i = 0; i < (int) imuQueImu.size(); ++i) {
  260. sensor_msgs::Imu *thisImu = &imuQueImu[i];
  261. double imuTime = ROS_TIME(thisImu); // 时间戳
  262. double dt = (lastImuQT < 0) ? (1.0 / 500.0) : (imuTime - lastImuQT);
  263. // 进行预计分
  264. imuIntegratorImu_->integrateMeasurement(
  265. gtsam::Vector3(thisImu->linear_acceleration.x, thisImu->linear_acceleration.y,
  266. thisImu->linear_acceleration.z),
  267. gtsam::Vector3(thisImu->angular_velocity.x, thisImu->angular_velocity.y,
  268. thisImu->angular_velocity.z), dt);
  269. lastImuQT = imuTime;
  270. }
  271. }
  272. ++key;
  273. doneFirstOpt = true;
  274. }
  275. bool failureDetection(const gtsam::Vector3 &velCur, const gtsam::imuBias::ConstantBias &biasCur) {
  276. // 检测预计分失败的函数, 即时爆出错误,重置积分器
  277. Eigen::Vector3f vel(velCur.x(), velCur.y(), velCur.z());
  278. if (vel.norm() > 10) {
  279. ROS_WARN("Large velocity, reset IMU-preintegration!");
  280. return true;
  281. }
  282. Eigen::Vector3f ba(biasCur.accelerometer().x(), biasCur.accelerometer().y(), biasCur.accelerometer().z());
  283. Eigen::Vector3f bg(biasCur.gyroscope().x(), biasCur.gyroscope().y(), biasCur.gyroscope().z());
  284. if (ba.norm() > 0.1 || bg.norm() > 0.1) {
  285. ROS_WARN("Large bias, reset IMU-preintegration!");
  286. return true;
  287. }
  288. return false;
  289. }
  290. void imuHandler(const sensor_msgs::Imu::ConstPtr &imu_raw) {
  291. // imu数据转换到雷达坐标系下
  292. sensor_msgs::Imu thisImu = imuConverter(*imu_raw);
  293. // publish static tf map->odom
  294. tfMap2Odom.sendTransform(tf::StampedTransform(map_to_odom, thisImu.header.stamp, "map", "odom"));
  295. // 两个双端队列分别装着优化前后的imu数据
  296. imuQueOpt.push_back(thisImu);
  297. imuQueImu.push_back(thisImu);
  298. // 检查有没有执行过一次优化,这里需要先在odomhandler中优化一次后再进行该函数后续的工作
  299. if (doneFirstOpt == false)
  300. return;
  301. // 获得时间间隔, 第一次为1/500,之后是两次imuTime间的差
  302. double imuTime = ROS_TIME(&thisImu);
  303. double dt = (lastImuT_imu < 0) ? (1.0 / 500.0) : (imuTime - lastImuT_imu);
  304. lastImuT_imu = imuTime;
  305. // integrate this single imu message
  306. // 进行预积分
  307. imuIntegratorImu_->integrateMeasurement(gtsam::Vector3(thisImu.linear_acceleration.x, thisImu.linear_acceleration.y,
  308. thisImu.linear_acceleration.z),
  309. gtsam::Vector3(thisImu.angular_velocity.x,
  310. thisImu.angular_velocity.y,
  311. thisImu.angular_velocity.z), dt);
  312. // predict odometry
  313. // 根据预计分结果, 预测odom
  314. gtsam::NavState currentState = imuIntegratorImu_->predict(prevStateOdom, prevBiasOdom);
  315. // publish odometry 发布新的odom
  316. nav_msgs::Odometry odometry;
  317. odometry.header.stamp = thisImu.header.stamp;
  318. odometry.header.frame_id = "odom";
  319. odometry.child_frame_id = "odom_imu";
  320. // transform imu pose to ldiar imu位姿转到雷达系
  321. // 预测值currentState获得imu位姿, 再由imu到雷达变换, 获得雷达位姿
  322. gtsam::Pose3 imuPose = gtsam::Pose3(currentState.quaternion(), currentState.position());
  323. gtsam::Pose3 lidarPose = imuPose.compose(imu2Lidar);
  324. odometry.pose.pose.position.x = lidarPose.translation().x();
  325. odometry.pose.pose.position.y = lidarPose.translation().y();
  326. odometry.pose.pose.position.z = lidarPose.translation().z();
  327. odometry.pose.pose.orientation.x = lidarPose.rotation().toQuaternion().x();
  328. odometry.pose.pose.orientation.y = lidarPose.rotation().toQuaternion().y();
  329. odometry.pose.pose.orientation.z = lidarPose.rotation().toQuaternion().z();
  330. odometry.pose.pose.orientation.w = lidarPose.rotation().toQuaternion().w();
  331. odometry.twist.twist.linear.x = currentState.velocity().x();
  332. odometry.twist.twist.linear.y = currentState.velocity().y();
  333. odometry.twist.twist.linear.z = currentState.velocity().z();
  334. odometry.twist.twist.angular.x = thisImu.angular_velocity.x + prevBiasOdom.gyroscope().x();
  335. odometry.twist.twist.angular.y = thisImu.angular_velocity.y + prevBiasOdom.gyroscope().y();
  336. odometry.twist.twist.angular.z = thisImu.angular_velocity.z + prevBiasOdom.gyroscope().z();
  337. odometry.pose.covariance[0] = double(imuPreintegrationResetId);
  338. pubImuOdometry.publish(odometry);
  339. // publish imu path
  340. // 预测的imu path, 只保留3s内的轨迹
  341. static nav_msgs::Path imuPath;
  342. static double last_path_time = -1;
  343. if (imuTime - last_path_time > 0.1) {
  344. last_path_time = imuTime;
  345. geometry_msgs::PoseStamped pose_stamped;
  346. pose_stamped.header.stamp = thisImu.header.stamp;
  347. pose_stamped.header.frame_id = "odom";
  348. pose_stamped.pose = odometry.pose.pose;
  349. imuPath.poses.push_back(pose_stamped);
  350. while (!imuPath.poses.empty() &&
  351. abs(imuPath.poses.front().header.stamp.toSec() - imuPath.poses.back().header.stamp.toSec()) > 3.0)
  352. imuPath.poses.erase(imuPath.poses.begin());
  353. if (pubImuPath.getNumSubscribers() != 0) {
  354. imuPath.header.stamp = thisImu.header.stamp;
  355. imuPath.header.frame_id = "odom";
  356. pubImuPath.publish(imuPath);
  357. }
  358. }
  359. // publish transformation
  360. // 发布odom->base_link的变换
  361. tf::Transform tCur;
  362. tf::poseMsgToTF(odometry.pose.pose, tCur);
  363. tf::StampedTransform odom_2_baselink = tf::StampedTransform(tCur, thisImu.header.stamp, "odom", "base_link");
  364. tfOdom2BaseLink.sendTransform(odom_2_baselink);
  365. }
  366. public:
  367. ros::Subscriber subImu;
  368. ros::Subscriber subOdometry;
  369. ros::Publisher pubImuOdometry;
  370. ros::Publisher pubImuPath;
  371. // map -> odom
  372. tf::Transform map_to_odom;
  373. tf::TransformBroadcaster tfMap2Odom;
  374. // odom -> base_link
  375. tf::TransformBroadcaster tfOdom2BaseLink;
  376. bool systemInitialized = false;
  377. gtsam::noiseModel::Diagonal::shared_ptr priorPoseNoise;
  378. gtsam::noiseModel::Diagonal::shared_ptr priorVelNoise;
  379. gtsam::noiseModel::Diagonal::shared_ptr priorBiasNoise;
  380. gtsam::noiseModel::Diagonal::shared_ptr correctionNoise;
  381. gtsam::Vector noiseModelBetweenBias;
  382. gtsam::PreintegratedImuMeasurements *imuIntegratorOpt_;
  383. gtsam::PreintegratedImuMeasurements *imuIntegratorImu_;
  384. std::deque<sensor_msgs::Imu> imuQueOpt;
  385. std::deque<sensor_msgs::Imu> imuQueImu;
  386. gtsam::Pose3 prevPose_;
  387. gtsam::Vector3 prevVel_;
  388. gtsam::NavState prevState_;
  389. gtsam::imuBias::ConstantBias prevBias_;
  390. gtsam::NavState prevStateOdom;
  391. gtsam::imuBias::ConstantBias prevBiasOdom;
  392. bool doneFirstOpt = false;
  393. double lastImuT_imu = -1;
  394. double lastImuT_opt = -1;
  395. gtsam::ISAM2 optimizer;
  396. gtsam::NonlinearFactorGraph graphFactors;
  397. gtsam::Values graphValues;
  398. const double delta_t = 0;
  399. int key = 1;
  400. int imuPreintegrationResetId = 0;
  401. // 雷达->imu外餐
  402. gtsam::Pose3 imu2Lidar = gtsam::Pose3(gtsam::Rot3(1, 0, 0, 0),
  403. gtsam::Point3(-extTrans.x(), -extTrans.y(), -extTrans.z()));
  404. gtsam::Pose3 lidar2Imu = gtsam::Pose3(gtsam::Rot3(1, 0, 0, 0),
  405. gtsam::Point3(extTrans.x(), extTrans.y(), extTrans.z()));;
  406. };
  407. int main(int argc, char **argv) {
  408. ros::init(argc, argv, "roboat_loam");
  409. IMUPreintegration ImuP;
  410. ROS_INFO("\033[1;32m----> IMU Preintegration Started.\033[0m");
  411. ros::spin();
  412. return 0;
  413. }

mapOptmization.cpp

  1. #include "utility.h"
  2. #include "lio_sam/cloud_info.h"
  3. #include <gtsam/geometry/Rot3.h>
  4. #include <gtsam/geometry/Pose3.h>
  5. #include <gtsam/slam/PriorFactor.h>
  6. #include <gtsam/slam/BetweenFactor.h>
  7. #include <gtsam/navigation/GPSFactor.h>
  8. #include <gtsam/navigation/ImuFactor.h>
  9. #include <gtsam/navigation/CombinedImuFactor.h>
  10. #include <gtsam/nonlinear/NonlinearFactorGraph.h>
  11. #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
  12. #include <gtsam/nonlinear/Marginals.h>
  13. #include <gtsam/nonlinear/Values.h>
  14. #include <gtsam/inference/Symbol.h>
  15. #include <gtsam/nonlinear/ISAM2.h>
  16. using namespace gtsam;
  17. using symbol_shorthand::X; // Pose3 (x,y,z,r,p,y)
  18. using symbol_shorthand::V; // Vel (xdot,ydot,zdot)
  19. using symbol_shorthand::B; // Bias (ax,ay,az,gx,gy,gz)
  20. using symbol_shorthand::G; // GPS pose
  21. /*
  22. * A point cloud type that has 6D pose info ([x,y,z,roll,pitch,yaw] intensity is time stamp)
  23. */
  24. struct PointXYZIRPYT {
  25. PCL_ADD_POINT4D
  26. PCL_ADD_INTENSITY; // preferred way of adding a XYZ+padding
  27. float roll;
  28. float pitch;
  29. float yaw;
  30. double time;
  31. EIGEN_MAKE_ALIGNED_OPERATOR_NEW // make sure our new allocators are aligned
  32. } EIGEN_ALIGN16; // enforce SSE padding for correct memory alignment
  33. POINT_CLOUD_REGISTER_POINT_STRUCT (PointXYZIRPYT,
  34. (float, x, x)(float, y, y)
  35. (float, z, z)(float, intensity, intensity)
  36. (float, roll, roll)(float, pitch, pitch)(float, yaw, yaw)
  37. (double, time, time))
  38. typedef PointXYZIRPYT PointTypePose;
  39. class mapOptimization : public ParamServer {
  40. public:
  41. mapOptimization() {
  42. ISAM2Params parameters;
  43. parameters.relinearizeThreshold = 0.1;
  44. parameters.relinearizeSkip = 1;
  45. isam = new ISAM2(parameters);
  46. // subscriber 主要订阅分类好的cloud_info以及gps,用于后端优化和回环检测,
  47. // 注意gps接受的是nav_msgs::Odometry消息, 是通过robot_localization节点融合imu和gps数据得到的
  48. // callback里面只是装数据到队列
  49. subLaserCloudInfo = nh.subscribe<lio_sam::cloud_info>("lio_sam/feature/cloud_info", 1,
  50. &mapOptimization::laserCloudInfoHandler, this,
  51. ros::TransportHints().tcpNoDelay());
  52. subGPS = nh.subscribe<nav_msgs::Odometry>(gpsTopic, 200, &mapOptimization::gpsHandler, this,
  53. ros::TransportHints().tcpNoDelay());
  54. // publisher 发布一些odometry之类的
  55. pubKeyPoses = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/trajectory", 1);
  56. pubLaserCloudSurround = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/map_global", 1); // 全局地图
  57. pubOdomAftMappedROS = nh.advertise<nav_msgs::Odometry>("lio_sam/mapping/odometry", 1); // 优化后的odom
  58. pubPath = nh.advertise<nav_msgs::Path>("lio_sam/mapping/path", 1);
  59. // 回环检测相关的一些历史帧
  60. pubHistoryKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/icp_loop_closure_history_cloud", 1);
  61. pubIcpKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/icp_loop_closure_corrected_cloud", 1);
  62. // local map
  63. pubRecentKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/map_local", 1);
  64. pubRecentKeyFrame = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/cloud_registered", 1);
  65. pubCloudRegisteredRaw = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/mapping/cloud_registered_raw", 1);
  66. // 不同的特征进行滤波
  67. downSizeFilterCorner.setLeafSize(mappingCornerLeafSize, mappingCornerLeafSize, mappingCornerLeafSize);
  68. downSizeFilterSurf.setLeafSize(mappingSurfLeafSize, mappingSurfLeafSize, mappingSurfLeafSize);
  69. downSizeFilterICP.setLeafSize(mappingSurfLeafSize, mappingSurfLeafSize, mappingSurfLeafSize);
  70. downSizeFilterSurroundingKeyPoses.setLeafSize(surroundingKeyframeDensity, surroundingKeyframeDensity,
  71. surroundingKeyframeDensity); // for surrounding key poses of scan-to-map optimization
  72. allocateMemory();
  73. }
  74. void allocateMemory() {
  75. // 初始化一些参数
  76. cloudKeyPoses3D.reset(new pcl::PointCloud<PointType>());
  77. cloudKeyPoses6D.reset(new pcl::PointCloud<PointTypePose>());
  78. kdtreeSurroundingKeyPoses.reset(new pcl::KdTreeFLANN<PointType>());
  79. kdtreeHistoryKeyPoses.reset(new pcl::KdTreeFLANN<PointType>());
  80. laserCloudCornerLast.reset(new pcl::PointCloud<PointType>()); // corner feature set from odoOptimization
  81. laserCloudSurfLast.reset(new pcl::PointCloud<PointType>()); // surf feature set from odoOptimization
  82. laserCloudCornerLastDS.reset(
  83. new pcl::PointCloud<PointType>()); // downsampled corner featuer set from odoOptimization
  84. laserCloudSurfLastDS.reset(
  85. new pcl::PointCloud<PointType>()); // downsampled surf featuer set from odoOptimization
  86. laserCloudOri.reset(new pcl::PointCloud<PointType>());
  87. coeffSel.reset(new pcl::PointCloud<PointType>());
  88. laserCloudOriCornerVec.resize(N_SCAN * Horizon_SCAN);
  89. coeffSelCornerVec.resize(N_SCAN * Horizon_SCAN);
  90. laserCloudOriCornerFlag.resize(N_SCAN * Horizon_SCAN);
  91. laserCloudOriSurfVec.resize(N_SCAN * Horizon_SCAN);
  92. coeffSelSurfVec.resize(N_SCAN * Horizon_SCAN);
  93. laserCloudOriSurfFlag.resize(N_SCAN * Horizon_SCAN);
  94. std::fill(laserCloudOriCornerFlag.begin(), laserCloudOriCornerFlag.end(), false);
  95. std::fill(laserCloudOriSurfFlag.begin(), laserCloudOriSurfFlag.end(), false);
  96. laserCloudCornerFromMap.reset(new pcl::PointCloud<PointType>());
  97. laserCloudSurfFromMap.reset(new pcl::PointCloud<PointType>());
  98. laserCloudCornerFromMapDS.reset(new pcl::PointCloud<PointType>());
  99. laserCloudSurfFromMapDS.reset(new pcl::PointCloud<PointType>());
  100. kdtreeCornerFromMap.reset(new pcl::KdTreeFLANN<PointType>());
  101. kdtreeSurfFromMap.reset(new pcl::KdTreeFLANN<PointType>());
  102. latestKeyFrameCloud.reset(new pcl::PointCloud<PointType>());
  103. nearHistoryKeyFrameCloud.reset(new pcl::PointCloud<PointType>());
  104. for (int i = 0; i < 6; ++i) {
  105. transformTobeMapped[i] = 0;
  106. }
  107. matP.setZero();
  108. }
  109. void laserCloudInfoHandler(const lio_sam::cloud_infoConstPtr &msgIn) {
  110. // extract time stamp
  111. timeLaserInfoStamp = msgIn->header.stamp;
  112. timeLaserCloudInfoLast = msgIn->header.stamp.toSec();
  113. // extract info and feature cloud
  114. // corner和surface点云
  115. cloudInfo = *msgIn;
  116. pcl::fromROSMsg(msgIn->cloud_corner, *laserCloudCornerLast);
  117. pcl::fromROSMsg(msgIn->cloud_surface, *laserCloudSurfLast);
  118. std::lock_guard<std::mutex> lock(mtx);
  119. // 0.15s更新一下map
  120. if (timeLaserCloudInfoLast - timeLastProcessing >= mappingProcessInterval) {
  121. timeLastProcessing = timeLaserCloudInfoLast;
  122. updateInitialGuess(); // imu预积分更新初始位姿
  123. extractSurroundingKeyFrames(); // 从关键帧里面提取附近回环候选帧
  124. downsampleCurrentScan(); // 不同的leaf size进行下采样,主要是corner cloud和surface cloud
  125. scan2MapOptimization(); // 构建点到平面、点到直线的残差, 用高斯牛顿法进行优化
  126. saveKeyFramesAndFactor(); // 添加factor,保存key pose之类的
  127. correctPoses(); // 更新位姿
  128. // publish odom
  129. publishOdometry(); // 发布增量平滑后的odom
  130. publishFrames(); // 发布一些关键帧点云之类的
  131. }
  132. }
  133. void gpsHandler(const nav_msgs::Odometry::ConstPtr &gpsMsg) {
  134. gpsQueue.push_back(*gpsMsg);
  135. }
  136. void pointAssociateToMap(PointType const *const pi, PointType *const po) {
  137. po->x = transPointAssociateToMap(0, 0) * pi->x + transPointAssociateToMap(0, 1) * pi->y +
  138. transPointAssociateToMap(0, 2) * pi->z + transPointAssociateToMap(0, 3);
  139. po->y = transPointAssociateToMap(1, 0) * pi->x + transPointAssociateToMap(1, 1) * pi->y +
  140. transPointAssociateToMap(1, 2) * pi->z + transPointAssociateToMap(1, 3);
  141. po->z = transPointAssociateToMap(2, 0) * pi->x + transPointAssociateToMap(2, 1) * pi->y +
  142. transPointAssociateToMap(2, 2) * pi->z + transPointAssociateToMap(2, 3);
  143. po->intensity = pi->intensity;
  144. }
  145. pcl::PointCloud<PointType>::Ptr
  146. transformPointCloud(pcl::PointCloud<PointType>::Ptr cloudIn, PointTypePose *transformIn) {
  147. pcl::PointCloud<PointType>::Ptr cloudOut(new pcl::PointCloud<PointType>());
  148. PointType *pointFrom;
  149. int cloudSize = cloudIn->size();
  150. cloudOut->resize(cloudSize);
  151. Eigen::Affine3f transCur = pcl::getTransformation(transformIn->x, transformIn->y, transformIn->z,
  152. transformIn->roll, transformIn->pitch, transformIn->yaw);
  153. for (int i = 0; i < cloudSize; ++i) {
  154. pointFrom = &cloudIn->points[i];
  155. cloudOut->points[i].x =
  156. transCur(0, 0) * pointFrom->x + transCur(0, 1) * pointFrom->y + transCur(0, 2) * pointFrom->z +
  157. transCur(0, 3);
  158. cloudOut->points[i].y =
  159. transCur(1, 0) * pointFrom->x + transCur(1, 1) * pointFrom->y + transCur(1, 2) * pointFrom->z +
  160. transCur(1, 3);
  161. cloudOut->points[i].z =
  162. transCur(2, 0) * pointFrom->x + transCur(2, 1) * pointFrom->y + transCur(2, 2) * pointFrom->z +
  163. transCur(2, 3);
  164. cloudOut->points[i].intensity = pointFrom->intensity;
  165. }
  166. return cloudOut;
  167. }
  168. gtsam::Pose3 pclPointTogtsamPose3(PointTypePose thisPoint) {
  169. return gtsam::Pose3(gtsam::Rot3::RzRyRx(double(thisPoint.roll), double(thisPoint.pitch), double(thisPoint.yaw)),
  170. gtsam::Point3(double(thisPoint.x), double(thisPoint.y), double(thisPoint.z)));
  171. }
  172. gtsam::Pose3 trans2gtsamPose(float transformIn[]) {
  173. return gtsam::Pose3(gtsam::Rot3::RzRyRx(transformIn[0], transformIn[1], transformIn[2]),
  174. gtsam::Point3(transformIn[3], transformIn[4], transformIn[5]));
  175. }
  176. Eigen::Affine3f pclPointToAffine3f(PointTypePose thisPoint) {
  177. return pcl::getTransformation(thisPoint.x, thisPoint.y, thisPoint.z, thisPoint.roll, thisPoint.pitch,
  178. thisPoint.yaw);
  179. }
  180. Eigen::Affine3f trans2Affine3f(float transformIn[]) {
  181. return pcl::getTransformation(transformIn[3], transformIn[4], transformIn[5], transformIn[0], transformIn[1],
  182. transformIn[2]);
  183. }
  184. PointTypePose trans2PointTypePose(float transformIn[]) {
  185. PointTypePose thisPose6D;
  186. thisPose6D.x = transformIn[3];
  187. thisPose6D.y = transformIn[4];
  188. thisPose6D.z = transformIn[5];
  189. thisPose6D.roll = transformIn[0];
  190. thisPose6D.pitch = transformIn[1];
  191. thisPose6D.yaw = transformIn[2];
  192. return thisPose6D;
  193. }
  194. void visualizeGlobalMapThread() {
  195. // 按一定的频率发布全局地图
  196. ros::Rate rate(0.2);
  197. while (ros::ok()) {
  198. rate.sleep();
  199. publishGlobalMap();
  200. }
  201. // 下面是保存各种地图
  202. if (savePCD == false)
  203. return;
  204. cout << "****************************************************" << endl;
  205. cout << "Saving map to pcd files ..." << endl;
  206. // create directory and remove old files;
  207. savePCDDirectory = std::getenv("HOME") + savePCDDirectory;
  208. int unused = system((std::string("exec rm -r ") + savePCDDirectory).c_str());
  209. unused = system((std::string("mkdir ") + savePCDDirectory).c_str());
  210. // save key frame transformations
  211. pcl::io::savePCDFileASCII(savePCDDirectory + "trajectory.pcd", *cloudKeyPoses3D);
  212. pcl::io::savePCDFileASCII(savePCDDirectory + "transformations.pcd", *cloudKeyPoses6D);
  213. // extract global point cloud map
  214. pcl::PointCloud<PointType>::Ptr globalCornerCloud(new pcl::PointCloud<PointType>());
  215. pcl::PointCloud<PointType>::Ptr globalCornerCloudDS(new pcl::PointCloud<PointType>());
  216. pcl::PointCloud<PointType>::Ptr globalSurfCloud(new pcl::PointCloud<PointType>());
  217. pcl::PointCloud<PointType>::Ptr globalSurfCloudDS(new pcl::PointCloud<PointType>());
  218. pcl::PointCloud<PointType>::Ptr globalMapCloud(new pcl::PointCloud<PointType>());
  219. for (int i = 0; i < cloudKeyPoses3D->size(); i++) {
  220. *globalCornerCloud += *transformPointCloud(cornerCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
  221. *globalSurfCloud += *transformPointCloud(surfCloudKeyFrames[i], &cloudKeyPoses6D->points[i]);
  222. cout << "\r" << std::flush << "Processing feature cloud " << i << " of " << cloudKeyPoses6D->size()
  223. << " ...";
  224. }
  225. // down-sample and save corner cloud
  226. downSizeFilterCorner.setInputCloud(globalCornerCloud);
  227. downSizeFilterCorner.filter(*globalCornerCloudDS);
  228. pcl::io::savePCDFileASCII(savePCDDirectory + "cloudCorner.pcd", *globalCornerCloudDS);
  229. // down-sample and save surf cloud
  230. downSizeFilterSurf.setInputCloud(globalSurfCloud);
  231. downSizeFilterSurf.filter(*globalSurfCloudDS);
  232. pcl::io::savePCDFileASCII(savePCDDirectory + "cloudSurf.pcd", *globalSurfCloudDS);
  233. // down-sample and save global point cloud map
  234. *globalMapCloud += *globalCornerCloud;
  235. *globalMapCloud += *globalSurfCloud;
  236. pcl::io::savePCDFileASCII(savePCDDirectory + "cloudGlobal.pcd", *globalMapCloud);
  237. cout << "****************************************************" << endl;
  238. cout << "Saving map to pcd files completed" << endl;
  239. }
  240. void publishGlobalMap() {
  241. if (pubLaserCloudSurround.getNumSubscribers() == 0)
  242. return;
  243. // cloudKeyPoses3Dc存的是关键帧的位姿
  244. if (cloudKeyPoses3D->points.empty() == true)
  245. return;
  246. pcl::KdTreeFLANN<PointType>::Ptr kdtreeGlobalMap(new pcl::KdTreeFLANN<PointType>());;
  247. pcl::PointCloud<PointType>::Ptr globalMapKeyPoses(new pcl::PointCloud<PointType>());
  248. pcl::PointCloud<PointType>::Ptr globalMapKeyPosesDS(new pcl::PointCloud<PointType>());
  249. pcl::PointCloud<PointType>::Ptr globalMapKeyFrames(new pcl::PointCloud<PointType>());
  250. pcl::PointCloud<PointType>::Ptr globalMapKeyFramesDS(new pcl::PointCloud<PointType>());
  251. // kd-tree to find near key frames to visualize
  252. std::vector<int> pointSearchIndGlobalMap;
  253. std::vector<float> pointSearchSqDisGlobalMap;
  254. // search near key frames to visualize
  255. mtx.lock();
  256. kdtreeGlobalMap->setInputCloud(cloudKeyPoses3D);
  257. kdtreeGlobalMap->radiusSearch(cloudKeyPoses3D->back(), globalMapVisualizationSearchRadius,
  258. pointSearchIndGlobalMap, pointSearchSqDisGlobalMap, 0);
  259. mtx.unlock();
  260. // 找到附近的点云帧并发布出来
  261. for (int i = 0; i < pointSearchIndGlobalMap.size(); ++i)
  262. globalMapKeyPoses->push_back(cloudKeyPoses3D->points[pointSearchIndGlobalMap[i]]);
  263. // downsample near selected key frames
  264. pcl::VoxelGrid<PointType> downSizeFilterGlobalMapKeyPoses; // for global map visualization
  265. downSizeFilterGlobalMapKeyPoses.setLeafSize(globalMapVisualizationPoseDensity,
  266. globalMapVisualizationPoseDensity,
  267. globalMapVisualizationPoseDensity); // for global map visualization
  268. downSizeFilterGlobalMapKeyPoses.setInputCloud(globalMapKeyPoses);
  269. downSizeFilterGlobalMapKeyPoses.filter(*globalMapKeyPosesDS);
  270. // extract visualized and downsampled key frames
  271. for (int i = 0; i < globalMapKeyPosesDS->size(); ++i) {
  272. if (pointDistance(globalMapKeyPosesDS->points[i], cloudKeyPoses3D->back()) >
  273. globalMapVisualizationSearchRadius)
  274. continue;
  275. int thisKeyInd = (int) globalMapKeyPosesDS->points[i].intensity;
  276. *globalMapKeyFrames += *transformPointCloud(cornerCloudKeyFrames[thisKeyInd],
  277. &cloudKeyPoses6D->points[thisKeyInd]);
  278. *globalMapKeyFrames += *transformPointCloud(surfCloudKeyFrames[thisKeyInd],
  279. &cloudKeyPoses6D->points[thisKeyInd]);
  280. }
  281. // downsample visualized points
  282. pcl::VoxelGrid<PointType> downSizeFilterGlobalMapKeyFrames; // for global map visualization
  283. downSizeFilterGlobalMapKeyFrames.setLeafSize(globalMapVisualizationLeafSize, globalMapVisualizationLeafSize,
  284. globalMapVisualizationLeafSize); // for global map visualization
  285. downSizeFilterGlobalMapKeyFrames.setInputCloud(globalMapKeyFrames);
  286. downSizeFilterGlobalMapKeyFrames.filter(*globalMapKeyFramesDS);
  287. publishCloud(&pubLaserCloudSurround, globalMapKeyFramesDS, timeLaserInfoStamp, "odom");
  288. }
  289. void loopClosureThread() {
  290. // 什么时候才进行回环检测?
  291. if (loopClosureEnableFlag == false)
  292. return;
  293. // 以一定的频率执行回环检测
  294. ros::Rate rate(0.2);
  295. while (ros::ok()) {
  296. rate.sleep();
  297. performLoopClosure();
  298. }
  299. }
  300. bool detectLoopClosure(int *latestID, int *closestID) {
  301. int latestFrameIDLoopCloure;
  302. int closestHistoryFrameID;
  303. latestKeyFrameCloud->clear();
  304. nearHistoryKeyFrameCloud->clear();
  305. std::lock_guard<std::mutex> lock(mtx);
  306. // find the closest history key frame
  307. std::vector<int> pointSearchIndLoop;
  308. std::vector<float> pointSearchSqDisLoop;
  309. kdtreeHistoryKeyPoses->setInputCloud(cloudKeyPoses3D);
  310. kdtreeHistoryKeyPoses->radiusSearch(cloudKeyPoses3D->back(), historyKeyframeSearchRadius, pointSearchIndLoop,
  311. pointSearchSqDisLoop, 0);
  312. // 两帧时间差也满足最小要求
  313. closestHistoryFrameID = -1;
  314. for (int i = 0; i < pointSearchIndLoop.size(); ++i) {
  315. int id = pointSearchIndLoop[i];
  316. if (abs(cloudKeyPoses6D->points[id].time - timeLaserCloudInfoLast) > historyKeyframeSearchTimeDiff) {
  317. closestHistoryFrameID = id;
  318. break;
  319. }
  320. }
  321. if (closestHistoryFrameID == -1)
  322. return false;
  323. if (cloudKeyPoses3D->size() - 1 == closestHistoryFrameID)
  324. return false;
  325. // save latest key frames
  326. latestFrameIDLoopCloure = cloudKeyPoses3D->size() - 1;
  327. *latestKeyFrameCloud += *transformPointCloud(cornerCloudKeyFrames[latestFrameIDLoopCloure],
  328. &cloudKeyPoses6D->points[latestFrameIDLoopCloure]);
  329. *latestKeyFrameCloud += *transformPointCloud(surfCloudKeyFrames[latestFrameIDLoopCloure],
  330. &cloudKeyPoses6D->points[latestFrameIDLoopCloure]);
  331. // save history near key frames
  332. bool nearFrameAvailable = false;
  333. for (int j = -historyKeyframeSearchNum; j <= historyKeyframeSearchNum; ++j) {
  334. if (closestHistoryFrameID + j < 0 || closestHistoryFrameID + j > latestFrameIDLoopCloure)
  335. continue;
  336. *nearHistoryKeyFrameCloud += *transformPointCloud(cornerCloudKeyFrames[closestHistoryFrameID + j],
  337. &cloudKeyPoses6D->points[closestHistoryFrameID + j]);
  338. *nearHistoryKeyFrameCloud += *transformPointCloud(surfCloudKeyFrames[closestHistoryFrameID + j],
  339. &cloudKeyPoses6D->points[closestHistoryFrameID + j]);
  340. nearFrameAvailable = true;
  341. }
  342. if (nearFrameAvailable == false)
  343. return false;
  344. *latestID = latestFrameIDLoopCloure;
  345. *closestID = closestHistoryFrameID;
  346. return true;
  347. }
  348. void performLoopClosure() {
  349. if (cloudKeyPoses3D->points.empty() == true)
  350. return;
  351. int latestFrameIDLoopCloure; // 关键帧队列中最新的关键帧id
  352. int closestHistoryFrameID; // 最近的关键帧id
  353. if (detectLoopClosure(&latestFrameIDLoopCloure, &closestHistoryFrameID) == false)
  354. return;
  355. // 检测到了回环进入以下流程,将两帧点云进行icp配准得到最终的trans
  356. // ICP Settings
  357. pcl::IterativeClosestPoint<PointType, PointType> icp;
  358. icp.setMaxCorrespondenceDistance(100);
  359. icp.setMaximumIterations(100);
  360. icp.setTransformationEpsilon(1e-6);
  361. icp.setEuclideanFitnessEpsilon(1e-6);
  362. icp.setRANSACIterations(0);
  363. // Downsample map cloud
  364. pcl::PointCloud<PointType>::Ptr cloud_temp(new pcl::PointCloud<PointType>());
  365. downSizeFilterICP.setInputCloud(nearHistoryKeyFrameCloud);
  366. downSizeFilterICP.filter(*cloud_temp);
  367. *nearHistoryKeyFrameCloud = *cloud_temp;
  368. // publish history near key frames
  369. publishCloud(&pubHistoryKeyFrames, nearHistoryKeyFrameCloud, timeLaserInfoStamp, "odom");
  370. // Align clouds 将回环帧与local map进行匹配
  371. icp.setInputSource(latestKeyFrameCloud);
  372. icp.setInputTarget(nearHistoryKeyFrameCloud);
  373. pcl::PointCloud<PointType>::Ptr unused_result(new pcl::PointCloud<PointType>());
  374. icp.align(*unused_result);
  375. // 通过icp score阈值判断是否匹配成功
  376. // std::cout << "ICP converg flag:" << icp.hasConverged() << ". Fitness score: " << icp.getFitnessScore() << std::endl;
  377. if (icp.hasConverged() == false || icp.getFitnessScore() > historyKeyframeFitnessScore)
  378. return;
  379. // publish corrected cloud
  380. if (pubIcpKeyFrames.getNumSubscribers() != 0) {
  381. pcl::PointCloud<PointType>::Ptr closed_cloud(new pcl::PointCloud<PointType>());
  382. pcl::transformPointCloud(*latestKeyFrameCloud, *closed_cloud, icp.getFinalTransformation());
  383. publishCloud(&pubIcpKeyFrames, closed_cloud, timeLaserInfoStamp, "odom");
  384. }
  385. // Get pose transformation
  386. float x, y, z, roll, pitch, yaw;
  387. Eigen::Affine3f correctionLidarFrame;
  388. // icp得到的两帧之间的转换
  389. correctionLidarFrame = icp.getFinalTransformation();
  390. // transform from world origin to wrong pose
  391. Eigen::Affine3f tWrong = pclPointToAffine3f(cloudKeyPoses6D->points[latestFrameIDLoopCloure]);
  392. // transform from world origin to corrected pose
  393. Eigen::Affine3f tCorrect =
  394. correctionLidarFrame * tWrong;// pre-multiplying -> successive rotation about a fixed frame
  395. pcl::getTranslationAndEulerAngles(tCorrect, x, y, z, roll, pitch, yaw);
  396. // gtsam中添加回环的约束
  397. gtsam::Pose3 poseFrom = Pose3(Rot3::RzRyRx(roll, pitch, yaw), Point3(x, y, z));
  398. gtsam::Pose3 poseTo = pclPointTogtsamPose3(cloudKeyPoses6D->points[closestHistoryFrameID]);
  399. gtsam::Vector Vector6(6);
  400. float noiseScore = icp.getFitnessScore();
  401. Vector6 << noiseScore, noiseScore, noiseScore, noiseScore, noiseScore, noiseScore;
  402. noiseModel::Diagonal::shared_ptr constraintNoise = noiseModel::Diagonal::Variances(Vector6);
  403. // Add pose constraint
  404. std::lock_guard<std::mutex> lock(mtx);
  405. gtSAMgraph.add(BetweenFactor<Pose3>(latestFrameIDLoopCloure, closestHistoryFrameID, poseFrom.between(poseTo),
  406. constraintNoise));
  407. isam->update(gtSAMgraph);
  408. isam->update();
  409. isam->update();
  410. isam->update();
  411. isam->update();
  412. isam->update();
  413. gtSAMgraph.resize(0);
  414. aLoopIsClosed = true;
  415. }
  416. void updateInitialGuess() {
  417. // 更新初始位姿, 来源可以是GPS ODOM, 也可以是上一帧的位姿, 存在transformTobeMapped中
  418. // initialization
  419. if (cloudKeyPoses3D->points.empty()) {
  420. // 第一帧点云进来
  421. transformTobeMapped[0] = cloudInfo.imuRollInit;
  422. transformTobeMapped[1] = cloudInfo.imuPitchInit;
  423. transformTobeMapped[2] = cloudInfo.imuYawInit;
  424. if (!useImuHeadingInitialization)
  425. transformTobeMapped[2] = 0;
  426. // 获取初始的transform
  427. lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit,
  428. cloudInfo.imuYawInit); // save imu before return;
  429. return;
  430. }
  431. // use imu pre-integration estimation for pose guess
  432. // odom可用的话, 使用mu odom作为初始位姿, 每个点在imuPreintexx.cpp中会实时进行预计分优化, 并存储其优化后的odom
  433. if (cloudInfo.odomAvailable == true && cloudInfo.imuPreintegrationResetId == imuPreintegrationResetId) {
  434. transformTobeMapped[0] = cloudInfo.initialGuessRoll;
  435. transformTobeMapped[1] = cloudInfo.initialGuessPitch;
  436. transformTobeMapped[2] = cloudInfo.initialGuessYaw;
  437. transformTobeMapped[3] = cloudInfo.initialGuessX;
  438. transformTobeMapped[4] = cloudInfo.initialGuessY;
  439. transformTobeMapped[5] = cloudInfo.initialGuessZ;
  440. lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit,
  441. cloudInfo.imuYawInit); // save imu before return;
  442. return;
  443. }
  444. // use imu incremental estimation for pose guess (only rotation)
  445. // imu可用的话, 使用imu计算一个旋转增量, 这里?
  446. if (cloudInfo.imuAvailable == true) {
  447. Eigen::Affine3f transBack = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit,
  448. cloudInfo.imuYawInit);
  449. Eigen::Affine3f transIncre = lastImuTransformation.inverse() * transBack;
  450. Eigen::Affine3f transTobe = trans2Affine3f(transformTobeMapped);
  451. Eigen::Affine3f transFinal = transTobe * transIncre;
  452. pcl::getTranslationAndEulerAngles(transFinal, transformTobeMapped[3], transformTobeMapped[4],
  453. transformTobeMapped[5],
  454. transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);
  455. lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit,
  456. cloudInfo.imuYawInit); // save imu before return;
  457. return;
  458. }
  459. }
  460. void extractForLoopClosure() {
  461. // 提取回环候选帧
  462. pcl::PointCloud<PointType>::Ptr cloudToExtract(new pcl::PointCloud<PointType>());
  463. int numPoses = cloudKeyPoses3D->size();
  464. for (int i = numPoses - 1; i >= 0; --i) {
  465. if (cloudToExtract->size() <= surroundingKeyframeSize)
  466. cloudToExtract->push_back(cloudKeyPoses3D->points[i]);
  467. else
  468. break;
  469. }
  470. extractCloud(cloudToExtract);
  471. }
  472. void extractNearby() {
  473. // 提取附近的点云帧, 包括corner和surface, cloudKeyPoses3D
  474. pcl::PointCloud<PointType>::Ptr surroundingKeyPoses(new pcl::PointCloud<PointType>());
  475. pcl::PointCloud<PointType>::Ptr surroundingKeyPosesDS(new pcl::PointCloud<PointType>());
  476. std::vector<int> pointSearchInd;
  477. std::vector<float> pointSearchSqDis;
  478. // extract all the nearby key poses and downsample them, 50m范围内的关键帧
  479. kdtreeSurroundingKeyPoses->setInputCloud(cloudKeyPoses3D); // create kd-tree
  480. kdtreeSurroundingKeyPoses->radiusSearch(cloudKeyPoses3D->back(), (double) surroundingKeyframeSearchRadius,
  481. pointSearchInd, pointSearchSqDis);
  482. // 将满足要求的点云帧加到surroundingKeyPoses中
  483. for (int i = 0; i < pointSearchInd.size(); ++i) {
  484. int id = pointSearchInd[i];
  485. surroundingKeyPoses->push_back(cloudKeyPoses3D->points[id]);
  486. }
  487. downSizeFilterSurroundingKeyPoses.setInputCloud(surroundingKeyPoses);
  488. downSizeFilterSurroundingKeyPoses.filter(*surroundingKeyPosesDS);
  489. // also extract some latest key frames in case the robot rotates in one position
  490. // 把10s内同方向的关键帧也加到surroundingKeyPosesDS中
  491. int numPoses = cloudKeyPoses3D->size();
  492. for (int i = numPoses - 1; i >= 0; --i) {
  493. // 10s内的位姿态都加进来
  494. if (timeLaserCloudInfoLast - cloudKeyPoses6D->points[i].time < 10.0)
  495. surroundingKeyPosesDS->push_back(cloudKeyPoses3D->points[i]);
  496. else
  497. break;
  498. }
  499. extractCloud(surroundingKeyPosesDS);
  500. }
  501. void extractCloud(pcl::PointCloud<PointType>::Ptr cloudToExtract) {
  502. // 根据pose提取点云
  503. std::vector<pcl::PointCloud<PointType>> laserCloudCornerSurroundingVec;
  504. std::vector<pcl::PointCloud<PointType>> laserCloudSurfSurroundingVec;
  505. laserCloudCornerSurroundingVec.resize(cloudToExtract->size());
  506. laserCloudSurfSurroundingVec.resize(cloudToExtract->size());
  507. // extract surrounding map
  508. #pragma omp parallel for num_threads(numberOfCores)
  509. for (int i = 0; i < cloudToExtract->size(); ++i) {
  510. // 遍历每个位姿
  511. if (pointDistance(cloudToExtract->points[i], cloudKeyPoses3D->back()) > surroundingKeyframeSearchRadius)
  512. continue;
  513. int thisKeyInd = (int) cloudToExtract->points[i].intensity;
  514. laserCloudCornerSurroundingVec[i] = *transformPointCloud(cornerCloudKeyFrames[thisKeyInd],
  515. &cloudKeyPoses6D->points[thisKeyInd]);
  516. laserCloudSurfSurroundingVec[i] = *transformPointCloud(surfCloudKeyFrames[thisKeyInd],
  517. &cloudKeyPoses6D->points[thisKeyInd]);
  518. }
  519. // fuse the map
  520. // 构建local map
  521. laserCloudCornerFromMap->clear();
  522. laserCloudSurfFromMap->clear();
  523. for (int i = 0; i < cloudToExtract->size(); ++i) {
  524. *laserCloudCornerFromMap += laserCloudCornerSurroundingVec[i];
  525. *laserCloudSurfFromMap += laserCloudSurfSurroundingVec[i];
  526. }
  527. // Downsample the surrounding corner key frames (or map)
  528. downSizeFilterCorner.setInputCloud(laserCloudCornerFromMap);
  529. downSizeFilterCorner.filter(*laserCloudCornerFromMapDS);
  530. laserCloudCornerFromMapDSNum = laserCloudCornerFromMapDS->size();
  531. // Downsample the surrounding surf key frames (or map)
  532. downSizeFilterSurf.setInputCloud(laserCloudSurfFromMap);
  533. downSizeFilterSurf.filter(*laserCloudSurfFromMapDS);
  534. laserCloudSurfFromMapDSNum = laserCloudSurfFromMapDS->size();
  535. }
  536. void extractSurroundingKeyFrames() {
  537. if (cloudKeyPoses3D->points.empty() == true)
  538. return;
  539. // 检测到了回环就提取回环帧,否则提取附近点云
  540. // 第一次进来loopClosureEnableFlag = false, 直接提取附近关键帧
  541. if (loopClosureEnableFlag == true) {
  542. extractForLoopClosure();
  543. } else {
  544. extractNearby();
  545. }
  546. }
  547. void downsampleCurrentScan() {
  548. // Downsample cloud from current scan
  549. laserCloudCornerLastDS->clear();
  550. downSizeFilterCorner.setInputCloud(laserCloudCornerLast);
  551. downSizeFilterCorner.filter(*laserCloudCornerLastDS);
  552. laserCloudCornerLastDSNum = laserCloudCornerLastDS->size();
  553. laserCloudSurfLastDS->clear();
  554. downSizeFilterSurf.setInputCloud(laserCloudSurfLast);
  555. downSizeFilterSurf.filter(*laserCloudSurfLastDS);
  556. laserCloudSurfLastDSNum = laserCloudSurfLastDS->size();
  557. }
  558. void updatePointAssociateToMap() {
  559. // 根据初始位姿将点云转换到Map系下
  560. transPointAssociateToMap = trans2Affine3f(transformTobeMapped);
  561. }
  562. void cornerOptimization() {
  563. updatePointAssociateToMap(); // 将points转到地图系
  564. #pragma omp parallel for num_threads(numberOfCores)
  565. // 遍历点云, 构建点到直线的约束
  566. for (int i = 0; i < laserCloudCornerLastDSNum; i++) {
  567. PointType pointOri, pointSel, coeff;
  568. std::vector<int> pointSearchInd;
  569. std::vector<float> pointSearchSqDis;
  570. // 在map中搜索当前点的5个紧邻点
  571. pointOri = laserCloudCornerLastDS->points[i];
  572. pointAssociateToMap(&pointOri, &pointSel);
  573. kdtreeCornerFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);
  574. cv::Mat matA1(3, 3, CV_32F, cv::Scalar::all(0));
  575. cv::Mat matD1(1, 3, CV_32F, cv::Scalar::all(0));
  576. cv::Mat matV1(3, 3, CV_32F, cv::Scalar::all(0));
  577. // 只有最近的点都在一定阈值内(1米)才进行计算
  578. if (pointSearchSqDis[4] < 1.0) {
  579. float cx = 0, cy = 0, cz = 0;
  580. for (int j = 0; j < 5; j++) {
  581. cx += laserCloudCornerFromMapDS->points[pointSearchInd[j]].x;
  582. cy += laserCloudCornerFromMapDS->points[pointSearchInd[j]].y;
  583. cz += laserCloudCornerFromMapDS->points[pointSearchInd[j]].z;
  584. }
  585. // 计算其算数平均值/均值
  586. cx /= 5;
  587. cy /= 5;
  588. cz /= 5;
  589. // 计算协方差
  590. float a11 = 0, a12 = 0, a13 = 0, a22 = 0, a23 = 0, a33 = 0;
  591. for (int j = 0; j < 5; j++) {
  592. float ax = laserCloudCornerFromMapDS->points[pointSearchInd[j]].x - cx;
  593. float ay = laserCloudCornerFromMapDS->points[pointSearchInd[j]].y - cy;
  594. float az = laserCloudCornerFromMapDS->points[pointSearchInd[j]].z - cz;
  595. a11 += ax * ax;
  596. a12 += ax * ay;
  597. a13 += ax * az;
  598. a22 += ay * ay;
  599. a23 += ay * az;
  600. a33 += az * az;
  601. }
  602. a11 /= 5;
  603. a12 /= 5;
  604. a13 /= 5;
  605. a22 /= 5;
  606. a23 /= 5;
  607. a33 /= 5;
  608. matA1.at<float>(0, 0) = a11;
  609. matA1.at<float>(0, 1) = a12;
  610. matA1.at<float>(0, 2) = a13;
  611. matA1.at<float>(1, 0) = a12;
  612. matA1.at<float>(1, 1) = a22;
  613. matA1.at<float>(1, 2) = a23;
  614. matA1.at<float>(2, 0) = a13;
  615. matA1.at<float>(2, 1) = a23;
  616. matA1.at<float>(2, 2) = a33;
  617. // 求协方差矩阵的特征值和特征向量, 特征值:matD1,特征向量:保存在矩阵matV1中。
  618. cv::eigen(matA1, matD1, matV1);
  619. // 其中一个特征值远远大于其他两个,则呈线状
  620. if (matD1.at<float>(0, 0) > 3 * matD1.at<float>(0, 1)) {
  621. float x0 = pointSel.x;
  622. float y0 = pointSel.y;
  623. float z0 = pointSel.z;
  624. float x1 = cx + 0.1 * matV1.at<float>(0, 0);
  625. float y1 = cy + 0.1 * matV1.at<float>(0, 1);
  626. float z1 = cz + 0.1 * matV1.at<float>(0, 2);
  627. float x2 = cx - 0.1 * matV1.at<float>(0, 0);
  628. float y2 = cy - 0.1 * matV1.at<float>(0, 1);
  629. float z2 = cz - 0.1 * matV1.at<float>(0, 2);
  630. // 与里程计的计算类似,计算到直线的距离
  631. float a012 = sqrt(((x0 - x1) * (y0 - y2) - (x0 - x2) * (y0 - y1)) *
  632. ((x0 - x1) * (y0 - y2) - (x0 - x2) * (y0 - y1))
  633. + ((x0 - x1) * (z0 - z2) - (x0 - x2) * (z0 - z1)) *
  634. ((x0 - x1) * (z0 - z2) - (x0 - x2) * (z0 - z1))
  635. + ((y0 - y1) * (z0 - z2) - (y0 - y2) * (z0 - z1)) *
  636. ((y0 - y1) * (z0 - z2) - (y0 - y2) * (z0 - z1)));
  637. float l12 = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2));
  638. float la = ((y1 - y2) * ((x0 - x1) * (y0 - y2) - (x0 - x2) * (y0 - y1))
  639. + (z1 - z2) * ((x0 - x1) * (z0 - z2) - (x0 - x2) * (z0 - z1))) / a012 / l12;
  640. float lb = -((x1 - x2) * ((x0 - x1) * (y0 - y2) - (x0 - x2) * (y0 - y1))
  641. - (z1 - z2) * ((y0 - y1) * (z0 - z2) - (y0 - y2) * (z0 - z1))) / a012 / l12;
  642. float lc = -((x1 - x2) * ((x0 - x1) * (z0 - z2) - (x0 - x2) * (z0 - z1))
  643. + (y1 - y2) * ((y0 - y1) * (z0 - z2) - (y0 - y2) * (z0 - z1))) / a012 / l12;
  644. float ld2 = a012 / l12;
  645. // 下面涉及到一个鲁棒核函数,作者简单地设计了这个核函数。
  646. float s = 1 - 0.9 * fabs(ld2);
  647. coeff.x = s * la;
  648. coeff.y = s * lb;
  649. coeff.z = s * lc;
  650. coeff.intensity = s * ld2;
  651. // 程序末尾根据s的值来判断是否将点云点放入点云集合laserCloudOri以及coeffSel中。
  652. if (s > 0.1) {
  653. laserCloudOriCornerVec[i] = pointOri;
  654. coeffSelCornerVec[i] = coeff;
  655. laserCloudOriCornerFlag[i] = true;
  656. }
  657. }
  658. }
  659. }
  660. }
  661. void surfOptimization() {
  662. updatePointAssociateToMap();
  663. #pragma omp parallel for num_threads(numberOfCores)
  664. for (int i = 0; i < laserCloudSurfLastDSNum; i++) {
  665. PointType pointOri, pointSel, coeff;
  666. std::vector<int> pointSearchInd;
  667. std::vector<float> pointSearchSqDis;
  668. // 寻找5个紧邻点, 计算其特征值和特征向量
  669. pointOri = laserCloudSurfLastDS->points[i];
  670. pointAssociateToMap(&pointOri, &pointSel);
  671. kdtreeSurfFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);
  672. Eigen::Matrix<float, 5, 3> matA0;
  673. Eigen::Matrix<float, 5, 1> matB0;
  674. Eigen::Vector3f matX0;
  675. matA0.setZero(); // 5*3 存储5个紧邻点
  676. matB0.fill(-1);
  677. matX0.setZero();
  678. // 只考虑附近1.0m内
  679. if (pointSearchSqDis[4] < 1.0) {
  680. for (int j = 0; j < 5; j++) {
  681. matA0(j, 0) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].x;
  682. matA0(j, 1) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].y;
  683. matA0(j, 2) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].z;
  684. }
  685. // 求maxA0中点构成的平面法向量
  686. matX0 = matA0.colPivHouseholderQr().solve(matB0);
  687. // 法向量参数 ax+by+cz +d = 0
  688. float pa = matX0(0, 0);
  689. float pb = matX0(1, 0);
  690. float pc = matX0(2, 0);
  691. float pd = 1;
  692. float ps = sqrt(pa * pa + pb * pb + pc * pc);
  693. pa /= ps;
  694. pb /= ps;
  695. pc /= ps;
  696. pd /= ps;
  697. // 这里再次判断求解的方向向量和每个点相乘,最后结果是不是在误差范围内。
  698. bool planeValid = true;
  699. for (int j = 0; j < 5; j++) {
  700. if (fabs(pa * laserCloudSurfFromMapDS->points[pointSearchInd[j]].x +
  701. pb * laserCloudSurfFromMapDS->points[pointSearchInd[j]].y +
  702. pc * laserCloudSurfFromMapDS->points[pointSearchInd[j]].z + pd) > 0.2) {
  703. planeValid = false;
  704. break;
  705. }
  706. }
  707. // 是有效的平面
  708. if (planeValid) {
  709. float pd2 = pa * pointSel.x + pb * pointSel.y + pc * pointSel.z + pd;
  710. float s = 1 - 0.9 * fabs(pd2) / sqrt(sqrt(pointSel.x * pointSel.x
  711. + pointSel.y * pointSel.y + pointSel.z * pointSel.z));
  712. coeff.x = s * pa;
  713. coeff.y = s * pb;
  714. coeff.z = s * pc;
  715. coeff.intensity = s * pd2;
  716. // 误差在允许的范围内的话把这个点放到点云laserCloudOri中去,把对应的向量coeff放到coeffSel中
  717. if (s > 0.1) {
  718. laserCloudOriSurfVec[i] = pointOri;
  719. coeffSelSurfVec[i] = coeff;
  720. laserCloudOriSurfFlag[i] = true;
  721. }
  722. }
  723. }
  724. }
  725. }
  726. void combineOptimizationCoeffs() {
  727. // 把两类损失和协方差丢到laserCloudOri和coeffSel中, 后续进行联合优化
  728. // combine corner coeffs
  729. for (int i = 0; i < laserCloudCornerLastDSNum; ++i) {
  730. if (laserCloudOriCornerFlag[i] == true) {
  731. laserCloudOri->push_back(laserCloudOriCornerVec[i]);
  732. coeffSel->push_back(coeffSelCornerVec[i]);
  733. }
  734. }
  735. // combine surf coeffs
  736. for (int i = 0; i < laserCloudSurfLastDSNum; ++i) {
  737. if (laserCloudOriSurfFlag[i] == true) {
  738. laserCloudOri->push_back(laserCloudOriSurfVec[i]);
  739. coeffSel->push_back(coeffSelSurfVec[i]);
  740. }
  741. }
  742. // reset flag for next iteration 重置参数, 下一帧还要继续用
  743. std::fill(laserCloudOriCornerFlag.begin(), laserCloudOriCornerFlag.end(), false);
  744. std::fill(laserCloudOriSurfFlag.begin(), laserCloudOriSurfFlag.end(), false);
  745. }
  746. bool LMOptimization(int iterCount) {
  747. // This optimization is from the original loam_velodyne by Ji Zhang, need to cope with coordinate transformation
  748. // lidar <- camera --- camera <- lidar
  749. // x = z --- x = y
  750. // y = x --- y = z
  751. // z = y --- z = x
  752. // roll = yaw --- roll = pitch
  753. // pitch = roll --- pitch = yaw
  754. // yaw = pitch --- yaw = roll
  755. // 高斯牛顿优化, 参考LOAM
  756. // lidar -> camera
  757. float srx = sin(transformTobeMapped[1]);
  758. float crx = cos(transformTobeMapped[1]);
  759. float sry = sin(transformTobeMapped[2]);
  760. float cry = cos(transformTobeMapped[2]);
  761. float srz = sin(transformTobeMapped[0]);
  762. float crz = cos(transformTobeMapped[0]);
  763. // 初次优化时,特征值门限设置为50,小于这个值认为是退化了,修改matX,matX=matP*matX2
  764. int laserCloudSelNum = laserCloudOri->size();
  765. if (laserCloudSelNum < 50) {
  766. return false;
  767. }
  768. cv::Mat matA(laserCloudSelNum, 6, CV_32F, cv::Scalar::all(0));
  769. cv::Mat matAt(6, laserCloudSelNum, CV_32F, cv::Scalar::all(0));
  770. cv::Mat matAtA(6, 6, CV_32F, cv::Scalar::all(0));
  771. cv::Mat matB(laserCloudSelNum, 1, CV_32F, cv::Scalar::all(0));
  772. cv::Mat matAtB(6, 1, CV_32F, cv::Scalar::all(0));
  773. cv::Mat matX(6, 1, CV_32F, cv::Scalar::all(0));
  774. cv::Mat matP(6, 6, CV_32F, cv::Scalar::all(0));
  775. PointType pointOri, coeff;
  776. for (int i = 0; i < laserCloudSelNum; i++) {
  777. // lidar -> camera
  778. pointOri.x = laserCloudOri->points[i].y;
  779. pointOri.y = laserCloudOri->points[i].z;
  780. pointOri.z = laserCloudOri->points[i].x;
  781. // lidar -> camera
  782. coeff.x = coeffSel->points[i].y;
  783. coeff.y = coeffSel->points[i].z;
  784. coeff.z = coeffSel->points[i].x;
  785. coeff.intensity = coeffSel->points[i].intensity;
  786. // in camera
  787. // 计算雅克比
  788. float arx = (crx * sry * srz * pointOri.x + crx * crz * sry * pointOri.y - srx * sry * pointOri.z) * coeff.x
  789. + (-srx * srz * pointOri.x - crz * srx * pointOri.y - crx * pointOri.z) * coeff.y
  790. + (crx * cry * srz * pointOri.x + crx * cry * crz * pointOri.y - cry * srx * pointOri.z) *
  791. coeff.z;
  792. float ary = ((cry * srx * srz - crz * sry) * pointOri.x
  793. + (sry * srz + cry * crz * srx) * pointOri.y + crx * cry * pointOri.z) * coeff.x
  794. + ((-cry * crz - srx * sry * srz) * pointOri.x
  795. + (cry * srz - crz * srx * sry) * pointOri.y - crx * sry * pointOri.z) * coeff.z;
  796. float arz =
  797. ((crz * srx * sry - cry * srz) * pointOri.x + (-cry * crz - srx * sry * srz) * pointOri.y) * coeff.x
  798. + (crx * crz * pointOri.x - crx * srz * pointOri.y) * coeff.y
  799. +
  800. ((sry * srz + cry * crz * srx) * pointOri.x + (crz * sry - cry * srx * srz) * pointOri.y) * coeff.z;
  801. // lidar -> camera
  802. matA.at<float>(i, 0) = arz;
  803. matA.at<float>(i, 1) = arx;
  804. matA.at<float>(i, 2) = ary;
  805. matA.at<float>(i, 3) = coeff.z;
  806. matA.at<float>(i, 4) = coeff.x;
  807. matA.at<float>(i, 5) = coeff.y;
  808. matB.at<float>(i, 0) = -coeff.intensity;
  809. }
  810. cv::transpose(matA, matAt);
  811. matAtA = matAt * matA;
  812. matAtB = matAt * matB;
  813. cv::solve(matAtA, matAtB, matX, cv::DECOMP_QR);
  814. if (iterCount == 0) {
  815. cv::Mat matE(1, 6, CV_32F, cv::Scalar::all(0));
  816. cv::Mat matV(6, 6, CV_32F, cv::Scalar::all(0));
  817. cv::Mat matV2(6, 6, CV_32F, cv::Scalar::all(0));
  818. cv::eigen(matAtA, matE, matV);
  819. matV.copyTo(matV2);
  820. isDegenerate = false;
  821. float eignThre[6] = {100, 100, 100, 100, 100, 100};
  822. for (int i = 5; i >= 0; i--) {
  823. if (matE.at<float>(0, i) < eignThre[i]) {
  824. for (int j = 0; j < 6; j++) {
  825. matV2.at<float>(i, j) = 0;
  826. }
  827. isDegenerate = true;
  828. } else {
  829. break;
  830. }
  831. }
  832. matP = matV.inv() * matV2;
  833. }
  834. if (isDegenerate) {
  835. cv::Mat matX2(6, 1, CV_32F, cv::Scalar::all(0));
  836. matX.copyTo(matX2);
  837. matX = matP * matX2;
  838. }
  839. transformTobeMapped[0] += matX.at<float>(0, 0);
  840. transformTobeMapped[1] += matX.at<float>(1, 0);
  841. transformTobeMapped[2] += matX.at<float>(2, 0);
  842. transformTobeMapped[3] += matX.at<float>(3, 0);
  843. transformTobeMapped[4] += matX.at<float>(4, 0);
  844. transformTobeMapped[5] += matX.at<float>(5, 0);
  845. float deltaR = sqrt(
  846. pow(pcl::rad2deg(matX.at<float>(0, 0)), 2) +
  847. pow(pcl::rad2deg(matX.at<float>(1, 0)), 2) +
  848. pow(pcl::rad2deg(matX.at<float>(2, 0)), 2));
  849. float deltaT = sqrt(
  850. pow(matX.at<float>(3, 0) * 100, 2) +
  851. pow(matX.at<float>(4, 0) * 100, 2) +
  852. pow(matX.at<float>(5, 0) * 100, 2));
  853. // 在判断是否是有效的优化时,要求旋转部分的模长小于0.05m,平移部分的模长也小于0.05度
  854. if (deltaR < 0.05 && deltaT < 0.05) {
  855. return true; // converged
  856. }
  857. return false; // keep optimizing
  858. }
  859. void scan2MapOptimization() {
  860. if (cloudKeyPoses3D->points.empty())
  861. return;
  862. // 特征需要满足一定要求才可以进行
  863. if (laserCloudCornerLastDSNum > edgeFeatureMinValidNum && laserCloudSurfLastDSNum > surfFeatureMinValidNum) {
  864. // 构建kdtree搜索的map, 两类
  865. kdtreeCornerFromMap->setInputCloud(laserCloudCornerFromMapDS);
  866. kdtreeSurfFromMap->setInputCloud(laserCloudSurfFromMapDS);
  867. // 迭代30次进行优化
  868. for (int iterCount = 0; iterCount < 30; iterCount++) {
  869. laserCloudOri->clear();
  870. coeffSel->clear();
  871. // 点到平面, 点到直线的残差, 这里写法还与aloam有点区别
  872. cornerOptimization();
  873. surfOptimization();
  874. // 联合两类的残差
  875. combineOptimizationCoeffs();
  876. // 高斯牛顿法迭代优化
  877. if (LMOptimization(iterCount) == true)
  878. break;
  879. }
  880. // 更新transform
  881. transformUpdate();
  882. } else {
  883. ROS_WARN("Not enough features! Only %d edge and %d planar features available.", laserCloudCornerLastDSNum,
  884. laserCloudSurfLastDSNum);
  885. }
  886. }
  887. void transformUpdate() {
  888. // IMU可用的话更新transformTobeMapped
  889. if (cloudInfo.imuAvailable == true) {
  890. if (std::abs(cloudInfo.imuPitchInit) < 1.4) {
  891. double imuWeight = 0.05;
  892. tf::Quaternion imuQuaternion;
  893. tf::Quaternion transformQuaternion;
  894. double rollMid, pitchMid, yawMid;
  895. // slerp roll
  896. transformQuaternion.setRPY(transformTobeMapped[0], 0, 0);
  897. imuQuaternion.setRPY(cloudInfo.imuRollInit, 0, 0);
  898. // 线性插值
  899. tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
  900. transformTobeMapped[0] = rollMid;
  901. // slerp pitch
  902. transformQuaternion.setRPY(0, transformTobeMapped[1], 0);
  903. imuQuaternion.setRPY(0, cloudInfo.imuPitchInit, 0);
  904. tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
  905. transformTobeMapped[1] = pitchMid;
  906. }
  907. }
  908. transformTobeMapped[0] = constraintTransformation(transformTobeMapped[0], rotation_tollerance);
  909. transformTobeMapped[1] = constraintTransformation(transformTobeMapped[1], rotation_tollerance);
  910. transformTobeMapped[5] = constraintTransformation(transformTobeMapped[5], z_tollerance);
  911. }
  912. float constraintTransformation(float value, float limit) {
  913. if (value < -limit)
  914. value = -limit;
  915. if (value > limit)
  916. value = limit;
  917. return value;
  918. }
  919. bool saveFrame() {
  920. if (cloudKeyPoses3D->points.empty())
  921. return true;
  922. Eigen::Affine3f transStart = pclPointToAffine3f(cloudKeyPoses6D->back());
  923. Eigen::Affine3f transFinal = pcl::getTransformation(transformTobeMapped[3], transformTobeMapped[4],
  924. transformTobeMapped[5],
  925. transformTobeMapped[0], transformTobeMapped[1],
  926. transformTobeMapped[2]);
  927. Eigen::Affine3f transBetween = transStart.inverse() * transFinal;
  928. float x, y, z, roll, pitch, yaw;
  929. pcl::getTranslationAndEulerAngles(transBetween, x, y, z, roll, pitch, yaw);
  930. if (abs(roll) < surroundingkeyframeAddingAngleThreshold &&
  931. abs(pitch) < surroundingkeyframeAddingAngleThreshold &&
  932. abs(yaw) < surroundingkeyframeAddingAngleThreshold &&
  933. sqrt(x * x + y * y + z * z) < surroundingkeyframeAddingDistThreshold)
  934. return false;
  935. return true;
  936. }
  937. void addOdomFactor() {
  938. if (cloudKeyPoses3D->points.empty()) {
  939. // 第一帧进来时初始化gtsam参数
  940. noiseModel::Diagonal::shared_ptr priorNoise = noiseModel::Diagonal::Variances(
  941. (Vector(6) << 1e-2, 1e-2, M_PI * M_PI, 1e8, 1e8, 1e8).finished()); // rad*rad, meter*meter
  942. // 先验因子
  943. gtSAMgraph.add(PriorFactor<Pose3>(0, trans2gtsamPose(transformTobeMapped), priorNoise));
  944. initialEstimate.insert(0, trans2gtsamPose(transformTobeMapped));
  945. } else {
  946. noiseModel::Diagonal::shared_ptr odometryNoise = noiseModel::Diagonal::Variances(
  947. (Vector(6) << 1e-6, 1e-6, 1e-6, 1e-4, 1e-4, 1e-4).finished());
  948. gtsam::Pose3 poseFrom = pclPointTogtsamPose3(cloudKeyPoses6D->points.back());
  949. gtsam::Pose3 poseTo = trans2gtsamPose(transformTobeMapped);
  950. // 二元因子
  951. gtSAMgraph.add(
  952. BetweenFactor<Pose3>(cloudKeyPoses3D->size() - 1, cloudKeyPoses3D->size(), poseFrom.between(poseTo),
  953. odometryNoise));
  954. initialEstimate.insert(cloudKeyPoses3D->size(), poseTo); // 添加值
  955. }
  956. }
  957. void addGPSFactor() {
  958. if (gpsQueue.empty())
  959. return;
  960. // wait for system initialized and settles down
  961. if (cloudKeyPoses3D->points.empty())
  962. return;
  963. else {
  964. if (pointDistance(cloudKeyPoses3D->front(), cloudKeyPoses3D->back()) < 5.0)
  965. return;
  966. }
  967. // pose covariance small, no need to correct
  968. if (poseCovariance(3, 3) < poseCovThreshold && poseCovariance(4, 4) < poseCovThreshold)
  969. return;
  970. // pose的协方差比较大的时候才去添加gps factor
  971. while (!gpsQueue.empty()) {
  972. // 时间戳对齐
  973. if (gpsQueue.front().header.stamp.toSec() < timeLaserCloudInfoLast - 0.2) {
  974. // message too old
  975. gpsQueue.pop_front();
  976. } else if (gpsQueue.front().header.stamp.toSec() > timeLaserCloudInfoLast + 0.2) {
  977. // message too new
  978. break;
  979. } else {
  980. nav_msgs::Odometry thisGPS = gpsQueue.front();
  981. gpsQueue.pop_front();
  982. // GPS too noisy, skip
  983. float noise_x = thisGPS.pose.covariance[0];
  984. float noise_y = thisGPS.pose.covariance[7];
  985. float noise_z = thisGPS.pose.covariance[14];
  986. if (noise_x > gpsCovThreshold || noise_y > gpsCovThreshold)
  987. continue;
  988. float gps_x = thisGPS.pose.pose.position.x;
  989. float gps_y = thisGPS.pose.pose.position.y;
  990. float gps_z = thisGPS.pose.pose.position.z;
  991. if (!useGpsElevation) {
  992. gps_z = transformTobeMapped[5]; // gps的z一般不可信
  993. noise_z = 0.01;
  994. }
  995. // GPS not properly initialized (0,0,0)
  996. if (abs(gps_x) < 1e-6 && abs(gps_y) < 1e-6)
  997. continue;
  998. // 添加GPS因子
  999. gtsam::Vector Vector3(3);
  1000. Vector3 << noise_x, noise_y, noise_z;
  1001. noiseModel::Diagonal::shared_ptr gps_noise = noiseModel::Diagonal::Variances(Vector3); // 噪声定义
  1002. gtsam::GPSFactor gps_factor(cloudKeyPoses3D->size(), gtsam::Point3(gps_x, gps_y, gps_z), gps_noise);
  1003. gtSAMgraph.add(gps_factor);
  1004. aLoopIsClosed = true;
  1005. break;
  1006. }
  1007. }
  1008. }
  1009. void saveKeyFramesAndFactor() {
  1010. if (saveFrame() == false)
  1011. return;
  1012. // 添加各种factor、保存关键帧
  1013. // odom factor
  1014. addOdomFactor();
  1015. // gps factor
  1016. addGPSFactor();
  1017. // cout << "****************************************************" << endl;
  1018. // gtSAMgraph.print("GTSAM Graph:\n");
  1019. // update iSAM
  1020. isam->update(gtSAMgraph, initialEstimate);
  1021. isam->update();
  1022. // update multiple-times till converge
  1023. if (aLoopIsClosed == true) {
  1024. isam->update();
  1025. isam->update();
  1026. isam->update();
  1027. isam->update();
  1028. isam->update();
  1029. }
  1030. gtSAMgraph.resize(0);
  1031. initialEstimate.clear();
  1032. //save key poses
  1033. PointType thisPose3D;
  1034. PointTypePose thisPose6D;
  1035. Pose3 latestEstimate;
  1036. // 最新的pose
  1037. isamCurrentEstimate = isam->calculateEstimate();
  1038. latestEstimate = isamCurrentEstimate.at<Pose3>(isamCurrentEstimate.size() - 1);
  1039. // cout << "****************************************************" << endl;
  1040. // isamCurrentEstimate.print("Current estimate: ");
  1041. // 这里不断的增加关键帧到cloudKeyPoses3D、cloudKeyPoses6D中
  1042. thisPose3D.x = latestEstimate.translation().x();
  1043. thisPose3D.y = latestEstimate.translation().y();
  1044. thisPose3D.z = latestEstimate.translation().z();
  1045. thisPose3D.intensity = cloudKeyPoses3D->size(); // this can be used as index
  1046. cloudKeyPoses3D->push_back(thisPose3D);
  1047. thisPose6D.x = thisPose3D.x;
  1048. thisPose6D.y = thisPose3D.y;
  1049. thisPose6D.z = thisPose3D.z;
  1050. thisPose6D.intensity = thisPose3D.intensity; // this can be used as index
  1051. thisPose6D.roll = latestEstimate.rotation().roll();
  1052. thisPose6D.pitch = latestEstimate.rotation().pitch();
  1053. thisPose6D.yaw = latestEstimate.rotation().yaw();
  1054. thisPose6D.time = timeLaserCloudInfoLast;
  1055. cloudKeyPoses6D->push_back(thisPose6D);
  1056. // cout << "****************************************************" << endl;
  1057. // cout << "Pose covariance:" << endl;
  1058. // cout << isam->marginalCovariance(isamCurrentEstimate.size()-1) << endl << endl;
  1059. // 边缘化得到每个变量的协方差
  1060. poseCovariance = isam->marginalCovariance(isamCurrentEstimate.size() - 1);
  1061. // save updated transform
  1062. transformTobeMapped[0] = latestEstimate.rotation().roll();
  1063. transformTobeMapped[1] = latestEstimate.rotation().pitch();
  1064. transformTobeMapped[2] = latestEstimate.rotation().yaw();
  1065. transformTobeMapped[3] = latestEstimate.translation().x();
  1066. transformTobeMapped[4] = latestEstimate.translation().y();
  1067. transformTobeMapped[5] = latestEstimate.translation().z();
  1068. // save all the received edge and surf points
  1069. pcl::PointCloud<PointType>::Ptr thisCornerKeyFrame(new pcl::PointCloud<PointType>());
  1070. pcl::PointCloud<PointType>::Ptr thisSurfKeyFrame(new pcl::PointCloud<PointType>());
  1071. pcl::copyPointCloud(*laserCloudCornerLastDS, *thisCornerKeyFrame);
  1072. pcl::copyPointCloud(*laserCloudSurfLastDS, *thisSurfKeyFrame);
  1073. // save key frame cloud
  1074. cornerCloudKeyFrames.push_back(thisCornerKeyFrame);
  1075. surfCloudKeyFrames.push_back(thisSurfKeyFrame);
  1076. // save path for visualization
  1077. updatePath(thisPose6D);
  1078. }
  1079. void correctPoses() {
  1080. if (cloudKeyPoses3D->points.empty())
  1081. return;
  1082. if (aLoopIsClosed == true) {
  1083. // clear path
  1084. globalPath.poses.clear();
  1085. // update key poses 更新位姿
  1086. int numPoses = isamCurrentEstimate.size();
  1087. for (int i = 0; i < numPoses; ++i) {
  1088. cloudKeyPoses3D->points[i].x = isamCurrentEstimate.at<Pose3>(i).translation().x();
  1089. cloudKeyPoses3D->points[i].y = isamCurrentEstimate.at<Pose3>(i).translation().y();
  1090. cloudKeyPoses3D->points[i].z = isamCurrentEstimate.at<Pose3>(i).translation().z();
  1091. cloudKeyPoses6D->points[i].x = cloudKeyPoses3D->points[i].x;
  1092. cloudKeyPoses6D->points[i].y = cloudKeyPoses3D->points[i].y;
  1093. cloudKeyPoses6D->points[i].z = cloudKeyPoses3D->points[i].z;
  1094. cloudKeyPoses6D->points[i].roll = isamCurrentEstimate.at<Pose3>(i).rotation().roll();
  1095. cloudKeyPoses6D->points[i].pitch = isamCurrentEstimate.at<Pose3>(i).rotation().pitch();
  1096. cloudKeyPoses6D->points[i].yaw = isamCurrentEstimate.at<Pose3>(i).rotation().yaw();
  1097. updatePath(cloudKeyPoses6D->points[i]);
  1098. }
  1099. aLoopIsClosed = false;
  1100. // ID for reseting IMU pre-integration
  1101. ++imuPreintegrationResetId;
  1102. }
  1103. }
  1104. void updatePath(const PointTypePose &pose_in) {
  1105. geometry_msgs::PoseStamped pose_stamped;
  1106. pose_stamped.header.stamp = timeLaserInfoStamp;
  1107. pose_stamped.header.frame_id = "odom";
  1108. pose_stamped.pose.position.x = pose_in.x;
  1109. pose_stamped.pose.position.y = pose_in.y;
  1110. pose_stamped.pose.position.z = pose_in.z;
  1111. tf::Quaternion q = tf::createQuaternionFromRPY(pose_in.roll, pose_in.pitch, pose_in.yaw);
  1112. pose_stamped.pose.orientation.x = q.x();
  1113. pose_stamped.pose.orientation.y = q.y();
  1114. pose_stamped.pose.orientation.z = q.z();
  1115. pose_stamped.pose.orientation.w = q.w();
  1116. globalPath.poses.push_back(pose_stamped);
  1117. }
  1118. void publishOdometry() {
  1119. // Publish odometry for ROS
  1120. nav_msgs::Odometry laserOdometryROS;
  1121. laserOdometryROS.header.stamp = timeLaserInfoStamp;
  1122. laserOdometryROS.header.frame_id = "odom";
  1123. laserOdometryROS.child_frame_id = "odom_mapping";
  1124. laserOdometryROS.pose.pose.position.x = transformTobeMapped[3];
  1125. laserOdometryROS.pose.pose.position.y = transformTobeMapped[4];
  1126. laserOdometryROS.pose.pose.position.z = transformTobeMapped[5];
  1127. laserOdometryROS.pose.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(transformTobeMapped[0],
  1128. transformTobeMapped[1],
  1129. transformTobeMapped[2]);
  1130. laserOdometryROS.pose.covariance[0] = double(imuPreintegrationResetId);
  1131. pubOdomAftMappedROS.publish(laserOdometryROS);
  1132. }
  1133. void publishFrames() {
  1134. if (cloudKeyPoses3D->points.empty())
  1135. return;
  1136. // publish key poses
  1137. publishCloud(&pubKeyPoses, cloudKeyPoses3D, timeLaserInfoStamp, "odom");
  1138. // Publish surrounding key frames
  1139. publishCloud(&pubRecentKeyFrames, laserCloudSurfFromMapDS, timeLaserInfoStamp, "odom");
  1140. // publish registered key frame
  1141. if (pubRecentKeyFrame.getNumSubscribers() != 0) {
  1142. pcl::PointCloud<PointType>::Ptr cloudOut(new pcl::PointCloud<PointType>());
  1143. PointTypePose thisPose6D = trans2PointTypePose(transformTobeMapped);
  1144. *cloudOut += *transformPointCloud(laserCloudCornerLastDS, &thisPose6D);
  1145. *cloudOut += *transformPointCloud(laserCloudSurfLastDS, &thisPose6D);
  1146. publishCloud(&pubRecentKeyFrame, cloudOut, timeLaserInfoStamp, "odom");
  1147. }
  1148. // publish registered high-res raw cloud
  1149. if (pubCloudRegisteredRaw.getNumSubscribers() != 0) {
  1150. pcl::PointCloud<PointType>::Ptr cloudOut(new pcl::PointCloud<PointType>());
  1151. pcl::fromROSMsg(cloudInfo.cloud_deskewed, *cloudOut);
  1152. PointTypePose thisPose6D = trans2PointTypePose(transformTobeMapped);
  1153. *cloudOut = *transformPointCloud(cloudOut, &thisPose6D);
  1154. publishCloud(&pubCloudRegisteredRaw, cloudOut, timeLaserInfoStamp, "odom");
  1155. }
  1156. // publish path
  1157. if (pubPath.getNumSubscribers() != 0) {
  1158. globalPath.header.stamp = timeLaserInfoStamp;
  1159. globalPath.header.frame_id = "odom";
  1160. pubPath.publish(globalPath);
  1161. }
  1162. }
  1163. public:
  1164. // gtsam
  1165. NonlinearFactorGraph gtSAMgraph;
  1166. Values initialEstimate;
  1167. Values optimizedEstimate;
  1168. ISAM2 *isam;
  1169. Values isamCurrentEstimate;
  1170. Eigen::MatrixXd poseCovariance;
  1171. ros::Publisher pubLaserCloudSurround;
  1172. ros::Publisher pubOdomAftMappedROS;
  1173. ros::Publisher pubKeyPoses;
  1174. ros::Publisher pubPath;
  1175. ros::Publisher pubHistoryKeyFrames;
  1176. ros::Publisher pubIcpKeyFrames;
  1177. ros::Publisher pubRecentKeyFrames;
  1178. ros::Publisher pubRecentKeyFrame;
  1179. ros::Publisher pubCloudRegisteredRaw;
  1180. ros::Subscriber subLaserCloudInfo;
  1181. ros::Subscriber subGPS;
  1182. std::deque<nav_msgs::Odometry> gpsQueue;
  1183. lio_sam::cloud_info cloudInfo;
  1184. vector<pcl::PointCloud<PointType>::Ptr> cornerCloudKeyFrames;
  1185. vector<pcl::PointCloud<PointType>::Ptr> surfCloudKeyFrames;
  1186. pcl::PointCloud<PointType>::Ptr cloudKeyPoses3D;
  1187. pcl::PointCloud<PointTypePose>::Ptr cloudKeyPoses6D;
  1188. pcl::PointCloud<PointType>::Ptr laserCloudCornerLast; // corner feature set from odoOptimization
  1189. pcl::PointCloud<PointType>::Ptr laserCloudSurfLast; // surf feature set from odoOptimization
  1190. pcl::PointCloud<PointType>::Ptr laserCloudCornerLastDS; // downsampled corner featuer set from odoOptimization
  1191. pcl::PointCloud<PointType>::Ptr laserCloudSurfLastDS; // downsampled surf featuer set from odoOptimization
  1192. pcl::PointCloud<PointType>::Ptr laserCloudOri;
  1193. pcl::PointCloud<PointType>::Ptr coeffSel;
  1194. std::vector<PointType> laserCloudOriCornerVec; // corner point holder for parallel computation
  1195. std::vector<PointType> coeffSelCornerVec;
  1196. std::vector<bool> laserCloudOriCornerFlag;
  1197. std::vector<PointType> laserCloudOriSurfVec; // surf point holder for parallel computation
  1198. std::vector<PointType> coeffSelSurfVec;
  1199. std::vector<bool> laserCloudOriSurfFlag;
  1200. pcl::PointCloud<PointType>::Ptr laserCloudCornerFromMap;
  1201. pcl::PointCloud<PointType>::Ptr laserCloudSurfFromMap;
  1202. pcl::PointCloud<PointType>::Ptr laserCloudCornerFromMapDS;
  1203. pcl::PointCloud<PointType>::Ptr laserCloudSurfFromMapDS;
  1204. pcl::KdTreeFLANN<PointType>::Ptr kdtreeCornerFromMap;
  1205. pcl::KdTreeFLANN<PointType>::Ptr kdtreeSurfFromMap;
  1206. pcl::KdTreeFLANN<PointType>::Ptr kdtreeSurroundingKeyPoses;
  1207. pcl::KdTreeFLANN<PointType>::Ptr kdtreeHistoryKeyPoses;
  1208. pcl::PointCloud<PointType>::Ptr latestKeyFrameCloud;
  1209. pcl::PointCloud<PointType>::Ptr nearHistoryKeyFrameCloud;
  1210. pcl::VoxelGrid<PointType> downSizeFilterCorner;
  1211. pcl::VoxelGrid<PointType> downSizeFilterSurf;
  1212. pcl::VoxelGrid<PointType> downSizeFilterICP;
  1213. pcl::VoxelGrid<PointType> downSizeFilterSurroundingKeyPoses; // for surrounding key poses of scan-to-map optimization
  1214. ros::Time timeLaserInfoStamp;
  1215. double timeLaserCloudInfoLast;
  1216. float transformTobeMapped[6];
  1217. std::mutex mtx;
  1218. double timeLastProcessing = -1;
  1219. bool isDegenerate = false;
  1220. Eigen::Matrix<float, 6, 6> matP;
  1221. int laserCloudCornerFromMapDSNum = 0;
  1222. int laserCloudSurfFromMapDSNum = 0;
  1223. int laserCloudCornerLastDSNum = 0;
  1224. int laserCloudSurfLastDSNum = 0;
  1225. bool aLoopIsClosed = false;
  1226. int imuPreintegrationResetId = 0;
  1227. nav_msgs::Path globalPath;
  1228. Eigen::Affine3f transPointAssociateToMap;
  1229. Eigen::Affine3f lastImuTransformation;
  1230. };
  1231. int main(int argc, char **argv) {
  1232. ros::init(argc, argv, "lio_sam");
  1233. mapOptimization MO;
  1234. ROS_INFO("\033[1;32m----> Map Optimization Started.\033[0m");
  1235. // 两个线程,一边按固定的频率进行回环检测、添加约束边,另外一边进行地图发布和保存
  1236. std::thread loopthread(&mapOptimization::loopClosureThread, &MO);
  1237. std::thread visualizeMapThread(&mapOptimization::visualizeGlobalMapThread, &MO);
  1238. // 这里不间断的执行callback
  1239. ros::spin();
  1240. return 0;
  1241. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/116683?site
推荐阅读
相关标签
  

闽ICP备14008679号