LeGo-LOAM中的ImageProjection节点
LeGo-LOAM是在LOAM基础上改进的激光雷达定位与建图方法,它对地面点进行了优化、提出了两步LM优化算法,加入了闭环检测和全局因子图优化等。
本系列博客只说明了关键代码的功能实现,以及一些关键地方的数学推导,详细的解释都写在了代码注释中,注释后的代码在融合相机后会传到github。同时也列出来一些问题提醒自己有条件时实际去验证。
这个模块接收1种消息:
1 | // 接收原始点云的topic,注意使用成员函数作为回调函数的写法,pointCloudTopic来自"utility.h" |
这个模块做一些点云预处理的操作,主要是进行深度图转换、地面分割、物体分割的操作。
主要的接口函数就是原始点云的消息接收函数:
1 | void cloudHandler(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg){ |
1、findStartEndAngle()
这个函数找到一帧点云中的起始点和终止点的角度,存在自定义格式的消息segMsg_中。
1 | /** |
这个代码说明,输入的点云最好是有序的点云(按照每一列点云采集的顺序存储的),自定义的消息格式为:
1 | Header header |
2、projectPointCloud()
将点云投影成rangeMat表示,rangeMat的大小是N_SCAN * Horizon_SCAN,对于HDL-64E,rangeMat的大小就是64 * 2000。这个函数就是计算输入点云laserCloudIn中每一点在rangeMat中的行索引和列索引的。行索引计算如下:
1 | thisPoint.x = laserCloudIn_->points[i].x; |
列索引计算如下:
1 | // 计算当前遍历点的列索引columnIdn(以velodyne雷达x轴作为image的中间列索引位置,顺时针索引减小,逆时针索引增加) |
这里计算列索引可用如下的图形描述:
雷达x轴处的点云被投影到了rangeMat的中间,并随着雷达顺时针的线扫rangMat向左填充,雷达转过-x轴rangMat从最右侧向中间填充。这里将点云投影成rangeMat的原因完全是因为工程上实现BFS比较方便。
3、groundRemoval()
这个函数主要用来标记地面点,首先地面点一般出现在竖直视场角0°之下,所以遍历rangeMat的行时可以做一个减枝。对于rangeMat中在同一列,相邻行的两激光点,计算这两个点与水平面的夹角,夹角小于10°,这两个点都被标记为地面点,代码如下:
1 | diffX = fullCloud_->points[upperInd].x - fullCloud_->points[lowerInd].x; |
一个改进的地面分割方法:
在x-y平面上,将点云等分为一系列的扇形区域,设每个扇形区域的角度为$\Delta \alpha$,则一共可以划分$\frac{2 \pi}{\Delta \alpha}$个扇形区。通过下面的公式将激光点映射到对应的扇区:
$$
S_i = floor (\frac{atan2 + \pi}{\Delta \alpha})
$$
对于每一个扇区,将该区域的点按照到圆心的距离再次等间距划分为一个个子区域:
如此将一个点的三维坐标(x,y,z)转化到u-z平面的(p,z)坐标:
$$
p_i = (u_i,z_i)^T \quad u_i = \sqrt{x_i^2 + y_i^2}
$$
对子区域中的所有点,计算平均值后取得该子区域的平均点(二维坐标形式)。地面分割算法通过计算从圆心向外相邻两子区域的平均点所在直线与激光雷达水平面的夹角,按照夹角大小是否小于给定的阈值判断其是否属于地面点。设当前两相邻平均点的直线公式为$z = ku + b$,则具体的判定标准如下:
- 直线斜率必须小于给定阈值,即地面的上升角度不应该太大;
- 直线的斜率小于给定阈值的同时,直线的截距也应该小于给定阈值,即位于高台上的点应该不被选为地面点;
4、cloudSegmentation()
点云聚类进行物体分割,物体分割的目的是判定两激光点是否同属于一个物体
如上图所示,O点是激光雷达的原点,A,B是激光雷达系中任意相邻的俩激光点,分别对应激光束OA、OB,其夹角为$\alpha$,$\alpha$的大小只取决于激光雷达的水平或者垂直角分辨率。Lego-LOAM中通过判定$\beta$角是否大于60°来判定两点是否属于同一物体。可这样计算,不失一般性,假设OA与OB中较长的一个为OA,公式如下:
$$
d_1 = ||OA|| \quad d_2 = ||OB||
$$
$$
\beta = arctan \frac{||BH||}{||AH||} = arctan\frac{d_2 sin \alpha}{d_1 - d_2 cons \alpha}
$$
在代码实现上是使用标准的广度优先算法(BFS)完成的,经典的BFS算法,一般使用stl::queue辅助实现,作者嫌弃效率低,使用足够大的一维数组代替了stl::queue。物体分割完成之后要判断分割的有效性,有效分割满足这俩条件之一即可:
- 分割得到的cluster点集大小>=30;
- 当前cluster点数仍>=5,且包含的竖直方向上激光线数目>=3;