A-LOAM :前端lidar点预处理部分代码解读

举报
月照银海似蛟龙 发表于 2022/07/29 09:26:26 2022/07/29
【摘要】 A-LOAM的cpp有四个,其中 kittiHelper.cpp 的作用是将kitti数据集转为rosbag 剩下的三个是作为 slam 的 部分,分别是: - laserMappin.cpp ++++ 当前帧到地图的优化 - laserOdometry.cpp ++++ 帧间里程计 - scanRegistration.cpp ++++ 前端li

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中点的预处理部分代码内容

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。