热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

LeGOLOAM学习

LOAMLOAM是一套非常有价值的LIDAR ODOMOTRY算法(它是一个历程计算法,没有回环检测和全局优化的部分)。LEGO LOAMLeGO LOAM它含有四个主要线程image project

LOAM

LOAM是一套非常有价值的LIDAR ODOMOTRY算法(它是一个历程计算法,没有回环检测和全局优化的部分)。

LEGO LOAM

LeGO LOAM

它含有四个主要线程

  • image projection: 相对于LOAM增加的模块,对每一帧的激光数据的预处理。包括了地面的提取(没有统一平面作为地面的假设),点云的实时分割。
  • feature optimization : 特征点的提取(和LOAM一样的方式),但是匹配的时候增加了预处理中分割标签的匹配。
  • map optimization :地图的优化,这里LEGO提出了另一种地图的存储方式,使用图优化模型来表示。
  • transform fusion : 这个和LOAM一致,将各个坐标系统一的线程。

1. Image Projection

主要步骤在cloudHandler这个callback函数里面。里面计算点云出于哪一条扫描线的第几个点的计算方法和LOAM中的原理一致(findStartEndAngle()函数)。 groundRemoval 和 cloudSegmentation 是最重要的两个函数,分别提取了地面点和实现了快速的点云分割。

void cloudHandler(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg){
// 1. Convert ros message to pcl point cloud
copyPointCloud(laserCloudMsg);
// 2. Start and end angle of a scan
findStartEndAngle();
// 3. Range image projection
projectPointCloud();
// 4. Mark ground points
groundRemoval();
// 5. Point cloud segmentation
cloudSegmentation();
// 6. Publish all clouds
publishCloud();
// 7. Reset parameters for next iteration
resetParameters();
}

1.1 projectPointCloud

这里将激光点云投影到了一个二维矩阵上(实际还存储在了一个向量数组中)。横坐标代表激光点云属于的扫描线(总共是16或者32或者64).纵坐标则表示它是这条线上的第几个数据。每一个矩阵元素的值是它距离激光设备的距离,在函数中变量名为range。

range = sqrt(thisPoint.x * thisPoint.x + thisPoint.y * thisPoint.y + thisPoint.z * thisPoint.z);

另外可以发现在后面的点云循环大多是对这样的二维矩阵循环。比如:

for (size_t j = 0; j for (size_t i = 0; i // whatever
}
}

1.2 groundRemoval

这一块做的工作可以很简单的从名字中理解,是提取地面点(不一定是标准的平面)。
这里考虑的是平面点应该是较为平滑的,所以相邻点的俯仰角度不会太大。另外同一条扫描线上的点可能属于同一个平面(比如墙),对他们筛选没有意义。所以考虑对相邻两条扫描线上的临近点筛选。

对每一个点,取当前的点和下一个扫描线的同样位置的点(在二维矩阵图像看来就是处在下一个row同样colum的点)。

lowerInd = j + ( i )*Horizon_SCAN;
upperInd = j + (i+1)*Horizon_SCAN;

下一步判断当前的点是不是有效点。

if (fullCloud->points[lowerInd].intensity == -1 ||
fullCloud->points[upperInd].intensity == -1){
// no info to check, invalid points
groundMat.at(i,j) = -1;
continue;
}

然后计算选取的两个点之间的角度(相对于激光扫描器XY平面的角度)。

diffX = fullCloud->points[upperInd].x - fullCloud->points[lowerInd].x;
diffY = fullCloud->points[upperInd].y - fullCloud->points[lowerInd].y;
diffZ = fullCloud->points[upperInd].z - fullCloud->points[lowerInd].z;
angle = atan2(diffZ, sqrt(diffX*diffX + diffY*diffY) ) * 180 / M_PI;

如果角度小于某一个阈值,则判断它是一个平面。

if (abs(angle - sensorMountAngle) <= 10){
groundMat.at(i,j) = 1;
groundMat.at(i+1,j) = 1;
}

这样得到的地面更类似于一个平面,但是由于它地面的提取筛选的角度是相对于扫描设备的xy平面,这其实是假设扫描设备一直是相对水平的。如果扫描设备没有保持相对水平,这一步就会失效,影响到每一个点的标签,会影响之后的特征点匹配,进而影响到之后的模块(这个我在实验中测试过,如果太大得偏转扫描器,整个系统了就失效了)。

1.3 cloudSegmentation

这里是对提取完成地面的点云进行分割。使用的方法来源于文章 “Fast image-based segmentation of sparse 3D laser scans for online ooperation”。其主要的原理是,假设同一个团簇的点云之间的连线和于扫描设备的连线的夹角应该小于一个阈值。详细可以见下图。
在这里插入图片描述
图中的A,B是两个团簇类,角度beta代表上面描述的参考值。同一类点的beta会比较大,不同类则会较小。另外需要注意的是:计算beta的两个点是相邻点(二维矩阵的colum或者row上的相邻点)。

在这里插入图片描述

在代码中上面的步骤是通过 labelComponents(i, j) 实现的。

d1 = std::max(rangeMat.at(fromIndX, fromIndY),
rangeMat.at(thisIndX, thisIndY));
d2 = std::min(rangeMat.at(fromIndX, fromIndY),
rangeMat.at(thisIndX, thisIndY));
if ((*iter).first == 0)
alpha = segmentAlphaX;
else
alpha = segmentAlphaY;
angle = atan2(d2*sin(alpha), (d1 -d2*cos(alpha)));
if (angle > segmentTheta){
// whatever
}

代码中的segmentTheta设置为10°,在论文中有讨论关于参数选取的部分,通过多组对比实验说明了10°是最优的取值。

2. Feature Association

下面是特征提取的线程。最主要的部分是 calculateSmoothness, 提取每一个点的平滑程度值,再根据这个值使用 extractFeatures 提取特征点。

/**
1. Feature Extraction
*/
adjustDistortion();
calculateSmoothness();
markOccludedPoints();
extractFeatures();
publishCloud(); // cloud for visualization

/**
2. Feature Association
*/
if (!systemInitedLM) {
checkSystemInitialization();
return;
}
updateInitialGuess();
updateTransformation();
integrateTransformation();
publishOdometry();
publishCloudsLast(); // cloud to mapOptimization

2.1 calculateSmoothness

在这里使用的方法基本和LOAM一致。提取每个点的平滑度都是通过一个1*11的卷积,kernel是[1,1,1,1,1,-10,1,1,1,1,1]。就是取左右各五个点然后计算这11个点的平滑趋势。

float diffRange = segInfo.segmentedCloudRange[i-5] + segInfo.segmentedCloudRange[i-4]
+ segInfo.segmentedCloudRange[i-3] + segInfo.segmentedCloudRange[i-2]
+ segInfo.segmentedCloudRange[i-1] - segInfo.segmentedCloudRange[i] * 10
+ segInfo.segmentedCloudRange[i+1] + segInfo.segmentedCloudRange[i+2]
+ segInfo.segmentedCloudRange[i+3] + segInfo.segmentedCloudRange[i+4]
+ segInfo.segmentedCloudRange[i+5];

除此以外还有很多工程方面的特征点匹配的代码,这里更合适的是阅读LOAM的代码之后,再联系寻找LEGO LOAM做了哪些修改。

2.2 extractFeatures

特征点提取的算法和LOAM基本一致,可以参加其他很棒的博主的分析LOAM的文章。

3. Map Optimization

最后是地图优化的线程。最重要的部分是 scan2MapOptimization 。

if (timeLaserOdometry - timeLastProcessing >= mappingProcessInterval) {
timeLastProcessing = timeLaserOdometry;
// transform to the same coordinate system
transformAssociateToMap();

// extract surrounding key frames based on the map format
extractSurroundingKeyFrames();

// down sample
downsampleCurrentScan();

// optimization
scan2MapOptimization();

saveKeyFramesAndFactor();
correctPoses();

// publishers
publishTF();
publishKeyPosesAndFrames();
clearCloud();
}

see compare for the comparsion of hdl_graph_slam, A_LOAM and LeGO LOAM


版权声明:本文为weixin_44492024原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_44492024/article/details/102221187
推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 如何使用Python从工程图图像中提取底部的方法?
    本文介绍了使用Python从工程图图像中提取底部的方法。首先将输入图片转换为灰度图像,并进行高斯模糊和阈值处理。然后通过填充潜在的轮廓以及使用轮廓逼近和矩形核进行过滤,去除非矩形轮廓。最后通过查找轮廓并使用轮廓近似、宽高比和轮廓区域进行过滤,隔离所需的底部轮廓,并使用Numpy切片提取底部模板部分。 ... [详细]
  • VSCode快速查看函数定义和代码追踪方法详解
    本文详细介绍了在VSCode中快速查看函数定义和代码追踪的方法,包括跳转到定义位置的三种方式和返回跳转前的位置的快捷键。同时,还介绍了代码追踪插件的使用以及对符号跳转的不足之处。文章指出,直接跳转到定义和实现的位置对于程序员来说非常重要,但需要语言本身的支持。以TypeScript为例,按下F12即可跳转到函数的定义处。 ... [详细]
  • 本文介绍了使用Rust语言编写、保存和编译程序的简单步骤。首先,打开记事本文件并编写程序代码,然后将代码保存到一个以.rs为扩展名的文件中。接下来,使用rustc命令来编译运行程序。最后,通过命令行运行编译后的程序,得到输出结果。如果遇到编译错误,可以下载Build Tools for Visual Studio 2017来解决。 ... [详细]
  • PatchODAX8: ... [详细]
author-avatar
好运娟_968
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有