2D icp实现


2D ICP原理和实现

ICP(Iterative Closest Point)是用于解决点云配准问题的一个经典算法,主要思想是先找到点集之间最近邻点之间的关系,然后用SVD分解计算变换矩阵,不断重复这个过程以达到最优效果。

ICP算法步骤

输入:给定的两个维度(二维)和数量相同的点集

  1. 使用某种算法得到点集之间各个点的对应关系,如(KNN, K最近邻算法)
  2. 使用两个点集及其对应关系,用SVD方法计算点集之间的旋转
  3. 使用得到的最优旋转矩阵更新点集
  4. 重复上述过程, 直到误差变化幅度不大时停止

代码实现

icp主干流程的实现:

def icp(A, B, max_iterations=100, error=0.0000001):
    # A和B是两个有n个点的n*2的二维点集
    src = np.copy(A)
    dst = np.copy(B)
    # 使用RSME作为误差
    RSME = 0
    for i in range(max_iterations):
        # get_correspondence函数用于计算两个点集之间的对应关系和距离
        # indices是dst点集中的点的下标
        distances, indices = get_correspondence(src, dst)
        # T是根据对应关系,使用SVD算法求解出来的最优的变换矩阵
        T = get_transform(src, dst[indices, :])
        # 使用变换矩阵更新src
        # 为了能够直接使用T矩阵,需要将点集转化为齐次坐标
        homogeneous_src = np.ones((3, A.shape[0]))
        homogeneous_src[:2, :] = np.copy(src.T)
        # 更新src,src=T*src
        src = np.copy(np.dot(T, homogeneous_src)[:2, :]).T
        # 计算RSME,继续进行迭代
        RSME = np.sqrt(np.mean(distances ** 2))
    print(RSME)
    # 计算最终的变换矩阵T,即原始点集A和经过多次变换之后的点集src之间的旋转
    T = get_transform(A, src)
    return T

get_correspondence函数实现:

def get_correspondence(src, dst):
        """
    Args:
        src: n points of m dims, n*m(n*2)
        dst: n points of m dims, n*m(n*2)

    Returns:
        distances: distances between corresponded pairs
        indices: correspondences index of dst
    """
    # 使用sklearn提供的KNN算法计算src和dst点集之间的对应关系
    neighbors = NearestNeighbors(n_neighbors=1)
    neighbors.fit(dst)
    distances, indices = neighbors.kneighbors(src, return_distance=True)
    # ravel()作用是将多维数组转化为一位数组
    return distances.ravel(), indices.ravel()

get_transform函数实现:

def get_transform(A, B):
        """
    get transform matrix between A and B, points in A and B are corresponded.
    Args:
        A: n*2 points set
        B: n*2 points set
    Returns:
        T: transform matrix between A and B
    """
    # center_A和center_B分别是A和B点集的质心
    center_A = np.mean(A, axis=0)
    center_B = np.mean(B, axis=0)

    # 将A和B的点坐标进行归一化(重心对齐)
    normal_A = A - center_A
    normal_B = B - center_B

    # SVD分解求旋转矩阵R
    # W = P*Q_T (公式中的P是2*N的矩阵,而normal_A是N*2的矩阵,所以代码中为normal_A.T*normal_B)
    # W = U*∑*V_T
    # R = V*U_T
    W = np.dot(normal_A.T, normal_B)
    U, S, V_T = np.linalg.svd(W)
    R = np.dot(V_T.T, U.T)

    # 参考代码,不是很懂这一步的意义
    if np.linalg.det(R) < 0:
        V_T[1, :] *= -1
        R = np.dot(V_T.T, U.T)
    # 计算平移
    t = center_B.T - np.dot(R, center_A.T)
    # 构造变换矩阵
    T = np.identity(3)
    T[:2, :2] = R[:2, :2]
    T[:2, 2] = t[:2]

    return T

代码实现主要参考了ICP, 第一次自己实现的时候代码结构比较混乱,所有步骤都写在了ICP函数中,这对后来的调试产生了比较大的困扰,参考其他人的代码之后对代码重新进行了整理,这里给出的是二维的情况,如果要实现其他维度的ICP算法,原理也是相同的,只需要在该代码的基础上少量修改即可。

另外这个版本的ICP算法并没有考虑到点集之间存在缩放关系的情况,后续可以进一步改进。


评论
 上一篇
zotero通过multiple_profiles实现多文库 zotero通过multiple_profiles实现多文库
zotero通过multiple-profiles实现多文库最近计划写一篇综述,这就需要读大量的文章,然而数百篇的文章如何有效管理就是一个很大的问题。简单的使用文件夹+tags的方法已经不能解决这个问题了,因此在和导师保持一致多方对比之后,
2022-02-11
下一篇 
服务器端使用jupyter lab配置Matlab踩坑记录 服务器端使用jupyter lab配置Matlab踩坑记录
服务器端使用jupyter lab配置Matlab踩坑记录jupyter配置前面的安装步骤很简单,安装教程安装即可: conda create -n matlab python=3.6 # 创建虚拟环境,不创建也可 conda instal
2021-10-25
  目录