LinK3D: Linear Keypoints Representation for 3D LiDAR Point Cloud
摘要
特征提取和匹配是许多机器人视觉任务的基本组成部分,如 2D 或 3D 目标检测、识别和配准。2D 特征提取和匹配已取得巨大成功。然而,在 3D 领域,当前方法由于描述性差和效率低,可能无法支持 3D LiDAR 传感器在机器人视觉任务中的广泛应用。为了解决这一限制,我们提出了一种新颖的 3D 特征表示方法:3D LiDAR 点云的线性关键点表示 (LinK3D)。LinK3D 的新颖之处在于它充分考虑了 LiDAR 点云的特性(如稀疏性和复杂性),并用其稳健的邻近关键点表示关键点,这在关键点描述中提供了强约束。我们在三个公共数据集上评估了 LinK3D,实验结果表明我们的方法取得了出色的匹配性能。更重要的是,LinK3D 还表现出优异的实时性能,比典型旋转 LiDAR 传感器的 10 Hz 帧率更快。在具有 Intel Core i7 处理器的计算机上,LinK3D 从 64 束 LiDAR 收集的点云中提取特征平均仅需 33 毫秒,而匹配两个 LiDAR 扫描仅需约 22 毫秒。此外,我们的方法可以扩展到 LiDAR 里程计任务,并显示出良好的可扩展性。我们在 GitHub 上发布了我们方法的实现。
索引词
3D LiDAR 点云、特征提取和匹配、实时性能、LiDAR 里程计
I. 引言
特征提取和匹配是大多数机器人视觉任务的基石,例如目标检测【1】和重建【2】任务。在2D视觉领域,已经提出并广泛使用了多种著名的2D特征提取方法(如SIFT【3】和ORB【4】)。然而,在3D视觉领域,仍然存在一些未解决的问题。当前的方法【5】–【11】可能不适用于高频(通常≥10Hz)的3D LiDAR和大规模复杂场景,特别是在效率和可靠性方面。LiDAR点云的不规则性、稀疏性和无序性使得直接应用2D方法于3D场景变得不可行。
现有的3D特征点表示方法主要分为两类:手工设计特征和基于学习的特征。手工设计的特征【5】-【7】、【12】主要以直方图的形式描述特征,并使用局部或全局统计信息来表示特征。由于LiDAR使用的大规模场景中通常存在许多相似的局部特征(例如局部平面),这些局部统计特征很容易导致匹配错误。全局特征【13】、【14】从直觉上看不太可能在点云内部生成准确的点对点匹配。基于学习的方法【8】–【11】、【15】、【16】取得了很大进展,但这些方法的效率和泛化性能仍有待提高。此外,一些方法【5】-【7】是为从小规模对象表面(例如,斯坦福兔子点云)中收集的点云提出的。显然,使用3D LiDAR的大规模场景(例如KITTI【17】的城市场景)与小规模对象之间存在一些差异。具体来说,主要差异如下:
小对象的表面通常是平滑且连续的,其局部表面是独特的。然而,3D LiDAR点云包含大量不连续和相似的局部表面(例如相似的局部平面、树木、杆子等),这些特征很容易导致匹配错误。
与小规模对象的点云相比,LiDAR点云通常更加稀疏,并且点在空间中分布不均匀。如果在固定大小的空间中没有足够的点,可能无法得到有效的统计描述。
与静态和完整的小规模对象不同,LiDAR扫描中通常存在动态对象(例如汽车、行人等)和遮挡。这很容易导致当前和后续LiDAR扫描中对相同局部表面的描述不一致。
根据这些差异,本文提出了一种用于LiDAR点云的新颖3D特征。我们的方法首先提取稳健的聚合关键点,然后将提取的聚合关键点输入到描述子生成算法中。如图2所示,算法生成了一种新颖的关键点表示形式的描述子。在获得LinK3D描述子后,匹配算法可以快速匹配两个LiDAR扫描的描述子。在实验中,提出的LinK3D实现了出色的匹配性能,并且还展示了令人印象深刻的效率。此外,LinK3D还可以潜在地应用于下游3D视觉任务,我们已经将LinK3D应用于LiDAR里程计任务。总结一下,我们的主要贡献如下:
强匹配性能。提出的LinK3D特征考虑了LiDAR点云的特性,并在稀疏LiDAR点云的匹配性能方面取得了显著进展。
实时性能。提出的LinK3D在CPU上的效率令人印象深刻,这使其更适合于移动机器人在计算资源有限情况下的3D应用。
良好的可扩展性。LinK3D可以潜在地应用于下游3D视觉任务。在本文中,LinK3D已应用于LiDAR里程计任务。
II. 相关工作
基于提取策略,当前的3D特征提取方法可以分为手工设计的方法和深度神经网络(DNN)方法。
手工设计方法:直方图通常用于表示局部表面的不同特性。PFH【12】生成支持区域内点对的多维直方图特征。FPFH【5】通过计算点与其邻居之间的关系,为每个点构建简化的点特征直方图(SPFH)。SHOT【6】结合了空间和几何统计信息,并对不同空间位置的表面法线直方图进行编码。为了提高匹配效率,提出了一种二进制量化方法B-SHOT【7】,将实值向量转换为二进制向量。3DHoPD【18】通过将3D关键点转换到一个新的3D空间中来生成直方图描述子。此外,全局描述子Seed【14】是一种基于分割的方法,用于LiDAR SLAM的地点识别任务。此外,GOSMatch【19】提取基于直方图的图形描述子,用于LiDAR SLAM的地点识别。由于LiDAR点云的稀疏性,当点数不足时,统计方法可能无法生成有效的特征表示。
基于DNN的方法:3DFeatNet【15】学习点云匹配的3D特征检测器和描述子,使用弱监督。FCGF【16】通过3D全卷积网络以单次传递提取3D特征,并呈现度量学习损失以提高性能。DeepVCP【8】基于一组候选点的学习匹配概率生成关键点。DH3D【9】设计了一个层次网络,执行局部特征检测、局部特征描述和单次前向传递的全局描述子提取。D3Feat【10】利用自监督检测器损失,由在线特征匹配结果指导训练。语义图表示方法【20】保留了原始点云的语义和拓扑信息,用于LiDAR SLAM的地点识别。Stick-yPillars【11】使用手工设计的方法提取关键点,并结合DNN方法生成描述子,该方法在关键点提取方面效率高,但在描述子生成方面效率低。Geo Transformer【21】编码成对距离和三重角度,使其在低重叠情况下具有鲁棒性并对刚体变换不变。总体而言,基于DNN的方法通常需要GPU加速处理。此外,这些方法的泛化能力尚待提高。
图1. 提出的LinK3D的核心思想和基于LinK3D的两次LiDAR扫描匹配结果。LinK3D表示当前关键点及其邻近关键点。绿色线条是有效匹配。通过匹配相应的LinK3D描述子可以获得精确的点对点匹配。
图2. 提出的LinK3D在关键点提取和描述方面的工作流程。首先执行关键点提取以生成聚合关键点。随后,执行描述子生成算法以获得有效的关键点描述子。
III. 方法论
我们的方法流程主要包括两个部分:特征提取和特征匹配。特征提取的过程如图1所示。首先提取LiDAR扫描的边缘点,然后将其输入到边缘关键点聚合算法中,在该算法中进一步提取稳健的聚合关键点,用于后续的描述子生成。在描述子生成算法中,构建了距离表和方向表,以快速生成描述子。
A. 关键点提取
-
边缘点提取:
在关键点提取中,我们将LiDAR点云大致分为两类:边缘点和平面点。边缘点和平面点的主要区别在于其所在局部表面的平滑度。给定一个3D LiDAR点云 P c P_c Pc,设 i i i是 P c P_c Pc中的一个点。 P s P_s Ps是一组与点 i i i位于同一扫描线上的连续点,并均匀分布在 i i i的两侧。 ∣ S ∣ |S| ∣S∣是 P s P_s Ps的基数。当前点 i i i的平滑项定义如下:∇ i = 1 ∣ S ∣ ∥ ∑ j ∈ P s , j ≠ i ( p j ⃗ − p i ⃗ ) ∥ 2 \nabla_i = \frac{1}{|S|} \left\| \sum_{j \in P_s, j \ne i} (\vec{p_j} - \vec{p_i}) \right\|^2 ∇i=∣S∣1 j∈Ps,j=i∑(pj−pi) 2
其中, p i ⃗ \vec{p_i} pi和 p j ⃗ \vec{p_j} pj分别是两个点 i i i和 j j j的坐标。提取的边缘点(如图3a所示)具有大于阈值 T h v T_hv Thv的 ∇ \nabla ∇。
详细解读:
-
点云分类:
- 将LiDAR点云大致分为两类:边缘点和平面点。
- 区分这两类点的主要依据是其所在局部表面的平滑度。
-
点的平滑度:
- 给定一个3D LiDAR点云 P c P_c Pc,设 i i i是 P c P_c Pc中的一个点。
- P s P_s Ps是一组与点 i i i位于同一扫描线上的连续点,并均匀分布在 i i i的两侧。 ∣ S ∣ |S| ∣S∣是 P s P_s Ps的基数(即点的数量)。
-
平滑项的计算:
- 当前点
i
i
i的平滑项定义为:
∇ i = 1 ∣ S ∣ ∥ ∑ j ∈ P s , j ≠ i ( p j ⃗ − p i ⃗ ) ∥ 2 \nabla_i = \frac{1}{|S|} \left\| \sum_{j \in P_s, j \ne i} (\vec{p_j} - \vec{p_i}) \right\|^2 ∇i=∣S∣1 j∈Ps,j=i∑(pj−pi) 2 - 其中, p i ⃗ \vec{p_i} pi和 p j ⃗ \vec{p_j} pj分别是两个点 i i i和 j j j的坐标。
- 当前点
i
i
i的平滑项定义为:
-
边缘点的识别:
- 根据计算出的平滑度 ∇ i \nabla_i ∇i,提取那些具有大于阈值 T h v T_{hv} Thv的点作为边缘点。
- 边缘点通常表示在局部表面上变化较大的区域,如物体的边缘。
解读公式:
- ∇ i \nabla_i ∇i 表示点 i i i 的平滑度。
- 1 ∣ S ∣ \frac{1}{|S|} ∣S∣1 是一个归一化系数,用于确保平滑度与点数量无关。
- ∑ j ∈ P s , j ≠ i \sum_{j \in P_s, j \ne i} ∑j∈Ps,j=i 是遍历点 i i i 附近的所有点 j j j 的累加求和。
- ( p j ⃗ − p i ⃗ ) (\vec{p_j} - \vec{p_i}) (pj−pi) 计算了点 i i i 和点 j j j 之间的向量差。
- ∥ ⋅ ∥ 2 \left\| \cdot \right\|^2 ∥⋅∥2 计算向量差的平方,反映了点之间的距离平方。
通过这种方式,算法能够区分出LiDAR点云中的边缘点和平面点,边缘点代表了表面特征的显著变化区域,这对于进一步的特征提取和匹配非常重要。
-
边缘关键点聚合:
在获取边缘点后,有很多点的 ∇ \nabla ∇超过阈值 T h v T_hv Thv,但它们并不稳定。具体来说,这些不稳定点出现在当前扫描中,但可能不会出现在下一次扫描中。如图3a中的红色虚线框所示,不稳定点通常是分散的。因此,有必要过滤掉这些点并找到有效的边缘关键点。如图3a中的蓝色虚线框所示,有效的边缘关键点通常垂直分布在簇中。在本文中,设计了一种关键点聚合算法以找到有效的边缘关键点。如图4所示,使用角度信息引导来加速聚合过程。动机是属于同一垂直边缘的点在LiDAR坐标系的XoY平面(假设Z轴向上)中通常具有大致相同的角度。点 p i ⃗ \vec{p_i} pi的角度由下式给出:
θ i = arctan ( p i ⃗ . y p i ⃗ . x ) \theta_i = \arctan(\frac{\vec{p_i}.y}{\vec{p_i}.x}) θi=arctan(pi.xpi.y)
因此,我们首先将以LiDAR坐标系原点为中心的XoY平面等分为 N s e c t N_{sect} Nsect个扇区区域,然后仅在每个扇区区域内聚类点,而不是在整个空间中聚类。
具体算法如算法1所示。值得注意的是,当我们在实验中将 N s e c t N_{sect} Nsect设置为120时,我们的算法运行速度比经典的K-Means算法快约25倍。提取的边缘关键点如图3b所示。可以看出,我们的算法能够过滤掉无效的边缘点并找到有效的边缘关键点。此外,计算每个簇点的质心并将其命名为聚合关键点,这将用于后续的描述子生成。
详细解读:
-
不稳定点的处理:
- 尽管有许多边缘点的平滑度 ∇ \nabla ∇超过了阈值 T h v T_hv Thv,但它们在扫描中并不总是稳定的,可能只出现在当前扫描中,而不会出现在下一次扫描中。
- 如图3a中的红色虚线框所示,这些不稳定点通常是分散的,因此需要将其过滤掉,以找到有效的边缘关键点。
- 如图3a中的蓝色虚线框所示,有效的边缘关键点通常垂直分布在簇中。
-
关键点聚合算法:
- 设计了一种关键点聚合算法来找到有效的边缘关键点,使用角度信息引导来加速聚合过程。
- 动机是属于同一垂直边缘的点在LiDAR坐标系的XoY平面中通常具有大致相同的角度。
- 点
p
i
⃗
\vec{p_i}
pi的角度计算公式为:
θ i = arctan ( p i ⃗ . y p i ⃗ . x ) \theta_i = \arctan(\frac{\vec{p_i}.y}{\vec{p_i}.x}) θi=arctan(pi.xpi.y)
-
扇区划分和聚类:
- 将以LiDAR坐标系原点为中心的XoY平面等分为 N s e c t N_{sect} Nsect个扇区区域,然后仅在每个扇区区域内聚类点,而不是在整个空间中聚类。
- 这种方法有效地提高了聚类速度和效率。
- 当在实验中将 N s e c t N_{sect} Nsect设置为120时,算法的运行速度比经典的K-Means算法快约25倍。
-
算法结果:
- 提取的边缘关键点如图3b所示。
- 算法能够过滤掉无效的边缘点并找到有效的边缘关键点。
- 计算每个簇点的质心,并将其命名为聚合关键点,这些关键点将用于后续的描述子生成。
图3. 散落的边缘点(由红色虚线框标记)和聚集的边缘关键点(由蓝色虚线框标记)在(a)中显示。我们需要的是聚集的边缘关键点。(b)显示了通过算法1提取的边缘关键点。
图4. 聚合过程的示意图。首先根据LiDAR坐标系的XoY平面的角度将点划分到相应的扇区区域。然后仅在每个扇区区域内执行聚类操作,而不是在整个空间内直接进行。紫色点是我们需要生成聚合关键点(红色点)的点,绿色点是将被过滤掉的散落点。
B. 描述子生成
-
描述子生成过程:
在描述子生成过程中,所有聚合关键点首先被投影到XoY平面(假设LiDAR的Z轴向上),这样可以消除沿Z轴方向分布不均的聚类边缘关键点所带来的影响。为了快速匹配,我们的LinK3D描述子表示为一个180维向量,使用0或当前关键点及其邻近关键点之间的距离来表示每个维度。如图5所示,我们将XoY平面划分为以当前关键点 k 0 k_0 k0为中心的180个扇区区域,每个描述子维度对应一个扇区区域。受2D描述子SIFT【3】的启发,该方法通过搜索主方向以确保旋转不变性,LinK3D的主方向也通过搜索表示为从当前关键点 k 0 k_0 k0到其最近关键点 k 1 k_1 k1的方向向量, k 1 k_1 k1位于第一个扇区区域中。其他扇区区域按逆时针方向排列。然后在每个扇区内搜索 k 0 k_0 k0的最近关键点。如果在一个扇区内存在最近关键点,则使用当前关键点和最近关键点之间的距离来表示描述子中相应维度的值,否则,该值设为0。在此过程中,当前点 k 0 k_0 k0到其他点 k j ( j ≠ 1 ) k_j (j \ne 1) kj(j=1)的方向表示为 m 0 j ⃗ \vec{m_{0j}} m0j,我们使用 m 0 j ⃗ \vec{m_{0j}} m0j和主方向 m 01 ⃗ \vec{m_{01}} m01之间的夹角来确定 k j k_j kj所属的扇区。角度计算如下:
θ j = { arccos ( m 01 ⃗ ⋅ m 0 j ⃗ ∣ m 01 ⃗ ∣ ∣ m 0 j ⃗ ∣ ) if D j > 0 2 π − arccos ( m 01 ⃗ ⋅ m 0 j ⃗ ∣ m 01 ⃗ ∣ ∣ m 0 j ⃗ ∣ ) if D j < 0 \theta_j = \begin{cases} \arccos \left( \frac{\vec{m_{01}} \cdot \vec{m_{0j}}}{|\vec{m_{01}}||\vec{m_{0j}}|} \right) & \text{if } D_j > 0 \\ 2\pi - \arccos \left( \frac{\vec{m_{01}} \cdot \vec{m_{0j}}}{|\vec{m_{01}}||\vec{m_{0j}}|} \right) & \text{if } D_j < 0 \end{cases} θj=⎩ ⎨ ⎧arccos(∣m01∣∣m0j∣m01⋅m0j)2π−arccos(∣m01∣∣m0j∣m01⋅m0j)if Dj>0if Dj<0
其中 D j D_j Dj定义如下:
D j = ∣ x 1 y 1 x j y j ∣ D_j = \begin{vmatrix} x_1 & y_1 \\ x_j & y_j \end{vmatrix} Dj= x1xjy1yj
详细解读:
-
描述子生成:
- 通过将所有聚合关键点投影到XoY平面,可以减小点在Z轴方向上分布不均的影响。
- LinK3D描述子是一个180维的向量,分别表示不同扇区内的距离信息。
-
扇区划分:
- 将XoY平面等分为180个扇区,每个描述子维度对应一个扇区。
- 主方向通过搜索当前关键点到最近关键点的方向向量来确定。
-
距离表示:
- 在每个扇区内搜索最近的关键点,如果存在则记录距离值,否则该维度值设为0。
-
方向计算:
- 当前点到其他点的方向表示为 m 0 j ⃗ \vec{m_{0j}} m0j,通过计算夹角 θ j \theta_j θj来确定点所属的扇区。
- θ j \theta_j θj的计算分为两种情况,根据方向矩阵 D j D_j Dj的符号决定是直接取夹角还是取 2 π 2\pi 2π减去夹角。
-
公式解读:
- θ j \theta_j θj 计算公式中,使用点乘计算两个方向向量的夹角,再通过 D j D_j Dj的符号调整夹角的范围。
- D j D_j Dj 表示方向矩阵,决定了点与主方向的相对位置。
这种方式使得描述子既能反映点之间的几何关系,又具有一定的旋转不变性,有利于后续的特征匹配。
-
描述子生成过程的问题:
上述算法存在两个主要问题。一个问题是算法对最近关键点非常敏感。如果存在来自外点关键点的干扰,匹配将会失败。另一个问题是我们需要频繁计算两点之间的相对距离和方向,因此会有大量重复计算。为了解决第一个问题,我们搜索一定数量的最近关键点。假设我们搜索三个最近关键点,并计算相应的三个描述子,如图6所示。Des1对应最近的关键点,Des3对应第三近的关键点。我们根据它们与当前关键点之间的距离定义优先级。Des1由于其最近距离具有最高优先级,而Des3由于其最远距离具有最低优先级。最终描述子的每个维度的值对应于具有最高优先级的非零值。图6中的红色虚线框标记的Des1具有非零值 D 0 1 D_0^1 D01,由于其高优先级,最终描述子中的相应值也设置为 D 0 1 D_0^1 D01。另外两种情况分别用紫色和黑色虚线框表示。为了解决第二个问题,我们为所有关键点构建距离表和方向表,通过查找表格来避免重复计算。具体的描述子生成过程如算法2所示,该算法展示了提取一个描述子的过程。
详细解读:
-
问题1:对最近关键点的敏感性:
- 算法对最近关键点非常敏感,如果存在来自外部的干扰关键点,匹配将会失败。
- 例如,如果最近的关键点是一个异常点或噪声点,它会对描述子的生成产生负面影响。
-
问题2:重复计算:
- 需要频繁计算两点之间的相对距离和方向,导致大量重复计算。
- 这种重复计算会增加计算时间和资源消耗,降低算法的效率。
-
解决方案:多关键点搜索:
- 为了解决第一个问题,我们搜索一定数量的最近关键点,例如搜索三个最近关键点。
- 每个最近关键点生成一个描述子,并根据距离定义优先级。
- Des1是最近的关键点,具有最高优先级;Des3是第三近的关键点,具有最低优先级。
-
优先级和最终描述子的值:
- 最终描述子的每个维度的值对应于具有最高优先级的非零值。
- 例如,图6中的红色虚线框标记的Des1具有非零值 D 0 1 D_0^1 D01,由于其高优先级,最终描述子中的相应值也设置为 D 0 1 D_0^1 D01。
- 其他两种情况分别用紫色和黑色虚线框表示。
通过上述改进,算法能够减少对单一最近关键点的依赖,提高鲁棒性。同时,通过构建距离表和方向表,避免了重复计算,提高了计算效率。
C. 匹配两个描述子
为了衡量两个描述子的相似性,我们只计算两个描述子的非零维度。具体来说,计算两个描述子中相应非零维度之间的绝对差值。如果差值小于0.2,相似度得分增加1。如果匹配的相似度得分大于阈值 T h s c o r e Th_{score} Thscore,则认为匹配是有效的。具体的匹配算法如算法3所示。在匹配两个描述子后,搜索簇中边缘关键点之间的匹配。具体来说,首先选择每条扫描线上具有最高 ∇ \nabla ∇(平滑项)的对应边缘关键点,然后匹配位于同一扫描线上的边缘关键点。
图5. 描述子生成的示意图。以当前关键点
k
0
k_0
k0为中心的XoY平面被划分为180个扇区区域。我们首先搜索
k
0
k_0
k0的最近关键点
k
1
k_1
k1,主方向是从
k
0
k_0
k0到
k
1
k_1
k1的向量。然后在每个扇区区域内搜索最近的关键点。搜索到的关键点
k
1
,
k
2
,
k
3
k_1, k_2, k_3
k1,k2,k3等用于描述当前的
k
0
k_0
k0。通过矢量化操作,使用
k
0
k_0
k0与
k
1
,
k
2
,
k
3
k_1, k_2, k_3
k1,k2,k3等之间的距离值,得到180维的LinK3D描述子。
图6. 最终描述子的每个维度的值对应于Des1, Des2和Des3中具有最高优先级的非零值。
算法1:关键点聚合算法
输入:
P
e
P_e
Pe:
∇
>
T
h
v
\nabla > T_{hv}
∇>Thv 边缘点
输出: ValidEdgeKeypoints, AggregationKeypoints
-
主循环:
- 对于每个点
p
i
∈
P
e
p_i \in P_e
pi∈Pe:
- 将点划分到扇区: S e c t o r s ← D i v i d e P o i n t T o S e c t o r B a s e d O n E q . 2 ( p i ) Sectors \leftarrow DividePointToSectorBasedOnEq.2(p_i) Sectors←DividePointToSectorBasedOnEq.2(pi)
- 结束
- 对于每个点
p
i
∈
P
e
p_i \in P_e
pi∈Pe:
-
对于每个 S e c t o r ∈ S e c t o r s Sector \in Sectors Sector∈Sectors:
- 初始化聚类:
- F i r s t C l u s t e r ← C r e a t e C l u s t e r FirstCluster \leftarrow CreateCluster FirstCluster←CreateCluster(任意 S e c t o r Sector Sector 中的点)
- C l u s t e r s . I n s e r t C l u s t e r ( F i r s t C l u s t e r ) Clusters.InsertCluster(FirstCluster) Clusters.InsertCluster(FirstCluster)
- 对于
S
e
c
t
o
r
Sector
Sector 中的其他点
p
j
p_j
pj:
- 对于每个
C
l
u
s
t
e
r
∈
C
l
u
s
t
e
r
s
Cluster \in Clusters
Cluster∈Clusters:
- 计算聚类中心:
- C e n t e r ← C o m p u t e C l u s t e r C e n t e r ( C l u s t e r ) Center \leftarrow ComputeClusterCenter(Cluster) Center←ComputeClusterCenter(Cluster)
- 计算水平距离:
- d i s t ← C o m p u t e H o r i z o n t a l D i s t ( p j , C e n t e r ) dist \leftarrow ComputeHorizontalDist(p_j, Center) dist←ComputeHorizontalDist(pj,Center)
- 如果
d
i
s
t
<
T
d
i
s
t
dist < T_{dist}
dist<Tdist:
- C l u s t e r . U p d a t e C l u s t e r ( p j ) Cluster.UpdateCluster(p_j) Cluster.UpdateCluster(pj)
- 否则如果当前聚类是最后一个:
- N e w C l u s t e r ← C r e a t e C l u s t e r ( p j ) NewCluster \leftarrow CreateCluster(p_j) NewCluster←CreateCluster(pj)
- C l u s t e r s . I n s e r t ( N e w C l u s t e r ) Clusters.Insert(NewCluster) Clusters.Insert(NewCluster)
- 否则:
- 继续
- 计算聚类中心:
- 结束
- 对于每个
C
l
u
s
t
e
r
∈
C
l
u
s
t
e
r
s
Cluster \in Clusters
Cluster∈Clusters:
- 结束
- 初始化聚类:
-
对于每个 C l u s t e r ∈ C l u s t e r s Cluster \in Clusters Cluster∈Clusters:
- 计算点的数量:
- N p o i n t ← C o u n t N u m b e r O f P o i n t ( C l u s t e r ) N_{point} \leftarrow CountNumberOfPoint(Cluster) Npoint←CountNumberOfPoint(Cluster)
- 计算扫描线的数量:
- N l i n e ← C o u n t N u m b e r O f S c a n L i n e ( C l u s t e r ) N_{line} \leftarrow CountNumberOfScanLine(Cluster) Nline←CountNumberOfScanLine(Cluster)
- 如果
N
p
o
i
n
t
>
T
p
o
i
n
t
N_{point} > T_{point}
Npoint>Tpoint 且
N
l
i
n
e
>
T
l
i
n
e
N_{line} > T_{line}
Nline>Tline:
- V a l i d E d g e K e y p o i n t s . I n s e r t ( C l u s t e r . P o i n t s ) ValidEdgeKeypoints.Insert(Cluster.Points) ValidEdgeKeypoints.Insert(Cluster.Points)
- A g g r e g a t i o n K e y p o i n t s . I n s e r t ( C l u s t e r . C e n t e r ) AggregationKeypoints.Insert(Cluster.Center) AggregationKeypoints.Insert(Cluster.Center)
- 结束
- 计算点的数量:
-
结束
算法2:描述子生成算法
输入:
K
a
K_a
Ka: 聚合关键点
输出: 描述子
-
主循环:
- 对于每个点
k
i
∈
K
a
k_i \in K_a
ki∈Ka:
- 对于每个点
k
j
∈
K
a
,
k
j
≠
k
i
k_j \in K_a, k_j \ne k_i
kj∈Ka,kj=ki:
- 计算距离: T a b l e d i s t ← C o m p u t e D i s t a n c e ( k i , k j ) Table_{dist} \leftarrow ComputeDistance(k_i, k_j) Tabledist←ComputeDistance(ki,kj)
- 计算方向: T a b l e d i r e ← C o m p u t e D i r e c t i o n ( k i , k j ) Table_{dire} \leftarrow ComputeDirection(k_i, k_j) Tabledire←ComputeDirection(ki,kj)
- 结束
- 对于每个点
k
j
∈
K
a
,
k
j
≠
k
i
k_j \in K_a, k_j \ne k_i
kj∈Ka,kj=ki:
- 结束
- 对于每个点
k
i
∈
K
a
k_i \in K_a
ki∈Ka:
-
对于每个点 k i ∈ K a k_i \in K_a ki∈Ka:
- 搜索最近点: C l o s e s t P t ← S e a r c h C l o s e s t P o i n t ( k i , T a b l e d i s t ) ClosestPt \leftarrow SearchClosestPoint(k_i, Table_{dist}) ClosestPt←SearchClosestPoint(ki,Tabledist)
- 将点插入到第一个扇区: S e c t o r s . I n s e r t P o i n t T o F i r s t S e c t o r ( C l o s e s t P t ) Sectors.InsertPointToFirstSector(ClosestPt) Sectors.InsertPointToFirstSector(ClosestPt)
- 获取主方向: M a i n D i r e ← G e t D i r e c t i o n ( k i , C l o s e s t P t , T a b l e d i r e ) MainDire \leftarrow GetDirection(k_i, ClosestPt, Table_{dire}) MainDire←GetDirection(ki,ClosestPt,Tabledire)
- 对于每个点
k
j
∈
K
a
,
k
j
≠
k
i
,
k
j
≠
C
l
o
s
e
s
t
P
t
k_j \in K_a, k_j \ne k_i, k_j \ne ClosestPt
kj∈Ka,kj=ki,kj=ClosestPt:
- 获取其他方向: O t h e r D i r e ← G e t D i r e c t i o n ( k i , k j , T a b l e d i r e ) OtherDire \leftarrow GetDirection(k_i, k_j, Table_{dire}) OtherDire←GetDirection(ki,kj,Tabledire)
- 计算角度: θ j ← C o m p u t e θ I n E q . 3 ( M a i n D i r e , O t h e r D i r e ) \theta_j \leftarrow Compute\theta In Eq.3(MainDire, OtherDire) θj←ComputeθInEq.3(MainDire,OtherDire)
- 将点根据 θ \theta θ 插入: S e c t o r s . I n s e r t P o i n t B a s e d O n θ ( k j , θ j ) Sectors.InsertPointBasedOn\theta (k_j, \theta_j) Sectors.InsertPointBasedOnθ(kj,θj)
- 结束
- 定义一个180维描述子: D e s c r i p t o r Descriptor Descriptor
- 对于每个扇区
∈
S
e
c
t
o
r
s
\in Sectors
∈Sectors:
- 如果扇区中点的数量为0:
- 对应描述子维度设置为0: C o r r e s p o n d i n g D i m I n D e s c r i p t o r = 0 CorrespondingDimInDescriptor = 0 CorrespondingDimInDescriptor=0
- 否则:
- 搜索最近距离: D i s ← S e a r c h C l o s e s t D i s t ( s e c t o r , T a b l e d i s t ) Dis \leftarrow SearchClosestDist(sector, Table_{dist}) Dis←SearchClosestDist(sector,Tabledist)
- 对应描述子维度设置为距离值: C o r r e s p o n d i n g D i m I n D e s c r i p t o r = D i s CorrespondingDimInDescriptor = Dis CorrespondingDimInDescriptor=Dis
- 结束
- 如果扇区中点的数量为0:
- 插入新的描述子: D e s c r i p t o r s . I n s e r t N e w D e s c r i p t o r ( D e s c r i p t o r ) Descriptors.InsertNewDescriptor(Descriptor) Descriptors.InsertNewDescriptor(Descriptor)
- 结束
-
结束
算法3:匹配算法
输入: Descriptors_1, Descriptors_2
输出: MatchPairs: 匹配的描述子索引对
-
主循环:
- 定义 S e t O f I D j SetOfID_j SetOfIDj;
- 定义 R B t r e e I D j − I D i RBtree_{ID_j-ID_i} RBtreeIDj−IDi, R B t r e e I D i − S c o r e RBtree_{ID_i-Score} RBtreeIDi−Score;
- 对于每个描述子
D
i
∈
D
e
s
c
r
i
p
t
o
r
s
1
D_i \in Descriptors_1
Di∈Descriptors1:
- 定义 H i g h e s t S c o r e HighestScore HighestScore 和 H i g h e s t S c o r e I D j HighestScoreID_j HighestScoreIDj;
- 对于每个描述子
D
j
∈
D
e
s
c
r
i
p
t
o
r
s
2
D_j \in Descriptors_2
Dj∈Descriptors2:
- 计算相似度得分: S c o r e ← G e t S i m i l a r i t y S c o r e ( D i , D j ) Score \leftarrow GetSimilarityScore(D_i, D_j) Score←GetSimilarityScore(Di,Dj)
- 如果
S
c
o
r
e
>
H
i
g
h
e
s
t
S
c
o
r
e
Score > HighestScore
Score>HighestScore:
- H i g h e s t S c o r e = S c o r e HighestScore = Score HighestScore=Score
- H i g h e s t S c o r e I D j = I D j HighestScoreID_j = ID_j HighestScoreIDj=IDj
- 结束
- 结束
- 插入 S e t O f I D j SetOfID_j SetOfIDj: S e t O f I D j . I n s e r t ( H i g h e s t S c o r e I D j ) SetOfID_j.Insert(HighestScoreID_j) SetOfIDj.Insert(HighestScoreIDj)
- 插入 R B t r e e I D j − I D i RBtree_{ID_j-ID_i} RBtreeIDj−IDi: R B t r e e I D j − I D i . I n s e r t ( H i g h e s t S c o r e I D j , I D i ) RBtree_{ID_j-ID_i}.Insert(HighestScoreID_j, ID_i) RBtreeIDj−IDi.Insert(HighestScoreIDj,IDi)
- 插入 R B t r e e I D i − S c o r e RBtree_{ID_i-Score} RBtreeIDi−Score: R B t r e e I D i − S c o r e . I n s e r t ( I D i , H i g h e s t S c o r e ) RBtree_{ID_i-Score}.Insert(ID_i, HighestScore) RBtreeIDi−Score.Insert(IDi,HighestScore)
- 结束
-
移除 D e s c r i p t o r s 2 Descriptors_2 Descriptors2 的一对多匹配:
- 对于每个
I
D
j
∈
S
e
t
O
f
I
D
j
ID_j \in SetOfID_j
IDj∈SetOfIDj:
- 获取所有 I D i ID_i IDi: A l l I D i ← G e t A l l O f D i ( I D j , R B t r e e I D j − I D i ) AllID_i \leftarrow GetAllOfD_i(ID_j, RBtree_{ID_j-ID_i}) AllIDi←GetAllOfDi(IDj,RBtreeIDj−IDi)
- 获取最高得分的 I D i ID_i IDi: H i g h e s t S c o r e I D i ← G e t H i g h e s t S c o r e D i ( A l l I D i , R B t r e e I D i − S c o r e ) HighestScore_{ID_i} \leftarrow GetHighestScoreD_i(AllID_i, RBtree_{ID_i-Score}) HighestScoreIDi←GetHighestScoreDi(AllIDi,RBtreeIDi−Score)
- 如果
H
i
g
h
e
s
t
S
c
o
r
e
I
D
i
.
S
c
o
r
e
≥
T
h
s
c
o
r
e
HighestScore_{ID_i}.Score \ge Th_{score}
HighestScoreIDi.Score≥Thscore:
- 插入匹配对: M a t c h P a i r s . I n s e r t ( H i g h e s t S c o r e I D i . I D i , I D j ) MatchPairs.Insert(HighestScore_{ID_i}.ID_i, ID_j) MatchPairs.Insert(HighestScoreIDi.IDi,IDj)
- 结束
- 对于每个
I
D
j
∈
S
e
t
O
f
I
D
j
ID_j \in SetOfID_j
IDj∈SetOfIDj:
- 结束
IV. 实验
在本节中,我们首先进行基本匹配性能和实时性能评估,然后将LinK3D扩展到LiDAR里程计任务。KITTI里程计【17】、M2DGR【22】和StevenVLP【23】数据集用于评估。KITTI中的点云由64束Velodyne LiDAR以10Hz的频率从不同街道环境(如市中心、郊区、森林和高速公路)收集。M2DGR的点云由32束Velodyne LiDAR收集,Steven的点云由16束Velodyne LiDAR以10Hz的频率从校园场景收集。LiDAR的频率(10Hz)要求所有算法的处理时间在100毫秒内。我们算法的参数设置如下: T h v = 10 Th_v = 10 Thv=10在Sec. III-A1; T h d i s t = 0.4 , T h p o i n t = 12 , T h l i n e = 4 Th_{dist} = 0.4, Th_{point} = 12, Th_{line} = 4 Thdist=0.4,Thpoint=12,Thline=4在算法1中; T h s c o r e = 3 Th_{score} = 3 Thscore=3在Sec. III-C。代码在具有Intel Core i7-12700 @ 2.10 GHz处理器和16 GB RAM的桌面上执行。
以下是参数设置的解释:
-
T h v = 10 Th_v = 10 Thv=10 在Sec. III-A1:
- Th_v 是平滑度的阈值。平滑度用来区分边缘点和平面点。在Sec. III-A1中,平滑度大于该阈值的点将被认为是边缘点。具体来说,如果一个点的平滑度计算值 ∇ \nabla ∇ 大于10,它将被标记为边缘点。
-
T h d i s t = 0.4 Th_{dist} = 0.4 Thdist=0.4, T h p o i n t = 12 Th_{point} = 12 Thpoint=12, T h l i n e = 4 Th_{line} = 4 Thline=4 在算法1中:
- Th_dist 是距离阈值,用于在关键点聚合过程中决定两个点是否属于同一簇。如果两个点之间的距离小于0.4,则它们被认为属于同一簇。
- Th_point 是点数阈值,用于确定一个簇是否有效。如果一个簇中的点数大于12,则该簇被认为是有效的。
- Th_line 是线数阈值,用于在簇内的不同扫描线上的点数。如果一个簇中的扫描线数大于4,则该簇被认为是有效的。
-
T h s c o r e = 3 Th_{score} = 3 Thscore=3 在Sec. III-C:
- Th_score 是相似性分数的阈值。在描述子匹配过程中,如果两个描述子的相似性分数大于3,则它们被认为是匹配的。这意味着在计算两个描述子之间的非零维度差异的绝对值后,如果分数超过3,则认为这两个描述子匹配。
这些参数都是在特定算法部分中使用的,以确保算法在不同场景下能够有效地提取、聚合和匹配关键点。设定这些阈值是为了过滤掉噪声数据,并确保只处理有效和可靠的数据点。
代码在具有Intel Core i7-12700 @ 2.10 GHz处理器和16 GB RAM的桌面上执行,这意味着实验是在一台具有高性能处理器和足够内存的计算机上进行的,以保证算法能够在合理的时间内处理LiDAR数据。
A. 与手工制作特征的匹配性能比较
特征匹配是手工制作的3D局部特征的基本功能。在本小节中,使用典型的城市场景(Seq.00)、农村场景(Seq.03)、动态场景(Sec.04)、大姿态(Sec.08)和森林场景(Sec.09)中的KITTI进行评估。M2DGR中的Gate_01、Street_03,以及Steven VLP16中的第一个序列用于评估。最先进的手工制作3D特征:3DSC【26】、PFH【12】、FPFH【5】、SHOT【27】、BSHOT【7】和3DHoPD【18】用于比较。我们首先提取我们的LinK3D聚合关键点用于比较方法,并在相同场景中比较它们的匹配结果。然后我们还提取ISS【24】和Sift3D【25】关键点用于相同场景中的比较描述符,两个关键点的数量与LinK3D中的边缘关键点相似。对于度量方法,我们遵循【4】中使用的度量标准,并使用内点数量和内点百分比(inliers%)作为验证指标。RANSAC【28】用于去除错误匹配,接受阈值设置为0.5。为了公平比较,我们首先计算出在不同场景中(除了大姿态情况,这是随机选择的反向回路)我们方法的平均内点数量和内点百分比,然后使用两个匹配的LiDAR扫描,其内点数量和内点百分比与平均值大致相同,而不是使用内点数量和内点百分比较高的扫描。然后我们提取相似数量的ISS和Sift3D关键点用于比较方法。所用扫描ID和特定数量的边缘关键点如表I所示。我们方法在每个场景中的匹配结果如图7所示,不同场景的比较结果如表II和表III所示。
从表II和表III中可以看出,我们的方法在每个场景中获得的内点数量比比较方法更多。在具有挑战性的森林场景中,即使边缘特征不明显并对我们方法的内点百分比有不良影响,LinK3D生成的内点数量仍然相当可观。在具有挑战性的动态场景中,高度动态的载体对我们方法有不良影响,这降低了我们方法的内点百分比。需要注意的是,我们的方法可以有效匹配具有大姿态转换(反向方向回路)的两个扫描,这可以在图7(d)中看到。此外,尽管比较方法在从64束LiDAR收集的点云上取得了可比的结果,但由于从32束和16束LiDAR收集的点更稀疏(可以从图7(f)(g)(h)中看到M2DGR和Steven VLP16数据集的稀疏性),大多数比较方法的内点数量为零,并且在由32束和16束LiDAR生成的点云上可能不可靠。
表1
关键点 | 城市场景 | 农村场景 | 动态场景 | 大姿态 | 森林场景 | Gate_01 | Street_03 | Steven VLP16 |
---|---|---|---|---|---|---|---|---|
(169/170) | (580/581) | (87/88) | (232/1647) | (17/18) | (121/122) | (109/110) | (22/23) | |
ISS [24] | 2985/2863 | 2459/2508 | 3460/3396 | 2467/2404 | 2869/2949 | 1184/1143 | 1153/1120 | 222/213 |
Sift3D [25] | 2897/2842 | 2485/2442 | 3428/3356 | 2497/2478 | 2853/2877 | 1162/1155 | 1248/1158 | 229/237 |
LinK3D(ours) | 2890/2902 | 2359/2284 | 3380/3428 | 2411/2604 | 2928/3006 | 1177/1162 | 1204/1156 | 218/226 |
解释表格内容:
第一行是不同场景的名称,包括城市场景、农村场景、动态场景、大姿态、森林场景、Gate_01、Street_03 和 Steven VLP16。
第二行是每个场景的扫描ID(两个扫描场景)。
第三行到第六行分别表示三种不同的关键点提取方法(ISS、Sift3D 和 LinK3D)的提取结果。每个场景下的两个数字分别表示两次匹配扫描中提取的关键点数量。
表1 数字解析
城市场景(City Scene 169/170)
-
ISS: 2985 / 2863
- 2985 表示在第169个扫描中提取的关键点数量。
- 2863 表示在第170个扫描中提取的关键点数量。
- 总体上,ISS方法在城市场景中提取的关键点数量分别为2985和2863。
-
Sift3D: 2897 / 2842
- 2897 表示在第169个扫描中提取的关键点数量。
- 2842 表示在第170个扫描中提取的关键点数量。
- 总体上,Sift3D方法在城市场景中提取的关键点数量分别为2897和2842。
-
LinK3D: 2890 / 2902
- 2890 表示在第169个扫描中提取的关键点数量。
- 2902 表示在第170个扫描中提取的关键点数量。
- 总体上,LinK3D方法在城市场景中提取的关键点数量分别为2890和2902。
以此类推…
结论
- LinK3D在不同场景中的表现与ISS和Sift3D大致相当,有些场景甚至超过了这两种方法。
- LinK3D方法能够在多种复杂环境中有效提取关键点,显示了其鲁棒性和适应性。
表2
内点(Inliers)
在点云配准或特征匹配的过程中,内点是指那些符合模型假设且与其他点紧密匹配的点。这些点是被认为真正匹配或正确对齐的点,与相应的外点(Outliers)相比,内点的匹配误差较小。内点的数量反映了匹配的准确性和鲁棒性。
内点率(Inliers%)
内点率是指内点数量占总匹配点数量的百分比。它是一个衡量匹配质量的重要指标,反映了匹配的精确程度。计算公式如下:
内点率 (Inliers%) =
(
内点数量
(
Inliers
)
总匹配点数量
(
Total Matches
)
)
×
100
%
\left( \frac{\text{内点数量} (\text{Inliers})}{\text{总匹配点数量} (\text{Total Matches})} \right) \times 100\%
(总匹配点数量(Total Matches)内点数量(Inliers))×100%
内点率越高,说明匹配的质量越好,匹配结果越可靠。反之,内点率低意味着匹配过程中存在较多的错误匹配。
表3
B. 实时性能评估
在本节中,我们评估了我们方法的效率,并将其与基于DNN的方法和手工方法进行了比较。
KITTI 00数据集用于手工方法的评估,其中包括4500多个LiDAR扫描,每个LiDAR扫描大约包含120000多个点。我们计算了在匹配连续LiDAR扫描时我们方法的平均运行时间。我们在IV-A节所用的城市场景上进行多次测量后,计算了其他手工方法的平均运行时间。对于基于DNN的方法,StickyPillars在KITTI上进行了评估,其他方法则在3DMatch [30]上进行评估,该数据集是从RGB-D相机收集的,每帧平均包含约27000多个点(小于KITTI的数据量)。表IV展示了比较结果。我们可以看到,尽管使用了更少的点且需要GPU,但基于DNN的方法通常需要更多时间。其他手工方法无法实现实时性能。LinK3D平均仅需约50毫秒即可提取和匹配特征,显示出出色的实时性能。
| 表4 | 不同方法的效率比较。3DFeatNet、3DSmoothNet和DH3D的数据来自[9],其他基于DNN的方法的数据来自其原始论文。所有单位均为秒。 |
表中数据解释:
- T e x t r a c t i o n T_{extraction} Textraction 表示特征提取时间。
- T m a t c h i n g T_{matching} Tmatching 表示特征匹配时间。
- T t o t a l T_{total} Ttotal 表示总时间。
- 需要GPU列标识该方法是否需要GPU。
LinK3D (ours) 在手工制作的方法中表现出色,且不需要GPU。其提取和匹配特征的总时间为0.050秒。相比之下,StickyPillars是基于DNN的方法,其总时间为0.116秒。其他基于DNN的方法虽然在提取时间上有一定优势,但通常需要GPU支持。
B. 实时性能评估
在本节中,我们评估我们方法的效率,并将其与基于DNN和手工制作的方法进行比较。
KITTI 00被用于手工制作方法的评估,其中包含4500多个LiDAR扫描,每个LiDAR扫描大约包含120000多个点。我们计算在匹配顺序LiDAR扫描时我们方法的平均运行时间。我们在IV-A节中使用的城市场景上进行多次测量后计算平均运行时间,用于其他手工制作的方法。对于基于DNN的方法,StickyPillars的运行时间在KITTI上进行评估,其他方法在3DMatch【30】上进行评估,该数据集从RGB-D相机采集,每帧大约包含27000多个点(< KITTI的平均点数)。表IV显示了比较结果。我们可以看到,尽管使用的点更少且需要GPU,DNN方法通常需要更多时间。其他手工制作的方法无法实现实时性能。LinK3D在平均情况下提取和匹配特征仅需大约50毫秒,显示出出色的实时性能。
C. 非结构化场景下的匹配失败案例
在我们评估KITTI(从序列00到10)算法时,我们发现,在非结构化的KITTI 01、02和09中,RANSAC之后,我们的方法可能无法生成两个连续LiDAR扫描之间的真实匹配,具有较少有效边缘特征。KITTI 01是从高速公路场景中收集的,而KITTI 02和09的一些LiDAR扫描是从森林场景中收集的。我们使用地面实况确定三个序列的成功率,如表V所示。结果表明,我们的方法可能对具有较少有效边缘特征的非结构化场景不够健壮。未来,我们将继续改进我们方法在非结构化场景中的鲁棒性。
D. 应用:LiDAR里程计
LiDAR里程计通常是LiDAR SLAM系统的前端,LiDAR里程计的估计结果会影响SLAM后端的准确性。在这一部分,我们将LinK3D与后续映射步骤结合。为此,我们用基于LinK3D匹配结果的SVD解决方案【31】替换A-LOAM的扫描到扫描注册步骤。遇到匹配失败案例时,使用A-LOAM的原始注册结果。我们在表VI中将我们的方法与CLS【32】、LOAM【33】和LO-Net【34】进行了比较,并使用KITTI里程计指标【17】定量分析LiDAR里程计的准确性。结果显示在表VI中。可以看出,基于LinK3D的里程计实现了可比较的估计结果。
V. 结论与未来工作
在这项工作中,我们提出了一种新的3D特征表示方法,解决了现有方法不完全适用于稀疏3D LiDAR点云的问题,称为LinK3D。LinK3D的核心思想是通过其邻近关键点描述当前关键点。实验结果表明,LinK3D可以在稀疏的16、32和64束LiDAR点云上实时生成大量有效匹配。未来,我们将继续改进LinK3D在非结构化场景中的鲁棒性。将LinK3D扩展到更多可能的3D视觉任务(例如,位置识别和移动机器人重新定位)以提高不同基于LiDAR的移动机器人系统的效率和准确性是很有希望的。