A-LOAM 与 LeGO-LOAM 特征提取前处理差异分析
A-LOAM 与 LeGO-LOAM 特征提取前处理差异分析A-LOAM 和 LeGO-LOAM 在前端特征提取时最终目标都是从一帧 LiDAR 点云中提取两类特征边缘点 edge/corner feature和平面点 surface/planar feature。二者的基本依据都是局部曲率曲率大的点通常表示周围几何变化剧烈更可能是边缘点曲率小的点通常表示周围比较平滑更可能是平面点。但是在真正进行曲率排序和特征点选择之前A-LOAM 和 LeGO-LOAM 对点云的预处理方式差异很明显。简单来说A-LOAM 更偏向于点级别的局部筛选它主要在 scan line 上根据前后邻近点的距离变化剔除遮挡点、距离突变点和不稳定点而LeGO-LOAM 更偏向于区域级别的结构筛选它会先将点云投影成 range image再进行地面检测和非地面连通域分割先保留稳定区域再从稳定点云中提取边缘点和平面点。1. 二者共同点最终都依赖曲率选特征无论是 A-LOAM 还是 LeGO-LOAM后续选边缘点和平面点时都需要计算点的局部曲率。常见做法是取当前点前后若干个邻近点比较当前点和邻域点之间的坐标变化。如果当前点相对于周围点变化很大说明它可能处在边缘、角点、物体轮廓或者遮挡边界附近如果变化很小则说明它可能位于平滑平面上。典型曲率计算逻辑可以简化理解为float diffX cloud[i - 5].x cloud[i - 4].x cloud[i - 3].x cloud[i - 2].x cloud[i - 1].x - 10 * cloud[i].x cloud[i 1].x cloud[i 2].x cloud[i 3].x cloud[i 4].x cloud[i 5].x; float diffY cloud[i - 5].y cloud[i - 4].y cloud[i - 3].y cloud[i - 2].y cloud[i - 1].y - 10 * cloud[i].y cloud[i 1].y cloud[i 2].y cloud[i 3].y cloud[i 4].y cloud[i 5].y; float diffZ cloud[i - 5].z cloud[i - 4].z cloud[i - 3].z cloud[i - 2].z cloud[i - 1].z - 10 * cloud[i].z cloud[i 1].z cloud[i 2].z cloud[i 3].z cloud[i 4].z cloud[i 5].z; cloudCurvature[i] diffX * diffX diffY * diffY diffZ * diffZ;这里的核心思想是如果当前点和前后邻域点差异很大cloudCurvature[i]就会比较大后续更可能被选为边缘点如果当前点和周围点变化平滑曲率就比较小后续更可能被选为平面点。但是问题在于曲率大不一定真的代表稳定边缘也可能是遮挡、噪声、离群点造成的假边缘。所以在曲率排序之前必须先做一定的坏点过滤。A-LOAM 和 LeGO-LOAM 的差异主要就体现在这里。2. A-LOAM点级别局部筛选A-LOAM 的处理方式比较直接。它会先按照 scan line 整理点云然后计算曲率再在选特征点之前标记一些不可靠点。典型的不可靠点包括遮挡边界点、距离突变点、与前后邻居差异过大的点。例如两个点虽然在点云数组中相邻但一个点打在近处柱子上另一个点打在远处墙面上那么它们在 scan line 上是邻近点但在真实三维空间中并不连续。如果这种点不提前过滤它们会因为距离突变导致曲率很大从而被误选为边缘点。A-LOAM 中常见的遮挡点标记逻辑可以概括为float depth1 sqrt(cloud[i].x * cloud[i].x cloud[i].y * cloud[i].y cloud[i].z * cloud[i].z); float depth2 sqrt(cloud[i 1].x * cloud[i 1].x cloud[i 1].y * cloud[i 1].y cloud[i 1].z * cloud[i 1].z); int columnDiff std::abs(int(cloud[i 1].intensity) - int(cloud[i].intensity)); if (columnDiff 10) { 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; } }这段逻辑的作用是如果两个相邻点的深度差突然变大就说明这里可能是遮挡边界。A-LOAM 会把当前点附近的一小段邻域标记为cloudNeighborPicked 1表示这些点后续不适合参与特征提取。A-LOAM 还会检查当前点与前后点的距离差如果当前点和两侧邻居都不连续也会将其标记为不可靠点float diff1 (cloud[i].x - cloud[i - 1].x) * (cloud[i].x - cloud[i - 1].x) (cloud[i].y - cloud[i - 1].y) * (cloud[i].y - cloud[i - 1].y) (cloud[i].z - cloud[i - 1].z) * (cloud[i].z - cloud[i - 1].z); float diff2 (cloud[i].x - cloud[i 1].x) * (cloud[i].x - cloud[i 1].x) (cloud[i].y - cloud[i 1].y) * (cloud[i].y - cloud[i 1].y) (cloud[i].z - cloud[i 1].z) * (cloud[i].z - cloud[i 1].z); float dis cloud[i].x * cloud[i].x cloud[i].y * cloud[i].y cloud[i].z * cloud[i].z; if (diff1 0.0002 * dis diff2 0.0002 * dis) { cloudNeighborPicked[i] 1; }这说明 A-LOAM 并不是完全直接从原始点云中选特征它也会提前过滤明显不稳定的点。但它的处理粒度主要是点级别和局部邻域级别。它关注的是“这个点附近是否有明显距离突变”“这个点是否处在遮挡边界附近”而不是判断一整片点云区域是不是稳定结构。3. A-LOAM 后续如何选边缘点和平面点在标记完不可靠点之后A-LOAM 会按 scan line 分段每段内部根据曲率排序。曲率大的点会被选为边缘点曲率小的点会被选为平面点。简化逻辑如下for (int i 0; i N_SCANS; i) { for (int j 0; j 6; j) { int sp scanStartInd[i] (scanEndInd[i] - scanStartInd[i]) * j / 6; int ep scanStartInd[i] (scanEndInd[i] - scanStartInd[i]) * (j 1) / 6 - 1; std::sort(cloudSmoothness.begin() sp, cloudSmoothness.begin() ep, by_value()); } }每根 scan line 分成 6 段的目的是让特征点分布更加均匀。否则如果直接对整帧点云排序边缘点和平面点可能集中在某一个局部区域导致后续匹配约束分布不均。边缘点选择逻辑可以概括为int largestPickedNum 0; for (int k ep; k sp; k--) { int ind cloudSmoothness[k].ind; if (cloudNeighborPicked[ind] 0 cloudCurvature[ind] edgeThreshold) { largestPickedNum; if (largestPickedNum 2) { cloudLabel[ind] 2; // sharp corner cornerPointsSharp.push_back(cloud[ind]); cornerPointsLessSharp.push_back(cloud[ind]); } else if (largestPickedNum 20) { cloudLabel[ind] 1; // less sharp corner cornerPointsLessSharp.push_back(cloud[ind]); } else { break; } cloudNeighborPicked[ind] 1; } }平面点选择逻辑可以概括为int smallestPickedNum 0; for (int k sp; k ep; k) { int ind cloudSmoothness[k].ind; if (cloudNeighborPicked[ind] 0 cloudCurvature[ind] surfThreshold) { cloudLabel[ind] -1; // flat surface surfPointsFlat.push_back(cloud[ind]); smallestPickedNum; if (smallestPickedNum 4) break; cloudNeighborPicked[ind] 1; } }因此A-LOAM 的整体逻辑可以总结为先在 scan line 上剔除遮挡点和局部不稳定点再把每根线束分成若干段在每段内按曲率选择边缘点和平面点。4. LeGO-LOAM区域级结构筛选LeGO-LOAM 的处理更加结构化。它不是直接在 scan line 上做局部坏点剔除而是先将点云投影成二维 range image。这个 range image 中行表示激光线束编号列表示水平角索引。range image: col 0 col 1 col 2 col 3 ... row 0 p p p p row 1 p p p p row 2 p p p p ... row N p p p p有了这个二维结构之后LeGO-LOAM 可以做两件 A-LOAM 中没有明确做的事情第一基于低线束之间的几何关系检测地面点第二对非地面点做连通域分割判断哪些点属于稳定连续结构。地面检测逻辑大致如下for (int j 0; j Horizon_SCAN; j) { for (int i 0; i groundScanInd; i) { lowerInd j i * Horizon_SCAN; upperInd j (i 1) * Horizon_SCAN; lowerPoint fullCloud-points[lowerInd]; upperPoint fullCloud-points[upperInd]; diffX upperPoint.x - lowerPoint.x; diffY upperPoint.y - lowerPoint.y; diffZ upperPoint.z - lowerPoint.z; angle atan2(diffZ, sqrt(diffX * diffX diffY * diffY)) * 180 / M_PI; if (abs(angle - sensorMountAngle) 10) { groundMat.atint8_t(i, j) 1; groundMat.atint8_t(i 1, j) 1; } } }这一步的核心是在同一列中比较上下相邻线束的点如果它们之间的连线角度接近雷达安装角就认为这两个点可能位于同一个地面平面上。地面点会被标记出来后续不参与普通非地面连通域分割但仍然可能作为平面特征参与优化。接着LeGO-LOAM 会对非地面点做连通域分割。它会从labelMat 0的未处理点开始检查上下左右邻居如果邻居点和当前点在几何上连续就把它们归为同一个 segment。连续性判断的核心逻辑可以概括为float d1 std::max(rangeMat.atfloat(fromIndX, fromIndY), rangeMat.atfloat(thisIndX, thisIndY)); float d2 std::min(rangeMat.atfloat(fromIndX, fromIndY), rangeMat.atfloat(thisIndX, thisIndY)); float alpha segmentAlphaX; // 左右邻居使用水平角分辨率 // float alpha segmentAlphaY; // 上下邻居使用垂直角分辨率 float angle atan2(d2 * sin(alpha), d1 - d2 * cos(alpha)); if (angle segmentTheta) { labelMat.atint(thisIndX, thisIndY) labelCount; queueIndX[queueSize] thisIndX; queueIndY[queueSize] thisIndY; queueSize; }这段代码的含义是如果两个相邻点的距离变化比较平滑计算出来的angle会比较大说明它们更可能属于同一个连续表面于是被放入同一个 segment如果距离突变明显说明它们可能位于遮挡边界或不同物体上就不把它们连在一起。LeGO-LOAM 在完成一个 segment 的生长后还会判断这个 segment 是否有效。典型判断包括点数是否足够多是否跨越足够多的扫描线。如果一个 segment 点数太少或者只出现在很局部的位置就可能被认为是小簇噪声或离群结构。bool feasibleSegment false; if (allPushedIndSize 30) { feasibleSegment true; } else if (allPushedIndSize segmentValidPointNum) { if (lineCount segmentValidLineNum) feasibleSegment true; } if (feasibleSegment true) { labelCount; } else { for (size_t i 0; i allPushedIndSize; i) { labelMat.atint(allPushedIndX[i], allPushedIndY[i]) 999999; } }这一步就是 LeGO-LOAM 和 A-LOAM 的一个关键区别A-LOAM 主要判断某个点附近是否不稳定LeGO-LOAM 则判断一整片点云区域是否是可靠结构。5. LeGO-LOAM 后续如何选边缘点和平面点经过地面检测和连通域分割之后LeGO-LOAM 会生成segmentedCloud。这个点云不是原始点云而是经过筛选后的点云里面主要包含有效非地面 segment 点和部分地面点不可靠小簇和离群点已经被过滤掉。后续 LeGO-LOAM 也会按照 scan line 分成若干小段例如每根线束分成 6 段然后在每一段中按曲率排序选择边缘点和平面点。for (int i 0; i N_SCAN; i) { for (int j 0; j 6; j) { int sp segmentedCloudScanStartInd[i] (segmentedCloudScanEndInd[i] - segmentedCloudScanStartInd[i]) * j / 6; int ep segmentedCloudScanStartInd[i] (segmentedCloudScanEndInd[i] - segmentedCloudScanStartInd[i]) * (j 1) / 6 - 1; std::sort(cloudSmoothness.begin() sp, cloudSmoothness.begin() ep, by_value()); } }这里需要注意LeGO-LOAM 的“6 段”不是把某个平面分成 6 块而是把每根 scan line 在水平方向上的点序列按索引范围分成 6 个子区间。然后在每个子区间中从segmentedCloud的稳定有效点里根据曲率大小选择特征。边缘点通常要求曲率大并且一般不是地面点if (cloudNeighborPicked[ind] 0 cloudCurvature[ind] edgeThreshold segInfo.segmentedCloudGroundFlag[ind] false) { cloudLabel[ind] 1; cornerPointsSharp.push_back(segmentedCloud-points[ind]); }平面点通常要求曲率小地面点也可以作为平面点候选if (cloudNeighborPicked[ind] 0 cloudCurvature[ind] surfThreshold) { cloudLabel[ind] -1; surfPointsFlat.push_back(segmentedCloud-points[ind]); }因此LeGO-LOAM 的特征提取可以理解为先通过地面检测和区域分割得到更干净的 segmentedCloud再在 segmentedCloud 中按 scan line 和 6 个子区间进行曲率排序最后选取边缘点和平面点。6. 二者核心区别A-LOAM 和 LeGO-LOAM 在特征提取前的处理可以总结为两种不同思路。A-LOAM 是“局部点级筛选”。它直接在 scan line 上检查相邻点之间的距离变化把遮挡边界、深度突变、不稳定邻域点标记掉。它不会显式判断一整片点云区域是不是稳定结构也不会专门进行地面检测。它的优点是实现简单、通用性强更接近原始 LOAM 的思路缺点是在低线束雷达和复杂地面场景下对小簇噪声、地面结构和非地面结构的区分不如 LeGO-LOAM 细。LeGO-LOAM 是“区域级结构筛选”。它先将点云投影成 range image然后利用低线束之间的几何关系检测地面点再对非地面点进行连通域分割保留稳定 segment过滤小簇、噪声和离群点。这样进入曲率排序的segmentedCloud已经比原始点云干净很多后续选出来的边缘点和平面点通常更稳定。尤其对于地面移动机器人和低线束 LiDARLeGO-LOAM 的地面约束和区域分割能有效提高特征提取质量。7. 总结A-LOAM 和 LeGO-LOAM 的特征提取最终都依赖曲率排序但两者在曲率排序之前的处理粒度不同。A-LOAM 更关注局部点的可靠性它通过深度差、遮挡关系和邻域距离变化提前标记不适合作为特征的点然后在 scan line 的分段中选择曲率大的边缘点和曲率小的平面点。它属于典型的点级别过滤方法结构简洁适合理解 LOAM 的基本特征提取思想。LeGO-LOAM 则更强调结构层面的稳定性。它不是直接从 scan line 中选特征而是先通过 range image 建立二维邻接关系再进行地面检测和非地面连通域分割。地面点被单独标记非地面点被分割成一个个 segment小簇和离群点被过滤最后再从segmentedCloud中按 scan line 和 6 个子区间提取边缘点和平面点。因此LeGO-LOAM 的特征候选点经过了更强的结构筛选尤其适合低线束激光雷达和地面车场景。一句话概括A-LOAM 是先剔除明显坏点再按曲率选特征LeGO-LOAM 是先找地面和稳定区域再在稳定点云中按曲率选特征。A-LOAM 的处理粒度是点级别LeGO-LOAM 的处理粒度是区域级别。版权声明 辛苦码字不易转载请注明原文出处和作者信息谢谢理解欢迎分享与交流但拒绝任何形式的商业转载或洗稿。