LIO-SAM代码逐行解读(3)-特征点提取

简介: LIO-SAM代码逐行解读(3)-特征点提取

准备工作

  • 引用头文件
// 引用自定义的函数
#include "utility.h"
// 自定义的消息类型
#include "lio_sam/cloud_info.h"
  • 平滑度结构体、比较平滑度大小
// 定义平滑度结构体  值 与 索引
struct smoothness_t{ 
    float value;
    size_t ind;
};
// 比较平滑度大小
struct by_value{ 
    bool operator()(smoothness_t const &left, smoothness_t const &right) { 
        return left.value < right.value;
    }
};
  • FeatureExtraction类中变量定义
// 定义特征提取类,继承一个参数服务器
class FeatureExtraction : public ParamServer
{
public:
    // 接听CloudInfo信息
    ros::Subscriber subLaserCloudInfo;
    // 发布LaserCloudInfo信息,角点与面片点
    ros::Publisher pubLaserCloudInfo;
    ros::Publisher pubCornerPoints;
    ros::Publisher pubSurfacePoints;
    // 提取的点云,角点与面片点  PointType  = pcl::PointXYZI
    pcl::PointCloud<PointType>::Ptr extractedCloud;
    pcl::PointCloud<PointType>::Ptr cornerCloud;
    pcl::PointCloud<PointType>::Ptr surfaceCloud;
    // 下采样
    pcl::VoxelGrid<PointType> downSizeFilter;
    // cloudInfo变量
    lio_sam::cloud_info cloudInfo;
    std_msgs::Header cloudHeader;
    // 平滑度变量
    std::vector<smoothness_t> cloudSmoothness;
    float *cloudCurvature;
    int *cloudNeighborPicked; // 近邻点是否已经被标记为特征点
    int *cloudLabel; // 点的类型
  • 构造函数
    FeatureExtraction()
    {
        // 接听cloud_info消息(去除畸变处理后)
        subLaserCloudInfo = nh.subscribe<lio_sam::cloud_info>("lio_sam/deskew/cloud_info", 1, &FeatureExtraction::laserCloudInfoHandler, this, ros::TransportHints().tcpNoDelay());
        // 发布特征提取后的cloud_info消息  角点与平面点
        pubLaserCloudInfo = nh.advertise<lio_sam::cloud_info> ("lio_sam/feature/cloud_info", 1);
        pubCornerPoints = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/feature/cloud_corner", 1);
        pubSurfacePoints = nh.advertise<sensor_msgs::PointCloud2>("lio_sam/feature/cloud_surface", 1);
        // 初始化值  分配内存
        initializationValue();
    }
  • 变量初始化,分配内存空间
    // 初始化变量
    void initializationValue()
    {
        cloudSmoothness.resize(N_SCAN*Horizon_SCAN);
        downSizeFilter.setLeafSize(odometrySurfLeafSize, odometrySurfLeafSize, odometrySurfLeafSize);
        extractedCloud.reset(new pcl::PointCloud<PointType>());
        cornerCloud.reset(new pcl::PointCloud<PointType>());
        surfaceCloud.reset(new pcl::PointCloud<PointType>());
        cloudCurvature = new float[N_SCAN*Horizon_SCAN];
        cloudNeighborPicked = new int[N_SCAN*Horizon_SCAN];
        cloudLabel = new int[N_SCAN*Horizon_SCAN];
    }

点云数据处理

  • 主要处理流程
    /**
     * @brief 主要的处理程序部分
     * 1、订阅imageProjection节点传入的cloud_info信息,得到header与点云数据(转换成PCL格式)
     * 2、
     * @param msgIn 
     */
    void laserCloudInfoHandler(const lio_sam::cloud_infoConstPtr& msgIn)
    {
        cloudInfo = *msgIn; // new cloud info
        cloudHeader = msgIn->header; // new cloud header
        // 把提取出来的有效的点转成pcl的格式
        pcl::fromROSMsg(msgIn->cloud_deskewed, *extractedCloud); // new cloud for extraction
        // 针对上一个节点提取出的有效点(extractedCloud),计算曲率,
        calculateSmoothness();
        // 标记被遮挡的点 与  与激光束平行的点
        markOccludedPoints();
        // 提取特征点
        extractFeatures();
        publishFeatureCloud();
    }
  • 计算每个激光点的曲率(平滑度)值
    /**
     * @brief 计算曲率
     * 计算每个点的曲率并存放在cloudCurvature与cloudSmoothness变量中;
     * 初始化近邻点是否被选中成为标记点、当前点类别标记等标志位变量
     */
    void calculateSmoothness()
    {
        int cloudSize = extractedCloud->points.size();
        for (int i = 5; i < cloudSize - 5; i++)
        {
            // 计算当前点和周围十个点的距离差  用的距离,而不是x,y,z
            float diffRange = cloudInfo.pointRange[i-5] + cloudInfo.pointRange[i-4]
                            + cloudInfo.pointRange[i-3] + cloudInfo.pointRange[i-2]
                            + cloudInfo.pointRange[i-1] - cloudInfo.pointRange[i] * 10
                            + cloudInfo.pointRange[i+1] + cloudInfo.pointRange[i+2]
                            + cloudInfo.pointRange[i+3] + cloudInfo.pointRange[i+4]
                            + cloudInfo.pointRange[i+5];            
            // 计算
            cloudCurvature[i] = diffRange*diffRange;//diffX * diffX + diffY * diffY + diffZ * diffZ;
            // 下面两个值赋成初始值
            cloudNeighborPicked[i] = 0;
            cloudLabel[i] = 0;
            // cloudSmoothness for sorting
            // 用来进行曲率排序  记录曲率的值,与当前索引
            cloudSmoothness[i].value = cloudCurvature[i];
            cloudSmoothness[i].ind = i;
        }
    }
  • 标记两种类型的无效点(被遮挡的点、与激光束几乎平行的点)

判断方式

1)被遮挡的点:两个列号接近的点(列号相差10),深度距离差值比较大(大于0.3m),则认为距离远的点被遮挡。

2) 与激光束平行的点:如果一个点与其前后两个点之间的距离差值较大(大于0.02*当前点到激光雷达中心距离)则认为其余激光束平行。

 /**
     * @brief 去除无效点
     * 标记一下遮挡的点
     * 标记与激光束平行的点 (后续就不使用这些点作为特征点了)
     */
    void markOccludedPoints()
    {
        int cloudSize = extractedCloud->points.size();
        // 标记被遮挡的点 与 与激光束平行的点
        // mark occluded points and parallel beam points
        for (int i = 5; i < cloudSize - 6; ++i)
        {
            // occluded points
            // 取出相邻两个点距离信息
            float depth1 = cloudInfo.pointRange[i];
            float depth2 = cloudInfo.pointRange[i+1];
            // 计算两个有效点之间的列id差  (距离图像中的列号)
            int columnDiff = std::abs(int(cloudInfo.pointColInd[i+1] - cloudInfo.pointColInd[i]));
            // 只有比较靠近才有意义 (被遮挡)
            if (columnDiff < 10){
                // 10 pixel diff in range image
                // 这样depth1容易被遮挡,因此其之前的5个点走设置为无效点
                if (depth1 - depth2 > 0.3){
                    cloudNeighborPicked[i - 5] = 1;
                    cloudNeighborPicked[i - 4] = 1;
                    cloudNeighborPicked[i - 3] = 1;
                    cloudNeighborPicked[i - 2] = 1;
                    cloudNeighborPicked[i - 1] = 1;
                    cloudNeighborPicked[i] = 1;
                }else if (depth2 - depth1 > 0.3){   // 这里同理
                    cloudNeighborPicked[i + 1] = 1;
                    cloudNeighborPicked[i + 2] = 1;
                    cloudNeighborPicked[i + 3] = 1;
                    cloudNeighborPicked[i + 4] = 1;
                    cloudNeighborPicked[i + 5] = 1;
                    cloudNeighborPicked[i + 6] = 1;
                }
            }
            // parallel beam (与激光束平行)
            float diff1 = std::abs(float(cloudInfo.pointRange[i-1] - cloudInfo.pointRange[i]));
            float diff2 = std::abs(float(cloudInfo.pointRange[i+1] - cloudInfo.pointRange[i]));
            // 如果两点距离比较大 就很可能是平行的点,也很可能失去观测
            if (diff1 > 0.02 * cloudInfo.pointRange[i] && diff2 > 0.02 * cloudInfo.pointRange[i])
                cloudNeighborPicked[i] = 1;
        }
    }
  • 提取角点与面片点
    /**
     * @brief 提取特征
     * 1、遍历所有的扫描线,每一扫描线划分为6份
     * 2、提取其中的角点与面片点,其中角点存放在cornerCloud变量中
     * 3、除了角点之外的剩余点都放在surfaceCloudScan变量中,进行下采样,放在面片点变量surfaceCloud中
     */
    void extractFeatures()
    {
        // 存储角点  面片点  两类特征点
        cornerCloud->clear();
        surfaceCloud->clear();
        // 平面点  平面点下采样(非特征点,除了前面标记的角点点之外的所有点都放在了这个集合中)
        pcl::PointCloud<PointType>::Ptr surfaceCloudScan(new pcl::PointCloud<PointType>());
        // 进行下采样,下采样后的点放在了surfaceCloud变量中
        pcl::PointCloud<PointType>::Ptr surfaceCloudScanDS(new pcl::PointCloud<PointType>());
        // 循环所有的扫描线
        for (int i = 0; i < N_SCAN; i++)
        {
            surfaceCloudScan->clear();
            // 把每一根scan等分成6份,每份分别提取特征点
            for (int j = 0; j < 6; j++)
            {
                // 根据之前得到的每个scan的起始和结束id来均分成6份
                int sp = (cloudInfo.startRingIndex[i] * (6 - j) + cloudInfo.endRingIndex[i] * j) / 6;
                int ep = (cloudInfo.startRingIndex[i] * (5 - j) + cloudInfo.endRingIndex[i] * (j + 1)) / 6 - 1;
                // 这种情况就不正常
                if (sp >= ep)
                    continue;
                // 针对每一份,按照曲率进行排序
                std::sort(cloudSmoothness.begin()+sp, cloudSmoothness.begin()+ep, by_value());
                // 开始收集角点
                int largestPickedNum = 0;
                for (int k = ep; k >= sp; k--)
                {
                    // 找到这个点对应的原先的索引(因为平滑度已经进行过排序,所以这里的k与ind的值不同)
                    int ind = cloudSmoothness[k].ind;
                    // 如果没有被认为是遮挡点或平行点 且曲率大于边缘点阈值(默认为1)
                    if (cloudNeighborPicked[ind] == 0 && cloudCurvature[ind] > edgeThreshold)
                    {
                        largestPickedNum++;
                        // 每段最多找20个角点
                        if (largestPickedNum <= 20){
                            // 标签置1表示是角点
                            cloudLabel[ind] = 1;
                            // 这个点收集进存储角点的点云中
                            cornerCloud->push_back(extractedCloud->points[ind]);
                        } else {
                            break;
                        }
                        // 将这个点周围的几个点设置成遮挡点,避免选取太集中(正方向5个点)
                        cloudNeighborPicked[ind] = 1;
                        for (int l = 1; l <= 5; l++)
                        {
                            int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l - 1]));
                            // 列idx距离太远就算了,空间上也不会太集中
                            if (columnDiff > 10)
                                break;
                            cloudNeighborPicked[ind + l] = 1;
                        }
                        // 同理,避免选取太集中(负方向5个点)
                        for (int l = -1; l >= -5; l--)
                        {
                            int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l + 1]));
                            if (columnDiff > 10)
                                break;
                            cloudNeighborPicked[ind + l] = 1;
                        }
                    }
                }
                // 开始收集面点
                for (int k = sp; k <= ep; k++)
                {
                    int ind = cloudSmoothness[k].ind;
                    // 同样要求不是遮挡点且曲率小于给定阈值
                    if (cloudNeighborPicked[ind] == 0 && cloudCurvature[ind] < surfThreshold)
                    {
                        // -1表示是面点
                        cloudLabel[ind] = -1;
                        // 同理 把周围的点都设置为遮挡点
                        cloudNeighborPicked[ind] = 1;
                        for (int l = 1; l <= 5; l++) {
                            int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l - 1]));
                            if (columnDiff > 10)
                                break;
                            cloudNeighborPicked[ind + l] = 1;
                        }
                        for (int l = -1; l >= -5; l--) {
                            int columnDiff = std::abs(int(cloudInfo.pointColInd[ind + l] - cloudInfo.pointColInd[ind + l + 1]));
                            if (columnDiff > 10)
                                break;
                            cloudNeighborPicked[ind + l] = 1;
                        }
                    }
                }
                // 不是角点的点  都被选做面片点
                for (int k = sp; k <= ep; k++)
                {
                    // 注意这里是小于等于0,也就是说不是角点的都认为是面点了
                    if (cloudLabel[k] <= 0){
                        surfaceCloudScan->push_back(extractedCloud->points[k]);
                    }
                }
            }
            surfaceCloudScanDS->clear();
            // 因为面点太多了,所以做一个下采样
            downSizeFilter.setInputCloud(surfaceCloudScan);
            downSizeFilter.filter(*surfaceCloudScanDS);
            // 下采样后的面片点存储到surfaceCloud变量中
            *surfaceCloud += *surfaceCloudScanDS;
        }
    }
  • 释放空间、发布提取的特征点
    // 将一些不会用到的存储空间释放掉(下一个节点不会用到)
    void freeCloudInfoMemory()
    {
        cloudInfo.startRingIndex.clear();
        cloudInfo.endRingIndex.clear();
        cloudInfo.pointColInd.clear();
        cloudInfo.pointRange.clear();
    }
    /**
     * @brief 发布相关消息
     * 释放空间
     * 发送角点 面片点 点云信息集合
     */
    void publishFeatureCloud()
    {
        // free cloud info memory  下一个节点不会用到的信息,释放空间
        freeCloudInfoMemory();
        // save newly extracted features
        // 把角点和面点发送给后端  发布提取的特征
        cloudInfo.cloud_corner  = publishCloud(&pubCornerPoints,  cornerCloud,  cloudHeader.stamp, lidarFrame);
        cloudInfo.cloud_surface = publishCloud(&pubSurfacePoints, surfaceCloud, cloudHeader.stamp, lidarFrame);
        // publish to mapOptimization  发布给后端优化?
        pubLaserCloudInfo.publish(cloudInfo);
    }

Main函数

初始化一个FeatureExtraction 类,并执行特征提取。

int main(int argc, char** argv)
{
    ros::init(argc, argv, "lio_sam");
    /**
     * @brief 接收经过去畸变预处理后的lio_sam/deskew/cloud_info信息
     * 提取 角点与平面点 两类特征点
     * 发布lio_sam/feature/cloud_corner与lio_sam/feature/cloud_surface两类消息
     * 发布"lio_sam/feature/cloud_info"消息,存储两类特征点
     */
    FeatureExtraction FE;
    ROS_INFO("\033[1;32m----> Feature Extraction Started.\033[0m");
    // 单线程处理
    ros::spin();
    return 0;
}
相关实践学习
使用ROS创建VPC和VSwitch
本场景主要介绍如何利用阿里云资源编排服务,定义资源编排模板,实现自动化创建阿里云专有网络和交换机。
ROS入门实践
本课程将基于基础设施即代码 IaC 的理念,介绍阿里云自动化编排服务ROS的概念、功能和使用方式,并通过实际应用场景介绍如何借助ROS实现云资源的自动化部署,使得云上资源部署和运维工作更为高效。
目录
相关文章
|
Shell
百度搜索:蓝易云【Ros终端出现找不到bash: /home/***/devel/setup.bash: 没有那个文件或目录怎么办?】
通过以上步骤,您应该能够解决 "找不到bash: /home/ *** /devel/setup.bash: 没有那个文件或目录" 错误,并正常使用ROS环境。如果问题仍然持续存在,建议您检查您的ROS安装和配置,并参考ROS官方文档或ROS社区寻求帮助。
703 0
|
3月前
|
Ubuntu 物联网 Linux
从零安装一个Linux操作系统几种方法,以Ubuntu18.04为例
一切就绪后,我们就可以安装操作系统了。当系统通过优盘引导起来之后,我们就可以看到跟虚拟机中一样的安装向导了。之后,大家按照虚拟机中的顺序安装即可。 好了,今天主要介绍了Ubuntu Server版操作系统的安装过程,关于如何使用该操作系统,及操作系统更深层的原理,还请关注本号及相关圈子。
|
存储 关系型数据库 MySQL
MySQL数据类型详解及实例应用
MySQL数据类型详解及实例应用
|
算法 数据可视化 定位技术
基于PCL库的通过ICP匹配多幅点云方法
基于PCL库的通过ICP匹配多幅点云方法
基于PCL库的通过ICP匹配多幅点云方法
|
机器学习/深度学习 安全 API
爱回收平台技术揭秘:构建高效、安全、用户友好的二手物品回收生态系统
爱回收利用微服务架构打造高效安全的二手电子回收平台。系统通过API Gateway处理前端请求,各微服务独立处理业务逻辑,如商品评估、订单创建和支付结算,采用机器学习算法预估价格。安全策略包括OAuth2.0授权、数据加密、访问控制和DDoS防护。性能优化涉及缓存、负载均衡及数据库优化,提供便捷、透明的服务,促进可持续发展。
632 1
|
缓存 监控 算法
京东购物车如何提升30%性能
【8月更文挑战第27天】以下是一些提升京东购物车性能的方法:1. 减少网络请求次数,采用异步请求;2. 使用本地和服务器端缓存;3. 优化购物车算法,如商品归堆和实时计算;4. 前端优化,如图片压缩和延迟加载;5. 后端架构优化,包括数据库和服务器资源优化;6. 建立性能监控系统并持续优化。这些措施可显著提升用户体验。
372 0
|
人工智能 监控 算法
未来技术趋势:人工智能与物联网的融合
【8月更文挑战第15天】本文深入探讨了人工智能(AI)与物联网(IoT)的结合如何引领技术革新,重塑行业格局。通过分析AI和IoT各自的发展趋势及其交汇点,我们揭示了这一融合对智能家居、工业自动化、健康医疗等领域带来的变革。文章还讨论了在追求这些先进技术时可能遇到的挑战和道德问题,为读者提供了一幅未来技术发展的蓝图。
|
消息中间件 安全 前端开发
字节面试:说说Java中的锁机制?
Java 中的锁(Locking)机制主要是为了解决多线程环境下,对共享资源并发访问时的同步和互斥控制,以确保共享资源的安全访问。 锁的作用主要体现在以下几个方面: 1. **互斥访问**:确保在任何时刻,只有一个线程能够访问特定的资源或执行特定的代码段。这防止了多个线程同时修改同一资源导致的数据不一致问题。 2. **内存可见性**:通过锁的获取和释放,可以确保在锁保护的代码块中对共享变量的修改对其他线程可见。这是因为 Java 内存模型(JMM)规定,对锁的释放会把修改过的共享变量从线程的工作内存刷新到主内存中,而获取锁时会从主内存中读取最新的共享变量值。 3. **保证原子性**:锁
175 1
|
安全 Linux 数据安全/隐私保护
Linux特殊权限解析:SUID、SGID和Sticky Bit
Linux特殊权限解析:SUID、SGID和Sticky Bit
683 0
LIO-SAM代码逐行解读(1)-准备工作
LIO-SAM代码逐行解读(1)-准备工作
635 0