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/sfm_data_filters.hpp"
10 #include "openMVG/sfm/sfm_data.hpp"
11 #include "openMVG/stl/stl.hpp"
12 #include "openMVG/system/logger.hpp"
13 #include "openMVG/tracks/union_find.hpp"
14 
15 #include <utility>
16 
17 namespace openMVG {
18 namespace sfm {
19 
20 /// List the view indexes that have valid camera intrinsic and pose.
Get_Valid_Views(const SfM_Data & sfm_data)21 std::set<IndexT> Get_Valid_Views
22 (
23   const SfM_Data & sfm_data
24 )
25 {
26   std::set<IndexT> valid_idx;
27   for (const auto & view_it : sfm_data.GetViews())
28   {
29     const View * v = view_it.second.get();
30     if (sfm_data.IsPoseAndIntrinsicDefined(v))
31     {
32       valid_idx.insert(v->id_view);
33     }
34   }
35   return valid_idx;
36 }
37 
38 // Remove tracks that have a small angle (tracks with tiny angle leads to instable 3D points)
39 // Return the number of removed tracks
RemoveOutliers_PixelResidualError(SfM_Data & sfm_data,const double dThresholdPixel,const unsigned int minTrackLength)40 IndexT RemoveOutliers_PixelResidualError
41 (
42   SfM_Data & sfm_data,
43   const double dThresholdPixel,
44   const unsigned int minTrackLength
45 )
46 {
47   IndexT outlier_count = 0;
48   Landmarks::iterator iterTracks = sfm_data.structure.begin();
49   while (iterTracks != sfm_data.structure.end())
50   {
51     Observations & obs = iterTracks->second.obs;
52     Observations::iterator itObs = obs.begin();
53     while (itObs != obs.end())
54     {
55       const View * view = sfm_data.views.at(itObs->first).get();
56       const geometry::Pose3 pose = sfm_data.GetPoseOrDie(view);
57       const cameras::IntrinsicBase * intrinsic = sfm_data.intrinsics.at(view->id_intrinsic).get();
58       const Vec2 residual = intrinsic->residual(pose(iterTracks->second.X), itObs->second.x);
59       if (residual.norm() > dThresholdPixel)
60       {
61         ++outlier_count;
62         itObs = obs.erase(itObs);
63       }
64       else
65         ++itObs;
66     }
67     if (obs.empty() || obs.size() < minTrackLength)
68       iterTracks = sfm_data.structure.erase(iterTracks);
69     else
70       ++iterTracks;
71   }
72   return outlier_count;
73 }
74 
75 // Remove tracks that have a small angle (tracks with tiny angle leads to instable 3D points)
76 // Return the number of removed tracks
RemoveOutliers_AngleError(SfM_Data & sfm_data,const double dMinAcceptedAngle)77 IndexT RemoveOutliers_AngleError
78 (
79   SfM_Data & sfm_data,
80   const double dMinAcceptedAngle
81 )
82 {
83   IndexT removedTrack_count = 0;
84   Landmarks::iterator iterTracks = sfm_data.structure.begin();
85   while (iterTracks != sfm_data.structure.end())
86   {
87     Observations & obs = iterTracks->second.obs;
88     double max_angle = 0.0;
89     for (Observations::const_iterator itObs1 = obs.begin();
90       itObs1 != obs.end(); ++itObs1)
91     {
92       const View * view1 = sfm_data.views.at(itObs1->first).get();
93       const geometry::Pose3 pose1 = sfm_data.GetPoseOrDie(view1);
94       const cameras::IntrinsicBase * intrinsic1 = sfm_data.intrinsics.at(view1->id_intrinsic).get();
95 
96       Observations::const_iterator itObs2 = itObs1;
97       ++itObs2;
98       for (; itObs2 != obs.end(); ++itObs2)
99       {
100         const View * view2 = sfm_data.views.at(itObs2->first).get();
101         const geometry::Pose3 pose2 = sfm_data.GetPoseOrDie(view2);
102         const cameras::IntrinsicBase * intrinsic2 = sfm_data.intrinsics.at(view2->id_intrinsic).get();
103 
104         const double angle = AngleBetweenRay(
105           pose1, intrinsic1, pose2, intrinsic2,
106           intrinsic1->get_ud_pixel(itObs1->second.x), intrinsic2->get_ud_pixel(itObs2->second.x));
107         max_angle = std::max(angle, max_angle);
108       }
109     }
110     if (max_angle < dMinAcceptedAngle)
111     {
112       iterTracks = sfm_data.structure.erase(iterTracks);
113       ++removedTrack_count;
114     }
115     else
116       ++iterTracks;
117   }
118   return removedTrack_count;
119 }
120 
eraseMissingPoses(SfM_Data & sfm_data,const IndexT min_points_per_pose)121 bool eraseMissingPoses
122 (
123   SfM_Data & sfm_data,
124   const IndexT min_points_per_pose
125 )
126 {
127   IndexT removed_elements = 0;
128   const Landmarks & landmarks = sfm_data.structure;
129 
130   // Count the observation poses occurrence
131   Hash_Map<IndexT, IndexT> map_PoseId_Count;
132   // Init with 0 count (in order to be able to remove non referenced elements)
133   for (const auto & pose_it : sfm_data.GetPoses())
134   {
135     map_PoseId_Count[pose_it.first] = 0;
136   }
137 
138   // Count occurrence of the poses in the Landmark observations
139   for (const auto & lanmark_it : landmarks)
140   {
141     const Observations & obs = lanmark_it.second.obs;
142     for (const auto obs_it : obs)
143     {
144       const IndexT ViewId = obs_it.first;
145       const View * v = sfm_data.GetViews().at(ViewId).get();
146       map_PoseId_Count[v->id_pose] += 1; // Default initialization is 0
147     }
148   }
149   // If usage count is smaller than the threshold, remove the Pose
150   for (const auto & it : map_PoseId_Count)
151   {
152     if (it.second < min_points_per_pose)
153     {
154       sfm_data.poses.erase(it.first);
155       ++removed_elements;
156     }
157   }
158   return removed_elements > 0;
159 }
160 
eraseObservationsWithMissingPoses(SfM_Data & sfm_data,const IndexT min_points_per_landmark)161 bool eraseObservationsWithMissingPoses
162 (
163   SfM_Data & sfm_data,
164   const IndexT min_points_per_landmark
165 )
166 {
167   IndexT removed_elements = 0;
168 
169   std::set<IndexT> pose_Index;
170   std::transform(sfm_data.poses.cbegin(), sfm_data.poses.cend(),
171     std::inserter(pose_Index, pose_Index.begin()), stl::RetrieveKey());
172 
173   // For each landmark:
174   //  - Check if we need to keep the observations & the track
175   Landmarks::iterator itLandmarks = sfm_data.structure.begin();
176   while (itLandmarks != sfm_data.structure.end())
177   {
178     Observations & obs = itLandmarks->second.obs;
179     Observations::iterator itObs = obs.begin();
180     while (itObs != obs.end())
181     {
182       const IndexT ViewId = itObs->first;
183       const View * v = sfm_data.GetViews().at(ViewId).get();
184       if (pose_Index.count(v->id_pose) == 0)
185       {
186         itObs = obs.erase(itObs);
187         ++removed_elements;
188       }
189       else
190         ++itObs;
191     }
192     if (obs.empty() || obs.size() < min_points_per_landmark)
193       itLandmarks = sfm_data.structure.erase(itLandmarks);
194     else
195       ++itLandmarks;
196   }
197   return removed_elements > 0;
198 }
199 
200 /// Remove unstable content from analysis of the sfm_data structure
eraseUnstablePosesAndObservations(SfM_Data & sfm_data,const IndexT min_points_per_pose,const IndexT min_points_per_landmark)201 bool eraseUnstablePosesAndObservations
202 (
203   SfM_Data & sfm_data,
204   const IndexT min_points_per_pose,
205   const IndexT min_points_per_landmark
206 )
207 {
208   // First remove orphan observation(s) (observation using an undefined pose)
209   eraseObservationsWithMissingPoses(sfm_data, min_points_per_landmark);
210   // Then iteratively remove orphan poses & observations
211   IndexT remove_iteration = 0;
212   bool bRemovedContent = false;
213   do
214   {
215     bRemovedContent = false;
216     if (eraseMissingPoses(sfm_data, min_points_per_pose))
217     {
218       bRemovedContent = eraseObservationsWithMissingPoses(sfm_data, min_points_per_landmark);
219       // Erase some observations can make some Poses index disappear so perform the process in a loop
220     }
221     remove_iteration += bRemovedContent ? 1 : 0;
222   }
223   while (bRemovedContent);
224 
225   return remove_iteration > 0;
226 }
227 
228 /// Tell if the sfm_data structure is one CC or not
IsTracksOneCC(const SfM_Data & sfm_data)229 bool IsTracksOneCC
230 (
231   const SfM_Data & sfm_data
232 )
233 {
234   // Compute the Connected Component from the tracks
235 
236   // Build a table to have contiguous view index in [0,n]
237   // (Use only the view index used in the observations)
238   Hash_Map<IndexT, IndexT> view_renumbering;
239   IndexT cpt = 0;
240   const Landmarks & landmarks = sfm_data.structure;
241   for (const auto & Landmark_it : landmarks)
242   {
243     const Observations & obs = Landmark_it.second.obs;
244     for (const auto & obs_it : obs)
245     {
246       if (view_renumbering.count(obs_it.first) == 0)
247       {
248         view_renumbering[obs_it.first] = cpt++;
249       }
250     }
251   }
252 
253   UnionFind uf_tree;
254   uf_tree.InitSets(view_renumbering.size());
255 
256   // Link track observations in connected component
257   for (const auto & Landmark_it : landmarks)
258   {
259     const Observations & obs = Landmark_it.second.obs;
260     std::set<IndexT> id_to_link;
261     for (const auto & obs_it : obs)
262     {
263       id_to_link.insert(view_renumbering.at(obs_it.first));
264     }
265     std::set<IndexT>::const_iterator iterI = id_to_link.cbegin();
266     std::set<IndexT>::const_iterator iterJ = id_to_link.cbegin();
267     std::advance(iterJ, 1);
268     while (iterJ != id_to_link.cend())
269     {
270       // Link I => J
271       uf_tree.Union(*iterI, *iterJ);
272       ++iterJ;
273     }
274   }
275 
276   // Run path compression to identify all the CC id belonging to every item
277   for (unsigned int i = 0; i < uf_tree.GetNumNodes(); ++i)
278   {
279     uf_tree.Find(i);
280   }
281 
282   // Count the number of CC
283   const std::set<unsigned int> parent_id(uf_tree.m_cc_parent.cbegin(), uf_tree.m_cc_parent.cend());
284   return parent_id.size() == 1;
285 }
286 
287 /// Keep the largest connected component of tracks from the sfm_data structure
KeepLargestViewCCTracks(SfM_Data & sfm_data)288 void KeepLargestViewCCTracks
289 (
290   SfM_Data & sfm_data
291 )
292 {
293   // Compute the Connected Component from the tracks
294 
295   // Build a table to have contiguous view index in [0,n]
296   // (Use only the view index used in the observations)
297   Hash_Map<IndexT, IndexT> view_renumbering;
298   {
299     IndexT cpt = 0;
300     const Landmarks & landmarks = sfm_data.structure;
301     for (const auto & Landmark_it : landmarks)
302     {
303       const Observations & obs = Landmark_it.second.obs;
304       for (const auto & obs_it : obs)
305       {
306         if (view_renumbering.count(obs_it.first) == 0)
307         {
308           view_renumbering[obs_it.first] = cpt++;
309         }
310       }
311     }
312   }
313 
314   UnionFind uf_tree;
315   uf_tree.InitSets(view_renumbering.size());
316 
317   // Link track observations in connected component
318   Landmarks & landmarks = sfm_data.structure;
319   for (const auto & Landmark_it : landmarks)
320   {
321     const Observations & obs = Landmark_it.second.obs;
322     std::set<IndexT> id_to_link;
323     for (const auto & obs_it : obs)
324     {
325       id_to_link.insert(view_renumbering.at(obs_it.first));
326     }
327     std::set<IndexT>::const_iterator iterI = id_to_link.cbegin();
328     std::set<IndexT>::const_iterator iterJ = id_to_link.cbegin();
329     std::advance(iterJ, 1);
330     while (iterJ != id_to_link.cend())
331     {
332       // Link I => J
333       uf_tree.Union(*iterI, *iterJ);
334       ++iterJ;
335     }
336   }
337 
338   // Count the number of CC
339   const std::set<unsigned int> parent_id(uf_tree.m_cc_parent.cbegin(), uf_tree.m_cc_parent.cend());
340   if (parent_id.size() > 1)
341   {
342     // There is many CC, look the largest one
343     // (if many CC have the same size, export the first that have been seen)
344     std::pair<IndexT, unsigned int> max_cc( UndefinedIndexT, std::numeric_limits<unsigned int>::min());
345     {
346       for (const unsigned int parent_id_it : parent_id)
347       {
348         if (uf_tree.m_cc_size[parent_id_it] > max_cc.second) // Update the component parent id and size
349         {
350           max_cc = {parent_id_it, uf_tree.m_cc_size[parent_id_it]};
351         }
352       }
353     }
354     // Delete track ids that are not contained in the largest CC
355     if (max_cc.first != UndefinedIndexT)
356     {
357       const unsigned int parent_id_largest_cc = max_cc.first;
358       Landmarks::iterator itLandmarks = landmarks.begin();
359       while (itLandmarks != landmarks.end())
360       {
361         // Since we built a view 'track' graph thanks to the UF tree,
362         //  checking the CC of each track is equivalent to check the CC of any observation of it.
363         // So we check only the first
364         const Observations & obs = itLandmarks->second.obs;
365         Observations::const_iterator itObs = obs.begin();
366         if (!obs.empty())
367         {
368           if (uf_tree.Find(view_renumbering.at(itObs->first)) != parent_id_largest_cc)
369           {
370             itLandmarks = landmarks.erase(itLandmarks);
371           }
372           else
373           {
374             ++itLandmarks;
375           }
376         }
377       }
378     }
379   }
380 }
381 
382 /**
383 * @brief Implement a statistical Structure filter that remove 3D points that have:
384 * - a depth that is too large (threshold computed as factor * median ~= X84)
385 * @param sfm_data The sfm scene to filter (inplace filtering)
386 * @param k_factor The factor applied to the median depth per view
387 * @param k_min_point_per_pose Keep only poses that have at least this amount of points
388 * @param k_min_track_length Keep only tracks that have at least this length
389 * @return The min_median_value observed for all the view
390 */
DepthCleaning(SfM_Data & sfm_data,const double k_factor,const IndexT k_min_point_per_pose,const IndexT k_min_track_length)391 double DepthCleaning
392 (
393   SfM_Data & sfm_data,
394   const double k_factor,
395   const IndexT k_min_point_per_pose,
396   const IndexT k_min_track_length
397 )
398 {
399   using DepthAccumulatorT = std::vector<double>;
400   std::map<IndexT, DepthAccumulatorT > map_depth_accumulator;
401 
402   // For each landmark accumulate the camera/point depth info for each view
403   for (const auto & landmark_it : sfm_data.structure)
404   {
405     const Observations & obs = landmark_it.second.obs;
406     for (const auto & obs_it : obs)
407     {
408       const View * view = sfm_data.views.at(obs_it.first).get();
409       if (sfm_data.IsPoseAndIntrinsicDefined(view))
410       {
411         const Pose3 pose = sfm_data.GetPoseOrDie(view);
412         const double depth = Depth(pose.rotation(), pose.translation(), landmark_it.second.X);
413         if (depth > 0)
414         {
415           map_depth_accumulator[view->id_view].push_back(depth);
416         }
417       }
418     }
419   }
420 
421   double min_median_value = std::numeric_limits<double>::max();
422   std::map<IndexT, double > map_median_depth;
423   for (const auto & iter : sfm_data.GetViews())
424   {
425     const View * v = iter.second.get();
426     const IndexT view_id = v->id_view;
427     if (map_depth_accumulator.count(view_id) == 0)
428       continue;
429     // Compute median from the depth distribution
430     const auto & acc = map_depth_accumulator.at(view_id);
431     double min, max, mean, median;
432     if (minMaxMeanMedian(acc.begin(), acc.end(), min, max, mean, median))
433     {
434 
435       min_median_value = std::min(min_median_value, median);
436       // Compute depth threshold for each view: factor * medianDepth
437       map_median_depth[view_id] = k_factor * median;
438     }
439   }
440   map_depth_accumulator.clear();
441 
442   // Delete invalid observations
443   size_t cpt = 0;
444   for (auto & landmark_it : sfm_data.structure)
445   {
446     Observations obs;
447     for (auto & obs_it : landmark_it.second.obs)
448     {
449       const View * view = sfm_data.views.at(obs_it.first).get();
450       if (sfm_data.IsPoseAndIntrinsicDefined(view))
451       {
452         const Pose3 pose = sfm_data.GetPoseOrDie(view);
453         const double depth = Depth(pose.rotation(), pose.translation(), landmark_it.second.X);
454         if ( depth > 0
455             && map_median_depth.count(view->id_view)
456             && depth < map_median_depth[view->id_view])
457           obs.insert(obs_it);
458         else
459           ++cpt;
460       }
461     }
462     landmark_it.second.obs.swap(obs);
463   }
464   OPENMVG_LOG_INFO << "#point depth filter: " << cpt << " measurements removed";
465 
466   // Remove orphans
467   eraseUnstablePosesAndObservations(sfm_data, k_min_point_per_pose, k_min_track_length);
468 
469   return min_median_value;
470 }
471 
472 } // namespace sfm
473 } // namespace openMVG
474