2D ICP原理和实现
ICP(Iterative Closest Point)是用于解决点云配准问题的一个经典算法,主要思想是先找到点集之间最近邻点之间的关系,然后用SVD分解计算变换矩阵,不断重复这个过程以达到最优效果。
ICP算法步骤
输入:给定的两个维度(二维)和数量相同的点集
- 使用某种算法得到点集之间各个点的对应关系,如(KNN, K最近邻算法)
- 使用两个点集及其对应关系,用SVD方法计算点集之间的旋转
- 使用得到的最优旋转矩阵更新点集
- 重复上述过程, 直到误差变化幅度不大时停止
代码实现
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算法并没有考虑到点集之间存在缩放关系的情况,后续可以进一步改进。