1 import cslam.utils.point_cloud2
as pc2_utils
2 from geometry_msgs.msg
import Transform
3 from scipy.spatial.transform
import Rotation
as R
4 from std_msgs.msg
import Header
5 from sensor_msgs.msg
import PointCloud2, PointField
10 from scipy.spatial
import cKDTree
14 PointField(name=
'x', offset=0, datatype=PointField.FLOAT32, count=1),
15 PointField(name=
'y', offset=4, datatype=PointField.FLOAT32, count=1),
16 PointField(name=
'z', offset=8, datatype=PointField.FLOAT32, count=1),
23 return np.asarray(pcd.points).T
27 radius_normal = voxel_size * 2
29 open3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal,
32 radius_feature = voxel_size * 5
33 fpfh = open3d.pipelines.registration.compute_fpfh_feature(
35 open3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature,
37 return np.array(fpfh.data).T
41 feat1tree = cKDTree(feat1)
42 dists, nn_inds = feat1tree.query(feat0, k=knn, workers=-1)
50 nns01 =
find_knn_cpu(feats0, feats1, knn=1, return_distance=
False)
51 corres01_idx0 = np.arange(len(nns01))
55 return corres01_idx0, corres01_idx1
57 nns10 =
find_knn_cpu(feats1, feats0, knn=1, return_distance=
False)
58 corres10_idx1 = np.arange(len(nns10))
61 mutual_filter = (corres10_idx0[corres01_idx1] == corres01_idx0)
62 corres_idx0 = corres01_idx0[mutual_filter]
63 corres_idx1 = corres01_idx1[mutual_filter]
65 return corres_idx0, corres_idx1
69 solver_params = teaserpp_python.RobustRegistrationSolver.Params()
70 solver_params.cbar2 = 1.0
71 solver_params.noise_bound = noise_bound
72 solver_params.estimate_scaling =
False
73 solver_params.inlier_selection_mode = \
74 teaserpp_python.RobustRegistrationSolver.INLIER_SELECTION_MODE.PMC_EXACT
75 solver_params.rotation_tim_graph = \
76 teaserpp_python.RobustRegistrationSolver.INLIER_GRAPH_FORMULATION.CHAIN
77 solver_params.rotation_estimation_algorithm = \
78 teaserpp_python.RobustRegistrationSolver.ROTATION_ESTIMATION_ALGORITHM.GNC_TLS
79 solver_params.rotation_gnc_factor = 1.4
80 solver_params.rotation_max_iterations = 10000
81 solver_params.rotation_cost_threshold = 1e-16
82 solver = teaserpp_python.RobustRegistrationSolver(solver_params)
94 open3d_cloud = open3d.geometry.PointCloud()
95 open3d_cloud.points = open3d.utility.Vector3dVector(points)
96 return open3d_cloud.voxel_down_sample(voxel_size=voxel_size)
109 src_corr = src_xyz[:, corrs_src]
110 dst_corr = dst_xyz[:, corrs_dst]
113 solver.solve(src_corr, dst_corr)
115 solution = solver.getSolution()
117 valid = len(solver.getInlierMaxClique()) > min_inliers
121 T_teaser =
Rt2T(solution.rotation, solution.translation)
122 icp_sol = open3d.pipelines.registration.registration_icp(
123 src, dst, voxel_size, T_teaser,
124 open3d.pipelines.registration.TransformationEstimationPointToPoint(
126 open3d.pipelines.registration.ICPConvergenceCriteria(
128 T_icp = icp_sol.transformation
129 solution.translation = T_icp[:3, 3]
130 solution.rotation = T_icp[:3, :3]
132 rclpy.logging.get_logger(
'cslam').info(
133 'Failed to compute loop closure. Number of inliers ( {} / {} )'.
134 format(len(solver.getInlierMaxClique()), min_inliers))
135 return valid, solution.translation, solution.rotation
140 T.translation.x = translation[0]
141 T.translation.y = translation[1]
142 T.translation.z = translation[2]
143 rotation_matrix = R.from_matrix(np.array(rotation))
144 q = rotation_matrix.as_quat()
155 points = np.asarray(open3d_cloud.points)
156 return pc2_utils.create_cloud(header, fields, points)
161 open3d_cloud = open3d.geometry.PointCloud()
162 open3d_cloud.points = open3d.utility.Vector3dVector(points)
167 return pc2_utils.read_points_numpy_filtered(pc_msg)[:, :3]
175 """Computes a 3D transform between 2 point clouds using TEASER++.
177 Be careful: TEASER++ computes the transform from dst to src.
180 src (Open3D point cloud): pointcloud from
181 dst (Open3D point cloud): pointcloud to
182 voxel_size (int): Voxel size
183 min_inliers (int): Minimum number of inlier points correspondance to consider the registration a success
186 (Transform, bool): transform and success flag
188 valid, translation, rotation =
solve_teaser(src, dst, voxel_size,
192 return transform, success
def find_correspondences(feats0, feats1, mutual_filter=True)
def ros_pointcloud_to_points(pc_msg)
def downsample(points, voxel_size)
def downsample_ros_pointcloud(pc_msg, voxel_size)
def find_knn_cpu(feat0, feat1, knn=1, return_distance=False)
def open3d_to_ros(open3d_cloud)
def extract_fpfh(pcd, voxel_size)
def get_teaser_solver(noise_bound)
def compute_transform(src, dst, voxel_size, min_inliers)
def to_transform_msg(translation, rotation)
def solve_teaser(src, dst, voxel_size, min_inliers)