大家好,又见面了,我是你们的朋友全栈君。
paper:《Lidar Odometry and Mapping in Real-time》 LOAM的参考代码链接: A-LOAM A-LOAM-Notes LOAM-notes
使用传感器介绍:

如果没有电机旋转,则雷达自身的扫描是一个平面的180度。如下:

加上电机的旋转,则该设备能扫描的范围为雷达前方的半球形。


将定位与建图分开运行,高频位姿估计+低频优化建图->实现实时,低计算量,低漂移。
LOAM主要包括四个节点:点云提取(scan registration)、雷达里程计(lidar odometry)、雷达建图(lidar mapping) 和位姿集成(transform integration)

判断公式:

P k P_k Pk第k次整体sweep中得到的点云。 X ( k , i ) L X^L_{(k,i)} X(k,i)L表示第k个sweep中的i点在雷达坐标系下的坐标。 设 i ∈ P k i\in P_k i∈Pk,表示点 i i i是第k次整体sweep中的点。选取 i i i点在同一个scan中相邻的前后的5个点 X ( k , j ) L , j ∈ S , j ≠ i X^L_{(k,j)}, j \in S, j\ne i X(k,j)L,j∈S,j=i,计算 Σ j X ( k , i ) L − X ( k , j ) L \Sigma _jX^L_{(k,i)}-X^L_{(k,j)} ΣjX(k,i)L−X(k,j)L。注意,论文中使用的是平面雷达,每个scan扫描区域是半平面,即,空间的平面,雷达一次scan会得到空间平面与雷达扫描平面交线上的点,会得到一条线的点,但是,如果雷达扫过角点,则就是类似平面v字型的点。) 即: 如果是平面,前后的5个点与i的距离相加=0,c->0 如果是边线角点,则 c > 0 c>0 c>0 c的计算方程的分母,用于归一化c值,防止,因为雷达的距离而带来的c值得过大或小。 可以参考LOAM论文和程序代码的解读中更详细的解释。

使用kdtree存储点云信息。 将第k帧扫描到的特征点 P k P_k Pk映射到第k+1帧的雷达坐标系下,记为 P k ‾ \overline{P_k} Pk:,将 P k ‾ \overline{P_k} Pk与第k+1帧的扫描点云 P k + 1 P_{k+1} Pk+1进行点云匹配。在LOAM代码中,计算 P k ‾ \overline{P_k} Pk是使用TransformToEnd()将 P k {P_k} Pk映射到本k时刻sweep的结束,即k+1时刻sweep的开始来得到的。 而当前时刻k+1时刻的点云映射到k+1时刻的初始是使用TransformToStart()实现。 假设k+1时刻的sweep中与前一k时刻的sweep的运动一致 T k + 1 L T^L_{k+1} Tk+1L。使用位置插值方法得到整个sweep点云对应的 T k + 1 , i L T^L_{k+1,i} Tk+1,iL,从而利用TransformToStart(),TransformToEnd()可将点云映射到sweep初始和结束时的坐标系下。 位姿插值 论文中,作者采用的是二维雷达加了一个电机旋转,每一次scan得到的点云的xyz是基于雷达的自身的坐标系,就是已经旋转后的雷达坐标系。 设雷达匀速旋转及匀速偏移,通过插值的方法,得到一个完整的sweep中,每一个scan对应的T(q,t),并将点云坐标变换到每一次的sweep初始坐标系。消除点云在sweep中的因雷达运动而产生的误差,得到undistorted 的点云 P ~ k \widetilde P_k P k。 详细实现在laserOdometry.cpp TransformToStart函数中。

对于边线点 i ∈ P k + 1 i\in P_{k+1} i∈Pk+1,对应找到 P k ‾ \overline{P_k} Pk相邻scan中最近的中两个点(j, l),则两个点则确定了边线,计算变换矩阵T能使,点i到边线(j, l)的距离最小。j为最近点,然后再j所在scan的相邻scan中,找到l点。 距离约束: 点到线的距离计算公式如下:原理是目标点到两个原始点组成的两个向量构成的平行四边形面积/底边长度。

对于平面点 i ∈ P k + 1 i\in P_{k+1} i∈Pk+1,对应找到 P k ~ \widetilde{P_k} Pk 相邻scan中最近的中三个点(j, l,m),则三个点则确定了平面,计算变换矩阵T能使,点i到平面的距离(j, l,m)最小。

点到面距离计算公式如下:原理:目标点到三个原始点组成的三个向量构成的斜方体/底面面积。 分子:第二行两个向量的叉乘结果值等于j,l,m三个点构成的三角形面积,方向垂直于平面向上。 结果点乘分子第一行,则可以得到i,j,l,m构成的斜三棱柱的体积。 分母:等同于分子第二行。 如下图所示:

即分别对应了点云匹配中的点到线及点到面的算法。 向量a×向量b= | i j k | |a1 b1 c1| |a2 b2 c2| =(b1c2-b2c1,c1a2-a1c2,a1b2-a2b1)
距离公式解释参见LOAM 论文及原理分析
这里列出平面点的距离约束计算对应代码:
// tripod1,tripod2,tripod3对应公式的j,l,m三点
tripod1 = _lastSurfaceCloud->points[_pointSearchSurfInd1[i]];
tripod2 = _lastSurfaceCloud->points[_pointSearchSurfInd2[i]];
tripod3 = _lastSurfaceCloud->points[_pointSearchSurfInd3[i]];
//pa,pb,pc为公式中的分母项各分量(利用叉乘的公式得到),pd为分母项的值
float pa = (tripod2.y - tripod1.y) * (tripod3.z - tripod1.z)
- (tripod3.y - tripod1.y) * (tripod2.z - tripod1.z);
float pb = (tripod2.z - tripod1.z) * (tripod3.x - tripod1.x)
- (tripod3.z - tripod1.z) * (tripod2.x - tripod1.x);
float pc = (tripod2.x - tripod1.x) * (tripod3.y - tripod1.y)
- (tripod3.x - tripod1.x) * (tripod2.y - tripod1.y);
float pd = -(pa * tripod1.x + pb * tripod1.y + pc * tripod1.z);
float ps = sqrt(pa * pa + pb * pb + pc * pc);
pa /= ps;
pb /= ps;
pc /= ps;
pd /= ps;
// pointSel对应公式的{\widetilde{X}}_{(k+1,i)}^L
float pd2 = pa * pointSel.x + pb * pointSel.y + pc * pointSel.z + pd;pointSel对应公式的 X ~ ( k + 1 , i ) L {\widetilde{X}}_{(k+1,i)}^L X (k+1,i)L
注:向量外积在数值上等于这两个向量构成的平行四边形的面积。 其中: X ‾ k = R X k − 1 + T \overline X_k=RX_{k-1}+T Xk=RXk−1+T X ~ k = R i n t e X k − 1 + T i n t e \widetilde X_k=R_{inte}X_{k-1}+T_{inte} X k=RinteXk−1+Tinte R i n t e 和 T i n t e R_{inte}和T_{inte} Rinte和Tinte为将每个sweep中不同的scan的雷达坐标变换,用于将sweep下每个scan都映射到sweep的起始坐标系。 R i n t e 和 T i n t e R_{inte}和T_{inte} Rinte和Tinte由 R 和 T R和T R和T插值得到。
而后,利用LM方法计算得到 T k + 1 L T^L_{k+1} Tk+1L,得到该时刻的定位值(雷达里程计)

融合高频粗略的运动估计和优化后的位姿估计,得到准确位姿。根据位姿及得到的点云进行建图,创建点云地图。

精优化的点到面约束代码实现过程(A-LOAM):
这个点(角点/平面点)特征的区分不同于雷达里程计的输入的点云提取,是因为,在雷达里程计的计算中,舍去了参考意义小或重复的点云。 下图为雷达里程计的输出和laser mapping下优化位姿变换的输出的关系: 雷达里程计的输出是在雷达坐标系下,高频,有漂移; 地图最后输出位姿变化是全局的,高精度,低频。

LOAM中Mapping线程中的帧与submap的特征匹配,用到的submap就是上图中的黄色区域,submap中的corner特征和surf特征在匹配中作为target(筛除无效点后存放在kdtree[Corner/Surf]FromMap);而当前帧的单帧点云中的两种特征在匹配中作为source(代码中存放在laserCloudxxxStack2中)。 [centerCubeI,centerCubeJ,centerCubeK]为当前帧点云(source)的cube坐标。因为保证索引的非负性,所以计算时要加上laserCloudCenWidth[/Height/Depth]。
此外要保证【3 < centerCubeI < 18, 3 < centerCubeJ < 8, 3 < centerCubeK < 18】,因为要保证【submap为当前中心cube相邻水平左右2两个cube,竖直上下两个cube,深度前后1个cube中的点云(一个方向上5个cube)】, 如果[centerCubeI,centerCubeJ,centerCubeK]不满足以上条件,如centerCubeI<3时,则对应调节预留的cubes的分布位置,保证submap的正确提取。
以下图片来自https://www.cnblogs.com/wellp/p/8877990.html

MAP的ICP也是将点云分为边线点(edge/corner)和平面点(planner/sur),来一起添加约束方程 边线点判断。边线点和平面点的分类,是通过计算点与周围点簇的协方差矩阵的特征值来判断的。 当最大的特征值远大于其他特征值,说明该点位于边线上; 当最小的特征值远小于其他特征值,说明该点位于平面上。
点云的曲率计算与odom一样,点云数量比odom多; 通过分析点云簇 S ′ S’ S′的协方差矩阵,分析边线及平面的方向; A-LOAM 的laserMapping.cpp集成LOAM的transformMaintenance.cpp中的odom输出的高频及map输出的低频集成高频的定位。q_wmap_wodom ,t_wmap_wodom 来消除odom相对于map的偏差。 当odom定位输出后,如果q_wmap_wodom ,t_wmap_wodom有输出则更新,如何没有,则按先前的q_wmap_wodom ,t_wmap_wodom更新odom的输出,从而实现高频的map定位输出。
定位问题分为两步:1、位姿预测;2、位姿优化 1、位姿预测( x p x_p xp):使用imu预测姿态,或前端点云计算得到位姿; 2、位姿优化( x u x_u xu):使用非线性最小二乘优化方法得到的位姿。 退化问题解决思路: 退化问题主要出现在位姿优化,当出现退化现象时,舍弃退化方向的值,使用预测值(由其他传感器估计或者模型估计计算得到)来填充退化方向上的位姿解。
求解问题如下:

可使用如下方法求解: 将 A x = b Ax=b Ax=b两边左乘 A T A^T AT,得到 A T A x = A T b A^TAx=A^Tb ATAx=ATb,保证 A T A A^TA ATA满秩,即可逆,则可根据 x = ( A T A ) − 1 A T b x=(A^TA)^{-1}A^Tb x=(ATA)−1ATb解出 x x x。
作者定义退化因子 D = λ m i n + 1 \mathcal{D}=\lambda_{min}+1 D=λmin+1, 其中 λ m i n \lambda_{min} λmin为 A T A A^TA ATA的特征值,特征值很小时,则表明该特征值对应的方向存在退化现象。 退化现象的表现:

(a)表示求解约束较好的情况,(b)退化情况,在解在蓝色方向上存在退化,表明,蓝色箭头方向解的不确定性大。
令 v 1 , . . . , v m v_1,…,v_m v1,...,vm 对应的特征值小于阈值,因此被视为退化方向向量。 v m + 1 , . . . , v n v_{m+1},…,v_n vm+1,...,vn对应的特征值大于阈值,因此被视为非退化方向向量。

则:

其中:

由以下公式得到上式:

V p x p V_px_p Vpxp将预测值(由传感器或者模型预测得到)映射到退化方向上; V u x u V_ux_u Vuxu将计算值映射到非退化方向上,其实就是舍弃退化部分; 因此,退化部分使用预测值和计算优化部分的非退化部分。
解释: V f x f = V p x p + V u x u V_fx_f=V_px_p+V_ux_u Vfxf=Vpxp+Vuxu

设预估值为 x p x_p xp,更新值为 x u x_u xu,更新值 x u x_u xu在特征向量 v 1 v_1 v1方向退化,将预估值 x p x_p xp映射到 v 1 v_1 v1方向,将更新值 x u x_u xu映射到 v 2 v_2 v2方向。最后合成为 x f x_f xf。
V p x p V_px_p Vpxp为 x p x_p xp与 V p V_p Vp中的各个特征向量的投影*对应向量的模
而在LOAM中,使用imu作为姿态预测,使用雷达点云的点到线、点到面来优化姿态角,并计算得到偏移。或者三前端计算得到位姿,后端批量优化。 令 Δ x u \Delta x_u Δxu为后端优化相对于前端预测值,得到的位姿偏差。此时:(与论文中的伪代码不同,包括代码中的matP都应为matU), V p V_p Vp应为 V u V_u Vu,即提出 Δ x u \Delta x_u Δxu的非退化部分。



#loam_velodyne.launch
<node pkg="loam_velodyne" type="scanRegistration" name="scanRegistration" output="screen"/>
<node pkg="loam_velodyne" type="laserOdometry" name="laserOdometry" output="screen" respawn="true">
</node>
<node pkg="loam_velodyne" type="laserMapping" name="laserMapping" output="screen"/>
<node pkg="loam_velodyne" type="transformMaintenance" name="transformMaintenance" output="screen"/>
<group if="$(arg rviz)">
<node launch-prefix="nice" pkg="rviz" type="rviz" name="rviz" args="-d $(find loam_velodyne)/rviz_cfg/loam_velodyne.rviz" />
</group>scanRegistration:点云提取,特征点分类 laserOdometry:点到线 点到面匹配,得到初始定位信息,高频输出10hz laserMapping:将点云分块处理,寻找最近邻的匹配点,且用于计算点云簇的法向量,类似point-to-plain方法,实现点到线及点到面的约束,最后得到去漂的优化定位值。低频输出。 transformMaintenance:将odom的高频粗匹配与map的低频高精度定位值进行集成,输出高频高精度的定位信息。
aloam 集成代码中的实现在laserMapping.cpp,当得到odom粗匹配则利用map的定位输出进行校正一下,然后发布校正后的定位信息。map校正是通过计算map与odom之间的坐标系关系匹配的。
每个cpp文件对应LOAM框架重的一个节点。 推荐两篇讲述论文的博客及文档 LOAM笔记及A-LOAM源码阅读 loam_velodyne 参考: https://zhuanlan.zhihu.com/p/57351961 LOAM笔记及A-LOAM源码阅读 3D 激光SLAM ->loam_velodyne论文与代码解析Lidar Odometry and Mapping LOAM论文和程序代码的解读 https://blog.csdn.net/shoufei403/article/details/103664877
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/194943.html原文链接:https://javaforall.cn