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