NASA Astrobee Robot Software  0.19.1
Flight software for the Astrobee robots operating inside the International Space Station.
pose_estimation.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 VISION_COMMON_POSE_ESTIMATION_H_
19 #define VISION_COMMON_POSE_ESTIMATION_H_
20 
31 
32 #include <Eigen/Geometry>
33 
34 #include <ceres/ceres.h>
35 #include <ceres/solver.h>
36 
37 #include <opencv2/calib3d/calib3d.hpp>
38 #include <opencv2/core/eigen.hpp>
39 #include <opencv2/imgcodecs.hpp>
40 #include <opencv2/imgproc.hpp>
41 #include <opencv2/opencv.hpp>
42 
43 #include <boost/optional.hpp>
44 
45 #include <algorithm>
46 #include <string>
47 #include <unordered_set>
48 #include <utility>
49 #include <vector>
50 
51 namespace vision_common {
52 void UndistortedPnP(const std::vector<cv::Point2d>& undistorted_image_points, const std::vector<cv::Point3d>& points_3d,
53  const cv::Mat& intrinsics, const int pnp_method, cv::Mat& rotation, cv::Mat& translation);
54 
55 std::vector<int> RandomNIndices(const int num_possible_indices, const int num_sampled_indices);
56 
57 template <typename DISTORTER>
58 int Inliers(const std::vector<Eigen::Vector2d>& image_points, const std::vector<Eigen::Vector3d>& points_3d,
59  const Eigen::Matrix3d& intrinsics, const Eigen::VectorXd& distortion,
60  const Eigen::Isometry3d& pose_estimate, const double max_inlier_threshold,
61  boost::optional<std::vector<int>&> inliers = boost::none);
62 
63 template <typename T>
64 std::vector<T> SampledValues(const std::vector<T>& values, const std::vector<int>& indices);
65 
66 template <typename DISTORTER>
67 boost::optional<PoseWithCovarianceAndInliers> RansacPnP(const std::vector<Eigen::Vector2d>& image_points,
68  const std::vector<Eigen::Vector3d>& points_3d,
69  const Eigen::Matrix3d& intrinsics,
70  const Eigen::VectorXd& distortion,
71  const RansacPnPParams& params);
72 
73 template <typename DISTORTER>
74 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimate(const std::vector<Eigen::Vector2d>& image_points,
75  const std::vector<Eigen::Vector3d>& points_3d,
76  const Eigen::Vector2d& focal_lengths,
77  const Eigen::Vector2d& principal_points,
78  const Eigen::VectorXd& distortion,
79  const ReprojectionPoseEstimateParams& params);
80 
81 template <typename DISTORTER>
82 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimate(const std::vector<Eigen::Vector2d>& image_points,
83  const std::vector<Eigen::Vector3d>& points_3d,
84  const Eigen::Matrix3d& intrinsics,
85  const Eigen::VectorXd& distortion,
86  const ReprojectionPoseEstimateParams& params);
87 
88 template <typename DISTORTER>
89 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimateWithInitialEstimate(
90  const std::vector<Eigen::Vector2d>& image_points, const std::vector<Eigen::Vector3d>& points_3d,
91  const Eigen::Vector2d& focal_lengths, const Eigen::Vector2d& principal_points, const Eigen::VectorXd& distortion,
92  const ReprojectionPoseEstimateParams& params, const Eigen::Isometry3d& initial_estimate,
93  const std::vector<int>& initial_inliers);
94 
95 template <typename DISTORTER>
96 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimateWithInitialEstimate(
97  const std::vector<Eigen::Vector2d>& image_points, const std::vector<Eigen::Vector3d>& points_3d,
98  const Eigen::Matrix3d& intrinsics, const Eigen::VectorXd& distortion, const ReprojectionPoseEstimateParams& params,
99  const Eigen::Isometry3d& initial_estimate, const std::vector<int>& initial_inliers);
100 
101 template <typename DISTORTER>
102 int Inliers(const std::vector<Eigen::Vector2d>& image_points, const std::vector<Eigen::Vector3d>& points_3d,
103  const Eigen::Matrix3d& intrinsics, const Eigen::VectorXd& distortion,
104  const Eigen::Isometry3d& pose_estimate, const double max_inlier_threshold,
105  boost::optional<std::vector<int>&> inliers) {
106  int num_inliers = 0;
107  for (int i = 0; i < static_cast<int>(image_points.size()); ++i) {
108  const Eigen::Vector2d& image_point = image_points[i];
109  const Eigen::Vector3d& point_3d = points_3d[i];
110  const Eigen::Vector3d transformed_point_3d = pose_estimate * point_3d;
111  const Eigen::Vector2d projected_image_point =
112  ProjectWithDistortion<DISTORTER>(transformed_point_3d, intrinsics, distortion);
113  const Eigen::Vector2d error = (image_point - projected_image_point);
114  const double error_norm = error.norm();
115  if (error_norm <= max_inlier_threshold) {
116  ++num_inliers;
117  if (inliers) inliers->emplace_back(i);
118  }
119  }
120  return num_inliers;
121 }
122 
123 template <typename T>
124 std::vector<T> SampledValues(const std::vector<T>& values, const std::vector<int>& indices) {
125  std::vector<T> sampled_values;
126  for (const int i : indices) {
127  sampled_values.emplace_back(values[i]);
128  }
129  return sampled_values;
130 }
131 
132 template <typename DISTORTER>
133 boost::optional<PoseWithCovarianceAndInliers> RansacPnP(const std::vector<Eigen::Vector2d>& image_points,
134  const std::vector<Eigen::Vector3d>& points_3d,
135  const Eigen::Matrix3d& intrinsics,
136  const Eigen::VectorXd& distortion,
137  const RansacPnPParams& params) {
138  if (image_points.size() < 4) {
139  LogError("RansacPnP: Too few matched points given.");
140  return boost::none;
141  }
142 
143  const DISTORTER distorter;
144  const std::vector<Eigen::Vector2d> undistorted_image_points =
145  distorter.Undistort(image_points, intrinsics, distortion);
146 
147  std::vector<cv::Point2d> undistorted_image_points_cv;
148  for (const auto& undistorted_image_point : undistorted_image_points) {
149  undistorted_image_points_cv.emplace_back(cv::Point2d(undistorted_image_point.x(), undistorted_image_point.y()));
150  }
151  std::vector<cv::Point3d> points_3d_cv;
152  for (const auto& point_3d : points_3d) {
153  points_3d_cv.emplace_back(point_3d.x(), point_3d.y(), point_3d.z());
154  }
155 
156  cv::Mat intrinsics_cv;
157  cv::eigen2cv(intrinsics, intrinsics_cv);
158  cv::Mat rodrigues_rotation_cv(3, 1, cv::DataType<double>::type, cv::Scalar(0));
159  cv::Mat translation_cv(3, 1, cv::DataType<double>::type, cv::Scalar(0));
160  int max_num_inliers = 0;
161  Eigen::Isometry3d best_pose_estimate;
162  // Use 4 values for P3P
163  // TODO(rsoussan): make this a param
164  constexpr int kNumSampledValues = 4;
165  for (int i = 0; i < params.num_iterations; ++i) {
166  const auto sampled_indices = RandomNIndices(image_points.size(), kNumSampledValues);
167  const auto sampled_undistorted_image_points = SampledValues(undistorted_image_points_cv, sampled_indices);
168  const auto sampled_points_3d = SampledValues(points_3d_cv, sampled_indices);
169  UndistortedPnP(sampled_undistorted_image_points, sampled_points_3d, intrinsics_cv, params.pnp_method,
170  rodrigues_rotation_cv, translation_cv);
171  const Eigen::Isometry3d pose_estimate = Isometry3d(rodrigues_rotation_cv, translation_cv);
172  const int num_inliers =
173  Inliers<DISTORTER>(image_points, points_3d, intrinsics, distortion, pose_estimate, params.max_inlier_threshold);
174  if (num_inliers > max_num_inliers) {
175  best_pose_estimate = pose_estimate;
176  max_num_inliers = num_inliers;
177  }
178  }
179 
180  if (max_num_inliers < params.min_num_inliers) {
181  LogError("RansacPnP: Failed to find pose with enough inliers.");
182  return boost::none;
183  }
184 
185  std::vector<int> inliers;
186  Inliers<DISTORTER>(image_points, points_3d, intrinsics, distortion, best_pose_estimate, params.max_inlier_threshold,
187  inliers);
188  // TODO(rsoussan): Get covariance for ransac pnp
189  return PoseWithCovarianceAndInliers(best_pose_estimate, Eigen::Matrix<double, 6, 6>::Identity(), inliers);
190 }
191 
192 template <typename DISTORTER>
193 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimate(const std::vector<Eigen::Vector2d>& image_points,
194  const std::vector<Eigen::Vector3d>& points_3d,
195  const Eigen::Vector2d& focal_lengths,
196  const Eigen::Vector2d& principal_points,
197  const Eigen::VectorXd& distortion,
198  const ReprojectionPoseEstimateParams& params) {
199  if (image_points.size() < 4) {
200  LogError("ReprojectionPoseEstimate: Too few matched points given.");
201  return boost::none;
202  }
203 
204  const Eigen::Matrix3d intrinsics = optimization_common::Intrinsics(focal_lengths, principal_points);
205  // Use RansacPnP for initial estimate since using identity transform can lead to image projection issues
206  // if any points_3d z values are 0.
207  const auto ransac_pnp_estimate =
208  RansacPnP<DISTORTER>(image_points, points_3d, intrinsics, distortion, params.ransac_pnp);
209  if (!ransac_pnp_estimate) {
210  LogError("ReprojectionPoseEstimate: Failed to get ransac pnp initial estimate.");
211  return boost::none;
212  }
213 
214  const int num_inliers = ransac_pnp_estimate->inliers.size();
215  if (num_inliers < params.ransac_pnp.min_num_inliers) {
216  LogError("ReprojectionPoseEstimate: Too few inliers found. Need " << params.ransac_pnp.min_num_inliers << ", got "
217  << num_inliers << ".");
218  return boost::none;
219  }
220 
221  return ReprojectionPoseEstimateWithInitialEstimate<DISTORTER>(
222  image_points, points_3d, focal_lengths, principal_points, distortion, params, ransac_pnp_estimate->pose,
223  ransac_pnp_estimate->inliers);
224 }
225 
226 template <typename DISTORTER>
227 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimateWithInitialEstimate(
228  const std::vector<Eigen::Vector2d>& image_points, const std::vector<Eigen::Vector3d>& points_3d,
229  const Eigen::Vector2d& focal_lengths, const Eigen::Vector2d& principal_points, const Eigen::VectorXd& distortion,
230  const ReprojectionPoseEstimateParams& params, const Eigen::Isometry3d& initial_estimate,
231  const std::vector<int>& initial_inliers) {
232  if (image_points.size() < 4) {
233  LogError("ReprojectionPoseEstimateWithInitialEstimate: Too few matched points given.");
234  return boost::none;
235  }
236 
237  const int num_initial_inliers = initial_inliers.size();
238  if (num_initial_inliers < params.ransac_pnp.min_num_inliers) {
239  LogError("ReprojectionPoseEstimateWithInitialEstimate: Too few inliers provided. Need "
240  << params.ransac_pnp.min_num_inliers << ", got " << num_initial_inliers << ".");
241  return boost::none;
242  }
243 
244  // TODO(rsoussan): Get covariance from ransac pnp
245  if (!params.optimize_estimate)
246  return PoseWithCovarianceAndInliers(initial_estimate, Eigen::Matrix<double, 6, 6>::Identity(), initial_inliers);
247 
248  ceres::Problem problem;
249  optimization_common::AddConstantParameterBlock(2, focal_lengths.data(), problem);
250  optimization_common::AddConstantParameterBlock(2, principal_points.data(), problem);
251  optimization_common::AddConstantParameterBlock(DISTORTER::kNumParams, distortion.data(), problem);
252 
253  Eigen::Matrix<double, 6, 1> pose_estimate_vector = optimization_common::VectorFromIsometry3d(initial_estimate);
254  problem.AddParameterBlock(pose_estimate_vector.data(), 6);
255 
256  for (int i = 0; i < num_initial_inliers; ++i) {
257  const int inlier_index = initial_inliers[i];
259  image_points[inlier_index], points_3d[inlier_index], pose_estimate_vector,
260  const_cast<Eigen::Vector2d&>(focal_lengths), const_cast<Eigen::Vector2d&>(principal_points),
261  const_cast<Eigen::VectorXd&>(distortion), problem, 1, params.optimization.huber_loss);
262  }
263 
264  ceres::Solver::Summary summary;
265  ceres::Solve(params.optimization.solver_options, &problem, &summary);
266  const Eigen::Isometry3d optimized_estimate = optimization_common::Isometry3d(pose_estimate_vector);
267  if (params.optimization.verbose) {
268  LogError("ReprojectionPoseEstimateWithInitialEstimate: Initial estimate: " << std::endl
269  << initial_estimate.matrix() << std::endl
270  << "Optimized estimate: " << std::endl
271  << optimized_estimate.matrix()
272  << std::endl
273  << "Summary: " << std::endl
274  << summary.FullReport());
275  }
276  if (!summary.IsSolutionUsable()) {
277  LogError("ReprojectionPoseEstimateWithInitialEstimate: Failed to find solution.");
278  return boost::none;
279  }
280 
281  ceres::Covariance::Options options;
282  ceres::Covariance covariance(options);
283  std::vector<std::pair<const double*, const double*>> covariance_blocks;
284  covariance_blocks.push_back(std::make_pair(pose_estimate_vector.data(), pose_estimate_vector.data()));
285  if (!covariance.Compute(covariance_blocks, &problem)) {
286  LogError("ReprojectionPoseEstimateWithInitialEstimate: Failed to compute covariances.");
287  return boost::none;
288  }
289 
290  localization_common::PoseCovariance pose_covariance;
291  covariance.GetCovarianceBlock(pose_estimate_vector.data(), pose_estimate_vector.data(), pose_covariance.data());
292 
293  std::vector<int> inliers;
294  const Eigen::Matrix3d intrinsics = optimization_common::Intrinsics(focal_lengths, principal_points);
295  Inliers<DISTORTER>(image_points, points_3d, intrinsics, distortion, optimized_estimate, params.max_inlier_threshold,
296  inliers);
297  return PoseWithCovarianceAndInliers(optimized_estimate, pose_covariance, inliers);
298 }
299 
300 template <typename DISTORTER>
301 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimate(const std::vector<Eigen::Vector2d>& image_points,
302  const std::vector<Eigen::Vector3d>& points_3d,
303  const Eigen::Matrix3d& intrinsics,
304  const Eigen::VectorXd& distortion,
305  const ReprojectionPoseEstimateParams& params) {
306  const auto focal_lengths = FocalLengths(intrinsics);
307  const auto principal_points = PrincipalPoints(intrinsics);
308  return ReprojectionPoseEstimate<DISTORTER>(image_points, points_3d, focal_lengths, principal_points, distortion,
309  params);
310 }
311 
312 template <typename DISTORTER>
313 boost::optional<PoseWithCovarianceAndInliers> ReprojectionPoseEstimateWithInitialEstimate(
314  const std::vector<Eigen::Vector2d>& image_points, const std::vector<Eigen::Vector3d>& points_3d,
315  const Eigen::Matrix3d& intrinsics, const Eigen::VectorXd& distortion, const ReprojectionPoseEstimateParams& params,
316  const Eigen::Isometry3d& initial_estimate, const std::vector<int>& initial_inliers) {
317  const auto focal_lengths = FocalLengths(intrinsics);
318  const auto principal_points = PrincipalPoints(intrinsics);
319  return ReprojectionPoseEstimateWithInitialEstimate<DISTORTER>(
320  image_points, points_3d, focal_lengths, principal_points, distortion, params, initial_estimate, initial_inliers);
321 }
322 } // namespace vision_common
323 #endif // VISION_COMMON_POSE_ESTIMATION_H_
optimization_common::Isometry3d
Eigen::Isometry3d Isometry3d(const Eigen::Matrix< double, 6, 1 > &isometry_vector)
Definition: utilities.cc:39
optimization_common::AddConstantParameterBlock
void AddConstantParameterBlock(const int num_parameters, double *const parameters, ceres::Problem &problem)
Definition: utilities.cc:60
cv::Point_
Definition: reprojection.h:37
node_adders::Covariance
gtsam::Matrix Covariance(const gtsam::SharedNoiseModel &robust_gaussian_noise)
Definition: utilities.cc:22
vision_common::FocalLengths
Eigen::Vector2d FocalLengths(const Eigen::Matrix3d &intrinsics)
Definition: utilities.cc:36
vision_common::ReprojectionPoseEstimate
boost::optional< PoseWithCovarianceAndInliers > ReprojectionPoseEstimate(const std::vector< Eigen::Vector2d > &image_points, const std::vector< Eigen::Vector3d > &points_3d, const Eigen::Vector2d &focal_lengths, const Eigen::Vector2d &principal_points, const Eigen::VectorXd &distortion, const ReprojectionPoseEstimateParams &params)
Definition: pose_estimation.h:193
vision_common::SampledValues
std::vector< T > SampledValues(const std::vector< T > &values, const std::vector< int > &indices)
Definition: pose_estimation.h:124
pose_with_covariance.h
logger.h
vision_common
Definition: brisk_feature_detector_and_matcher.h:25
vision_common::PrincipalPoints
Eigen::Vector2d PrincipalPoints(const Eigen::Matrix3d &intrinsics)
Definition: utilities.cc:40
vision_common::RansacPnPParams::pnp_method
int pnp_method
Definition: ransac_pnp_params.h:29
LogError
#define LogError(msg)
Definition: logger.h:55
utilities.h
vision_common::RansacPnPParams::num_iterations
int num_iterations
Definition: ransac_pnp_params.h:26
optimization_common::OptimizationParams::verbose
bool verbose
Definition: optimization_params.h:26
vision_common::Isometry3d
Eigen::Isometry3d Isometry3d(const cv::Mat &rodrigues_rotation_cv, const cv::Mat &translation_cv)
Definition: utilities.cc:58
vision_common::RansacPnPParams::min_num_inliers
int min_num_inliers
Definition: ransac_pnp_params.h:27
localization_common::PoseCovariance
Eigen::Matrix< double, 6, 6 > PoseCovariance
Definition: pose_with_covariance.h:27
vision_common::ReprojectionPoseEstimateParams
Definition: reprojection_pose_estimate_params.h:25
vision_common::UndistortedPnP
void UndistortedPnP(const std::vector< cv::Point2d > &undistorted_image_points, const std::vector< cv::Point3d > &points_3d, const cv::Mat &intrinsics, const int pnp_method, cv::Mat &rotation, cv::Mat &translation)
Definition: pose_estimation.cc:31
optimization_common::Intrinsics
Eigen::Matrix3d Intrinsics(const Eigen::Vector2d &focal_lengths, const Eigen::Vector2d &principal_points)
Definition: utilities.cc:45
localization_common::Isometry3d
Eigen::Isometry3d Isometry3d(const Eigen::Vector3d &translation, const RotationType &rotation)
Definition: utilities.h:226
vision_common::ReprojectionPoseEstimateWithInitialEstimate
boost::optional< PoseWithCovarianceAndInliers > ReprojectionPoseEstimateWithInitialEstimate(const std::vector< Eigen::Vector2d > &image_points, const std::vector< Eigen::Vector3d > &points_3d, const Eigen::Vector2d &focal_lengths, const Eigen::Vector2d &principal_points, const Eigen::VectorXd &distortion, const ReprojectionPoseEstimateParams &params, const Eigen::Isometry3d &initial_estimate, const std::vector< int > &initial_inliers)
Definition: pose_estimation.h:227
vision_common::Inliers
int Inliers(const std::vector< Eigen::Vector2d > &image_points, const std::vector< Eigen::Vector3d > &points_3d, const Eigen::Matrix3d &intrinsics, const Eigen::VectorXd &distortion, const Eigen::Isometry3d &pose_estimate, const double max_inlier_threshold, boost::optional< std::vector< int > & > inliers=boost::none)
Definition: pose_estimation.h:102
point_cloud_common::Vector3d
Eigen::Vector3d Vector3d(const PointType &point)
Definition: utilities.h:328
residuals.h
vision_common::ReprojectionPoseEstimateParams::max_inlier_threshold
double max_inlier_threshold
Definition: reprojection_pose_estimate_params.h:29
pose_with_covariance_and_inliers.h
optimization_common::VectorFromIsometry3d
Eigen::Matrix< double, 6, 1 > VectorFromIsometry3d(const Eigen::Isometry3d &isometry_3d)
Definition: utilities.cc:22
vision_common::PoseWithCovarianceAndInliers
Definition: pose_with_covariance_and_inliers.h:28
optimization_common::OptimizationParams::huber_loss
double huber_loss
Definition: optimization_params.h:27
vision_common::RansacPnPParams::max_inlier_threshold
double max_inlier_threshold
Definition: ransac_pnp_params.h:25
utilities.h
reprojection_pose_estimate_params.h
vision_common::RandomNIndices
std::vector< int > RandomNIndices(const int num_possible_indices, const int num_sampled_indices)
Definition: pose_estimation.cc:38
vision_common::RansacPnPParams
Definition: ransac_pnp_params.h:24
eigen_vectors.h
vision_common::RansacPnP
boost::optional< PoseWithCovarianceAndInliers > RansacPnP(const std::vector< Eigen::Vector2d > &image_points, const std::vector< Eigen::Vector3d > &points_3d, const Eigen::Matrix3d &intrinsics, const Eigen::VectorXd &distortion, const RansacPnPParams &params)
Definition: pose_estimation.h:133
error
uint8_t error
Definition: signal_lights.h:89
utilities.h
optimization_common::ReprojectionError::AddCostFunction
static void AddCostFunction(const Eigen::Vector2d &image_point, const Eigen::Vector3d &point_3d, Eigen::Matrix< double, 6, 1 > &camera_T_target, Eigen::Vector2d &focal_lengths, Eigen::Vector2d &principal_points, Eigen::VectorXd &distortion, ceres::Problem &problem, const double scale_factor=1, const double huber_threshold=1.345)
Definition: residuals.h:201
vision_common::ReprojectionPoseEstimateParams::ransac_pnp
RansacPnPParams ransac_pnp
Definition: reprojection_pose_estimate_params.h:27
vision_common::ReprojectionPoseEstimateParams::optimize_estimate
bool optimize_estimate
Definition: reprojection_pose_estimate_params.h:28
vision_common::ReprojectionPoseEstimateParams::optimization
optimization_common::OptimizationParams optimization
Definition: reprojection_pose_estimate_params.h:26
optimization_common::OptimizationParams::solver_options
ceres::Solver::Options solver_options
Definition: optimization_params.h:25
ransac_pnp_params.h