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/global/GlobalSfM_translation_averaging.hpp"
10 
11 #ifdef OPENMVG_USE_OPENMP
12 #include <omp.h>
13 #endif
14 
15 #include "openMVG/graph/graph.hpp"
16 #include "openMVG/types.hpp"
17 #include "openMVG/cameras/Camera_Intrinsics.hpp"
18 #include "openMVG/cameras/Camera_Pinhole.hpp"
19 #include "openMVG/linearProgramming/linearProgramming.hpp"
20 #include "openMVG/multiview/essential.hpp"
21 #include "openMVG/multiview/translation_averaging_common.hpp"
22 #include "openMVG/multiview/translation_averaging_solver.hpp"
23 #include "openMVG/robust_estimation/robust_estimator_ACRansac.hpp"
24 #include "openMVG/sfm/pipelines/global/mutexSet.hpp"
25 #include "openMVG/sfm/pipelines/global/sfm_global_reindex.hpp"
26 #include "openMVG/sfm/pipelines/global/triplet_t_ACRansac_kernelAdaptator.hpp"
27 #include "openMVG/sfm/pipelines/sfm_features_provider.hpp"
28 #include "openMVG/sfm/pipelines/sfm_matches_provider.hpp"
29 #include "openMVG/sfm/sfm_data_io.hpp"
30 #include "openMVG/sfm/sfm_filters.hpp"
31 #include "openMVG/stl/stl.hpp"
32 #include "openMVG/system/loggerprogress.hpp"
33 #include "openMVG/system/timer.hpp"
34 
35 #include <vector>
36 
37 namespace openMVG{
38 namespace sfm{
39 
40 using namespace openMVG::cameras;
41 using namespace openMVG::geometry;
42 using namespace openMVG::matching;
43 
44 /// Use features in normalized camera frames
Run(ETranslationAveragingMethod eTranslationAveragingMethod,openMVG::sfm::SfM_Data & sfm_data,const openMVG::sfm::Features_Provider * features_provider,const openMVG::sfm::Matches_Provider * matches_provider,const Hash_Map<IndexT,Mat3> & map_globalR,matching::PairWiseMatches & tripletWise_matches)45 bool GlobalSfM_Translation_AveragingSolver::Run
46 (
47   ETranslationAveragingMethod eTranslationAveragingMethod,
48   openMVG::sfm::SfM_Data & sfm_data,
49   const openMVG::sfm::Features_Provider * features_provider,
50   const openMVG::sfm::Matches_Provider * matches_provider,
51   const Hash_Map<IndexT, Mat3> & map_globalR,
52   matching::PairWiseMatches & tripletWise_matches
53 )
54 {
55   // Compute the relative translations and save them to vec_initialRijTijEstimates:
56   Compute_translations(
57     sfm_data,
58     features_provider,
59     matches_provider,
60     map_globalR,
61     tripletWise_matches);
62 
63   const bool b_translation = Translation_averaging(
64     eTranslationAveragingMethod,
65     sfm_data,
66     map_globalR);
67 
68   // Filter matches to keep only them link to view that have valid poses
69   // (necessary since multiple components exists before translation averaging)
70   std::set<IndexT> valid_view_ids;
71   for (const auto & view : sfm_data.GetViews())
72   {
73     if (sfm_data.IsPoseAndIntrinsicDefined(view.second.get()))
74       valid_view_ids.insert(view.first);
75   }
76   KeepOnlyReferencedElement(valid_view_ids, tripletWise_matches);
77 
78   return b_translation;
79 }
80 
Translation_averaging(ETranslationAveragingMethod eTranslationAveragingMethod,sfm::SfM_Data & sfm_data,const Hash_Map<IndexT,Mat3> & map_globalR)81 bool GlobalSfM_Translation_AveragingSolver::Translation_averaging(
82   ETranslationAveragingMethod eTranslationAveragingMethod,
83   sfm::SfM_Data & sfm_data,
84   const Hash_Map<IndexT, Mat3> & map_globalR)
85 {
86   //-------------------
87   //-- GLOBAL TRANSLATIONS ESTIMATION from initial triplets t_ij guess
88   //-------------------
89 
90   // Keep the largest Biedge connected component graph of relative translations
91   Pair_Set pairs;
92   for (const openMVG::RelativeInfo_Vec & iter : vec_relative_motion_)
93   {
94     for (const relativeInfo & rel : iter)
95     {
96       pairs.insert(rel.first);
97     }
98   }
99   const std::set<IndexT> set_remainingIds =
100     openMVG::graph::CleanGraph_KeepLargestBiEdge_Nodes<Pair_Set, IndexT>(pairs);
101   KeepOnlyReferencedElement(set_remainingIds, vec_relative_motion_);
102 
103   {
104     const std::set<IndexT> index = getIndexT(vec_relative_motion_);
105 
106     const size_t iNview = index.size();
107     OPENMVG_LOG_INFO << "\n-------------------------------" << "\n"
108       << " Global translations computation: " << "\n"
109       << "   - Ready to compute " << iNview << " global translations." << "\n"
110       << "     from #relative translations: " << vec_relative_motion_.size()*3;
111 
112     if (iNview < 3)
113     {
114       OPENMVG_LOG_ERROR << "Motion averaging is not possible. The image graph is not BiEdge compatible.";
115       return false;
116     }
117     //-- Update initial estimates from [minId,maxId] to range [0->Ncam]
118     std::vector<RelativeInfo_Vec> vec_relative_motion_cpy = vec_relative_motion_;
119     const Pair_Set pairs = getPairs(vec_relative_motion_cpy);
120     Hash_Map<IndexT,IndexT> reindex_forward, reindex_backward;
121     reindex(pairs, reindex_forward, reindex_backward);
122     for (openMVG::RelativeInfo_Vec & iter : vec_relative_motion_cpy)
123     {
124       for (relativeInfo & it : iter)
125       {
126         it.first = Pair(reindex_forward[it.first.first], reindex_forward[it.first.second]);
127       }
128     }
129 
130     openMVG::system::Timer timerLP_translation;
131 
132     switch (eTranslationAveragingMethod)
133     {
134       case TRANSLATION_AVERAGING_L1:
135       {
136         double gamma = -1.0;
137         std::vector<double> vec_solution;
138         {
139           vec_solution.resize(iNview*3 + vec_relative_motion_cpy.size() + 1);
140           using namespace openMVG::linearProgramming;
141           OSI_CLP_SolverWrapper solverLP(vec_solution.size());
142 
143           lInfinityCV::Tifromtij_ConstraintBuilder cstBuilder(vec_relative_motion_cpy);
144 
145           LP_Constraints_Sparse constraint;
146           //-- Setup constraint and solver
147           cstBuilder.Build(constraint);
148           solverLP.setup(constraint);
149           //--
150           // Solving
151           const bool bFeasible = solverLP.solve();
152           //--
153           if (bFeasible)  {
154             solverLP.getSolution(vec_solution);
155             gamma = vec_solution[vec_solution.size()-1];
156           }
157           else  {
158             OPENMVG_LOG_ERROR << "Compute global translations: TRANSLATION_AVERAGING_L1 failed";
159             return false;
160           }
161         }
162 
163         const double timeLP_translation = timerLP_translation.elapsed();
164         //-- Export triplet statistics:
165         {
166 
167           std::ostringstream os;
168           os << "Translation fusion statistics.\n"
169             << "-------------------------------" << "\n"
170             << "-- #relative estimates: " << vec_relative_motion_cpy.size()
171             << " converge with gamma: " << gamma << ".\n"
172             << " timing (s): " << timeLP_translation << ".\n"
173             << "-------------------------------";
174           OPENMVG_LOG_INFO << os.str();
175         }
176 
177         std::ostringstream os;
178         os << "Found solution:\n";
179         std::copy(vec_solution.cbegin(), vec_solution.cend(), std::ostream_iterator<double>(os, " "));
180 
181         std::vector<double> vec_camTranslation(iNview*3,0);
182         std::copy(&vec_solution[0], &vec_solution[iNview*3], &vec_camTranslation[0]);
183 
184         std::vector<double> vec_camRelLambdas(&vec_solution[iNview*3], &vec_solution[iNview*3 + vec_relative_motion_cpy.size()]);
185         os << "\ncam position: ";
186         std::copy(vec_camTranslation.cbegin(), vec_camTranslation.cend(), std::ostream_iterator<double>(os, " "));
187 
188 
189         os << "\ncam Lambdas: ";
190         std::copy(vec_camRelLambdas.cbegin(), vec_camRelLambdas.cend(), std::ostream_iterator<double>(os, " "));
191         OPENMVG_LOG_INFO << os.str();
192 
193         // Update the view poses according the found camera centers
194         for (size_t i = 0; i < iNview; ++i)
195         {
196           const Vec3 t(vec_camTranslation[i*3], vec_camTranslation[i*3+1], vec_camTranslation[i*3+2]);
197           const IndexT pose_id = reindex_backward[i];
198           const Mat3 & Ri = map_globalR.at(pose_id);
199           sfm_data.poses[pose_id] = Pose3(Ri, -Ri.transpose()*t);
200         }
201       }
202       break;
203 
204       case TRANSLATION_AVERAGING_SOFTL1:
205       {
206         std::vector<Vec3> vec_translations;
207         if (!solve_translations_problem_softl1(
208           vec_relative_motion_cpy, vec_translations))
209         {
210           OPENMVG_LOG_ERROR << "Compute global translations: TRANSLATION_AVERAGING_SOFTL1 failed";
211           return false;
212         }
213 
214         // A valid solution was found:
215         // - Update the view poses according the found camera translations
216         for (size_t i = 0; i < iNview; ++i)
217         {
218           const Vec3 & t = vec_translations[i];
219           const IndexT pose_id = reindex_backward[i];
220           const Mat3 & Ri = map_globalR.at(pose_id);
221           sfm_data.poses[pose_id] = Pose3(Ri, -Ri.transpose()*t);
222         }
223       }
224       break;
225 
226       case TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL:
227       {
228         std::vector<int> vec_edges;
229         vec_edges.reserve(vec_relative_motion_cpy.size() * 2);
230         std::vector<double> vec_poses;
231         vec_poses.reserve(vec_relative_motion_cpy.size() * 3);
232         std::vector<double> vec_weights;
233         vec_weights.reserve(vec_relative_motion_cpy.size());
234 
235         for (const openMVG::RelativeInfo_Vec & iter : vec_relative_motion_cpy)
236         {
237           for (const relativeInfo & rel : iter)
238           {
239             vec_edges.push_back(rel.first.first);
240             vec_edges.push_back(rel.first.second);
241             // Since index have been remapped
242             // (use the backward indexing to retrieve the second global rotation)
243             const IndexT secondId = reindex_backward[rel.first.second];
244             const View * view = sfm_data.views.at(secondId).get();
245             const Mat3 & Ri = map_globalR.at(view->id_pose);
246             const Vec3 direction = -(Ri.transpose() * rel.second.second.normalized());
247 
248             vec_poses.push_back(direction(0));
249             vec_poses.push_back(direction(1));
250             vec_poses.push_back(direction(2));
251 
252             vec_weights.push_back(1.0);
253           }
254         }
255 
256         const double function_tolerance = 1e-7, parameter_tolerance = 1e-8;
257         const int max_iterations = 500;
258 
259         const double loss_width = 0.0; // No loss in order to compare with TRANSLATION_AVERAGING_L1
260 
261         std::vector<double> X(iNview*3, 0.0);
262         if (!solve_translations_problem_l2_chordal(
263           &vec_edges[0],
264           &vec_poses[0],
265           &vec_weights[0],
266           vec_relative_motion_cpy.size()*3,
267           loss_width,
268           &X[0],
269           function_tolerance,
270           parameter_tolerance,
271           max_iterations))  {
272             OPENMVG_LOG_ERROR << "Compute global translations: TRANSLATION_AVERAGING_L2_DISTANCE_CHORDAL failed";
273             return false;
274         }
275 
276         // Update camera center for each view
277         for (size_t i = 0; i < iNview; ++i)
278         {
279           const Vec3 C(X[i*3], X[i*3+1], X[i*3+2]);
280           const IndexT pose_id = reindex_backward[i]; // undo the reindexing
281           const Mat3 & Ri = map_globalR.at(pose_id);
282           sfm_data.poses[pose_id] = Pose3(Ri, C);
283         }
284       }
285       break;
286       default:
287       {
288         OPENMVG_LOG_ERROR << "Unknown translation averaging method";
289         return false;
290       }
291     }
292   }
293   return true;
294 }
295 
Compute_translations(const sfm::SfM_Data & sfm_data,const sfm::Features_Provider * features_provider,const sfm::Matches_Provider * matches_provider,const Hash_Map<IndexT,Mat3> & map_globalR,matching::PairWiseMatches & tripletWise_matches)296 void GlobalSfM_Translation_AveragingSolver::Compute_translations
297 (
298   const sfm::SfM_Data & sfm_data,
299   const sfm::Features_Provider * features_provider,
300   const sfm::Matches_Provider * matches_provider,
301   const Hash_Map<IndexT, Mat3> & map_globalR,
302   matching::PairWiseMatches &tripletWise_matches
303 )
304 {
305   OPENMVG_LOG_INFO
306     << "\n-------------------------------" << "\n"
307     << " Relative translations computation: " << "\n"
308     << "-------------------------------";
309 
310   // Compute relative translations over the graph of global rotations
311   //  thanks to an edge coverage algorithm
312   ComputePutativeTranslation_EdgesCoverage(
313     sfm_data,
314     map_globalR,
315     features_provider,
316     matches_provider,
317     vec_relative_motion_,
318     tripletWise_matches);
319 }
320 
321 //-- Perform a trifocal estimation of the graph contain in vec_triplets with an
322 // edge coverage algorithm. Its complexity is sub-linear in term of edges count.
ComputePutativeTranslation_EdgesCoverage(const sfm::SfM_Data & sfm_data,const Hash_Map<IndexT,Mat3> & map_globalR,const sfm::Features_Provider * features_provider,const sfm::Matches_Provider * matches_provider,std::vector<RelativeInfo_Vec> & vec_triplet_relative_motion,matching::PairWiseMatches & newpairMatches)323 void GlobalSfM_Translation_AveragingSolver::ComputePutativeTranslation_EdgesCoverage
324 (
325   const sfm::SfM_Data & sfm_data,
326   const Hash_Map<IndexT, Mat3> & map_globalR,
327   const sfm::Features_Provider * features_provider,
328   const sfm::Matches_Provider * matches_provider,
329   std::vector<RelativeInfo_Vec> & vec_triplet_relative_motion,
330   matching::PairWiseMatches & newpairMatches
331 )
332 {
333   openMVG::system::Timer timerLP_triplet;
334 
335   //--
336   // Compute the relative translations using triplets of rotations over the rotation graph.
337   //--
338   //
339   // 1. List plausible triplets over the global rotation pose graph Ids.
340   //   - list all edges that have support in the rotation pose graph
341   //
342   Pair_Set rotation_pose_id_graph;
343   std::set<IndexT> set_pose_ids;
344   std::transform(map_globalR.cbegin(), map_globalR.cend(),
345     std::inserter(set_pose_ids, set_pose_ids.begin()), stl::RetrieveKey());
346   // List shared correspondences (pairs) between poses
347   for (const auto & match_iterator : matches_provider->pairWise_matches_)
348   {
349     const Pair pair = match_iterator.first;
350     const View * v1 = sfm_data.GetViews().at(pair.first).get();
351     const View * v2 = sfm_data.GetViews().at(pair.second).get();
352 
353     if (// Consider the pair iff it is supported by the rotation graph
354         (v1->id_pose != v2->id_pose)
355         && set_pose_ids.count(v1->id_pose)
356         && set_pose_ids.count(v2->id_pose))
357     {
358       rotation_pose_id_graph.insert({v1->id_pose, v2->id_pose});
359     }
360   }
361   // List putative triplets (from global rotations Ids)
362   const std::vector<graph::Triplet> vec_triplets =
363     graph::TripletListing(rotation_pose_id_graph);
364   OPENMVG_LOG_INFO << "#Triplets: " << vec_triplets.size();
365 
366   {
367     // Compute triplets of translations
368     // Avoid to cover each edge of the graph by using an edge coverage algorithm
369     // An estimated triplets of translation mark three edges as estimated.
370 
371     //-- Alias (list triplet ids used per edges)
372     using myEdge = Pair; // An edge between two pose id
373     Hash_Map<myEdge, std::vector<uint32_t>> map_tripletIds_perEdge;
374     for (size_t i = 0; i < vec_triplets.size(); ++i)
375     {
376       const graph::Triplet & triplet = vec_triplets[i];
377       map_tripletIds_perEdge[{triplet.i, triplet.j}].push_back(i);
378       map_tripletIds_perEdge[{triplet.i, triplet.k}].push_back(i);
379       map_tripletIds_perEdge[{triplet.j, triplet.k}].push_back(i);
380     }
381 
382     //-- precompute the visibility count per triplets (sum of their 2 view matches)
383     Hash_Map<IndexT, IndexT> map_tracksPerTriplets;
384     for (const auto & match_iterator : matches_provider->pairWise_matches_)
385     {
386       const Pair pair = match_iterator.first;
387       const View * v1 = sfm_data.GetViews().at(pair.first).get();
388       const View * v2 = sfm_data.GetViews().at(pair.second).get();
389       if (v1->id_pose != v2->id_pose)
390       {
391         // Consider the pair iff it is supported by 2 different pose id
392         const myEdge edge(v1->id_pose,v2->id_pose);
393         if (map_tripletIds_perEdge.count(edge) != 0)
394         {
395           const auto & edge_tripletIds = map_tripletIds_perEdge.at(edge);
396           for (const auto & triplet_id : edge_tripletIds)
397           {
398             if (map_tracksPerTriplets.count(triplet_id) == 0)
399               map_tracksPerTriplets[triplet_id] = match_iterator.second.size();
400             else
401               map_tracksPerTriplets[triplet_id] += match_iterator.second.size();
402           }
403         }
404       }
405     }
406     // Collect edges that are covered by the triplets
407     std::vector<myEdge> vec_edges;
408     std::transform(map_tripletIds_perEdge.cbegin(),
409                    map_tripletIds_perEdge.cend(),
410                    std::back_inserter(vec_edges),
411                    stl::RetrieveKey());
412 
413     openMVG::sfm::MutexSet<myEdge> m_mutexSet;
414 
415     system::LoggerProgress my_progress_bar(vec_edges.size(),
416       "Relative translations computation (edge coverage algorithm)");
417 
418 #  ifdef OPENMVG_USE_OPENMP
419     std::vector<std::vector<RelativeInfo_Vec>> initial_estimates(omp_get_max_threads());
420 #  else
421     std::vector<std::vector<RelativeInfo_Vec>> initial_estimates(1);
422 #  endif
423 
424     #ifdef OPENMVG_USE_OPENMP
425     #pragma omp parallel for schedule(dynamic)
426     #endif
427     for (int k = 0; k < static_cast<int>(vec_edges.size()); ++k)
428     {
429       const myEdge & edge = vec_edges[k];
430       ++my_progress_bar;
431 
432       if (m_mutexSet.count(edge) == 0 && m_mutexSet.size() != vec_edges.size())
433       {
434         // Find the triplets that are supporting the given edge
435         const auto & vec_possibleTripletIndexes = map_tripletIds_perEdge.at(edge);
436 
437         //-- Sort the triplets according their number of track
438         std::vector<uint32_t> vec_commonTracksPerTriplets;
439         for (const uint32_t triplet_index : vec_possibleTripletIndexes)
440         {
441           vec_commonTracksPerTriplets.push_back(map_tracksPerTriplets[triplet_index]);
442         }
443 
444         using namespace stl::indexed_sort;
445         std::vector<sort_index_packet_descend<uint32_t,uint32_t>> packet_vec(vec_commonTracksPerTriplets.size());
446         sort_index_helper(packet_vec, &vec_commonTracksPerTriplets[0]);
447 
448         std::vector<uint32_t> vec_triplet_ordered(vec_commonTracksPerTriplets.size(), 0);
449         for (size_t i = 0; i < vec_commonTracksPerTriplets.size(); ++i) {
450           vec_triplet_ordered[i] = vec_possibleTripletIndexes[packet_vec[i].index];
451         }
452 
453         // Try to solve a triplet of translations for the given edge
454         for (const uint32_t triplet_index : vec_triplet_ordered)
455         {
456           const graph::Triplet & triplet = vec_triplets[triplet_index];
457 
458           // If the triplet is already estimated by another thread; try the next one
459           if (m_mutexSet.count({triplet.i, triplet.j}) &&
460               m_mutexSet.count({triplet.i, triplet.k}) &&
461               m_mutexSet.count({triplet.j, triplet.k}))
462           {
463             continue;
464           }
465 
466           //--
467           // Try to estimate this triplet of translations
468           //--
469           double dPrecision = 4.0; // upper bound of the residual pixel reprojection error
470 
471           std::vector<Vec3> vec_tis(3);
472           std::vector<uint32_t> vec_inliers;
473           openMVG::tracks::STLMAPTracks pose_triplet_tracks;
474 
475           const std::string sOutDirectory = "./";
476 
477           const bool bTriplet_estimation = Estimate_T_triplet(
478               sfm_data,
479               map_globalR,
480               features_provider,
481               matches_provider,
482               triplet,
483               vec_tis,
484               dPrecision,
485               vec_inliers,
486               pose_triplet_tracks,
487               sOutDirectory);
488 
489           if (bTriplet_estimation)
490           {
491             // Since new translation edges have been computed, mark their corresponding edges as estimated
492             #ifdef OPENMVG_USE_OPENMP
493               #pragma omp critical
494             #endif
495             {
496               m_mutexSet.insert({triplet.i, triplet.j});
497               m_mutexSet.insert({triplet.j, triplet.k});
498               m_mutexSet.insert({triplet.i, triplet.k});
499             }
500 
501             // Compute the triplet relative motions (IJ, JK, IK)
502             {
503               const Mat3
504                 RI = map_globalR.at(triplet.i),
505                 RJ = map_globalR.at(triplet.j),
506                 RK = map_globalR.at(triplet.k);
507               const Vec3
508                 ti = vec_tis[0],
509                 tj = vec_tis[1],
510                 tk = vec_tis[2];
511 
512               Mat3 Rij;
513               Vec3 tij;
514               RelativeCameraMotion(RI, ti, RJ, tj, &Rij, &tij);
515 
516               Mat3 Rjk;
517               Vec3 tjk;
518               RelativeCameraMotion(RJ, tj, RK, tk, &Rjk, &tjk);
519 
520               Mat3 Rik;
521               Vec3 tik;
522               RelativeCameraMotion(RI, ti, RK, tk, &Rik, &tik);
523 
524               #ifdef OPENMVG_USE_OPENMP
525                 const int thread_id = omp_get_thread_num();
526               #else
527                 const int thread_id = 0;
528               #endif
529 
530               RelativeInfo_Vec triplet_relative_motion;
531               triplet_relative_motion.push_back(
532                 {{triplet.i, triplet.j}, {Rij, tij}});
533               triplet_relative_motion.push_back(
534                 {{triplet.j, triplet.k}, {Rjk, tjk}});
535               triplet_relative_motion.push_back(
536                 {{triplet.i, triplet.k}, {Rik, tik}});
537 
538               initial_estimates[thread_id].emplace_back(triplet_relative_motion);
539 
540               #ifdef OPENMVG_USE_OPENMP
541                 #pragma omp critical
542               #endif
543               {
544                 // Add inliers as valid pairwise matches
545                 for (const uint32_t & inlier_it : vec_inliers)
546                 {
547                   tracks::STLMAPTracks::const_iterator it_tracks = pose_triplet_tracks.begin();
548                   std::advance(it_tracks, inlier_it);
549                   const tracks::submapTrack & track = it_tracks->second;
550 
551                   // create pairwise matches from the inlier track
552                   tracks::submapTrack::const_iterator iter_I = track.begin();
553                   tracks::submapTrack::const_iterator iter_J = track.begin();
554                   std::advance(iter_J, 1);
555                   while (iter_J != track.end())
556                   { // matches(pair(view_id(I), view_id(J))) <= IndMatch(feat_id(I), feat_id(J))
557                     newpairMatches[{iter_I->first, iter_J->first}]
558                      .emplace_back(iter_I->second, iter_J->second);
559                     ++iter_I;
560                     ++iter_J;
561                   }
562                 }
563               }
564             }
565             // Since a relative translation have been found for the edge: vec_edges[k],
566             //  we break and start to estimate the translations for some other edges.
567             break;
568           }
569         }
570       }
571     }
572     // Merge thread(s) estimates
573     for (auto & vec : initial_estimates)
574     {
575       vec_triplet_relative_motion.insert( vec_triplet_relative_motion.end(),
576         std::make_move_iterator(vec.begin()), std::make_move_iterator(vec.end()));
577     }
578   }
579 
580   const double timeLP_triplet = timerLP_triplet.elapsed();
581   OPENMVG_LOG_INFO << "TRIPLET COVERAGE TIMING:\n"
582     << "-------------------------------" << "\n"
583     << "-- #Relative triplet of translations estimates: " << vec_triplet_relative_motion.size()
584     << " computed from " << vec_triplets.size() << " triplets.\n"
585     << "-- resulting in " << vec_triplet_relative_motion.size()*3 << " translations estimation.\n"
586     << "-- time to compute triplets of relative translations: " << timeLP_triplet << " seconds.\n"
587     << "-------------------------------";
588 }
589 
590 // Robust estimation and refinement of a triplet of translations
Estimate_T_triplet(const sfm::SfM_Data & sfm_data,const Hash_Map<IndexT,Mat3> & map_globalR,const sfm::Features_Provider * features_provider,const sfm::Matches_Provider * matches_provider,const graph::Triplet & poses_id,std::vector<Vec3> & vec_tis,double & dPrecision,std::vector<uint32_t> & vec_inliers,openMVG::tracks::STLMAPTracks & tracks,const std::string & sOutDirectory) const591 bool GlobalSfM_Translation_AveragingSolver::Estimate_T_triplet
592 (
593   const sfm::SfM_Data & sfm_data,
594   const Hash_Map<IndexT, Mat3> & map_globalR,
595   const sfm::Features_Provider * features_provider,
596   const sfm::Matches_Provider * matches_provider,
597   const graph::Triplet & poses_id,
598   std::vector<Vec3> & vec_tis,
599   double & dPrecision, // UpperBound of the precision found by the AContrario estimator
600   std::vector<uint32_t> & vec_inliers,
601   openMVG::tracks::STLMAPTracks & tracks,
602   const std::string & sOutDirectory
603 ) const
604 {
605   // List matches that belong to the triplet of poses
606   PairWiseMatches map_triplet_matches;
607   const std::set<IndexT> set_pose_ids {poses_id.i, poses_id.j, poses_id.k};
608   // List shared correspondences (pairs) between poses
609   for (const auto & match_iterator : matches_provider->pairWise_matches_)
610   {
611     const Pair & pair = match_iterator.first;
612     const View * v1 = sfm_data.GetViews().at(pair.first).get();
613     const View * v2 = sfm_data.GetViews().at(pair.second).get();
614     if (// Consider the pair iff it is supported by the triplet graph & 2 different pose id
615         (v1->id_pose != v2->id_pose)
616         && set_pose_ids.count(v1->id_pose)
617         && set_pose_ids.count(v2->id_pose))
618     {
619       map_triplet_matches.insert( match_iterator );
620     }
621   }
622 
623   openMVG::tracks::TracksBuilder tracksBuilder;
624   tracksBuilder.Build(map_triplet_matches);
625   tracksBuilder.Filter(3);
626   tracksBuilder.ExportToSTL(tracks);
627 
628   if (tracks.size() < 30)
629     return false;
630 
631   // Data conversion
632   // Fill image observations as a unique matrix array
633   Mat
634     x1(2, tracks.size()),
635     x2(2, tracks.size()),
636     x3(2, tracks.size());
637 
638   Mat* xxx[3] = {&x1, &x2, &x3};
639   std::set<IndexT> intrinsic_ids;
640   size_t cpt = 0;
641   for (const auto & tracks_it : tracks)
642   {
643     const tracks::submapTrack & track = tracks_it.second;
644     uint32_t index = 0;
645     for (const auto & track_it : track)
646     {
647       const uint32_t idx_view = track_it.first;
648       const View * view = sfm_data.views.at(idx_view).get();
649       const IntrinsicBase * cam = sfm_data.intrinsics.at(view->id_intrinsic).get();
650       intrinsic_ids.insert(view->id_intrinsic);
651       const features::PointFeature pt = features_provider->getFeatures(idx_view)[track_it.second];
652       xxx[index++]->col(cpt) = ((*cam)(cam->get_ud_pixel(pt.coords().cast<double>()))).colwise().hnormalized();
653     }
654     ++cpt;
655   }
656   // Retrieve the smallest focal value, for threshold normalization
657   double min_focal = std::numeric_limits<double>::max();
658   for (const auto & ids : intrinsic_ids)
659   {
660     const cameras::IntrinsicBase * intrinsicPtr = sfm_data.GetIntrinsics().at(ids).get();
661     const cameras::Pinhole_Intrinsic * intrinsic = dynamic_cast< const cameras::Pinhole_Intrinsic * > (intrinsicPtr);
662     if (intrinsic)
663     {
664       min_focal = std::min(min_focal, intrinsic->focal());
665     }
666   }
667   if (min_focal == std::numeric_limits<double>::max())
668   {
669     return false;
670   }
671 
672   // Get rotations:
673   const std::vector<Mat3> vec_global_R_Triplet =
674     {map_globalR.at(poses_id.i), map_globalR.at(poses_id.j), map_globalR.at(poses_id.k)};
675 
676   using namespace openMVG::trifocal;
677   using namespace openMVG::trifocal::kernel;
678 
679   using KernelType =
680     TranslationTripletKernel_ACRansac<
681       translations_Triplet_Solver,
682       translations_Triplet_Solver,
683       TrifocalTensorModel>;
684   const double ThresholdUpperBound = 1.0e-2; // upper bound of the pixel residual (normalized coordinates)
685   KernelType kernel(x1, x2, x3, vec_global_R_Triplet, Mat3::Identity(), ThresholdUpperBound);
686 
687   const size_t ORSA_ITER = 320;  // max number of iterations of AC-RANSAC
688 
689   TrifocalTensorModel T;
690   const std::pair<double,double> acStat =
691     robust::ACRANSAC(kernel, vec_inliers, ORSA_ITER, &T, dPrecision/min_focal, false);
692   // If robust estimation fails => stop.
693   if (dPrecision == std::numeric_limits<double>::infinity())
694     return false;
695 
696   // Update output parameters
697   dPrecision = acStat.first * min_focal;
698 
699   vec_tis.resize(3);
700   Mat3 K, R;
701   KRt_From_P(T.P1, &K, &R, &vec_tis[0]);
702   KRt_From_P(T.P2, &K, &R, &vec_tis[1]);
703   KRt_From_P(T.P3, &K, &R, &vec_tis[2]);
704 
705 #ifdef DEBUG_TRIPLET
706   // compute 3D scene base on motion estimation
707   SfM_Data    tiny_scene;
708 
709   // initialize poses (which are now shared by a group of images)
710   tiny_scene.poses[poses_id.i] = Pose3(vec_global_R_Triplet[0], -vec_global_R_Triplet[0].transpose() * vec_tis[0]);
711   tiny_scene.poses[poses_id.j] = Pose3(vec_global_R_Triplet[1], -vec_global_R_Triplet[1].transpose() * vec_tis[1]);
712   tiny_scene.poses[poses_id.k] = Pose3(vec_global_R_Triplet[2], -vec_global_R_Triplet[2].transpose() * vec_tis[2]);
713 
714   // insert views used by the relative pose pairs
715   for (const auto & pairIterator : map_triplet_matches)
716   {
717     // initialize camera indexes
718     const IndexT I = pairIterator.first.first;
719     const IndexT J = pairIterator.first.second;
720 
721     // add views
722     tiny_scene.views.insert(*sfm_data.GetViews().find(I));
723     tiny_scene.views.insert(*sfm_data.GetViews().find(J));
724 
725     // add intrinsics
726     const View * view_I = sfm_data.GetViews().at(I).get();
727     const View * view_J = sfm_data.GetViews().at(J).get();
728     tiny_scene.intrinsics.insert(*sfm_data.GetIntrinsics().find(view_I->id_intrinsic));
729     tiny_scene.intrinsics.insert(*sfm_data.GetIntrinsics().find(view_J->id_intrinsic));
730   }
731 
732   // Fill sfm_data with the inliers tracks. Feed image observations: no 3D yet.
733   Landmarks & structure = tiny_scene.structure;
734   for (const uint32_t & inlier_it : vec_inliers)
735   {
736     tracks::STLMAPTracks::const_iterator it_tracks = tracks.begin();
737     std::advance(it_tracks, inlier_it);
738     const tracks::submapTrack & track = it_tracks->second;
739     Observations & obs = structure[inlier_it].obs;
740     for (tracks::submapTrack::const_iterator it = track.begin(); it != track.end(); ++it)
741     {
742       // get view Id and feat ID
743       const uint32_t viewIndex = it->first;
744       const uint32_t featIndex = it->second;
745 
746       // get feature
747       const features::PointFeature & pt = features_provider->getFeatures(viewIndex)[featIndex];
748       obs[viewIndex] = Observation(pt.coords().cast<double>(), featIndex);
749     }
750   }
751 
752   // Compute 3D landmark positions (triangulation of the observations)
753   {
754     SfM_Data_Structure_Computation_Blind structure_estimator(false);
755     structure_estimator.triangulate(tiny_scene);
756   }
757 
758   // Refine structure and poses (keep intrinsic constant)
759   Bundle_Adjustment_Ceres::BA_Ceres_options options(false, false);
760   Bundle_Adjustment_Ceres bundle_adjustment_obj(options);
761   if (bundle_adjustment_obj.Adjust(tiny_scene,
762         Optimize_Options(
763           cameras::Intrinsic_Parameter_Type::NONE,
764           Extrinsic_Parameter_Type::ADJUST_ALL,
765           Structure_Parameter_Type::ADJUST_ALL)
766         )
767       )
768   {
769     // export scene for visualization
770     std::ostringstream os;
771     os << poses_id.i << "_" << poses_id.j << "_" << poses_id.k << ".ply";
772     Save(tiny_scene, os.str(), ESfM_Data(STRUCTURE | EXTRINSICS));
773 
774     // Export refined translations
775     vec_tis[0] = tiny_scene.poses[poses_id.i].translation();
776     vec_tis[1] = tiny_scene.poses[poses_id.j].translation();
777     vec_tis[2] = tiny_scene.poses[poses_id.k].translation();
778   }
779 
780   std::ostringstream os;
781   os << poses_id.i << "_" << poses_id.j << "_" << poses_id.k << ".ply";
782   Save(tiny_scene, os.str(), ESfM_Data(STRUCTURE | EXTRINSICS));
783 
784 #endif
785 
786   // Keep the model iff it has a sufficient inlier count
787   const bool bTest = ( vec_inliers.size() > 30 && 0.33 * tracks.size() );
788 
789 #ifdef DEBUG_TRIPLET
790   {
791     OPENMVG_LOG_INFO << "Triplet : status: " << bTest
792       << " AC: " << std::sqrt(dPrecision)
793       << " inliers % " << double(vec_inliers.size()) / tracks.size() * 100.0
794       << " total putative " << tracks.size();
795   }
796 #endif
797 
798   return bTest;
799 }
800 
801 } // namespace sfm
802 } // namespace openMVG
803