lego-loam学习笔记(三)

news2024/12/27 13:47:27

前言:

对于lego-loam中点云聚类源码的学习,它使用了广度优先算法,并且使用了数组双指针技巧。

主要分为两个部分:

第一个是labelComponents函数,它的功能是为每个点及其相邻的4个点运算角度,在对角度小于60度的点进行标记。

第二个是cloudSegmentation函数,它的功能是对于标记过的点进行聚类划分。

一、标记

labelComponents函数中实现。

        float d1, d2, alpha, angle;
        int fromIndX, fromIndY, thisIndX, thisIndY; 
        bool lineCountFlag[N_SCAN] = {false};

d1,d2是邻近点之间的最大距离和最小距离;

fromIndX是当前点的x坐标;

fromIndY是当前点的y坐标;

thisIndX是邻近点的x坐标;

thisIndY是邻近点的y坐标;

lineCountFlag是记录scans的是否使用的flag。

        queueIndX[0] = row;
        queueIndY[0] = col;
        int queueSize = 1;
        int queueStartInd = 0;
        int queueEndInd = 1;

将输入的row赋值给queueIndX[0];

将输入的col赋值给queueIndY[0];

将queueSize赋值为1;

将queueStartInd赋值为0;

将queueEndInd赋值为1。

queueStartInd和queueEndInd是双指针,用来遍历每一个queue中的点云。

        allPushedIndX[0] = row;
        allPushedIndY[0] = col;
        int allPushedIndSize = 1;

allPushedIndX表示遍历过的indexX;

allPushedIndY表示遍历过的indexY;

设置allPushedIndSize为1表示已经有一个点遍历过了。

        while(queueSize > 0){
...
}

这是进入循环,根据queueSize的大小作为循环的条件,小于等于0表示queue中没有点了。

            // Pop point
            fromIndX = queueIndX[queueStartInd];
            fromIndY = queueIndY[queueStartInd];
            --queueSize;
            ++queueStartInd;

将点云的在mat中的索引值放入fromIndX和fromInY,之后将queueSize值减小一个,并将queueStartInd指针向后移动一位。

            // Mark popped point
            labelMat.at<int>(fromIndX, fromIndY) = labelCount;

在labelMat矩阵中将该点赋值为labelCount,但这个labelCount前面没有赋值,难道是赋值为0?

下面就进入了前面说的根据上下左右的点计算距离的for循环

首先要定义一下上下左右的方向。

    std::vector<std::pair<int8_t, int8_t> > neighborIterator; // neighbor iterator for segmentaiton process

定义neighborIterator为一个vector里面是一个pair<int8_t, int8_t>。

        std::pair<int8_t, int8_t> neighbor;
        neighbor.first = -1; neighbor.second =  0; neighborIterator.push_back(neighbor);
        neighbor.first =  0; neighbor.second =  1; neighborIterator.push_back(neighbor);
        neighbor.first =  0; neighbor.second = -1; neighborIterator.push_back(neighbor);
        neighbor.first =  1; neighbor.second =  0; neighborIterator.push_back(neighbor);

将上下左右四个方向存入neighborIterator。

for (auto iter = neighborIterator.begin(); iter != neighborIterator.end(); ++iter){
...
}

结合上上面的neighborIterator存放的四个数据一起,可以看出这个是遍历点上下左右四个方向邻近点。

下面分析for里面的内容。

                // new index
                thisIndX = fromIndX + (*iter).first;
                thisIndY = fromIndY + (*iter).second;
                // index should be within the boundary
                if (thisIndX < 0 || thisIndX >= N_SCAN)
                    continue;
                // at range image margin (left or right side)
                if (thisIndY < 0)
                    thisIndY = Horizon_SCAN - 1;
                if (thisIndY >= Horizon_SCAN)
                    thisIndY = 0;
                // prevent infinite loop (caused by put already examined point back)
                if (labelMat.at<int>(thisIndX, thisIndY) != 0)
                    continue;

fromIndX和fromIndY就是当前点,然后通过迭代器的值,求出下右左上点的索引值并赋值为thisIndX和thisIndY。

之后就是几个范围划定条件,不能小于0和大于N_SCAN。当thisIndY小于0时,邻点分到最右边的点。当thisIndY大于Horizon_SCAN就分到最左边的点。因为这个矩阵表示点云的一帧,一帧是360度的。

最后一个if是判断该点是否标记为已经经过计算的点,如果是,就直接跳过。

                d1 = std::max(rangeMat.at<float>(fromIndX, fromIndY), 
                              rangeMat.at<float>(thisIndX, thisIndY));
                d2 = std::min(rangeMat.at<float>(fromIndX, fromIndY), 
                              rangeMat.at<float>(thisIndX, thisIndY));

d1是选中点和邻点与激光lidar距离最大的点;

d2是选中点和邻点与激光lidar距离最小的点。

                if ((*iter).first == 0)
                    alpha = segmentAlphaX;
                else
                    alpha = segmentAlphaY;

这里是用来区分是水平分辨率还是竖直分辨率。

 如上图所示,这是根据激光lidar的参数来划定的,用法在下面体现。

                angle = atan2(d2*sin(alpha), (d1 -d2*cos(alpha)));

 理由如上图所示。

                if (angle > segmentTheta){

                    queueIndX[queueEndInd] = thisIndX;
                    queueIndY[queueEndInd] = thisIndY;
                    ++queueSize;
                    ++queueEndInd;

                    labelMat.at<int>(thisIndX, thisIndY) = labelCount;
                    lineCountFlag[thisIndX] = true;

                    allPushedIndX[allPushedIndSize] = thisIndX;
                    allPushedIndY[allPushedIndSize] = thisIndY;
                    ++allPushedIndSize;
                }

segmentTheta设置为60度,当角度大于60度,作者就认为它是值的聚类。

并将它的邻点的索引值放入queueIndX和queueIndY中。然后queueSize自增1,尾指针向右移动一位。

然后将labelCount赋值给labelMat矩阵,并将记录每个scan是否被使用置为true。

最后把allPushedIndX和allPushedIndY数组填充如thisIndX和thisIndY,并将allPushedIndSize加1。

for循环的内容结束

        // check if this segment is valid
        bool feasibleSegment = false;
        if (allPushedIndSize >= 30)
            feasibleSegment = true;
        else if (allPushedIndSize >= segmentValidPointNum){
            int lineCount = 0;
            for (size_t i = 0; i < N_SCAN; ++i)
                if (lineCountFlag[i] == true)
                    ++lineCount;
            if (lineCount >= segmentValidLineNum)
                feasibleSegment = true;            
        }

定义一个标志位feasibleSegment,并设置为false。

如果allPushedIndSize大于30就设置为true;

如果allPushedIndSize大于5就进入判断,设置lineCount为0,根据前面lineCountFlag数组,判断它是否被使用过,如果使用过就加一。最后当lineCount大于等于3就将feasibleSegment设置为true。

        // segment is valid, mark these points
        if (feasibleSegment == true){
            ++labelCount;
        }else{ // segment is invalid, mark these points
            for (size_t i = 0; i < allPushedIndSize; ++i){
                labelMat.at<int>(allPushedIndX[i], allPushedIndY[i]) = 999999;
            }
        }

这里就是判断,如果feasibleSegment是true,就将labelCount自增,如果是false就将所有的点都设置为999999。

二、聚类

聚类的实现是在cloudSegmentation函数里面。

        // segmentation process
        for (size_t i = 0; i < N_SCAN; ++i)
            for (size_t j = 0; j < Horizon_SCAN; ++j)
                if (labelMat.at<int>(i,j) == 0)
                    labelComponents(i, j);

首先将所有的点进行标记。

        int sizeOfSegCloud = 0;
        // extract segmented cloud for lidar odometry
        for (size_t i = 0; i < N_SCAN; ++i) {

            segMsg.startRingIndex[i] = sizeOfSegCloud-1 + 5;

            for (size_t j = 0; j < Horizon_SCAN; ++j) {
                if (labelMat.at<int>(i,j) > 0 || groundMat.at<int8_t>(i,j) == 1){
                    // outliers that will not be used for optimization (always continue)
                    if (labelMat.at<int>(i,j) == 999999){
                        if (i > groundScanInd && j % 5 == 0){
                            outlierCloud->push_back(fullCloud->points[j + i*Horizon_SCAN]);
                            continue;
                        }else{
                            continue;
                        }
                    }
                    // majority of ground points are skipped
                    if (groundMat.at<int8_t>(i,j) == 1){
                        if (j%5!=0 && j>5 && j<Horizon_SCAN-5)
                            continue;
                    }
                    // mark ground points so they will not be considered as edge features later
                    segMsg.segmentedCloudGroundFlag[sizeOfSegCloud] = (groundMat.at<int8_t>(i,j) == 1);
                    // mark the points' column index for marking occlusion later
                    segMsg.segmentedCloudColInd[sizeOfSegCloud] = j;
                    // save range info
                    segMsg.segmentedCloudRange[sizeOfSegCloud]  = rangeMat.at<float>(i,j);
                    // save seg cloud
                    segmentedCloud->push_back(fullCloud->points[j + i*Horizon_SCAN]);
                    // size of seg cloud
                    ++sizeOfSegCloud;
                }
            }

            segMsg.endRingIndex[i] = sizeOfSegCloud-1 - 5;
        }

首先设置sizeOfSegCloud为0;

然后进入两个for循环遍历所有的点;

然后判断labelMat矩阵大于0和groundMat矩阵中为1的进入循环处理;

将labelMat中999999的点中i大于3且j每隔5个的点放入outline中;

将groundMat矩阵中为1的点中j大于5且j小于Horizon_SCAN-5且不被5整除的j都continue,留下的是可以被5整除的。

后面部分就是对于segMsg的设置赋值,并保存等操作。

        if (pubSegmentedCloudPure.getNumSubscribers() != 0){
            for (size_t i = 0; i < N_SCAN; ++i){
                for (size_t j = 0; j < Horizon_SCAN; ++j){
                    if (labelMat.at<int>(i,j) > 0 && labelMat.at<int>(i,j) != 999999){
                        segmentedCloudPure->push_back(fullCloud->points[j + i*Horizon_SCAN]);
                        segmentedCloudPure->points.back().intensity = labelMat.at<int>(i,j);
                    }
                }
            }
        }

将全部符合条件的点云放入segmentedCloudPure里面。

三、总结

对于广度优先算法的实现理解不够透彻,需要学习一下相关的实现。lego-loam中关于label部分的实现很巧妙,给我很大的启发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/183025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序开发

微信小程序开发 | 前言&#xff1a;本文章中的很大一部分内容的图片&#xff0c;文字信息来源于微信小程序官方文档和网络资源&#xff0c;感谢大家的支持&#xff0c;如文章中有不足和错误的地方&#xff0c;请及时联系作者-白泽。并协同修改&#xff0c;相信大家的帮助会使这…

屏蔽360阻止远程执行变更注册表自启动数据的办法

屏蔽360阻止远程执行变更注册表自启动数据的办法 运程服务器上的程序&#xff0c;由于需要。我在服务器中&#xff0c;加入更新升级自身&#xff08;exe&#xff09;文件&#xff0c;并变更操作系统自启动数据的代码。 实践证明&#xff0c;通过客户端&#xff0c;调用运程服务…

spring 声明式事务 @Transactional 运行原理

注意&#xff1a;如果想要理解spring 的声明式事务&#xff0c;必须先理解AOP 的原理。 一、spring注册 InfrastructureAdvisorAutoProxyCreator 通过 EnableTransactionManagement 可以看到先把TransactionManagementConfigurationSelector通过Import注册到spring。同时注意…

VULNCMS靶机

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;i3j0 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2022.03 信息收集 1.查看靶机ip地址 2.探测目标靶机开放端口和服务情况。 nmap -p- -sV -A 192.168.1.108 漏洞…

嵌入式串行接口标准

在嵌入式系统中&#xff0c;经常使用UART接口实现通讯、调试日志数据等功能&#xff0c;但UART是一种异步通信协议&#xff0c;并未定义物理层的电气接口标准。 在板件通信时&#xff0c;UART接口之间通常基于IO直接连接进行通信&#xff08;TTL/CMOS电平标准&#xff0c;3.3V电…

梦熊杯-十二月月赛-白银组题解-B.契约

B. Problem B.契约&#xff08;contract.cpp&#xff09; 内存限制&#xff1a;256 MiB 时间限制&#xff1a;1000 ms 标准输入输出 题目类型&#xff1a;传统 评测方式&#xff1a;文本比较 题目描述&#xff1a; 「璃月」是「契约」的国度。 摩拉克斯认为&#xff0c…

Lua 字符串

Lua 字符串 参考至菜鸟教程。 字符串或串(String)是由数字、字母、下划线组成的一串字符。 Lua 语言中字符串可以使用以下三种方式来表示&#xff1a; 单引号间的一串字符。双引号间的一串字符。[[ 与 ]] 间的一串字符。 以上三种方式的字符串实例如下&#xff1a; string1 …

基于语义分割Ground Truth(GT)转换yolov5目标检测标签(路面积水检测例子)

基于语义分割Ground Truth&#xff08;GT&#xff09;转换yolov5目标检测标签&#xff08;路面积水检测例子&#xff09; 概述 许多目标检测的数据是通过直接标注或者公开平台获得&#xff0c;如果存在语义分割Ground Truth的标签文件&#xff0c;怎么样实现yolov5的目标检测…

【图论】求欧拉回路

前言 你的qq密码是否在圆周率中出现&#xff1f; 一个有意思的编码问题&#xff1a;假设密码是固定位数&#xff0c;设有nnn位&#xff0c;每位是数字0-9&#xff0c;那么这样最短的“圆周率”的长度是多少&#xff1f;或者说求一个最短的数字串定包含所有密码。 理论 一些…

acwing1264_动态求连续区间和

目录 算法分类&#xff1a; 问题描述 算法适用题目范围&#xff1a; 实现代码&#xff1a; 算法分类&#xff1a; 树状数组/线段树 问题描述 给定 n个数组成的一个数列&#xff0c;规定有两种操作&#xff0c;一是修改某个元素&#xff0c;二是求子数列 [a,b]的连续和。 …

1602_MIT 6.828试验环境搭建

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 最近尝试看一下MIT的操作系统教程&#xff0c;找到了一个6.828的课程。看了一下网络上的介绍&#xff0c;看起来这个大家的认可度还是很高的。开动之前&#xff0c…

Android面经_111道安卓基础问题(四大组件BroadCast、内容提供者篇)

该文章涉及的内容主要是&#xff1a;BroadCast、内容提供者&#xff1b; Android基础问题——四大组件之BroadCast、ContentProvider 内容提供者1、BroadCast1.1、Android的广播分类1.2、Android的广播注册方式1.3、广播作用域2、内容提供者Content provider2.1、什么是内容提供…

Google Protobuf 实践使用开发

Android 敏捷开发助手 Lottie动画 轻松使用PNG、JPG等普通图片高保真转SVG图Android 完美的蒙层方案Android MMKV框架引入使用强大无匹的自定义下拉列表Google Protobuf 实践使用开发 Protobuf 实践使用前言Protobuf基本介绍Protobuf 使用配置protobuf 基本语法1. 基本使用2. …

JavaWeb-Ajax

JavaWeb-Ajax 3&#xff0c;Ajax 3.1 概述 AJAX (Asynchronous JavaScript And XML)&#xff1a;异步的 JavaScript 和 XML。 我们先来说概念中的 JavaScript 和 XML&#xff0c;JavaScript 表明该技术和前端相关&#xff1b;XML 是指以此进行数据交换。 3.1.1 作用 AJAX…

用Python绘制傅里叶级数和泰勒级数逼近已知函数的动态过程

文章目录Taylor级数Fourier级数本文代码&#xff1a; Fourier级数和Taylor级数对原函数的逼近动画Taylor级数 级数是对已知函数的一种逼近&#xff0c;比较容易理解的是Taylor级数&#xff0c;通过多项式来逼近有限区间内的函数&#xff0c;其一般形式为 f(x)∑n0Nanxnf(x)\su…

Lua 运算符 - 较为特殊部分

Lua 运算符 - 较为特殊部分 参考至菜鸟教程。 算术运算符 操作符描述实例^乘幂A^2 输出结果 100-负号-A 输出结果 -10//整除运算符(>lua5.3)5//2 输出结果 2在 lua 中&#xff0c;/ 用作除法运算&#xff0c;计算结果包含小数部分&#xff0c;// 用作整除运算&#xff0c;计…

Shiro学习文档

Shiro Java安全框架 1.什么是权限管理 ​ 基本上涉及到用户参与的系统都要进行权限管理&#xff0c;权限管理属于系统安全的范畴&#xff0c;权限管理实现对用户访问系统的控制&#xff0c;按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。 权限管理…

oracle mysql postgresql opengauss 批量kill session

oracle alter system kill session sid,serial# immed; &#xff08;根据v$session中查出sid和serial#进行替换&#xff09; 这里提供一个常用脚本&#xff0c;支持跨实例kill会话 &#xff08;替换&1条件或放到脚本调用都行&#xff09; select alter system kill sessi…

Dubbo 简介

Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力&#xff0c; 利用 Dubbo 提供的丰富服务治理特性&…

嵌入式工程师的2022 || 2023

因为一些个人关系&#xff0c;2022年初我从北京回到了石家庄。在找工作&#xff0c;包括后续的研发工作中&#xff0c;不同地点的经历在对比中我逐渐总结出了一些经验。关于“人”方面的感悟我就不赘述了&#xff0c;下面主要在这里总结一些找工作&#xff0c;做工作的经验&…