激光SLAM:ALOAM---后端lasermapping地图栅格化处理与提取

简介: 不同于前端的scan-to-scan的过程,ALOAM的后端是scan-to-map的算法,具体来说就是把当前帧和地图进行匹配,得到更准确的位姿同时也可以构建更好的地图.由于是scan-to-map的算法,因此计算量会明显高于scan-to-scan的前端,所以后端通常处于一个低频的运行频率,但是由于scan-to-map的精度往往优于scan-to-scan.因此后端也有比前端更高的精度.为了提高后端的处理速度,所以要进行地图的栅格化处理

前言

栅格点云地图处理原因
不同于前端的scan-to-scan的过程,ALOAM的后端是scan-to-map的算法,具体来说就是把当前帧和地图进行匹配,得到更准确的位姿同时也可以构建更好的地图.由于是scan-to-map的算法,因此计算量会明显高于scan-to-scan的前端,所以后端通常处于一个低频的运行频率,但是由于scan-to-map的精度往往优于scan-to-scan.因此后端也有比前端更高的精度.为了提高后端的处理速度,所以要进行地图的栅格化处理

栅格地图构成原理
地图通常是当前帧通过匹配得在地图坐标系下的准确位姿之后拼接而成.如果保留所有拼接的点云,此时随着时间的运行,内存很容易吃不消,为此考虑存储离当前帧比较近的部分地图,同时,为了便于地图更新和调整,在原始LOAM中,使用的是基于栅格的地图存储方式.具体来说,将整个地图分成212111个栅格,每个栅格是一个边长50m的正方体,当地图逐渐累加时,栅格之外的部分就被舍弃,这样可以保证内存空间不会随着程序的运行而爆炸.

水平方向上就是2150=1050m的地图 纵向1150=550m

栅格地图调整
如果当前位姿原理栅格覆盖的范围,则地图就没有意义了,因此栅格地图也需要随着当前位姿动态调整,从而保证我们可以从栅格地图中取出离当前位姿比较近的点云来进行scan-to-map的算法,获得最优位姿估计
当当前位姿即将到达地图栅格边界时,调整栅格地图,具体做法会在下面代码分析中展示

代码解析

具体代码在lasermapping.cpp中

transformAssociateToMap();//初值估计

初值估计, 和接收到里程计的数据的操作一致
将当前帧到里程计坐标系下的位姿,转到当前帧到map坐标系下,根据里程计坐标系和map坐标系的变换关系
里程计回调函数是为了以前端的频率向外发布位姿
这里主要提供一个估计的初值

            int centerCubeI = int((t_w_curr.x() + 25.0) / 50.0) + laserCloudCenWidth;
            int centerCubeJ = int((t_w_curr.y() + 25.0) / 50.0) + laserCloudCenHeight;
            int centerCubeK = int((t_w_curr.z() + 25.0) / 50.0) + laserCloudCenDepth;

t_w_curr就是当前帧的位置(估计初值),因为ALOAM针对机械式雷达,360度扫描,所以没有考虑旋转,如果要是livox的固态雷达,则必须考虑旋转了.
上面代码的功能就是根据初值估计值计算寻找当前位姿在地图中的索引,一个各自边长是50m
当前位置/50然后加上偏移量.
在这里插入图片描述
因为栅格是212111,所以初始是10,10,5,为了上初始时刻,在栅格地图的中心.
加25就是四舍五入的操作.

            if (t_w_curr.x() + 25.0 < 0)
                centerCubeI--;
            if (t_w_curr.y() + 25.0 < 0)
                centerCubeJ--;
            if (t_w_curr.z() + 25.0 < 0)
                centerCubeK--;

同样是取整的操作,C语言的取整是向0取整,所以要自减1

下面是动态调整栅格地图的部分,即处理快要出边界的情况,对栅格地图做动态调整

首先是当x轴,当前帧栅格索引小于三,说明块出边界了,让整体向x方向移动

            while (centerCubeI < 3)
            {
                for (int j = 0; j < laserCloudHeight; j++)
                {
                    for (int k = 0; k < laserCloudDepth; k++)
                    { 

判断当前帧位置索引小于3了,j和k都不动,所以整体for循环
在这里插入图片描述

                        int i = laserCloudWidth - 1;
                        //从x最大值开始
                        pcl::PointCloud<PointType>::Ptr laserCloudCubeCornerPointer =
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k]; 
                        pcl::PointCloud<PointType>::Ptr laserCloudCubeSurfPointer =
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];

i值先取最大,从x最大值开始处理.然后取出了最右边的一片点云
[图]

                        //整体右移
                        for (; i >= 1; i--)
                        {
                            //移动地图角点
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudCornerArray[i - 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                            //移动地图面点
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudSurfArray[i - 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        }

整体向右移
第一次循环

最后一次循环
在这里插入图片描述

                        laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeCornerPointer;
                        laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeSurfPointer;

此时i=0,也就是最左边的格子赋值了之前最右边的格子

                        laserCloudCubeCornerPointer->clear();
                        laserCloudCubeSurfPointer->clear();

该点云清零,由于是指针操作,相当于最左边的格子清空了

                //索引右移
                centerCubeI++;
                laserCloudCenWidth++;
            }

索引右移

while (centerCubeI >= laserCloudWidth - 3)

然后是 当前帧位置接近右边的边界,和上面的类似,相当于整体左移

while (centerCubeJ < 3)
while (centerCubeJ >= laserCloudHeight - 3)
while (centerCubeK < 3)
while (centerCubeK >= laserCloudDepth - 3)

然后就是J方向和K方向的处理,和I类似,不再赘述

上面随着当前帧位置调整完栅格地图后,下面则根据当前位置取出该位置附近的地图

            int laserCloudValidNum = 0;
            int laserCloudSurroundNum = 0;
            // 从氮气格子为中心,选出一定范围内的点云
            for (int i = centerCubeI - 2; i <= centerCubeI + 2; i++)
            {
                for (int j = centerCubeJ - 2; j <= centerCubeJ + 2; j++)
                {
                    for (int k = centerCubeK - 1; k <= centerCubeK + 1; k++)
                    {
                        if (i >= 0 && i < laserCloudWidth &&
                            j >= 0 && j < laserCloudHeight &&
                            k >= 0 && k < laserCloudDepth)
                        {     
                            //把索引记下来
                            laserCloudValidInd[laserCloudValidNum] = i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k;
                            laserCloudValidNum++;
                            laserCloudSurroundInd[laserCloudSurroundNum] = i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k;
                            laserCloudSurroundNum++;
                        }
                    }
                }
            }

以当前帧的位置索引为中心,I方向前后取2个格子,J方向前后去2个格子,K方向前后取一个格子.
然后通过循环把格子里面的点的索引记下来.

            //清空上一次从地图出取出的 角点地图和面点地图 
            laserCloudCornerFromMap->clear();
            laserCloudSurfFromMap->clear();
            //构建用来优化当前帧的局部地图,
            for (int i = 0; i < laserCloudValidNum; i++)
            {
                *laserCloudCornerFromMap += *laserCloudCornerArray[laserCloudValidInd[i]];
                *laserCloudSurfFromMap += *laserCloudSurfArray[laserCloudValidInd[i]];
            }
            int laserCloudCornerFromMapNum = laserCloudCornerFromMap->points.size();
            int laserCloudSurfFromMapNum = laserCloudSurfFromMap->points.size();

根据上面获得的需要格子的索引,把角点和面点分别取出来,构成用来优化当前帧的局部地图

            pcl::PointCloud<PointType>::Ptr laserCloudCornerStack(new pcl::PointCloud<PointType>());
            downSizeFilterCorner.setInputCloud(laserCloudCornerLast);
            downSizeFilterCorner.filter(*laserCloudCornerStack);
            int laserCloudCornerStackNum = laserCloudCornerStack->points.size();

            pcl::PointCloud<PointType>::Ptr laserCloudSurfStack(new pcl::PointCloud<PointType>());
            downSizeFilterSurf.setInputCloud(laserCloudSurfLast);
            downSizeFilterSurf.filter(*laserCloudSurfStack);
            int laserCloudSurfStackNum = laserCloudSurfStack->points.size();

之后就是对角点地图和面点地图进行体素滤波

总结

  • 根据前端的结果得到后端一个估计的初值位姿
  • 根据位置计算当前帧的栅格索引
  • 处理当I J K方向索引过于边界后的栅格位置调整
  • 根据前帧的栅格索引,取出周围一定数量的栅格中的点云,构成角度地图和面点地图
  • 进行角点地图和面点地图体素滤波处理
相关文章
|
存储 传感器 编解码
3D激光SLAM:LeGO-LOAM论文解读---完整篇
![在这里插入图片描述](https://img-blog.csdnimg.cn/348d0b4467a24296a22413207566c67e.png) 论文的标题是:**LeGO-LOAM: Lightweight and Ground-Optimized Lidar Odometry and Mapping on Variable Terrain** - 标题给出的应用场景是 **可变地形** - 重点是 **轻量级** 并 利用 **地面优化** - 本质依然是一个 **激光雷达里程计和建图**
3D激光SLAM:LeGO-LOAM论文解读---完整篇
|
存储 监控 安全
如何确保 React Native 热更新的安全性?
确保React Native热更新的安全性至关重要
|
算法 数据处理
点云地面点滤波(Progressive Morphological Filter)算法介绍(PCL库)
点云地面点滤波(Progressive Morphological Filter)算法介绍(PCL库)
2242 0
点云地面点滤波(Progressive Morphological Filter)算法介绍(PCL库)
|
9月前
|
机器学习/深度学习 算法 PyTorch
125_训练加速:FlashAttention集成 - 推导注意力优化的独特内存节省
2025年,大型语言模型的训练面临着前所未有的挑战。随着模型参数量和序列长度的不断增加,传统注意力机制的内存瓶颈问题日益突出。FlashAttention作为一种突破性的注意力算法,通过创新的内存访问模式和计算优化,显著提升了训练效率和内存利用。
910 3
|
Ubuntu 机器人 定位技术
Loam在Ubuntu 18.04上的一站式安装指南
现在,你已经完成了Loam在Ubuntu 18.04上的一站式安装盛宴。从更新系统清洁,到搭建魔法环境的工作空间,再到召唤和激活Loam精髓的艺术——每步都妙不可言,每步都至关重要,让你在这场技术的饕餮盛宴中大显身手。
290 8
|
人工智能 缓存 Ubuntu
【Ubuntu】Ubuntu安装PCL(安装PCL/卸载PCL/查看PCL版本/PCL报错处理相关操作)(史上最详细)
【Ubuntu】Ubuntu安装PCL(安装PCL/卸载PCL/查看PCL版本/PCL报错处理相关操作)(史上最详细)
|
机器学习/深度学习 传感器 算法
深度学习之基于视觉的机器人导航
基于深度学习的视觉机器人导航是一种通过深度学习算法结合视觉感知系统(如摄像头、LiDAR等)实现机器人在复杂环境中的自主导航的技术。
1052 5
|
Python
从bag包中提取图片和点云数据为pcd格式点云文件
从bag包中提取图片和点云数据为pcd格式点云文件
1648 0
|
人工智能 弹性计算 算法
华人开源最强「AI 程序员」炸场,让 GPT-4 自己修 Bug!
普林斯顿大学推出开源软件工程代理SWE-agent,利用GPT-4转化成能修复GitHub错误的AI程序员。在某些基准测试中,SWE-agent的表现与Devin相当,甚至在修复Bug速度上超越Devin,平均只需93秒。其特点是拥有开源接口,支持代码编辑和执行,提高了与代码库的交互效率。
|
SQL 前端开发 数据库
代码生成器使用指南 —JeecgBoot 低代码平台
JeecgBoot 提供强大的代码生成器,让前后端代码一键生成,实现低代码开发。支持单表、树列表、一对多、一对一等数据模型,增删改查功能一键生成,菜单配置直接使用。 同时提供强大模板机制,支持自定义模板,目前提供四套风格模板(单表两套、树模型一套、一对多三套)
1703 0