ICP算法详解


ICP算法详解

原论文浅析&补充:A Method for Registration of 3-D Shapes

ICP算法最早于1992年由论文《A Method for Registration of 3-D Shapes》提出,论文提到ICP算法可以用于以下的几种情况:

  1. 点集
  2. 线段集(折线)
  3. 隐式曲线(用数学公式表示曲线)
  4. 参数曲线(用参数映射表示曲线)
  5. 三角面片(应该就是现在的mesh)
  6. 隐式表面(用数学公式表示的表面)
  7. 参数表面(用参数映射表示的表面)

关于3、4、6、7点有疑惑的,可以看几何的隐式表示与显示表示,或者看GAMES101: 现代计算机图形学入门第5周、第6周的课件。

由于我们主要关注点集之间的配准,因此下面主要讲论文的IV. THE ITERATIVE CLOSEST POINT ALGORITHM部分,其他部分可以看原论文。

ICP算法的定义

从下面可以看出,ICP算法最核心的问题就两个,一个是如何计算最近点(最近邻问题),一个是如何求解旋转和平移(非线性最小二乘问题)

对于有$N_p$个点${p_i}$的点集$P$,和有$N_x$个点、线段或三角面片的目标形状$X$,ICP算法可以定义为:

  • $P_k$表示第$k$次迭代更新之后的点集,$q_k$表示第$k$次迭代中计算得到的旋转和平移,其中$P_0=P$,$q_0=[1,0,0,0,0,0,0]^t$,$q_0[0:3]$是单位四元数,表示旋转,$q_0[4:6]$是平移向量,表示平移。

  • 算法的流程为:

    • 计算本次迭代的最近点集,$Y_k = C(P_k,X)$,平均复杂度是$O(N_p logN_x)$,最坏的情况下复杂度是$O(N_pN_x)$。需要注意,$Y_k$是目标形状$X$上的点。

      计算最近点集的问题也叫做最近邻问题,后面会详细展开讲。这里ICP认为距离最近的点就是对应点,根据对应点能计算旋转和平移。

      这部分和快速排序的时间复杂度一样

    • 计算本次迭代的旋转和平移,$(q_k,d_k)=Q(P_0,Y_k)$,时间复杂度是$O(N_p)$

      这部分在论文III. MATHEMATICAL PRELIMINARIES部分有介绍,下面也会详细展开

    • 应用本次计算的旋转和平移,更新点集,$P_{k+1} = q_k(P_0)$,时间复杂度是$O(N_p)$

    • 当均方误差的变化低于预设的阈值时,终止迭代。这个阈值指定了配准过程的期望精确度。

ICP核心问题一:最近邻问题

这里给一个高翔新书《自动驾驶与机器人中的SLAM技术-从理论到实践》中的部分介绍。

ICP核心问题二:非线性最小二乘问题

原论文方法一

对应论文中III. MATHEMATICAL PRELIMINARIES部分的C. Corresponding Point Set Registration

对应上述方法的第二步,即“计算本次迭代的旋转和平移,$(q_k,d_k)=Q(P_0,Y_k)$,时间复杂度是$O(N_p)$”。

下面的方法应该就是SVD分解的方法。

下面是论文中提到的求解方法,比较类似SVD分解。

原论文方法二

对应论文中IV. THE ITERATIVE CLOSEST POINT ALGORITHM部分的C. An Accelerated ICP Algorithm

对应上述方法的第二步,但是替换掉了旋转和平移的求解方法。

下面的方法应该是梯度下降法。

下面是原论文中提到的方法,比较类似梯度下降法,和求解最小二乘问题

常用方法一:SVD分解

常用方法二:梯度下降法

经典ICP算法框架

在高翔新书《自动驾驶与机器人中的SLAM技术-从理论到实践》中,对3D点对点ICP算法的描述如下所示。

G-ICP论文中给出了标准ICP算法框架,如下所示。

拓展:Gaussian Splatting中的最近邻问题

在gaussian splatting中,包括了submodules/simple-knn这个simple-knn并不是聚类算法,而是一个K近邻算法

代码对应部分

Gaussian Splatting中使用K近邻算法的目的是为了实现初始化,每个初始化高斯点的尺度是其到最近的k个点的平均距离。

image-20240628212740328

simple-knn代码实现

Gaussian Splatting中直接调用的函数如下:

// 定义一个函数 distCUDA2,参数是一个常量引用 torch::Tensor 类型的 points,返回值类型是 torch::Tensor
torch::Tensor
distCUDA2(const torch::Tensor& points)
{
  // 获取 points 张量的第一个维度的大小,即点的数量 P
  const int P = points.size(0);

  // 创建一个选项对象 float_opts,类型与 points 的数据类型相同,但数据类型设置为 torch::kFloat32
  auto float_opts = points.options().dtype(torch::kFloat32);
  
  // 使用 float_opts 选项创建一个大小为 P 的张量 means,初始值为 0.0,数据类型为 float32
  torch::Tensor means = torch::full({P}, 0.0, float_opts);
  
  // 调用 SimpleKNN::knn 函数,传入 P(点的数量)、points 的连续内存数据(转换为 float3 指针)、means 的连续内存数据
  // 代码中默认的K=3,这里means得到的就是每个点到最近的三个点的平均距离的平方
  SimpleKNN::knn(P, (float3*)points.contiguous().data<float>(), means.contiguous().data<float>());

  // 返回 means 张量
  return means;
}

其中,SimpleKNN::knn是一个“基于 Morton 编码的 K 近邻(KNN)查找算法”,这里用GPT根据submodules/simple-knn的代码生成了如下伪代码,方便理解:

// Step 1: Normalize points and compute Morton codes
// 计算xyz坐标的最小值和最大值,并且进行归一化&计算morton编码
minn = min(points)
maxx = max(points)
for each point in points:
    morton_code = compute_morton_code(point, minn, maxx)

// Step 2: Sort Morton codes and indices
// 对morton编码进行排序和索引
sort(morton_codes, indices)

// Step 3: Divide points into blocks and compute bounding boxes
// 分块,并且计算每个块的最小值最大值
blocks = divide_into_blocks(points, block_size)
for each block in blocks:
    compute_min_max(block)

// Step 4: KNN search
// K近邻搜索
for each point in points:
	// 首先从该点所属的block中找最近点
    nearest_neighbors = find_nearest_neighbors_in_block(point, block)
    for each block in blocks:
		// 之后再从其他的block中找最近点并且进行更新
        if distance_to_block(point, block) < furthest_distance(nearest_neighbors):
            update_nearest_neighbors(point, block, nearest_neighbors)
    // 计算与最近K个点的平均距离的平方
    mean_distance = compute_mean_distance(nearest_neighbors)

Morton编码

Morton 编码(Morton Code),也称为 Z-order 曲线(Z-order Curve),是一种将多维数据映射到一维的空间填充曲线。在这种映射中,具有相邻多维坐标的点在映射到一维后也保持相邻的概率较高。

Morton 编码的原理

Morton 编码通过交替取每个维度的比特位来实现。例如,对于三维坐标 (x, y, z),它将每个坐标的二进制表示交替组合,形成一个新的整数。具体步骤如下:

  1. 将每个坐标归一化到 [0, 1] 范围内: 这通过将坐标减去最小值并除以最大值减去最小值来实现。这样可以确保所有坐标都在相同的范围内,有助于后续的比特操作。
  2. 将归一化后的坐标映射到一个固定范围内的整数: 例如,将归一化后的 [0, 1] 范围内的坐标映射到 [0, 1023] 范围内(即 10 位二进制数)。
  3. 将每个维度的比特交替合并: 将每个坐标的二进制表示交替取出,并组合成一个新的整数。这个过程叫做“bit interleaving”。


文章作者: Immortalqx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Immortalqx !
评论
  目录