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

int main(int argc, char **argv)
{
    //节点 名称:scanRegistration
    ros::init(argc, argv, "scanRegistration");
    ros::NodeHandle nh;//ros 句柄   

    //从配置参数中 读取 scan_line 参数, 多少线的激光雷达  在launch文件中配置的
    nh.param<int>("scan_line", N_SCANS, 16);

    //从配置参数中 读取 minimum_range 参数, 最小有效距离  在launch文件中配置的   踢出雷达上的载体出现在视野里的影响
    nh.param<double>("minimum_range", MINIMUM_RANGE, 0.1);

从main函数开始

首先是ros的基本操作,初始化节点和 声明句柄

然后从参数服务器中读取两个参数

  • scan_line 多少线的激光雷达 在launch文件中配置的
  • minimum_range 最小有效距离 在launch文件中配置的 踢出雷达上的载体出现在视野里的影响

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

    //打印线束
    printf("scan line number %d \n", N_SCANS);

    //做一个线束的判断 
    if(N_SCANS != 16 && N_SCANS != 32 && N_SCANS != 64)
    {
        printf("only support velodyne with 16, 32 or 64 scan line!");
        return 0;
    }

做一个线束的判断
目前仅支持 16线 32线 64线的 机械式lidar
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //订阅 velodyne 的 lidar消息 收到一个消息包 则进入 laserCloudHandler 回调函数一次
    ros::Subscriber subLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>("/velodyne_points", 100, laserCloudHandler);

    //定义要发布的消息
    pubLaserCloud = nh.advertise<sensor_msgs::PointCloud2>("/velodyne_cloud_2", 100);

    pubCornerPointsSharp = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_sharp", 100);

    pubCornerPointsLessSharp = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_less_sharp", 100);

    pubSurfPointsFlat = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_flat", 100);

    pubSurfPointsLessFlat = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_less_flat", 100);

    pubRemovePoints = nh.advertise<sensor_msgs::PointCloud2>("/laser_remove_points", 100);

本节点订阅和发布的消息
从这里可以看出来,这个节点是接收lidar的原始数据

  • "/velodyne_points"

然后发布处理后的数据

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

下面来看这个节点的主要工作,接收到lidar数据后的处理,和特征提取部分,在回调函数laserCloudHandler 中

void laserCloudHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg)
{
    // 判断系统是否进行初始化, 如果没有 则缓冲几帧 目前 systemDelay为0,自己用可以设置
    if (!systemInited)
    { 
        systemInitCount++;
        if (systemInitCount >= systemDelay)
        {
            systemInited = true;
        }
        else
            return;
    }

首先是进行一个初始化的判断,前几帧可以不要
源码的systemDelay为0
实际使用的时候可以设置个大小
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //将ros点云转为pcl点云格式
    pcl::PointCloud<pcl::PointXYZ> laserCloudIn;//声明pcl点云
    pcl::fromROSMsg(*laserCloudMsg, laserCloudIn);//将ros点云转为pcl点云格式

将ros点云转为pcl点云格式

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

    std::vector<int> indices;//这个变量保存了下面去除nan点的序号
    //去除点云中的nan点
    pcl::removeNaNFromPointCloud(laserCloudIn, laserCloudIn, indices);
    //去除点云中的距离小于阈值的点  
    removeClosedPointCloud(laserCloudIn, laserCloudIn, MINIMUM_RANGE);

去除点云中的nan点
去除点云中的距离小于阈值的点
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //计算起始点和结束点的水平角度,与x轴的夹角,由于激光雷达是顺时针旋转,这里取反就相当于转成了逆时针
    int cloudSize = laserCloudIn.points.size();

    float startOri = -atan2(laserCloudIn.points[0].y, laserCloudIn.points[0].x);
    //atan2的范围是 [-Pi,Pi] ,这里加上2Pi是为了保证起始到结束相差2PI,符合实际
    float endOri = -atan2(laserCloudIn.points[cloudSize - 1].y,
                          laserCloudIn.points[cloudSize - 1].x) +
                   2 * M_PI;

    // 总会有一些例外, 转换到合理的范围内
    if (endOri - startOri > 3 * M_PI)
    {
        endOri -= 2 * M_PI;
    }
    else if (endOri - startOri < M_PI)
    {
        endOri += 2 * M_PI;
    }

计算起始点和结束点的水平角度,与x轴的夹角
为了给后面计算相对起点的时间戳用的

这里要说下 机械式lidar的坐标系
在这里插入图片描述

所以在求与x轴的夹角的时候是 arctan(y/x)
结束点的水平角加上了2pi,主要的目的是 将角的范围转为 0~360度,因为结束点大部分为负值,比如起点,30度,结束点为-90,转完即为[30,270]

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

    //遍历每一个点
    for (int i = 0; i < cloudSize; i++)
    {
        point.x = laserCloudIn.points[i].x;
        point.y = laserCloudIn.points[i].y;
        point.z = laserCloudIn.points[i].z;

        //计算俯仰角  单位是角度  目的是用来判断是第几个线束
        float angle = atan(point.z / sqrt(point.x * point.x + point.y * point.y)) * 180 / M_PI;
        int scanID = 0;//线束的id
        //计算是第几个线束 scan
        if (N_SCANS == 16)
        {
            //垂直是30度  每个线之间的夹角是2度
            scanID = int((angle + 15) / 2 + 0.5);
            if (scanID > (N_SCANS - 1) || scanID < 0)
            {
                count--;
                continue;
            }
        }
        else if (N_SCANS == 32)
        {
            scanID = int((angle + 92.0/3.0) * 3.0 / 4.0);
            if (scanID > (N_SCANS - 1) || scanID < 0)
            {
                count--;
                continue;
            }
        }
        else if (N_SCANS == 64)
        {   
            if (angle >= -8.83)
                scanID = int((2 - angle) * 3.0 + 0.5);
            else
                scanID = N_SCANS / 2 + int((-8.83 - angle) * 2.0 + 0.5);

            // use [0 50]  > 50 remove outlies 
            if (angle > 2 || angle < -24.33 || scanID > 50 || scanID < 0)
            {
                count--;
                continue;
            }
        }
        else
        {
            printf("wrong scan number\n");
            ROS_BREAK();
        }
        //printf("angle %f scanID %d \n", angle, scanID);

遍历每一个点
通过计算的俯仰角度 来判断 这个点 在哪个线的scan上面

在这里插入图片描述
float angle = atan(point.z / sqrt(point.x point.x + point.y point.y)) * 180 / M_PI;
计算俯仰角的代码就是根据上面这个图

在这里插入图片描述
scanID = int((angle + 15) / 2 + 0.5);
根据俯仰角度求对应的线束就是 根据 上面这图 ,从最下面的线束算1,然后大约每2°,一根线,所以是 (angle + 15) / 2。最后加的0.5是为了进一位,因为是从1开始数的嘛

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

        //计算水平角
        float ori = -atan2(point.y, point.x);
        /* 把计算的水平角 放到 开始角度和结束角度 合理 的区间之内  */
        if (!halfPassed)
        { 
            if (ori < startOri - M_PI / 2)
            {
                ori += 2 * M_PI;
            }
            else if (ori > startOri + M_PI * 3 / 2)//这种情况不会发生
            {
                ori -= 2 * M_PI;
            }
            //如果超过了180,就说明过一半了
            if (ori - startOri > M_PI)
            {
                halfPassed = true;
            }
        }
        else
        {
            ori += 2 * M_PI;
            if (ori < endOri - M_PI * 3 / 2)
            {
                ori += 2 * M_PI;
            }
            else if (ori > endOri + M_PI / 2)
            {
                ori -= 2 * M_PI;
            }
        }

计算水平角
主要有 -pi 到 pi 的区间, 分成两个半圆算的,
主要就是保证把计算的水平角 放到 开始角度和结束角度 合理 的区间之内
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        //角度的计算是为了计算相对的起始时刻的时间  为点云畸变补偿使用
        float relTime = (ori - startOri) / (endOri - startOri);//计算当前点 在起始和结束之间的比率
        //整数部分是sacn的id ,小数部分是相对起始时刻的时间
        point.intensity = scanID + scanPeriod * relTime;
        //根据scan的ID存入各自数组
        laserCloudScans[scanID].push_back(point); 
    }

角度的计算是为了计算相对的起始时刻的时间 为点云畸变补偿使用
计算当前点 在起始和结束之间的比率
整数部分是sacn的id ,小数部分是相对起始时刻的时间
根据scan的ID存入各自数组

以上从回调函数开始 整个 很多行的代码 就在 完成一个功能,求点 相对的起始时刻的时间

现在有的lidar是把每个点的时间戳自带了
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //当前有效的点云的数目
    cloudSize = count;

    printf("points size %d \n", cloudSize);

    pcl::PointCloud<PointType>::Ptr laserCloud(new pcl::PointCloud<PointType>());//缓存叠加的每条scan的点云

    //处理每个scan  每条scan上面的 点的起始 id 为 前5个点不要  结束的id 为后6个点不要
    for (int i = 0; i < N_SCANS; i++)
    { 
        scanStartInd[i] = laserCloud->size() + 5;
        *laserCloud += laserCloudScans[i];
        scanEndInd[i] = laserCloud->size() - 6;
    }

处理每个scan 每条scan上面的 点的起始 id 为 前5个点不要 结束的id 为后6个点不要
点很多的,这10个点无所谓,主要是求曲率方便

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

以上为A-LOAM中点的预处理部分代码内容

相关实践学习
使用ROS创建VPC和VSwitch
本场景主要介绍如何利用阿里云资源编排服务,定义资源编排模板,实现自动化创建阿里云专有网络和交换机。
ROS入门实践
本课程将基于基础设施即代码 IaC 的理念,介绍阿里云自动化编排服务ROS的概念、功能和使用方式,并通过实际应用场景介绍如何借助ROS实现云资源的自动化部署,使得云上资源部署和运维工作更为高效。
相关文章
|
人工智能 自然语言处理 前端开发
DeepSite:基于DeepSeek的开源AI前端开发神器,一键生成游戏/网页代码
DeepSite是基于DeepSeek-V3模型的在线开发工具,无需配置环境即可通过自然语言描述快速生成游戏、网页和应用代码,并支持实时预览效果,显著降低开发门槛。
2229 93
DeepSite:基于DeepSeek的开源AI前端开发神器,一键生成游戏/网页代码
|
缓存 前端开发 JavaScript
利用代码分割优化前端性能:策略与实践
在现代Web开发中,代码分割是提升页面加载性能的有效手段。本文介绍代码分割的概念、重要性及其实现策略,包括动态导入、路由分割等方法,并探讨在React、Vue、Angular等前端框架中的具体应用。
|
自然语言处理 前端开发 IDE
用通义灵码全新智能体+MCP实现从设计稿到前端代码,个人免费用
通义灵码全新升级,发布国内首个支持“自主决策+工具链闭环”的编程智能体,面向个人免费!新增功能包括智能体模式、混合推理模型Qwen3支持、全面集成MCP中文社区(涵盖2400+服务)及长期记忆能力。用户可通过IDE插件使用,兼容主流开发环境如JetBrains、VS Code和Visual Studio。教程展示如何将MasterGo设计稿转化为前端代码,简化开发流程。探索链接:[通义灵码官网](https://lingma.aliyun.com/)。
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
1243 9
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
缓存 监控 前端开发
探索前端性能优化:关键策略与代码实例
本文深入探讨前端性能优化的关键策略,结合实际代码示例,帮助开发者提升网页加载速度和用户体验,涵盖资源压缩、懒加载、缓存机制等技术。
|
前端开发 JavaScript
前端界的革命:掌握这些新技术,让你的代码简洁到让人惊叹!
前端技术的快速发展带来了许多令人惊叹的新特性。ES6及其后续版本引入了箭头函数、模板字符串等简洁语法,极大减少了代码冗余。React通过虚拟DOM和组件化思想,提高了代码的可维护性和效率。Webpack等构建工具通过模块化和代码分割,优化了应用性能和加载速度。这些新技术正引领前端开发的革命,使代码更加简洁、高效、可维护。
326 2
|
Web App开发 缓存 监控
前端性能优化实战:从代码到部署的全面策略
前端性能优化实战:从代码到部署的全面策略
535 1
|
Web App开发 前端开发 JavaScript
前端性能优化实战:从代码到部署的全面指南
前端性能优化实战:从代码到部署的全面指南
465 1
|
缓存 监控 前端开发
前端性能优化:从代码到部署的全面策略
前端性能优化:从代码到部署的全面策略

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
    1429
  • 2
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
    708
  • 3
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
    564
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(七):学习ransform属性;本文学习 rotate旋转、scale缩放、skew扭曲、tanslate移动、matrix矩阵 多个参数
    571
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
    658
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
    839
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
    1638
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(三):元素继承关系、层叠样式规则、字体属性、文本属性;针对字体和文本作样式修改
    393
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
    1356
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
    669