OPEN3D学习笔记(六)——Multiway registration

Multiway registration

多向配准是在全局空间中对齐多个几何图形的过程。通常,输入是一组几何图形(例如,点云或RGBD图像) {Pi}。输出是一组刚性变换{Ti} ,因此变换后的点云{TiPi}在全局空间中对齐。

Input

读取,并下采样

# 返回的是一个list,里面都是降采样的(如果体素大小为0则源点云)
def load_point_clouds(voxel_size=0.0):
    pcds = []
    for i in range(3):
        pcd = o3d.io.read_point_cloud("../../TestData/ICP/cloud_bin_%d.pcd" % i)
        pcd_down = pcd.voxel_down_sample(voxel_size=voxel_size)
        pcds.append(pcd_down)
    return pcds

voxel_size = 0.02  # 体素大小
pcds_down = load_point_clouds(voxel_size)  # 获得一个经过降采样的点云list
o3d.visualization.draw_geometries(pcds_down)  # 可视化这些点云,看看他们的位置,一般是没对准的,很乱

Pose graph

姿势图具有两个关键元素:nodes,edges。
我的理解:nodes就是每个点云,edges连接两个相邻的node(点云),这两个点云有最多的重叠部分

逐对配准容易出错,因此分成两类:
Odometry edges
Loop closure edges
测渗法边缘连接时间上接近的相邻节点。本地注册算法(例如ICP)可以可靠地对齐它们。闭环边缘连接任何不相邻的节点。该对齐方式是通过全局注册找到的,可靠性较低。在Open3D中,这两类边缘由PoseGraphEdge的初始化程序中的不确定参数来区分。

# 两两配准
def pairwise_registration(source, target):
    print("Apply point-to-plane ICP")
    icp_coarse = o3d.registration.registration_icp(
        source, target, max_correspondence_distance_coarse, np.identity(4),
        o3d.registration.TransformationEstimationPointToPlane())  # 用一个初始4*4转换矩阵,粗配准
    icp_fine = o3d.registration.registration_icp(
        source, target, max_correspondence_distance_fine,
        icp_coarse.transformation,
        o3d.registration.TransformationEstimationPointToPlane())  # 用粗配准的出的旋转矩阵,进行再次配准
    transformation_icp = icp_fine.transformation  # 配准后的出的旋转矩阵
    information_icp = o3d.registration.get_information_matrix_from_point_clouds(
        source, target, max_correspondence_distance_fine,
        icp_fine.transformation)  # 从变换矩阵计算信息矩阵
    return transformation_icp, information_icp


# 全局配准
def full_registration(pcds, max_correspondence_distance_coarse, max_correspondence_distance_fine):
    pose_graph = o3d.registration.PoseGraph()  # 姿势图
    odometry = np.identity(4)  # 对角线是1的方阵,4*4
    pose_graph.nodes.append(o3d.registration.PoseGraphNode(odometry))  # 不懂这句,odometry是边
    n_pcds = len(pcds)
    for source_id in range(n_pcds):  # 遍历每个点云
        for target_id in range(source_id + 1, n_pcds):  # 遍历两两点云
            transformation_icp, information_icp = pairwise_registration(pcds[source_id], pcds[target_id])  # 进行两两配对,获得转换矩阵、信息矩阵
            print("Build o3d.registration.PoseGraph")
            if target_id == source_id + 1:  # odometry case 在姿势图相邻的两个点云
                odometry = np.dot(transformation_icp, odometry)  # 两个矩阵相乘
                pose_graph.nodes.append(
                    o3d.registration.PoseGraphNode(np.linalg.inv(odometry)))  # 求逆矩阵,往图里添加node
                pose_graph.edges.append(
                    o3d.registration.PoseGraphEdge(source_id,
                                                   target_id,
                                                   transformation_icp,
                                                   information_icp,
                                                   uncertain=False))  # uncertain用于区分是Odometry edges
            else:  # loop closure case
                pose_graph.edges.append(
                    o3d.registration.PoseGraphEdge(source_id,
                                                   target_id,
                                                   transformation_icp,
                                                   information_icp,
                                                   uncertain=True))  # 添加边Loop closure edges,非邻居的点云的边
    return pose_graph  # 返回图

print("Full registration ...")
max_correspondence_distance_coarse = voxel_size * 15  # 粗配准距离阈值,衡量两个对应点的距离
max_correspondence_distance_fine = voxel_size * 1.5  # 精配准距离阈值
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm:  # 上下文管理器
    pose_graph = full_registration(pcds_down,
                                   max_correspondence_distance_coarse,
                                   max_correspondence_distance_fine)

Open3D使用函数global_optimization执行姿势图优化。
可以选择两种类型的优化方法:GlobalOptimizationGaussNewton或GlobalOptimizationLevenbergMarquardt。
推荐使用后者,因为它具有更好的收敛性。
GlobalOptimizationConvergenceCriteria类可用于设置最大迭代次数和各种优化参数。
GlobalOptimizationOption类定义了两个选项。max_correspondence_distance确定对应阈值。edge_prune_threshold是修剪异常边缘的阈值。reference_node是被视为全局空间的节点ID。


print("Optimizing PoseGraph ...")
option = o3d.registration.GlobalOptimizationOption(
    max_correspondence_distance=max_correspondence_distance_fine,
    edge_prune_threshold=0.25,
    reference_node=0)  # 优化全局配准
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm:
    o3d.registration.global_optimization(
        pose_graph, o3d.registration.GlobalOptimizationLevenbergMarquardt(),
        o3d.registration.GlobalOptimizationConvergenceCriteria(), option)  # 进行全局优化

全局优化在姿势图上执行两次。第一遍将考虑所有边缘的情况优化原始姿态图的姿态,并尽最大努力区分不确定边缘之间的错误对齐。这些错误对齐的行处理权重较小,并且在第一次通过后将其修剪。第二遍没有它们运行,并且产生了紧密的全局对齐。在此示例中,所有边缘均被视为真实对齐,因此第二遍将立即终止。

Visualize optimization

使用draw_geometries列出并可视化已变换的点云

print("Transform points and display")
for point_id in range(len(pcds_down)):
    print(pose_graph.nodes[point_id].pose)
    pcds_down[point_id].transform(pose_graph.nodes[point_id].pose)  # 每一个点云都使用旋转矩阵进行变换位置,变换到同一坐标系下
o3d.visualization.draw_geometries(pcds_down)  # 可视化配准后的点云
'''
输出
Transform points and display
[[ 1.00000000e+00 -2.50509994e-19  0.00000000e+00  0.00000000e+00]
 [-3.35636805e-20  1.00000000e+00  1.08420217e-19 -8.67361738e-19]
 [-1.08420217e-19 -1.08420217e-19  1.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00]]
[[ 0.8401689  -0.14645453  0.52217554  0.34785474]
 [ 0.00617659  0.96536804  0.2608187  -0.39427149]
 [-0.54228965 -0.2159065   0.81197679  1.7300472 ]
 [ 0.          0.          0.          1.        ]]
[[ 0.96271237 -0.07178412  0.2608293   0.3765243 ]
 [-0.00196124  0.96227508  0.27207136 -0.48956598]
 [-0.27051994 -0.26243801  0.92625334  1.29770817]
 [ 0.          0.          0.          1.        ]]
'''

Make a combined point cloud

合并点云
PointCloud具有便捷的运算符+,可以将两个点云合并为一个。合并后,将使用voxel_down_sample对点进行统一重新采样。建议在合并点云之后进行后处理,因为这可以减轻重复的点或过度密集的点。

pcds = load_point_clouds(voxel_size)
pcd_combined = o3d.geometry.PointCloud()
for point_id in range(len(pcds)):
    pcds[point_id].transform(pose_graph.nodes[point_id].pose)  # 每一个点云都转换位置
    pcd_combined += pcds[point_id]  # 点云不断累加
pcd_combined_down = pcd_combined.voxel_down_sample(voxel_size=voxel_size)  # 降采样,因为累加会有很多重复点
o3d.io.write_point_cloud("multiway_registration.pcd", pcd_combined_down)  # 保存配准后的点云
o3d.visualization.draw_geometries([pcd_combined_down])  # 渲染配准结果

完美~~

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值