3D激光SLAM--A-LOAM :前端lidar点特征提取部分代码解读

简介: A-LOAM的cpp有四个,其中 kittiHelper.cpp 的作用是将kitti数据集转为rosbag剩下的三个是作为 slam 的 部分,分别是:- laserMappin.cpp ++++ 当前帧到地图的优化- laserOdometry.cpp ++++ 帧间里程计- scanRegistration.cpp ++++ 前端lidar点预处理及特征提取本片主要解读 前端lidar点特征提取部分的代码

A-LOAM代码的结构

A-LOAM的cpp有四个,其中 kittiHelper.cpp 的作用是将kitti数据集转为rosbag
剩下的三个是作为 slam 的 部分,分别是:

  • laserMappin.cpp ++++ 当前帧到地图的优化
  • laserOdometry.cpp ++++ 帧间里程计
  • scanRegistration.cpp ++++ 前端lidar点预处理及特征提取

本片主要解读 前端lidar点特征提取部分的代码

之前要做点的预处理,这部分的代码在之前分析过了
链接

Code

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //计算曲率
    for (int i = 5; i < cloudSize - 5; i++)
    { 
        //计算x维度的曲率
        float diffX = laserCloud->points[i - 5].x + laserCloud->points[i - 4].x + laserCloud->points[i - 3].x + laserCloud->points[i - 2].x + laserCloud->points[i - 1].x - 10 * laserCloud->points[i].x + laserCloud->points[i + 1].x + laserCloud->points[i + 2].x + laserCloud->points[i + 3].x + laserCloud->points[i + 4].x + laserCloud->points[i + 5].x;
        float diffY = laserCloud->points[i - 5].y + laserCloud->points[i - 4].y + laserCloud->points[i - 3].y + laserCloud->points[i - 2].y + laserCloud->points[i - 1].y - 10 * laserCloud->points[i].y + laserCloud->points[i + 1].y + laserCloud->points[i + 2].y + laserCloud->points[i + 3].y + laserCloud->points[i + 4].y + laserCloud->points[i + 5].y;
        float diffZ = laserCloud->points[i - 5].z + laserCloud->points[i - 4].z + laserCloud->points[i - 3].z + laserCloud->points[i - 2].z + laserCloud->points[i - 1].z - 10 * laserCloud->points[i].z + laserCloud->points[i + 1].z + laserCloud->points[i + 2].z + laserCloud->points[i + 3].z + laserCloud->points[i + 4].z + laserCloud->points[i + 5].z;

        //存储曲率,索引
        cloudCurvature[i] = diffX * diffX + diffY * diffY + diffZ * diffZ;
        //保存 原来点的索引   因为后面要排序 
        cloudSortInd[i] = i;
        //这俩个是标志位
        cloudNeighborPicked[i] = 0;//此标志位置1时代表这个点被选位特征点了
        cloudLabel[i] = 0;//特征点的标签
    }

计算每个点的曲率
并将每个点的曲率保存在一个向量中
将每个点的索引保存在一个向量中
初始化的点标签向量

这里的计算曲率就是对应的论文中的这个公式
在这里插入图片描述
在代码里就是分别求一个轴维度的曲率,以x轴为例,就是
前五个点x的值+后五个点x的值-10*当前点x的值
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //声明 特征点的点云
    pcl::PointCloud<PointType> cornerPointsSharp;   //角点特征 点云
    pcl::PointCloud<PointType> cornerPointsLessSharp;  //弱角点特征 点云
    pcl::PointCloud<PointType> surfPointsFlat;//面点特征 点云
    pcl::PointCloud<PointType> surfPointsLessFlat;//弱面点特征 点云

声明 特征点的点云
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
注意:下面代码是一个大的嵌套循环
按每个scan一个循环
每个scan等分6份,每份一个循环

    //声明 特征点的点云
    pcl::PointCloud<PointType> cornerPointsSharp;   //角点特征 点云
    pcl::PointCloud<PointType> cornerPointsLessSharp;  //弱角点特征 点云
    pcl::PointCloud<PointType> surfPointsFlat;//面点特征 点云
    pcl::PointCloud<PointType> surfPointsLessFlat;//弱面点特征 点云 经过体素滤波处理

声明 特征点的点云

    //遍历每个scan
    for (int i = 0; i < N_SCANS; i++)
    {        
    // 没有有效的点了,就continue
        if( scanEndInd[i] - scanStartInd[i] < 6)
            continue;

        //用来存储不太平整的点  不经过体素滤波  
        pcl::PointCloud<PointType>::Ptr surfPointsLessFlatScan(new pcl::PointCloud<PointType>);

遍历每个scan
当这个scan上面的 起始点和结束点的id差小于6 ,也就是 有效点过少,则不处理这个scan直接continue
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
注意这个循环是嵌套在上面的那个里面的

        // 将每个scan等分成6等分   分别计算里面的特征点
        for (int j = 0; j < 6; j++)
        {
            // 计算等分 的 每个起始点 和 结束点 的 id
            int sp = scanStartInd[i] + (scanEndInd[i] - scanStartInd[i]) * j / 6;  //起始点的id
            int ep = scanStartInd[i] + (scanEndInd[i] - scanStartInd[i]) * (j + 1) / 6 - 1;//结束点的id

            TicToc t_tmp;//算时间用的

            //对点云按照曲率进行排序,小的在前,大的在后
            std::sort (cloudSortInd + sp, cloudSortInd + ep + 1, comp);

            t_q_sort += t_tmp.toc();//计算时间

            int largestPickedNum = 0;//挑选最大的曲率特征点的个数  即角点的个数

这个地方涉及到数组的处理了,要好好看下

A-LOAM算法为了让特征点分部均匀,将每一条scan分成6份,在每一份中分部提取曲率最大的和最小的几个点作为角点和面点

在这里要把前面的数据给总结下了,要不基本是乱的

laserCloud 是依次放的由 scan1 到 scan16的点云
图
cloudCurvature 存放的上面的每个点对应的 曲率值
图
cloudSortInd 存放的点对应的id ,一开始 每个元素是由 5~n,后面会根据曲率大小重新排
图
scanStartInd[i] 是第i个scan的起始点的id scanEndInd[i] 是第i个scan的结束点的id
图

sp和ep分别是一条scan上面等分6份,中一份的 起始点id和结束点id
所以计算sp的时候是这样的:
sp = scanStartInd[i] + (scanEndInd[i] - scanStartInd[i]) * j / 6
计算ep的时候是这样的:
ep = scanStartInd[i] + (scanEndInd[i] - scanStartInd[i]) * (j + 1) / 6 - 1;

然后下面的代码 sort那部分,是利用std的排序方法,将cloudSortInd 对应的那部分 将点云的id进行了重新排序,曲率小的在前面,曲率大的在后面
图

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

         int largestPickedNum = 0;//挑选最大的曲率特征点的个数  即角点的个数
            //挑选曲率比较大的部分
            for (int k = ep; k >= sp; k--)
            {
                //排序后顺序就乱了,  用之前存的 索引值 取到 点的id 
                int ind = cloudSortInd[k]; 

重新排序后,cloudSortInd 上的ep位置就是存放的曲率最大点的id,取出来这个id

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                //1 看这个点是否是有效点,同时曲率是否大于阈值
                if (cloudNeighborPicked[ind] == 0 &&
                    cloudCurvature[ind] > 0.1)
                {
                    //2 满足要求,则选择的 角点个数 加1
                    largestPickedNum++;

                    // 每段选择2个曲率最大的点
                    if (largestPickedNum <= 2)
                    {
                        //3 label 为2 是曲率大的标记                        
                        cloudLabel[ind] = 2;
                        //4 cornerPointsSharp存放曲率大的点
                        cornerPointsSharp.push_back(laserCloud->points[ind]);
                        //cornerPointsLessSharp 存放曲率稍微大的点
                        cornerPointsLessSharp.push_back(laserCloud->points[ind]);
                    }
                    //5 以及20个曲率稍微大些的点
                    else if (largestPickedNum <= 20)
                    {  
                        // 6 label 为1 是曲率稍微大的标记                       
                        cloudLabel[ind] = 1; 
                        //7
                        cornerPointsLessSharp.push_back(laserCloud->points[ind]);
                    }
                    //超过20个的就不要了
                    else
                    {
                        break;
                    }

                    //8 这个点被选中后 pick标志位置 1
                    cloudNeighborPicked[ind] = 1; 
                    
                    //9 为了保证特征点不过度集中,将选中的点周围5个点都置1 避免后续会选到
                    for (int l = 1; l <= 5; l++)
                    {
                        //查看相邻点距离是否差异过大,如果差异过大,说明点云在此不连续,是特征边缘,就是新的特征,就不置位了
                        float diffX = laserCloud->points[ind + l].x - laserCloud->points[ind + l - 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l - 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l - 1].z;


                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                    for (int l = -1; l >= -5; l--)
                    {
                        float diffX = laserCloud->points[ind + l].x - laserCloud->points[ind + l + 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l + 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l + 1].z;
                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                }
            }

上面的代码对照注释依次做了如下事情,注释里都标了对应的序号:

  1. cloudNeighborPicked[ind] == 0 代表这个点可以挑选,然后曲率大于0.1 则满足要求
  2. 满足要求则选择的点个数+1
  3. 将最大的两个曲率点标签置为2
  4. 然后将这两个点存入cornerPointsSharp
  5. 然后再选较大的20个点,
  6. 标签置位1
  7. 然后存入cornerPointsLessSharp
  8. 被选为 角点和弱角点后,点的选取标志位置为1,则这个点就不能在后面被选取了,因为一开始有那个判断
  9. 为了保证特征点不过度集中,将选中的点周围5个点都置1 避免后续会选到

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

            int smallestPickedNum = 0;
            for (int k = sp; k <= ep; k++)
            {
                int ind = cloudSortInd[k];

                if (cloudNeighborPicked[ind] == 0 &&
                    cloudCurvature[ind] < 0.1)
                {

                    cloudLabel[ind] = -1; 
                    surfPointsFlat.push_back(laserCloud->points[ind]);

                    smallestPickedNum++;

                    //选择4个面点
                    if (smallestPickedNum >= 4)
                    { 
                        break;//够数了则跳出循环
                    }

                    cloudNeighborPicked[ind] = 1;
                    for (int l = 1; l <= 5; l++)
                    { 
                        float diffX = laserCloud->points[ind + l].x - laserCloud->points[ind + l - 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l - 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l - 1].z;
                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                    for (int l = -1; l >= -5; l--)
                    {
                        float diffX = laserCloud->points[ind + l].x - laserCloud->points[ind + l + 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l + 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l + 1].z;
                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                }
            }

选择面点的时候和角点基本一致,不再细写.
有区别的地方就是:
只选4个面点,没有像角点一样选几个弱面点,这部分在下面

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

            //除了角点和弱角点的都是弱面点
            for (int k = sp; k <= ep; k++)
            {
                if (cloudLabel[k] <= 0)
                {
                    surfPointsLessFlatScan->push_back(laserCloud->points[k]);
                }
            }

弱面点的处理就在接着的下面
通过标签判断,前面的标签已将打上了 角点为-2 ,弱角点为-1 ,面点为1 .剩下的均为弱面点

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        //将弱面点 进行体素滤波  因为剩下的都是弱面点了,点就很多了
        pcl::PointCloud<PointType> surfPointsLessFlatScanDS;
        pcl::VoxelGrid<PointType> downSizeFilter;
        downSizeFilter.setInputCloud(surfPointsLessFlatScan);
        downSizeFilter.setLeafSize(0.2, 0.2, 0.2);
        downSizeFilter.filter(surfPointsLessFlatScanDS);

        surfPointsLessFlat += surfPointsLessFlatScanDS;

将弱面点 进行体素滤波 因为剩下的都是弱面点了,点就很多了

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //发布所有的点云
    sensor_msgs::PointCloud2 laserCloudOutMsg;
    pcl::toROSMsg(*laserCloud, laserCloudOutMsg);
    laserCloudOutMsg.header.stamp = laserCloudMsg->header.stamp;
    laserCloudOutMsg.header.frame_id = "/camera_init";
    pubLaserCloud.publish(laserCloudOutMsg);

    //发布角点
    sensor_msgs::PointCloud2 cornerPointsSharpMsg;
    pcl::toROSMsg(cornerPointsSharp, cornerPointsSharpMsg);
    cornerPointsSharpMsg.header.stamp = laserCloudMsg->header.stamp;
    cornerPointsSharpMsg.header.frame_id = "/camera_init";
    pubCornerPointsSharp.publish(cornerPointsSharpMsg);

    //发布弱角点
    sensor_msgs::PointCloud2 cornerPointsLessSharpMsg;
    pcl::toROSMsg(cornerPointsLessSharp, cornerPointsLessSharpMsg);
    cornerPointsLessSharpMsg.header.stamp = laserCloudMsg->header.stamp;
    cornerPointsLessSharpMsg.header.frame_id = "/camera_init";
    pubCornerPointsLessSharp.publish(cornerPointsLessSharpMsg);


    //发布面点
    sensor_msgs::PointCloud2 surfPointsFlat2;
    pcl::toROSMsg(surfPointsFlat, surfPointsFlat2);
    surfPointsFlat2.header.stamp = laserCloudMsg->header.stamp;
    surfPointsFlat2.header.frame_id = "/camera_init";
    pubSurfPointsFlat.publish(surfPointsFlat2);

    //发布弱面点
    sensor_msgs::PointCloud2 surfPointsLessFlat2;
    pcl::toROSMsg(surfPointsLessFlat, surfPointsLessFlat2);
    surfPointsLessFlat2.header.stamp = laserCloudMsg->header.stamp;
    surfPointsLessFlat2.header.frame_id = "/camera_init";
    pubSurfPointsLessFlat.publish(surfPointsLessFlat2);

将上面整理的特征点转为ros的格式,然后发布topic出去
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

以上为A-LOAM 特征点提取部分的代码解读
之前要做点的预处理,这部分的代码在之前分析过了
链接

相关文章
|
26天前
|
缓存 前端开发 JavaScript
利用代码分割优化前端性能:策略与实践
在现代Web开发中,代码分割是提升页面加载性能的有效手段。本文介绍代码分割的概念、重要性及其实现策略,包括动态导入、路由分割等方法,并探讨在React、Vue、Angular等前端框架中的具体应用。
|
22天前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
85 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
16天前
|
缓存 监控 前端开发
探索前端性能优化:关键策略与代码实例
本文深入探讨前端性能优化的关键策略,结合实际代码示例,帮助开发者提升网页加载速度和用户体验,涵盖资源压缩、懒加载、缓存机制等技术。
|
29天前
|
Web App开发 缓存 监控
前端性能优化实战:从代码到部署的全面策略
前端性能优化实战:从代码到部署的全面策略
23 1
|
29天前
|
Web App开发 前端开发 JavaScript
前端性能优化实战:从代码到部署的全面指南
前端性能优化实战:从代码到部署的全面指南
30 1
|
1月前
|
前端开发 JavaScript
前端界的革命:掌握这些新技术,让你的代码简洁到让人惊叹!
前端技术的快速发展带来了许多令人惊叹的新特性。ES6及其后续版本引入了箭头函数、模板字符串等简洁语法,极大减少了代码冗余。React通过虚拟DOM和组件化思想,提高了代码的可维护性和效率。Webpack等构建工具通过模块化和代码分割,优化了应用性能和加载速度。这些新技术正引领前端开发的革命,使代码更加简洁、高效、可维护。
26 2
|
1月前
|
前端开发 JavaScript 测试技术
前端工程师的必修课:如何写出优雅、可维护的代码?
前端工程作为数字世界的门面,编写优雅、可维护的代码至关重要。本文从命名规范、模块化设计、注释与文档、遵循最佳实践四个方面,提供了提升代码质量的方法。通过清晰的命名、合理的模块划分、详细的注释和持续的学习,前端工程师可以写出高效且易于维护的代码,为项目的成功打下坚实基础。
33 2
|
1月前
|
监控 前端开发 JavaScript
前端开发的终极奥义:如何让你的代码既快又美,还不易出错?
【10月更文挑战第31天】前端开发是一个充满挑战与机遇的领域,本文从性能优化、代码美化和错误处理三个方面,探讨了如何提升代码的效率、可读性和健壮性。通过减少DOM操作、懒加载、使用Web Workers等方法提升性能;遵循命名规范、保持一致的缩进与空行、添加注释与文档,让代码更易读;通过输入验证、try-catch捕获异常、日志与监控,增强代码的健壮性。追求代码的“快、美、稳”,是每个前端开发者的目标。
38 3
|
1月前
|
前端开发 JavaScript 开发者
前端开发的终极技巧:如何让你的代码既简洁又高效,还能减少bug?
【10月更文挑战第30天】前端开发充满挑战与创新,如何编写简洁高效且少bug的代码是开发者关注的重点。本文介绍五大技巧:1. 模块化,提高代码复用性;2. 组件化,降低代码耦合度;3. 使用现代框架,提高开发效率;4. 统一代码规范,降低沟通成本;5. 利用工具,优化代码质量。掌握这些技巧,让前端开发更高效。
75 1
|
27天前
|
缓存 监控 前端开发
前端性能优化:从代码到部署的全面策略
前端性能优化:从代码到部署的全面策略