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