github地址:Fast LOAM (Lidar Odometry And Mapping)

Fast LOAM提供了mapping和localization的两个节点,目前只使用其定位部分,以velodyne为例分析一下源码。

    <node pkg="floam" type="floam_odom_estimation_node" name="floam_odom_estimation_node" output="screen"/><node pkg="floam" type="floam_laser_processing_node" name="floam_laser_processing_node" output="screen"/><node pkg="floam" type="floam_laser_mapping_node" name="floam_laser_mapping_node" output="screen"/>

即只需要运行floam_odom_estimation_nodefloam_laser_processing_node两个节点即可!

1. laserProcessingNode.cpp

先从点云处理开始,看一下其订阅和发布的topic

ros::Subscriber subLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>("/velodyne_points", 100, velodyneHandler);
pubLaserCloudFiltered = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_points_filtered", 100);
pubEdgePoints = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_edge", 100);
pubSurfPoints = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_surf", 100);

其订阅了velodyne发来的点云数据,并将其不断的存到pointCloudBuf中,用于处理。
将其所有帧提取到的面特征和边缘特征拼接之后发出去velodyne_points_filtered,其时间戳同处理的该帧点云,frame_id则为base_link

//拼接
*pointcloud_filtered+=*pointcloud_edge;
*pointcloud_filtered+=*pointcloud_surf;
//设定时间戳和frame
laserCloudFilteredMsg.header.stamp = pointcloud_time;
laserCloudFilteredMsg.header.frame_id = "/base_link";

在该节点中主要函数为laser_processing(),每次从pointCloudBuf中取出一帧,即pointcloud_in,提取其面特征和边缘特征。

laserProcessing.featureExtraction(pointcloud_in,pointcloud_edge,pointcloud_surf);

该函数后面会在laserProcessingClass.cpp中介绍。
然后便发布了计算得到的面特征laser_cloud_surf和边缘特征laser_cloud_edge

2.laserProcessingClass.cpp

参考博客:http://xchu.net/2020/08/17/49floam/
在该文件中主要有函数featureExtractionfeatureExtractionFromSector

(1)featureExtraction

首先计算点云线束并分类

// 遍历点云,给点云的每个点进行分类,具体属于哪个线束for (int i = 0; i < (int) pc_in->points.size(); i++){int scanID=0;   // 比较远或者过近的点去掉double distance = sqrt(pc_in->points[i].x * pc_in->points[i].x + pc_in->points[i].y * pc_in->points[i].y);if(distance<lidar_param.min_distance || distance>lidar_param.max_distance)continue;// 计算点在垂直方向的角度,用来计算属于哪个线束double angle = atan(pc_in->points[i].z / distance) * 180 / M_PI;// 根据激光雷达的线束进行点的分类if (N_SCANS == 16){scanID = int((angle + 15) / 2 + 0.5);// 16线两线束间间隔30度,从-15到+15共16根线,这里为了避免出现负数,统一加15度if (scanID > (N_SCANS - 1) || scanID < 0){continue;}
}laserCloudScans[scanID]->push_back(pc_in->points[i]);  // 将每个点装入不同的容器}

接下来计算每个点的曲率

// 按点云线束进行遍历
for(int i = 0; i < N_SCANS; i++){if(laserCloudScans[i]->points.size()<131){continue;} // 某一束点云少于131则不适用// 计算每个点的曲率(粗糙度),参考LOAMstd::vector<Double2d> cloudCurvature; int total_points = laserCloudScans[i]->points.size()-10;// 当前点前后各五个点,计算其三个方向上距离和的平方for(int j = 5; j < (int)laserCloudScans[i]->points.size() - 5; j++){... Double2d distance(j,diffX * diffX + diffY * diffY + diffZ * diffZ);cloudCurvature.push_back(distance);}

将点云均匀划分成6个子图,保证各方向都能提取到特征

 for(int j=0;j<6;j++){int sector_length = (int)(total_points/6);int sector_start = sector_length *j;int sector_end = sector_length *(j+1)-1;if (j==5){sector_end = total_points - 1; } std::vector<Double2d> subCloudCurvature(cloudCurvature.begin()+sector_start,cloudCurvature.begin()+sector_end); // 特征提取featureExtractionFromSector(laserCloudScans[i],subCloudCurvature, pc_out_edge, pc_out_surf);
}

(2)LaserProcessingClass

在特征提取时,将点云按曲率大小进行排序,遍历点云,根据曲率判断其是平面点还是边缘点。
找到曲率最大的20个点,认为是边缘点,其他的则为平面点。

3.odomEstimationNode.cpp

在经过点云处理之后,发出面特征和边缘特征数据,在该节点中订阅。

ros::Subscriber subEdgeLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_edge", 100,velodyneEdgeHandler);
ros::Subscriber subSurfLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_surf", 100,velodyneSurfHandler);

首先是时间戳对齐,去掉较老时间戳的点云,然后有一个初始化的过程,该函数会在后面说到:

if(is_odom_inited == false){odomEstimation.initMapWithPoints(pointcloud_edge_in, pointcloud_surf_in);is_odom_inited = true;ROS_INFO("odom inited");
}

初始化完成之后,便将两种特征传入到updatePointsToMap函数中,求解优化得到位姿odomEstimation.odom

odomEstimation.updatePointsToMap(pointcloud_edge_in, pointcloud_surf_in,filter_distance);

此处filter_distance,是我修改引入的参数,为了解决保留点云太多时,求解位姿效率太低,导致发出的tf延迟较高问题,后面在函数中会详细说明。

最终将其tf广播出去,即transform

br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), odom_frame_id, "base_link"));

此处的odom_frame_id即为velodyne发出的里程计,tf的时间为当前的时间,如果为了解决两台主机时间不同步的问题,可以将该tfros::Time::now()修改成点云数据的时间pointcloud_time,但是此时忽略了位姿求解的时间,影响还有待测试。

4.odomEstimationClass.cpp

初始化initMapWithPoints的过程,即直接把第一帧加入到地图中去,毕竟第一帧也没什么可参考的。
当初始化完成后的updatePointsToMap中,设置了一些优化的代价和参数。
在初始化完成之后optimization_count便被设置成12,然后迭代次数不断减少至2,即只迭代两次。

假设当前帧和上一帧有相同的运动,然后开始优化:

for (int iterCount = 0; iterCount < optimization_count; iterCount++){ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);ceres::Problem::Options problem_options;ceres::Problem problem(problem_options);problem.AddParameterBlock(parameters, 7, new PoseSE3Parameterization());  addEdgeCostFactor(downsampledEdgeCloud,laserCloudCornerMap,problem,loss_function);addSurfCostFactor(downsampledSurfCloud,laserCloudSurfMap,problem,loss_function);ceres::Solver::Options options;options.linear_solver_type = ceres::DENSE_QR;options.max_num_iterations = 4;options.minimizer_progress_to_stdout = false;options.check_gradients = false;options.gradient_check_relative_precision = 1e-4;ceres::Solver::Summary summary;ceres::Solve(options, &problem, &summary);}

其中主要为添加两种特征点云分别到平面和边缘的残差项,即addEdgeCostFactoraddSurfCostFactor
此外,在使用该里程计时,实测中高度信息并不准确,不知是环境问题还是参数问题,有待解决,因此目前只使用其2D信息,添加如下修改:

//like
Eigen::Vector3d tran(t_w_curr(0),t_w_curr(1),0);
odom.translation() = tran;
(1) addEdgeCostFactor && addSurfCostFactor

KD tree参考博客:https://blog.csdn.net/weixin_38275649/article/details/80972554

遍历所有边缘点!
以该点为中心,即point_temp,计算近邻的索引值pointSearchInd、近邻的中心距pointSearchSqDis,寻找最邻近的5个点!

  • 该点point_temp是经过pointAssociateToMap,转换到地图坐标系下的点
 kdtreeEdgeMap->nearestKSearch(point_temp, 5, pointSearchInd, pointSearchSqDis);

然后构建点到平面/直线的残差,具体便不再详细介绍!

(2) addPointsToMap

该函数则是将点云添加到地图中,并且进行过滤下采样。
首先则是把得到的边缘特征和面特征不断的加入到map当中,虽然我们没用到其建图功能,但是在优化求解位姿时,需要和地图中的点云作为参考。

    for (int i = 0; i < (int)downsampledEdgeCloud->points.size(); i++){pcl::PointXYZI point_temp;pointAssociateToMap(&downsampledEdgeCloud->points[i], &point_temp);laserCloudCornerMap->push_back(point_temp); }for (int i = 0; i < (int)downsampledSurfCloud->points.size(); i++){pcl::PointXYZI point_temp;pointAssociateToMap(&downsampledSurfCloud->points[i], &point_temp);laserCloudSurfMap->push_back(point_temp);}

为了避免场景过大导致优化计算位姿太耗时,需要对加入到地图中的点云进行剔除

double x_min = +odom.translation().x()-filter_distance;
double y_min = +odom.translation().y()-filter_distance;
double z_min = +odom.translation().z()-filter_distance;
double x_max = +odom.translation().x()+filter_distance;
double y_max = +odom.translation().y()+filter_distance;
double z_max = +odom.translation().z()+filter_distance;
cropBoxFilter.setMin(Eigen::Vector4f(x_min, y_min, z_min, 1.0));
cropBoxFilter.setMax(Eigen::Vector4f(x_max, y_max, z_max, 1.0));
cropBoxFilter.setNegative(false);

即以filter_distance为范围,将当前位置附近阈值之外的点进行过滤。
然后经过VoxelGrid体素滤波器对点云进行下采样,最终加入到地图中!

激光SLAM系统Fast LOAM (Lidar Odometry And Mapping)源码解析相关推荐

  1. Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(一)

    Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(一) 1. LiDAR inertial odometry and mapping简介 ...

  2. Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(四)

    Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(四) 3. Joint optimization 3.3 IMU preintegrat ...

  3. Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(二)

    Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(二) 3. Joint optimization 3.1 Marginalization ...

  4. Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(三)

    Tightly Coupled LiDAR Inertial Odometry and Mapping源码解析(三) 3. Joint optimization 3.2 IMU preintegrat ...

  5. LOAM: Lidar Odometry and Mapping in Real-time

    一.概述 Loam主要是一种激光匹配slam的方法,也就是一种定位与建图的方法,相比于其他的制图方法,loam主要解决的是雷达快速移动的过程中的畸变问题,如果雷达的扫描速度和雷达的移动速度相差太大,会 ...

  6. 一套就够了!室内+室外激光SLAM关键算法讲解与工程实现(源码和数据开源)...

    应用背景介绍 高精地图作为自动驾驶的眼睛,在自动驾驶研发中占据极大的份额,而激光SLAM则是高精地图定位导航算法的关键技术,其重要性不言而喻,在AI产品如矿卡.汽车.清扫车和扫地机器人等领域都占据一席 ...

  7. LOAM源码解析1一scanRegistration

    鉴于工作和学习需要,学习了激光salm算法loam,并阅读了作者的原版论文,现将学习过程中的理解与一些源码剖析记录整理下来,也是对于学习slam的阶段性总结!!! 一.综述 LOAM这篇论文是发表于2 ...

  8. loam源码解析5 : laserOdometry(三)

    transformMaintenance.cpp解析 八.位姿估计 1. 雅可比计算 2. 矩阵求解 3. 退化问题分析 4. 姿态更新 5. 坐标转换 loam源码地址: https://githu ...

  9. loam源码解析1 : scanRegistration(一)

    scanRegistration.cpp解析 一.概述 二.变量说明 三.主函数 四.IMU回调函数laserCloudHandler 1 接受IMU的角度和加速度信息 2 AccumulateIMU ...

最新文章

  1. mysql之case_mysql存储过程之case语句
  2. 关于 运行root.sh 时出错误Timed out waiting for the CRS stack to start.的解决
  3. leetcode 66. 加一(C语言)
  4. 在IIS服务器上安装SSL证书
  5. 强化学习《基于策略 - on plolicy - off plolicy》
  6. html+css面试题 行内元素padding和margin
  7. 计算流体力学漫谈-1 (可压缩向)
  8. rda分析怎么做_PCA、PCoA、NMDS 、RDA和CCA等排序分析方法
  9. 《高性能MySQL》读书笔记(1~6章)
  10. 利用Eigen求广义逆矩阵
  11. win7系统提示此windows副本不是正版怎么办?
  12. IDEA 安装字体 安装JetBrains Mono字体
  13. 数据分析工具的深度对比:FineBI vs PowerBI
  14. 用request获取请求地址Ip
  15. 非谓语动词 + 情态动词学习笔记
  16. 3个套路带你玩转Excel动态图表
  17. android 轮询框架,Android 轮询总结
  18. Spring中同一个service类中方法相互调用事务不生效问题解决方案
  19. 盖茨基金会:全球至少要到2108年才能实现性别平等,比期望晚了三代人 | 美通社头条...
  20. 【转载】互联网知名博客收揽

热门文章

  1. 国家电网一二次融合配电终端(FTU)发展新方向馈线自动化:具备集中型馈线自动化/就地型馈线自动化包括电压时间型、电压电流型、自适应综合型)及零序电流,零序电压或外施信号法的单相接地故障选线功能
  2. 大数据处理算法--Bloom Filter布隆过滤
  3. [AHK]在当前目录中运行DOS命令行--DosHere
  4. 西安电大计算机文化基础中考,计算机文化基础试题3.pdf
  5. 周期训练理论与方法pdf_周期_PDF图书下载_(美) 图德·邦帕 (Tudor O.Bompa) (美)_免费PDF电子书下载_第一图书网...
  6. 封头名义厚度如何圆整_封头规格
  7. 单页双曲面 matlab,如何画双叶双曲面
  8. 2022-2028年中国电子商务行业市场深度分析及投资前景展望报告
  9. Java并发编程与技术内幕:ThreadFactory、ThreadLocal
  10. 当一个SQL语句同时出现了where,group by,having,order by的时候,执行顺序和编写顺序...