LinK3D: Linear Keypoints Representation for 3D LiDAR Point Cloud【SLAM-翻译与解读】

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. 关键点提取
  1. 边缘点提取:
    在关键点提取中,我们将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=S1 jPs,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

详细解读

  1. 点云分类:

    • 将LiDAR点云大致分为两类:边缘点和平面点。
    • 区分这两类点的主要依据是其所在局部表面的平滑度。
  2. 点的平滑度:

    • 给定一个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的基数(即点的数量)。
  3. 平滑项的计算:

    • 当前点 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=S1 jPs,j=i(pj pi ) 2
    • 其中, p i ⃗ \vec{p_i} pi p j ⃗ \vec{p_j} pj 分别是两个点 i i i j j j的坐标。
  4. 边缘点的识别:

    • 根据计算出的平滑度 ∇ i \nabla_i i,提取那些具有大于阈值 T h v T_{hv} Thv的点作为边缘点。
    • 边缘点通常表示在局部表面上变化较大的区域,如物体的边缘。

解读公式

  • ∇ i \nabla_i i 表示点 i i i 的平滑度。
  • 1 ∣ S ∣ \frac{1}{|S|} S1 是一个归一化系数,用于确保平滑度与点数量无关。
  • ∑ j ∈ P s , j ≠ i \sum_{j \in P_s, j \ne i} jPs,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点云中的边缘点和平面点,边缘点代表了表面特征的显著变化区域,这对于进一步的特征提取和匹配非常重要。

  1. 边缘关键点聚合:
    在获取边缘点后,有很多点的 ∇ \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所示。可以看出,我们的算法能够过滤掉无效的边缘点并找到有效的边缘关键点。此外,计算每个簇点的质心并将其命名为聚合关键点,这将用于后续的描述子生成。

详细解读:

  1. 不稳定点的处理:

    • 尽管有许多边缘点的平滑度 ∇ \nabla 超过了阈值 T h v T_hv Thv,但它们在扫描中并不总是稳定的,可能只出现在当前扫描中,而不会出现在下一次扫描中。
    • 如图3a中的红色虚线框所示,这些不稳定点通常是分散的,因此需要将其过滤掉,以找到有效的边缘关键点。
    • 如图3a中的蓝色虚线框所示,有效的边缘关键点通常垂直分布在簇中。
  2. 关键点聚合算法:

    • 设计了一种关键点聚合算法来找到有效的边缘关键点,使用角度信息引导来加速聚合过程。
    • 动机是属于同一垂直边缘的点在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)
  3. 扇区划分和聚类:

    • 将以LiDAR坐标系原点为中心的XoY平面等分为 N s e c t N_{sect} Nsect个扇区区域,然后仅在每个扇区区域内聚类点,而不是在整个空间中聚类。
    • 这种方法有效地提高了聚类速度和效率。
    • 当在实验中将 N s e c t N_{sect} Nsect设置为120时,算法的运行速度比经典的K-Means算法快约25倍。
  4. 算法结果:

    • 提取的边缘关键点如图3b所示。
    • 算法能够过滤掉无效的边缘点并找到有效的边缘关键点。
    • 计算每个簇点的质心,并将其命名为聚合关键点,这些关键点将用于后续的描述子生成。

在这里插入图片描述
图3. 散落的边缘点(由红色虚线框标记)和聚集的边缘关键点(由蓝色虚线框标记)在(a)中显示。我们需要的是聚集的边缘关键点。(b)显示了通过算法1提取的边缘关键点。

在这里插入图片描述
图4. 聚合过程的示意图。首先根据LiDAR坐标系的XoY平面的角度将点划分到相应的扇区区域。然后仅在每个扇区区域内执行聚类操作,而不是在整个空间内直接进行。紫色点是我们需要生成聚合关键点(红色点)的点,绿色点是将被过滤掉的散落点。

B. 描述子生成
  1. 描述子生成过程:
    在描述子生成过程中,所有聚合关键点首先被投影到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 表示方向矩阵,决定了点与主方向的相对位置。

这种方式使得描述子既能反映点之间的几何关系,又具有一定的旋转不变性,有利于后续的特征匹配。

  1. 描述子生成过程的问题:
    上述算法存在两个主要问题。一个问题是算法对最近关键点非常敏感。如果存在来自外点关键点的干扰,匹配将会失败。另一个问题是我们需要频繁计算两点之间的相对距离和方向,因此会有大量重复计算。为了解决第一个问题,我们搜索一定数量的最近关键点。假设我们搜索三个最近关键点,并计算相应的三个描述子,如图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

  1. 主循环:

    1. 对于每个点 p i ∈ P e p_i \in P_e piPe:
      • 将点划分到扇区: 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) SectorsDividePointToSectorBasedOnEq.2(pi)
    2. 结束
  2. 对于每个 S e c t o r ∈ S e c t o r s Sector \in Sectors SectorSectors:

    1. 初始化聚类:
      • 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 FirstClusterCreateCluster(任意 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)
    2. 对于 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 ClusterClusters:
        1. 计算聚类中心:
          • 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) CenterComputeClusterCenter(Cluster)
        2. 计算水平距离:
          • 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) distComputeHorizontalDist(pj,Center)
        3. 如果 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)
        4. 否则如果当前聚类是最后一个:
          • 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) NewClusterCreateCluster(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)
        5. 否则:
          • 继续
      • 结束
    3. 结束
  3. 对于每个 C l u s t e r ∈ C l u s t e r s Cluster \in Clusters ClusterClusters:

    1. 计算点的数量:
      • 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) NpointCountNumberOfPoint(Cluster)
    2. 计算扫描线的数量:
      • 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) NlineCountNumberOfScanLine(Cluster)
    3. 如果 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)
    4. 结束
  4. 结束

算法2:描述子生成算法

输入: K a K_a Ka: 聚合关键点
输出: 描述子

  1. 主循环:

    1. 对于每个点 k i ∈ K a k_i \in K_a kiKa:
      • 对于每个点 k j ∈ K a , k j ≠ k i k_j \in K_a, k_j \ne k_i kjKa,kj=ki:
        1. 计算距离: 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) TabledistComputeDistance(ki,kj)
        2. 计算方向: 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) TabledireComputeDirection(ki,kj)
      • 结束
    2. 结束
  2. 对于每个点 k i ∈ K a k_i \in K_a kiKa:

    1. 搜索最近点: 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}) ClosestPtSearchClosestPoint(ki,Tabledist)
    2. 将点插入到第一个扇区: 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)
    3. 获取主方向: 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}) MainDireGetDirection(ki,ClosestPt,Tabledire)
    4. 对于每个点 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 kjKa,kj=ki,kj=ClosestPt:
      1. 获取其他方向: 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}) OtherDireGetDirection(ki,kj,Tabledire)
      2. 计算角度: θ 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) θjComputeθInEq.3(MainDire,OtherDire)
      3. 将点根据 θ \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)
    • 结束
    1. 定义一个180维描述子: D e s c r i p t o r Descriptor Descriptor
    2. 对于每个扇区 ∈ S e c t o r s \in Sectors Sectors:
      1. 如果扇区中点的数量为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
      2. 否则:
        1. 搜索最近距离: 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}) DisSearchClosestDist(sector,Tabledist)
        2. 对应描述子维度设置为距离值: 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
      • 结束
    3. 插入新的描述子: 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. 结束

算法3:匹配算法

输入: Descriptors_1, Descriptors_2
输出: MatchPairs: 匹配的描述子索引对

  1. 主循环:

    1. 定义 S e t O f I D j SetOfID_j SetOfIDj
    2. 定义 R B t r e e I D j − I D i RBtree_{ID_j-ID_i} RBtreeIDjIDi, R B t r e e I D i − S c o r e RBtree_{ID_i-Score} RBtreeIDiScore
    3. 对于每个描述子 D i ∈ D e s c r i p t o r s 1 D_i \in Descriptors_1 DiDescriptors1:
      • 定义 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 DjDescriptors2:
        1. 计算相似度得分: 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) ScoreGetSimilarityScore(Di,Dj)
        2. 如果 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} RBtreeIDjIDi 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) RBtreeIDjIDi.Insert(HighestScoreIDj,IDi)
      • 插入 R B t r e e I D i − S c o r e RBtree_{ID_i-Score} RBtreeIDiScore 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) RBtreeIDiScore.Insert(IDi,HighestScore)
    • 结束
  2. 移除 D e s c r i p t o r s 2 Descriptors_2 Descriptors2 的一对多匹配:

    1. 对于每个 I D j ∈ S e t O f I D j ID_j \in SetOfID_j IDjSetOfIDj:
      • 获取所有 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}) AllIDiGetAllOfDi(IDj,RBtreeIDjIDi)
      • 获取最高得分的 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}) HighestScoreIDiGetHighestScoreDi(AllIDi,RBtreeIDiScore)
      • 如果 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.ScoreThscore:
        1. 插入匹配对: 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)
    • 结束
  • 结束

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的桌面上执行。

以下是参数设置的解释:

  1. T h v = 10 Th_v = 10 Thv=10 在Sec. III-A1:

    • Th_v 是平滑度的阈值。平滑度用来区分边缘点和平面点。在Sec. III-A1中,平滑度大于该阈值的点将被认为是边缘点。具体来说,如果一个点的平滑度计算值 ∇ \nabla 大于10,它将被标记为边缘点。
  2. 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,则该簇被认为是有效的。
  3. 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_01Street_03Steven VLP16
(169/170)(580/581)(87/88)(232/1647)(17/18)(121/122)(109/110)(22/23)
ISS [24]2985/28632459/25083460/33962467/24042869/29491184/11431153/1120222/213
Sift3D [25]2897/28422485/24423428/33562497/24782853/28771162/11551248/1158229/237
LinK3D(ours)2890/29022359/22843380/34282411/26042928/30061177/11621204/1156218/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的移动机器人系统的效率和准确性是很有希望的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/799421.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

最新 Kubernetes 集群部署 + Containerd容器运行时 + flannel 网络插件(保姆级教程,最新 K8S 1.28.2 版本)

资源列表 操作系统配置主机名IP所需插件CentOS 7.92C4Gk8s-master192.168.60.143flannel-cni-plugin、flannel、coredns、etcd、kube-apiserver、kube-controller-manager、kube-proxy、 kube-scheduler 、containerd、pause 、crictlCentOS 7.92C4Gk8s-node01192.168.60.144f…

在VSCode上创建Vue项目详细教程

1.前期环境准备 搭建Vue项目使用的是Vue-cli 脚手架。前期环境需要准备Node.js环境&#xff0c;就像Java开发要依赖JDK环境一样。 1.1 Node.js环境配置 1&#xff09;具体安装步骤操作即可&#xff1a; npm 安装教程_如何安装npm-CSDN博客文章浏览阅读836次。本文主要在Win…

yolo格式数据集之野生动物类4种数据集已划分好|可以直接使用|yolov5|v6|v7|v8|v9|v10通用

本数据为野生动物类检测数据集&#xff0c;数据集数量如下&#xff1a; 总共有:1504张 训练集&#xff1a;1203张 验证集&#xff1a;150张 类别数量&#xff1a;4 测试集&#xff1a;151 类别名&#xff1a; [‘buffalo’, ‘elephant’, ‘rhino’, ‘zebra’] 占用空间&…

阿里云ECS服务器安装jdk并运行jar包,访问成功详解

安装 OpenJDK 8 使用 yum 包管理器安装 OpenJDK 8 sudo yum install -y java-1.8.0-openjdk-devel 验证安装 安装完成后&#xff0c;验证 JDK 是否安装成功&#xff1a; java -version设置 JAVA_HOME 环境变量&#xff1a; 为了确保系统中的其他应用程序可以找到 JDK&…

【自监督学习】iBOT in ICLR 2022

一、引言 论文&#xff1a; iBOT&#x1f916;: Image BERT Pre-Training with Online Tokenizer 作者&#xff1a; ByteDance 代码&#xff1a; iBOT 注意&#xff1a; 该方法是在另一个自监督预训练方法基础上的改进&#xff0c;学习之前建议掌握DINO。 特点&#xff1a; 对…

聊点基础---Java和.NET开发技术异同全方位分析

1. C#语言基础 1.1 C#语法概览 欢迎来到C#的世界&#xff01;对于刚从Java转过来的开发者来说&#xff0c;你会发现C#和Java有很多相似之处&#xff0c;但C#也有其独特的魅力和强大之处。让我们一起来探索C#的基本语法&#xff0c;并比较一下与Java的异同。 程序结构 C#程序…

从零开始学习cartographer源码 | 番外:如何在wsl内使用clion阅读cartographer源码

从零开始学习cartographer源码 | 番外&#xff1a;如何在wsl内使用clion阅读cartographer源码 安装WSL2及Clion安装WSL2-Ubuntu20.04安装Clion安装ROS 安装Cartographer一键安装Cartographer 在Clion打开cartographer工程安装gdb手动创建CMakeLists.txt打开项目配置wsl工具链配…

CV07_深度学习模块之间的缝合教学(2)--维度转换

教学&#xff08;1&#xff09;&#xff1a;链接 1.1 预备知识 问题&#xff1a;假如说我们使用的模型张量是三维的&#xff0c;但是我们要缝合的模块是四维的&#xff0c;应该怎么办&#xff1f; 方法&#xff1a;pytorch中常用的函数&#xff1a;(1)view函数&#xff08;2…

【LeetCode 链表合集】

文章目录 1. LeetCode 206 反转链表2. NC40 链表相加 1. LeetCode 206 反转链表 题目链接&#x1f517; 解题思路&#xff1a; &#x1f50d; &#x1f427;创建一个新的节点&#xff0c;使用链表头插的方法&#xff1b; 2. NC40 链表相加 题目链接&#x1f517; 解题思路…

如何用STM32实现modbus-RTU?

Modbus RTU是一种广泛应用于工业自动化领域的通信协议,基于主从架构,通过串行通信进行数据传输。本文将详细介绍Modbus RTU协议的基本原理,并提供在STM32微控制器上实现Modbus RTU通信的完整代码示例。 1. Modbus RTU协议概述 Modbus RTU的定义和特点 Modbus RTU(Remote Te…

mysql的窗口函数

一、窗口函数 1.1 什么是窗口函数 窗口函数&#xff0c;也叫OLAP函数&#xff08;Online Analytical Processing&#xff0c;联机分析处理&#xff09;&#xff0c;可以对数据库进行实时分析处理&#xff0c;窗口函数的基本语法如下&#xff1a; <窗口函数> over (part…

Web前端开发

1. 介绍 本文将覆盖Web前端开发的方方面面&#xff0c;包括HTML、CSS、JavaScript三大基础知识&#xff0c;vue3框架以及项目实战&#xff0c;帮助读者从零开始掌握前端开发。 2. Web前端开发基础 2.1 HTML5 2.1.1 什么是HTML5 HTML&#xff08;HyperText Markup Language…

【深海王国】初中生也能画的电路板?手把手教你制作第一个PCB电路板(2)

Hi~ (o^^o)♪, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~ 辛苦工作的你今天也辛苦啦(/≧ω) 今天大都督将继续为大家带来系列——初中生也能画的电路板&#xff0c;帮你一周内快速入门PCB设计&#xff0c;手把手教你从元器件库添加、电路原理图绘制、PCB布局设计…

SparkStreaming--scala

文章目录 第1关&#xff1a;QueueStream代码 第2关&#xff1a;File Streams代码 第1关&#xff1a;QueueStream 任务描述 本关任务&#xff1a;编写一个清洗QueueStream数据的SparkStreaming程序。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.如何使用S…

【07】分布式事务解决方案

1、事务简介 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中&#xff0c;一个事务由一组SQL语句组成。事务应该具有ACID四个特性&#xff1a;原子性、一致性、隔离性、持久性。任何事务机制在实现时&#xff0c;都应该考虑事务…

mac生成.dmg压缩镜像文件

mac生成.dmg压缩镜像文件 背景准备内容步骤1&#xff0c;找一个文件夹2&#xff0c;制作application替身1&#xff0c;终端方式2&#xff0c;黄金右手方式 3&#xff0c;.app文件放入文件夹4&#xff0c;制作.dmg压缩镜像文件5&#xff0c;安装.dmg 总结 背景 为绕开App Store…

纯css实现语音播报动画效果

先来看看效果图 黑色以下代码 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAAF6fiUAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAO8SURBVGiB1ZlPaBxVHMe/v7fbkmUZErRs9/1moUtYBBfxklZR8aYg5KAgvcT25MFDvUhPTaGnHoKIlOpJ0VsPUvAgKNjSS0EPAVGKya…

防火墙综合实验之NAT和智能选路

目录 前言&#xff1a; 一、实验题目 二、实验操作 需求一 需求二 需求三 需求四、需求五 需求六 需求七 ​编辑 需求八 需求九 需求十 需求十一 三、需求测试 前言&#xff1a; 本篇文章是延续上一篇文章&#xff0c;简单来说就是防火墙实验的完善和延续&#…

Zabbix6.0监控Freeswitch状态

一、前提环境说明 1、最终实现Freeswitch监控指标信息&#xff1a; 2、环境需求&#xff1a; &#xff08;1&#xff09;需要使用Zabbix6.0及以上 &#xff08;2&#xff09;需要使用zabbix_agent2 二、实现步骤 1、zabbix_agent2添加监控键值 cat /etc/zabbix/conf.d/fr…

唯众物联网综合实训台 物联网实验室建设方案

物联网综合实训装置 物联网工程应用综合实训台是我公司针对职业院校物联网行业综合技能型人才培养&#xff0c;综合运用传感器技术、RFID技术、接口控制技术、无线传感网技术、Android应用开发等&#xff0c;配合实训台上的433M无线通信设备、ZigBee节点、射频设备、控制设备、…