NASA Astrobee Robot Software  0.19.1
Flight software for the Astrobee robots operating inside the International Space Station.
point_to_plane_icp.h
Go to the documentation of this file.
1 /* Copyright (c) 2017, United States Government, as represented by the
2  * Administrator of the National Aeronautics and Space Administration.
3  *
4  * All rights reserved.
5  *
6  * The Astrobee platform is licensed under the Apache License, Version 2.0
7  * (the "License"); you may not use this file except in compliance with the
8  * License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15  * License for the specific language governing permissions and limitations
16  * under the License.
17  */
18 #ifndef POINT_CLOUD_COMMON_POINT_TO_PLANE_ICP_H_
19 #define POINT_CLOUD_COMMON_POINT_TO_PLANE_ICP_H_
20 
28 
29 #include <boost/optional.hpp>
30 
31 // TODO(rsoussan): Switch back to this when PCL bug is fixed
32 // #include <pcl/registration/correspondence_rejection_surface_normal.h>
34 // TODO(rsoussan): Remove this when PCL upgraded
36 #include <pcl/point_cloud.h>
37 #include <pcl/point_types.h>
38 #include <pcl/registration/correspondence_rejection_median_distance.h>
39 #include <pcl/registration/icp.h>
40 
41 #include <vector>
42 
43 namespace point_cloud_common {
44 template <typename PointType>
46  public:
47  explicit PointToPlaneICP(const PointToPlaneICPParams& params);
48  boost::optional<localization_common::PoseWithCovariance> ComputeRelativeTransform(
49  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
50  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals,
51  const Eigen::Isometry3d& initial_target_T_source_estimate = Eigen::Isometry3d::Identity());
52  const boost::optional<ICPCorrespondences>& correspondences() const;
53 
54  private:
55  boost::optional<localization_common::PoseWithCovariance> RunPointToPlaneICP(
56  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
57  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals,
58  const Eigen::Isometry3d& initial_target_T_source_estimate = Eigen::Isometry3d::Identity());
59  boost::optional<localization_common::PoseWithCovariance> RunCoarseToFinePointToPlaneICP(
60  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
61  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals,
62  const Eigen::Isometry3d& initial_target_T_source_estimate = Eigen::Isometry3d::Identity());
63  boost::optional<localization_common::PoseWithCovariance> RunDownsampledPointToPlaneICP(
64  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
65  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals, const double leaf_size,
66  const Eigen::Isometry3d& initial_target_T_source_estimate = Eigen::Isometry3d::Identity());
67  void SaveCorrespondences(const pcl::IterativeClosestPointWithNormals<PointType, PointType>& icp,
68  const typename pcl::PointCloud<PointType>::Ptr source_cloud,
69  const typename pcl::PointCloud<PointType>::Ptr source_cloud_transformed);
70  boost::optional<ICPCorrespondences> correspondences_;
71  PointToPlaneICPParams params_;
72 };
73 
74 // Implemenation
75 template <typename PointType>
77 
78 template <typename PointType>
79 boost::optional<localization_common::PoseWithCovariance> PointToPlaneICP<PointType>::ComputeRelativeTransform(
80  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
81  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals,
82  const Eigen::Isometry3d& initial_target_T_source_estimate) {
83  if (params_.coarse_to_fine) {
84  return RunCoarseToFinePointToPlaneICP(source_cloud_with_normals, target_cloud_with_normals,
85  initial_target_T_source_estimate);
86  } else {
87  return RunPointToPlaneICP(source_cloud_with_normals, target_cloud_with_normals, initial_target_T_source_estimate);
88  }
89 }
90 
91 template <typename PointType>
92 boost::optional<localization_common::PoseWithCovariance> PointToPlaneICP<PointType>::RunPointToPlaneICP(
93  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
94  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals,
95  const Eigen::Isometry3d& initial_target_T_source_estimate) {
96  pcl::IterativeClosestPointWithNormals<PointType, PointType> icp;
97 
98  if (params_.symmetric_objective) {
99  auto symmetric_transformation_estimation =
100  boost::make_shared<pcl::registration::TransformationEstimationSymmetricPointToPlaneLLS2<PointType, PointType>>();
101  symmetric_transformation_estimation->setEnforceSameDirectionNormals(params_.enforce_same_direction_normals);
102  icp.transformation_estimation_ = symmetric_transformation_estimation;
103  }
104 
105  if (params_.correspondence_rejector_surface_normal) {
107  correspondence_rejector_surface_normal(new pcl::registration::CorrespondenceRejectorSurfaceNormal2<PointType>());
108  correspondence_rejector_surface_normal->initializeDataContainer();
109  correspondence_rejector_surface_normal->setThreshold(params_.correspondence_rejector_surface_normal_threshold);
110  correspondence_rejector_surface_normal->setInputNormals(source_cloud_with_normals);
111  correspondence_rejector_surface_normal->setTargetNormals(target_cloud_with_normals);
112  icp.addCorrespondenceRejector(correspondence_rejector_surface_normal);
113  }
114 
115  if (params_.correspondence_rejector_median_distance) {
116  pcl::registration::CorrespondenceRejectorMedianDistance::Ptr correspondence_rejector_median_distance(
117  new pcl::registration::CorrespondenceRejectorMedianDistance());
118  correspondence_rejector_median_distance->setMedianFactor(params_.correspondence_rejector_median_distance_factor);
119  icp.addCorrespondenceRejector(correspondence_rejector_median_distance);
120  }
121 
122  icp.setInputSource(source_cloud_with_normals);
123  icp.setInputTarget(target_cloud_with_normals);
124  icp.setMaximumIterations(params_.max_iterations);
125  typename pcl::PointCloud<PointType>::Ptr result(new pcl::PointCloud<PointType>);
126  icp.align(*result, initial_target_T_source_estimate.matrix().cast<float>());
127 
128  if (!icp.hasConverged()) {
129  LogError("Icp: Failed to converge.");
130  return boost::none;
131  }
132 
133  if (params_.use_fitness_threshold_rejection) {
134  const double fitness_score = icp.getFitnessScore();
135  if (fitness_score > params_.fitness_threshold) {
136  LogError("Icp: Fitness score too large: " << fitness_score << ".");
137  return boost::none;
138  }
139  }
140 
141  const Eigen::Isometry3d estimated_target_T_source(
142  Eigen::Isometry3f(icp.getFinalTransformation().matrix()).cast<double>());
143  SaveCorrespondences(icp, source_cloud_with_normals, result);
144  const auto covariance = PointToPlaneCovariance(correspondences_->source_points, correspondences_->target_normals,
145  estimated_target_T_source);
146  if (!covariance) {
147  LogError("Icp: Failed to get covariance.");
148  return boost::none;
149  }
150  return localization_common::PoseWithCovariance(estimated_target_T_source, *covariance);
151 }
152 
153 template <typename PointType>
154 boost::optional<localization_common::PoseWithCovariance> PointToPlaneICP<PointType>::RunDownsampledPointToPlaneICP(
155  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
156  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals, const double leaf_size,
157  const Eigen::Isometry3d& initial_target_T_source_estimate) {
158  typename pcl::PointCloud<PointType>::Ptr downsampled_source_cloud_with_normals =
159  DownsamplePointCloud<PointType>(source_cloud_with_normals, leaf_size);
160  typename pcl::PointCloud<PointType>::Ptr downsampled_target_cloud_with_normals =
161  DownsamplePointCloud<PointType>(target_cloud_with_normals, leaf_size);
162  return RunPointToPlaneICP(downsampled_source_cloud_with_normals, downsampled_target_cloud_with_normals,
163  initial_target_T_source_estimate);
164 }
165 
166 template <typename PointType>
167 boost::optional<localization_common::PoseWithCovariance> PointToPlaneICP<PointType>::RunCoarseToFinePointToPlaneICP(
168  const typename pcl::PointCloud<PointType>::Ptr source_cloud_with_normals,
169  const typename pcl::PointCloud<PointType>::Ptr target_cloud_with_normals,
170  const Eigen::Isometry3d& initial_target_T_source_estimate) {
171  boost::optional<localization_common::PoseWithCovariance> estimated_target_T_source =
172  localization_common::PoseWithCovariance(initial_target_T_source_estimate, Eigen::Matrix<double, 6, 6>());
173  for (int i = 0; i < params_.num_coarse_to_fine_levels; ++i) {
174  // Final iteration and no downsampling enabled for last iteration
175  if (i == params_.num_coarse_to_fine_levels - 1 && !params_.downsample_last_coarse_to_fine_iteration) {
176  estimated_target_T_source =
177  RunPointToPlaneICP(source_cloud_with_normals, target_cloud_with_normals, estimated_target_T_source->pose);
178  } else { // Downsampled for other iterations
179  const double leaf_size =
180  static_cast<double>(params_.num_coarse_to_fine_levels - i) * params_.coarse_to_fine_final_leaf_size;
181  estimated_target_T_source = RunDownsampledPointToPlaneICP(source_cloud_with_normals, target_cloud_with_normals,
182  leaf_size, estimated_target_T_source->pose);
183  }
184  if (!estimated_target_T_source) {
185  LogWarning("RunCoarseToFinePointToPlaneICP: Failed to get relative transform.");
186  return boost::none;
187  }
188  }
189  return estimated_target_T_source;
190 }
191 
192 template <typename PointType>
193 void PointToPlaneICP<PointType>::SaveCorrespondences(
194  const pcl::IterativeClosestPointWithNormals<PointType, PointType>& icp,
195  const typename pcl::PointCloud<PointType>::Ptr source_cloud,
196  const typename pcl::PointCloud<PointType>::Ptr source_cloud_transformed) {
197  icp.correspondence_estimation_->setInputSource(source_cloud_transformed);
198  pcl::Correspondences pcl_correspondences;
199  // Assumes normals for input source aren't needed and there are no correspondence rejectors added to ICP object
200  icp.correspondence_estimation_->determineCorrespondences(pcl_correspondences, icp.corr_dist_threshold_);
201  const auto& target_cloud = icp.target_;
202  FilterCorrespondences(*source_cloud, *target_cloud, pcl_correspondences);
203  std::vector<Eigen::Vector3d> source_points;
204  std::vector<Eigen::Vector3d> target_points;
205  std::vector<Eigen::Vector3d> target_normals;
206  for (const auto& correspondence : pcl_correspondences) {
207  const auto& pcl_source_point = (*source_cloud)[correspondence.index_query];
208  const auto& pcl_target_point = (*target_cloud)[correspondence.index_match];
209  source_points.emplace_back(Vector3d(pcl_source_point));
210  target_points.emplace_back(Vector3d(pcl_target_point));
211  target_normals.emplace_back(NormalVector3d(pcl_target_point));
212  }
213  correspondences_ = ICPCorrespondences(source_points, target_points, target_normals);
214 }
215 
216 template <typename PointType>
217 const boost::optional<ICPCorrespondences>& PointToPlaneICP<PointType>::correspondences() const {
218  return correspondences_;
219 }
220 } // namespace point_cloud_common
221 
222 #endif // POINT_CLOUD_COMMON_POINT_TO_PLANE_ICP_H_
point_cloud_common
Definition: icp_correspondences.h:25
utilities.h
pose_with_covariance.h
logger.h
point_cloud_common::PointToPlaneCovariance
boost::optional< Eigen::Matrix< double, 6, 6 > > PointToPlaneCovariance(const std::vector< Eigen::Vector3d > &source_points, const std::vector< Eigen::Vector3d > &target_normals, const Eigen::Isometry3d &target_T_source)
Definition: utilities.cc:122
pcl::registration::CorrespondenceRejectorSurfaceNormal2::Ptr
boost::shared_ptr< CorrespondenceRejectorSurfaceNormal2 > Ptr
Definition: correspondence_rejection_surface_normal2.h:69
LogError
#define LogError(msg)
Definition: logger.h:55
utilities.h
point_cloud_common::NormalVector3d
Eigen::Vector3d NormalVector3d(const PointType &point_with_normal)
Definition: utilities.h:333
localization_common::Isometry3d
Eigen::Isometry3d Isometry3d(const Eigen::Vector3d &translation, const RotationType &rotation)
Definition: utilities.h:226
point_cloud_common::PointToPlaneICP::PointToPlaneICP
PointToPlaneICP(const PointToPlaneICPParams &params)
Definition: point_to_plane_icp.h:76
point_cloud_common::Vector3d
Eigen::Vector3d Vector3d(const PointType &point)
Definition: utilities.h:328
point_to_plane_icp_params.h
time.h
localization_common::PoseWithCovariance
Definition: pose_with_covariance.h:28
point_cloud_common::FilterCorrespondences
void FilterCorrespondences(const typename pcl::PointCloud< PointType > &input_cloud, const typename pcl::PointCloud< PointType > &target_cloud, pcl::Correspondences &correspondences)
Definition: utilities.h:242
point_cloud_common::PointToPlaneICPParams
Definition: point_to_plane_icp_params.h:22
LogWarning
#define LogWarning(msg)
Definition: logger.h:48
point_cloud_common::PointToPlaneICP::correspondences
const boost::optional< ICPCorrespondences > & correspondences() const
Definition: point_to_plane_icp.h:217
icp_correspondences.h
point_cloud_common::PointToPlaneICP::ComputeRelativeTransform
boost::optional< localization_common::PoseWithCovariance > ComputeRelativeTransform(const typename pcl::PointCloud< PointType >::Ptr source_cloud_with_normals, const typename pcl::PointCloud< PointType >::Ptr target_cloud_with_normals, const Eigen::Isometry3d &initial_target_T_source_estimate=Eigen::Isometry3d::Identity())
Definition: point_to_plane_icp.h:79
correspondence_rejection_surface_normal2.h
transformation_estimation_symmetric_point_to_plane_lls.h
point_cloud_common::PointToPlaneICP
Definition: point_to_plane_icp.h:45
pcl::registration::CorrespondenceRejectorSurfaceNormal2
Definition: correspondence_rejection_surface_normal2.h:63