defdraw_registration_result(source,target,transformation):# Since the functions transform() and paint_uniform_color() change the point cloud, we call copy.deepcopy to make copies and protect the original point clouds.source_temp=copy.deepcopy(source)target_temp=copy.deepcopy(target)source_temp.paint_uniform_color([1,0.706,0])target_temp.paint_uniform_color([0,0.651,0.929])source_temp.transform(transformation)o3d.visualization.draw_geometries([source_temp,target_temp],zoom=0.4459,front=[0.9288,-0.2951,-0.2242],lookat=[1.6784,2.0612,1.4451],up=[-0.3402,-0.9189,-0.1996])demo_icp_pcds=o3d.data.DemoICPPointClouds()source=o3d.io.read_point_cloud(demo_icp_pcds.paths[0])target=o3d.io.read_point_cloud(demo_icp_pcds.paths[1])# Initial guess transform between the two point-cloud.# ICP algortihm requires a good initial allignment to converge efficiently.trans_init=np.asarray([[0.862,0.011,-0.507,0.5],[-0.139,0.967,-0.215,0.7],[0.487,0.255,0.835,-1.4],[0.0,0.0,0.0,1.0]])draw_registration_result(source,target,trans_init)
Evaluation
The function evaluate_registration() calculates two main metrics:
fitness, which measures the overlapping area (# of inlier correspondences / # of points in target).
The higher the better.
inlier_rmse, which measures the RMSE of all inlier correspondences.
The lower the better.
1
2
3
4
5
6
7
8
9
10
11
12
print("Initial alignment")threshold=0.02evaluation=o3d.pipelines.registration.evaluate_registration(source,target,threshold,trans_init)print(evaluation)'''
RegistrationResult with
- fitness=1.747228e-01,
- inlier_rmse=1.177106e-02, and
- correspondence_set size of 34741
Access transformation to get result.
'''
Idea of ICP Algorithm
In general, the ICP algorithm iterates over two steps:
Find correspondence set $K = {(p,q)}$ from target point cloud P, and source point cloud Q transformed with current transformation matrix T
Update the transformation T by minimizing an objective function E(T) defined over the correspondence set K.
Different variants of ICP use different objective functions E(T)
Point-to-point ICP[1]
Point-to-plane ICP[2]
Colored ICP[3]
2. Point-to-Point ICP
The class TransformationEstimationPointToPoint provides functions to compute the residuals and Jacobian matrices of the point-to-point ICP objective.
The function registration_icp() takes it as a parameter and runs point-to-point ICP to obtain the results.
Update the trans_init (4 x 4 matrix) by minimizing an objective function: $E(T)=\sum_{(p,q) \in K} ||p-Tq||^2$
The fitness score increases from 0.174723 to 0.372450. The inlier_rmsereduces from 0.011771 to 0.007760, and correspondence_set size is 74056 from 34741
By default, registration_icp() runs until convergence or reaches a maximum number of iterations (30 by default).
It can be changed to allow more computation time and to improve the results further.
by adding o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=2000)) as a parameter
The final alignment is tight. The fitness score improves to 0.621123. The inlier_rmsereduces to 0.006583. The correspondence_set size improves 123501 from 74056.
3. Point-to-Plane ICP
[4] has shown that the point-to-plane ICP algorithm has a faster convergence speed than the point-to-point ICP algorithm.
by using a different objective function $E(T)=\sum_{(p,q) \in K} ((p-Tq) \times n_p)^2$
$n_p$ is the normal of point $p$
If normals are not given, they can be computed with Vertex normal estimation.
We first run Point-to-plane ICP as a baseline approach.
The visualization below shows misaligned green triangle textures.
This is because a geometric constraint does not prevent two planar surfaces from slipping.
1
2
3
4
5
6
7
8
9
10
# point to plane ICPcurrent_transformation=np.identity(4)print("2. Point-to-plane ICP registration is applied on original point")print(" clouds to refine the alignment. Distance threshold 0.02.")result_icp=o3d.pipelines.registration.registration_icp(source,target,0.02,current_transformation,o3d.pipelines.registration.TransformationEstimationPointToPlane())print(result_icp)draw_registration_result_original_color(source,target,result_icp.transformation)
Colored ICP
To further improve efficiency, [3] proposes a multi-scale registration scheme. In total, 3 layers of multi-resolution point clouds are created with voxel_down_sample.
as voxel_down_sample decreases from 0.04 to 0.01, even less max_iter (from 50 to 14), we can still achieve better alignment (correspondence_set increases from 2084 to 24737)
# colored pointcloud registration# This is implementation of following paper# J. Park, Q.-Y. Zhou, V. Koltun,# Colored Point Cloud Registration Revisited, ICCV 2017voxel_radius=[0.04,0.02,0.01]max_iter=[50,30,14]current_transformation=np.identity(4)print("3. Colored point cloud registration")forscaleinrange(3):iter=max_iter[scale]radius=voxel_radius[scale]print([iter,radius,scale])print("3-1. Downsample with a voxel size %.2f"%radius)source_down=source.voxel_down_sample(radius)target_down=target.voxel_down_sample(radius)print("3-2. Estimate normal.")source_down.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=radius*2,max_nn=30))target_down.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=radius*2,max_nn=30))print("3-3. Applying colored point cloud registration")result_icp=o3d.pipelines.registration.registration_colored_icp(source_down,target_down,radius,current_transformation,o3d.pipelines.registration.TransformationEstimationForColoredICP(),o3d.pipelines.registration.ICPConvergenceCriteria(relative_fitness=1e-6,relative_rmse=1e-6,max_iteration=iter))current_transformation=result_icp.transformationprint(result_icp)draw_registration_result(source,target,result_icp.transformation)