记录三维点云配准的经典Iterative Closest Point(ICP)算法。
之前记录了一个简单的基于marker的配准算法,但是这个算法非常初级,而且要求坐标是成组排序的。在实际应用中就会比较麻烦,一般只是用于手工选取极少数marker点来进行配准。而ICP算法依赖于点之间的几何关系,而非点的索引顺序,因此在较多的点的配准上更加方便。因为我们可以通过简单的强度过滤就筛选出绝大部分marker,然后再利用ICP算法进行配准,既方便又准确。
效果如上图所示,红色为source_points,绿色为 target_points,通过ICP算法可以得到从 source 到 target 的变换矩阵,通过应用该矩阵对 source再次进行变换,可以看到能够与 target_points重合。
可以看到对于包含1000个点的点云配准,ICP21次迭代后就完成了,基本就是几秒钟的时间。这对于一般成像的基于marker的通道配准足够了。
代码如下:
import numpy as npimport open3d as o3dimport copy
# 生成示例点云并打乱顺序def generate_sample_point_clouds(n_points=1000): # 生成随机源点云 source_points = np.random.rand(n_points, 3) * 10 # 打乱源点云顺序 np.random.shuffle(source_points) source = o3d.geometry.PointCloud() source.points = o3d.utility.Vector3dVector(source_points) # 生成目标点云(通过随机变换) rotation = o3d.geometry.get_rotation_matrix_from_xyz([0.3, 0.2, 0.1]) translation = np.array([1.0, 2.0, 3.0]) target_points = (source_points @ rotation.T) + translation # 打乱目标点云顺序 np.random.shuffle(target_points) target = o3d.geometry.PointCloud() target.points = o3d.utility.Vector3dVector(target_points) return source, target
# ICP算法实现def icp(source, target, max_iterations=50, threshold=0.01): """ 实现ICP算法 参数: source: 源点云 (Open3D PointCloud) target: 目标点云 (Open3D PointCloud) max_iterations: 最大迭代次数 threshold: 收敛阈值 返回: transformation: 最终变换矩阵 (4x4) """ source_points = np.asarray(source.points) target_points = np.asarray(target.points)
# 初始化变换矩阵 transformation = np.eye(4) for i in range(max_iterations): # 构建KD树以查找最近点 target_tree = o3d.geometry.KDTreeFlann(target) correspondences = [] # 寻找最近点对 for j, point in enumerate(source_points): [_, idx, _] = target_tree.search_knn_vector_3d(point, 1) correspondences.append([j, idx[0]]) # 提取对应点 src_corr = source_points[[c[0] for c in correspondences]] tgt_corr = target_points[[c[1] for c in correspondences]] # 计算质心 src_centroid = np.mean(src_corr, axis=0) tgt_centroid = np.mean(tgt_corr, axis=0) # 去中心化 src_centered = src_corr - src_centroid tgt_centered = tgt_corr - tgt_centroid # 计算协方差矩阵 H = src_centered.T @ tgt_centered # 使用SVD分解计算旋转矩阵 U, _, Vt = np.linalg.svd(H) rotation = Vt.T @ U.T # 计算平移向量 translation = tgt_centroid - rotation @ src_centroid # 更新变换矩阵 delta_transform = np.eye(4) delta_transform[:3, :3] = rotation delta_transform[:3, 3] = translation
# 应用变换 transformation = delta_transform @ transformation source_points = (rotation @ source_points.T).T + translation # 检查收敛 error = np.mean(np.linalg.norm(src_corr - tgt_corr, axis=1)) if error < threshold: print(f"ICP converged after {i+1} iterations with error {error}") break # 更新源点云 source.points = o3d.utility.Vector3dVector(source_points) return transformation
# 应用变换矩阵进行点云配准def apply_transformation(source, transformation): """ 应用变换矩阵到源点云 参数: source: 源点云 (Open3D PointCloud) transformation: 变换矩阵 (4x4) 返回: transformed: 配准后的点云 """ transformed = copy.deepcopy(source) transformed.transform(transformation) return transformed
# 可视化点云def visualize_point_clouds(source, target, transformed, offset=[0.0, 0.0, 0.0]): """ 可视化点云,添加偏移以区分配准后的点云 参数: source: 原始源点云 (红色) target: 目标点云 (绿色) transformed: 配准后的点云 (蓝色) offset: 配准点云的偏移量,仅用于可视化 """ source = copy.deepcopy(source) target = copy.deepcopy(target) transformed = copy.deepcopy(transformed) # 打印点云点数以调试 print(f"Source point cloud has {len(source.points)} points") print(f"Target point cloud has {len(target.points)} points") print(f"Transformed point cloud has {len(transformed.points)} points") # 应用偏移(仅用于可视化) if offset != [0.0, 0.0, 0.0]: transformed_points = np.asarray(transformed.points) + np.array(offset) transformed.points = o3d.utility.Vector3dVector(transformed_points) source.paint_uniform_color([1, 0, 0]) # 红色 target.paint_uniform_color([0, 1, 0]) # 绿色 transformed.paint_uniform_color([0, 0, 1]) # 蓝色 # 显示所有点云 o3d.visualization.draw_geometries([source, target, transformed], window_name="ICP Registration with Shuffled Points", width=800, height=600) # 单独显示配准后的点云和目标点云,便于检查 o3d.visualization.draw_geometries([target, transformed], window_name="Target and Transformed Point Clouds", width=800, height=600)
def main(): # 生成点云并打乱顺序 source, target = generate_sample_point_clouds() # 复制源点云用于变换 source_copy = copy.deepcopy(source) # 运行ICP算法 transformation = icp(source_copy, target) print("Final transformation matrix:\n", transformation) # 应用变换进行配准 transformed_source = apply_transformation(source, transformation) # 可视化,添加小偏移以区分蓝色点云 visualize_point_clouds(source, target, transformed_source, offset=[0.1, 0.1, 0.1])
if __name__ == "__main__": main()