1 // This file is part of OpenMVG, an Open Multiple View Geometry C++ library.
2 
3 // Copyright (c) 2015 Pierre MOULON.
4 
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
9 #include "openMVG/sfm/pipelines/localization/SfM_Localizer.hpp"
10 
11 #include "openMVG/cameras/Camera_Common.hpp"
12 #include "openMVG/cameras/Camera_Intrinsics.hpp"
13 #include "openMVG/cameras/Camera_Pinhole.hpp"
14 #include "openMVG/multiview/solver_resection_kernel.hpp"
15 #include "openMVG/multiview/solver_resection_p3p.hpp"
16 #include "openMVG/multiview/solver_resection_up2p_kukelova.hpp"
17 #include "openMVG/sfm/sfm_data.hpp"
18 #include "openMVG/sfm/sfm_data_BA.hpp"
19 #include "openMVG/sfm/sfm_data_BA_ceres.hpp"
20 #include "openMVG/sfm/sfm_landmark.hpp"
21 #include "openMVG/robust_estimation/robust_estimator_ACRansac.hpp"
22 #include "openMVG/robust_estimation/robust_estimator_ACRansacKernelAdaptator.hpp"
23 #include "openMVG/system/logger.hpp"
24 
25 #include <memory>
26 #include <utility>
27 
28 namespace openMVG
29 {
30 /// Pose/Resection Kernel adapter for the A contrario model estimator with
31 ///  known camera intrinsics.
32 template <typename SolverArg,
33   typename ModelArg = Mat34>
34 class ACKernelAdaptorResection_Intrinsics
35 {
36 public:
37   using Solver = SolverArg;
38   using Model = ModelArg;
39 
ACKernelAdaptorResection_Intrinsics(const Mat & x2d,const Mat & x3D,const cameras::IntrinsicBase * camera)40   ACKernelAdaptorResection_Intrinsics
41   (
42     const Mat & x2d, // Undistorted 2d feature_point location
43     const Mat & x3D, // 3D corresponding points
44     const cameras::IntrinsicBase * camera
45   ):x2d_(x2d),
46     x3D_(x3D),
47     logalpha0_(log10(M_PI)),
48     N1_(Mat3::Identity()),
49     camera_(camera)
50   {
51     N1_.diagonal().head(2) *= camera->imagePlane_toCameraPlaneError(1.0);
52     assert(2 == x2d_.rows());
53     assert(3 == x3D_.rows());
54     assert(x2d_.cols() == x3D_.cols());
55     bearing_vectors_= camera->operator()(x2d_);
56   }
57 
58   enum { MINIMUM_SAMPLES = Solver::MINIMUM_SAMPLES };
59   enum { MAX_MODELS = Solver::MAX_MODELS };
60 
Fit(const std::vector<uint32_t> & samples,std::vector<Model> * models) const61   void Fit(const std::vector<uint32_t> &samples, std::vector<Model> *models) const {
62     Solver::Solve(ExtractColumns(bearing_vectors_, samples), // bearing vectors
63                   ExtractColumns(x3D_, samples), // 3D points
64                   models); // Found model hypothesis
65   }
66 
Errors(const Model & model,std::vector<double> & vec_errors) const67   void Errors(const Model & model, std::vector<double> & vec_errors) const
68   {
69     // Convert the found model into a Pose3
70     const Vec3 t = model.block(0, 3, 3, 1);
71     const geometry::Pose3 pose(model.block(0, 0, 3, 3),
72                                - model.block(0, 0, 3, 3).transpose() * t);
73 
74     vec_errors.resize(x2d_.cols());
75 
76     const bool ignore_distortion = true; // We ignore distortion since we are using undistorted bearing vector as input
77 
78     for (Mat::Index sample = 0; sample < x2d_.cols(); ++sample)
79     {
80       vec_errors[sample] = (camera_->residual(pose(x3D_.col(sample)),
81                               x2d_.col(sample),
82                               ignore_distortion) * N1_(0,0)).squaredNorm();
83     }
84   }
85 
NumSamples() const86   size_t NumSamples() const { return x2d_.cols(); }
87 
Unnormalize(Model * model) const88   void Unnormalize(Model * model) const {
89   }
90 
logalpha0() const91   double logalpha0() const {return logalpha0_;}
multError() const92   double multError() const {return 1.0;} // point to point error
normalizer1() const93   Mat3 normalizer1() const {return Mat3::Identity();}
normalizer2() const94   Mat3 normalizer2() const {return N1_;}
unormalizeError(double val) const95   double unormalizeError(double val) const {return sqrt(val) / N1_(0,0);}
96 
97 private:
98   Mat x2d_, bearing_vectors_;
99   const Mat & x3D_;
100   Mat3 N1_;
101   double logalpha0_;  // Alpha0 is used to make the error adaptive to the image size
102   const cameras::IntrinsicBase * camera_;   // Intrinsic camera parameter
103 };
104 } // namespace openMVG
105 
106 namespace openMVG {
107 namespace sfm {
108 
Localize(const resection::SolverType & solver_type,const Pair & image_size,const cameras::IntrinsicBase * optional_intrinsics,Image_Localizer_Match_Data & resection_data,geometry::Pose3 & pose)109   bool SfM_Localizer::Localize
110   (
111     const resection::SolverType & solver_type,
112     const Pair & image_size,
113     const cameras::IntrinsicBase * optional_intrinsics,
114     Image_Localizer_Match_Data & resection_data,
115     geometry::Pose3 & pose
116   )
117   {
118     // --
119     // Compute the camera pose (resectioning)
120     // --
121     Mat34 P;
122     resection_data.vec_inliers.clear();
123 
124     // Setup the admissible upper bound residual error
125     const double dPrecision =
126       resection_data.error_max == std::numeric_limits<double>::infinity() ?
127       std::numeric_limits<double>::infinity() :
128       Square(resection_data.error_max);
129 
130     size_t MINIMUM_SAMPLES = 0;
131 
132     switch (solver_type)
133     {
134       case resection::SolverType::DLT_6POINTS:
135       {
136         //--
137         // Classic resection (try to compute the entire P matrix)
138         using SolverType = openMVG::resection::kernel::SixPointResectionSolver;
139         MINIMUM_SAMPLES = SolverType::MINIMUM_SAMPLES;
140 
141         using KernelType =
142           openMVG::robust::ACKernelAdaptorResection<
143             SolverType,
144             resection::SquaredPixelReprojectionError,
145             openMVG::robust::UnnormalizerResection,
146             Mat34>;
147 
148         KernelType kernel(resection_data.pt2D, image_size.first, image_size.second,
149           resection_data.pt3D);
150         // Robust estimation of the pose and its precision
151         const std::pair<double,double> ACRansacOut =
152           openMVG::robust::ACRANSAC(kernel,
153                                     resection_data.vec_inliers,
154                                     resection_data.max_iteration,
155                                     &P,
156                                     dPrecision,
157                                     true);
158         // Update the upper bound precision of the model found by AC-RANSAC
159         resection_data.error_max = ACRansacOut.first;
160       }
161       break;
162       case resection::SolverType::P3P_NORDBERG_ECCV18:
163       {
164         if (!optional_intrinsics)
165         {
166           std::cerr << "Intrinsic data is required for P3P solvers." << std::endl;
167           return false;
168         }
169         //--
170         // Since the intrinsic data is known, compute only the pose
171         using SolverType = openMVG::euclidean_resection::P3PSolver_Nordberg;
172         MINIMUM_SAMPLES = SolverType::MINIMUM_SAMPLES;
173 
174         using KernelType =
175           ACKernelAdaptorResection_Intrinsics<
176             SolverType,
177             Mat34>;
178 
179         KernelType kernel(resection_data.pt2D, resection_data.pt3D, optional_intrinsics);
180         // Robust estimation of the pose matrix and its precision
181         const auto ACRansacOut =
182           openMVG::robust::ACRANSAC(kernel,
183                                     resection_data.vec_inliers,
184                                     resection_data.max_iteration,
185                                     &P,
186                                     dPrecision,
187                                     true);
188         // Update the upper bound precision of the model found by AC-RANSAC
189         resection_data.error_max = ACRansacOut.first;
190       }
191       break;
192       case resection::SolverType::P3P_KE_CVPR17:
193       {
194         if (!optional_intrinsics)
195         {
196           OPENMVG_LOG_ERROR << "Intrinsic data is required for P3P solvers.";
197           return false;
198         }
199         //--
200         // Since the intrinsic data is known, compute only the pose
201         using SolverType = openMVG::euclidean_resection::P3PSolver_Ke;
202         MINIMUM_SAMPLES = SolverType::MINIMUM_SAMPLES;
203 
204         using KernelType =
205           ACKernelAdaptorResection_Intrinsics<
206             SolverType,
207             Mat34>;
208 
209         KernelType kernel(resection_data.pt2D, resection_data.pt3D, optional_intrinsics);
210         // Robust estimation of the pose matrix and its precision
211         const auto ACRansacOut =
212           openMVG::robust::ACRANSAC(kernel,
213                                     resection_data.vec_inliers,
214                                     resection_data.max_iteration,
215                                     &P,
216                                     dPrecision,
217                                     true);
218         // Update the upper bound precision of the model found by AC-RANSAC
219         resection_data.error_max = ACRansacOut.first;
220       }
221       break;
222       case resection::SolverType::P3P_KNEIP_CVPR11:
223       {
224         if (!optional_intrinsics)
225         {
226           OPENMVG_LOG_ERROR << "Intrinsic data is required for P3P solvers.";
227           return false;
228         }
229         //--
230         // Since the intrinsic data is known, compute only the pose
231         using SolverType = openMVG::euclidean_resection::P3PSolver_Kneip;
232         MINIMUM_SAMPLES = SolverType::MINIMUM_SAMPLES;
233 
234         using KernelType =
235           ACKernelAdaptorResection_Intrinsics<
236             SolverType,
237             Mat34>;
238 
239         KernelType kernel(resection_data.pt2D, resection_data.pt3D, optional_intrinsics);
240 
241         // Robust estimation of the pose matrix and its precision
242         const auto ACRansacOut =
243           openMVG::robust::ACRANSAC(kernel,
244                                     resection_data.vec_inliers,
245                                     resection_data.max_iteration,
246                                     &P,
247                                     dPrecision,
248                                     true);
249         // Update the upper bound precision of the model found by AC-RANSAC
250         resection_data.error_max = ACRansacOut.first;
251       }
252       break;
253       case resection::SolverType::UP2P_KUKELOVA_ACCV10:
254       {
255         if (!optional_intrinsics)
256         {
257           std::cerr << "Intrinsic data is required for P3P solvers." << std::endl;
258           return false;
259         }
260         //--
261         // Since the intrinsic data is known, compute only the pose
262         using SolverType = openMVG::euclidean_resection::UP2PSolver_Kukelova;
263         MINIMUM_SAMPLES = SolverType::MINIMUM_SAMPLES;
264 
265         using KernelType =
266           ACKernelAdaptorResection_Intrinsics<
267             SolverType,
268             Mat34>;
269 
270         KernelType kernel(resection_data.pt2D, resection_data.pt3D, optional_intrinsics);
271 
272         // Robust estimation of the pose matrix and its precision
273         const auto ACRansacOut =
274           openMVG::robust::ACRANSAC(kernel,
275                                     resection_data.vec_inliers,
276                                     resection_data.max_iteration,
277                                     &P,
278                                     dPrecision,
279                                     true);
280         // Update the upper bound precision of the model found by AC-RANSAC
281         resection_data.error_max = ACRansacOut.first;
282       }
283       break;
284       default:
285       {
286         OPENMVG_LOG_ERROR << "Unknown absolute pose solver type.";
287         return false;
288       }
289     }
290 
291     // Test if the mode support some points (more than those required for estimation)
292     const bool bResection = (resection_data.vec_inliers.size() > 2.5 * MINIMUM_SAMPLES);
293 
294     if (bResection)
295     {
296       resection_data.projection_matrix = P;
297       Mat3 K, R;
298       Vec3 t;
299       KRt_From_P(P, &K, &R, &t);
300       pose = geometry::Pose3(R, -R.transpose() * t);
301     }
302 
303     OPENMVG_LOG_INFO << "\n"
304       << "-------------------------------" << "\n"
305       << "-- Robust Resection statistics: " << "\n"
306       << "-- Resection status: " << bResection << "\n"
307       << "-- #Points used for Resection: " << resection_data.pt2D.cols() << "\n"
308       << "-- #Points validated by robust Resection: " << resection_data.vec_inliers.size() << "\n"
309       << "-- Threshold: " << resection_data.error_max << "\n"
310       << "-------------------------------";
311 
312     return bResection;
313   }
314 
RefinePose(cameras::IntrinsicBase * intrinsics,geometry::Pose3 & pose,Image_Localizer_Match_Data & matching_data,bool b_refine_pose,bool b_refine_intrinsic)315   bool SfM_Localizer::RefinePose
316   (
317     cameras::IntrinsicBase * intrinsics,
318     geometry::Pose3 & pose,
319     Image_Localizer_Match_Data & matching_data,
320     bool b_refine_pose,
321     bool b_refine_intrinsic
322   )
323   {
324     if (!b_refine_pose && !b_refine_intrinsic)
325     {
326       // Nothing to do (There is no parameter to refine)
327       return false;
328     }
329 
330     // Setup a tiny SfM scene with the corresponding 2D-3D data
331     SfM_Data sfm_data;
332     // view
333     sfm_data.views.insert({0, std::make_shared<View>("",0, 0, 0)});
334     // pose
335     sfm_data.poses[0] = pose;
336     // intrinsic
337     std::shared_ptr<cameras::IntrinsicBase> shared_intrinsics(intrinsics->clone());
338     sfm_data.intrinsics[0] = shared_intrinsics;
339     // structure data (2D-3D correspondences)
340     for (size_t i = 0; i < matching_data.vec_inliers.size(); ++i)
341     {
342       const size_t idx = matching_data.vec_inliers[i];
343       Landmark landmark;
344       landmark.X = matching_data.pt3D.col(idx);
345       landmark.obs[0] = Observation(matching_data.pt2D.col(idx), UndefinedIndexT);
346       sfm_data.structure[i] = std::move(landmark);
347     }
348 
349     // Configure BA options (refine the intrinsic and the pose parameter only if requested)
350     const Optimize_Options ba_refine_options
351     (
352       (b_refine_intrinsic) ? cameras::Intrinsic_Parameter_Type::ADJUST_ALL : cameras::Intrinsic_Parameter_Type::NONE,
353       (b_refine_pose) ? Extrinsic_Parameter_Type::ADJUST_ALL : Extrinsic_Parameter_Type::NONE,
354       Structure_Parameter_Type::NONE // STRUCTURE must remain constant
355     );
356     Bundle_Adjustment_Ceres bundle_adjustment_obj;
357     const bool b_BA_Status = bundle_adjustment_obj.Adjust(
358       sfm_data,
359       ba_refine_options);
360     if (b_BA_Status)
361     {
362       pose = sfm_data.poses[0];
363       if (b_refine_intrinsic)
364         intrinsics->updateFromParams(shared_intrinsics->getParams());
365     }
366 
367     return b_BA_Status;
368   }
369 
370 } // namespace sfm
371 } // namespace openMVG
372