18
18
#include < boost/program_options.hpp>
19
19
#include < boost/filesystem.hpp>
20
20
#include < boost/program_options.hpp>
21
+ #include < boost/accumulators/accumulators.hpp>
22
+ #include < boost/accumulators/statistics.hpp>
21
23
22
24
#include < stdlib.h>
23
25
#include < stdio.h>
@@ -42,9 +44,42 @@ using namespace aliceVision::sfmDataIO;
42
44
43
45
namespace po = boost::program_options;
44
46
namespace fs = boost::filesystem;
47
+ using namespace boost ::accumulators;
45
48
46
49
static const std::size_t MAX_LEAF_ELEMENTS = 64 ;
47
50
51
+ struct ObservationsAdaptator
52
+ {
53
+ using Derived = ObservationsAdaptator; // !< In this case the dataset class is myself.
54
+ using T = double ;
55
+
56
+ // / CRTP helper method
57
+ inline const Derived& derived () const { return *static_cast <const Derived*>(this ); }
58
+ // / CRTP helper method
59
+ inline Derived& derived () { return *static_cast <Derived*>(this ); }
60
+
61
+ const std::vector<const Observation*> _data;
62
+ ObservationsAdaptator (const std::vector<const Observation*>& data)
63
+ : _data(data)
64
+ {
65
+ }
66
+
67
+ // Must return the number of data points
68
+ inline size_t kdtree_get_point_count () const { return _data.size (); }
69
+
70
+ // Returns the dim'th component of the idx'th point in the class:
71
+ inline T kdtree_get_pt (const size_t idx, int dim) const { return _data[idx]->x (dim); }
72
+
73
+ // Optional bounding-box computation: return false to default to a standard bbox computation loop.
74
+ // Return true if the BBOX was already computed by the class and returned in "bb" so it can be avoided to redo it
75
+ // again. Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3 for point clouds)
76
+ template <class BBOX >
77
+ bool kdtree_get_bbox (BBOX& bb) const
78
+ {
79
+ return false ;
80
+ }
81
+ };
82
+
48
83
struct LandmarksAdaptator
49
84
{
50
85
using Derived = LandmarksAdaptator; // !< In this case the dataset class is myself.
@@ -89,16 +124,42 @@ struct LandmarksAdaptator
89
124
}
90
125
};
91
126
92
- using KdTree = nanoflann::KDTreeSingleIndexAdaptor<
127
+ using KdTree2D = nanoflann::KDTreeSingleIndexAdaptor<
128
+ nanoflann::L2_Simple_Adaptor<double , ObservationsAdaptator>,
129
+ ObservationsAdaptator, 2 /* dim */ , size_t >;
130
+
131
+ using KdTree3D = nanoflann::KDTreeSingleIndexAdaptor<
93
132
nanoflann::L2_Simple_Adaptor<double , LandmarksAdaptator>,
94
- LandmarksAdaptator,
95
- 3 , /* dim */
96
- size_t
97
- >;
98
-
99
- /* *
100
- * A result-set class used when performing a radius based search.
101
- */
133
+ LandmarksAdaptator, 3 /* dim */ ,size_t >;
134
+
135
+ class RadiusKnnSearch
136
+ {
137
+ public:
138
+ const double _radius_sq;
139
+ const int _nb_neighbors;
140
+ int nb_found = 0 ;
141
+
142
+ inline RadiusKnnSearch (double radius, int k)
143
+ : _radius_sq(radius * radius)
144
+ , _nb_neighbors(k)
145
+ {
146
+ }
147
+
148
+ inline bool full () const { return nb_found == _nb_neighbors; }
149
+
150
+ inline bool addPoint (double dist, IndexT index)
151
+ {
152
+ if (dist < _radius_sq)
153
+ {
154
+ nb_found++;
155
+ return nb_found < _nb_neighbors;
156
+ }
157
+ return true ;
158
+ }
159
+
160
+ inline double worstDist () const { return _radius_sq; }
161
+ };
162
+
102
163
class PixSizeSearch
103
164
{
104
165
public:
@@ -118,6 +179,7 @@ class PixSizeSearch
118
179
119
180
inline bool addPoint (double dist, size_t index)
120
181
{
182
+ // strict comparison because a point in the tree can be a neighbor of itself
121
183
if (dist < _radius_sq && _pixSize[index] < _pixSize_i)
122
184
{
123
185
found = true ;
@@ -169,7 +231,7 @@ bool filterLandmarks(SfMData& sfmData, double radiusScale, bool useFeatureScale,
169
231
{
170
232
filteredLandmarks[newIdx] = std::make_pair (newIdx, landmarksData[newIdx]);
171
233
}
172
- sfmData.getLandmarks () = Landmarks (filteredLandmarks.begin (), filteredLandmarks.end ());
234
+ sfmData.getLandmarks () = std::move ( Landmarks (filteredLandmarks.begin (), filteredLandmarks.end () ));
173
235
return true ;
174
236
}
175
237
@@ -203,9 +265,9 @@ bool filterLandmarks(SfMData& sfmData, double radiusScale, bool useFeatureScale,
203
265
// // sort landmarks by descending pixSize order
204
266
// std::stable_sort(landmarksPixSize.begin(), landmarksPixSize.end(), std::greater<>{});
205
267
206
- ALICEVISION_LOG_INFO (" Build nanoflann KdTree index." );
268
+ ALICEVISION_LOG_INFO (" Build nanoflann KdTree index for landmarks in 3D ." );
207
269
LandmarksAdaptator data (landmarksData);
208
- KdTree tree (3 , data, nanoflann::KDTreeSingleIndexAdaptorParams (MAX_LEAF_ELEMENTS));
270
+ KdTree3D tree (3 , data, nanoflann::KDTreeSingleIndexAdaptorParams (MAX_LEAF_ELEMENTS));
209
271
tree.buildIndex ();
210
272
ALICEVISION_LOG_INFO (" KdTree created for " << landmarksData.size () << " points." );
211
273
std::vector<bool > toRemove (landmarksData.size (), false );
@@ -249,7 +311,7 @@ bool filterLandmarks(SfMData& sfmData, double radiusScale, bool useFeatureScale,
249
311
return true ;
250
312
}
251
313
252
- bool filterObservations (SfMData& sfmData, int maxNbObservationsPerLandmark, int nbNeighbors , double neighborsInfluence,
314
+ bool filterObservations3D (SfMData& sfmData, int maxNbObservationsPerLandmark, int nbNeighbors3D , double neighborsInfluence,
253
315
int nbIterations)
254
316
{
255
317
// store in vector for faster access
@@ -316,13 +378,13 @@ bool filterObservations(SfMData& sfmData, int maxNbObservationsPerLandmark, int
316
378
ALICEVISION_LOG_INFO (" Computing initial observation scores based on distance to observing view: done" );
317
379
318
380
ALICEVISION_LOG_INFO (" Computing landmark neighbors and distance-based weights: started" );
319
- ALICEVISION_LOG_INFO (" Build nanoflann KdTree index." );
381
+ ALICEVISION_LOG_INFO (" Build nanoflann KdTree index for landmarks in 3D ." );
320
382
LandmarksAdaptator dataAdaptor (landmarksData);
321
- KdTree tree (3 , dataAdaptor, nanoflann::KDTreeSingleIndexAdaptorParams (MAX_LEAF_ELEMENTS));
383
+ KdTree3D tree (3 , dataAdaptor, nanoflann::KDTreeSingleIndexAdaptorParams (MAX_LEAF_ELEMENTS));
322
384
tree.buildIndex ();
323
385
ALICEVISION_LOG_INFO (" KdTree created for " << landmarksData.size () << " points." );
324
386
// note that the landmark is a neighbor to itself with zero distance, hence the +/- 1
325
- int nbNeighbors_ = std::min (nbNeighbors , static_cast <int >(landmarksData.size () - 1 )) + 1 ;
387
+ int nbNeighbors_ = std::min (nbNeighbors3D , static_cast <int >(landmarksData.size () - 1 )) + 1 ;
326
388
// contains the neighbor landmarks ids with their corresponding weights
327
389
std::vector<std::pair<std::vector<size_t >, std::vector<double >>> neighborsData (landmarksData.size ());
328
390
#pragma omp parallel for
@@ -372,7 +434,7 @@ bool filterObservations(SfMData& sfmData, int maxNbObservationsPerLandmark, int
372
434
// accumulator for normalisation
373
435
double viewScores_total = 0 .;
374
436
auto & [indices_, weights_] = neighborsData[id];
375
- for (auto j = 0 ; j < nbNeighbors ; j++)
437
+ for (auto j = 0 ; j < nbNeighbors3D ; j++)
376
438
{
377
439
const auto & neighborId = indices_[j];
378
440
const auto & neighborWeight = weights_[j];
@@ -467,19 +529,118 @@ bool filterObservations(SfMData& sfmData, int maxNbObservationsPerLandmark, int
467
529
return true ;
468
530
}
469
531
532
+ bool filterObservations2D (SfMData& sfmData, int nbNeighbors2D, float percentile,
533
+ HashMap<IndexT, double >& estimatedRadii)
534
+ {
535
+ std::set<IndexT> viewIds = sfmData.getValidViews ();
536
+ std::vector<double > estimatedRadii_ (viewIds.size (), -1 .);
537
+ auto observationsPerView = getObservationsPerViews (sfmData);
538
+
539
+ #pragma omp parallel for
540
+ for (int i = 0 ; i < viewIds.size (); ++i)
541
+ {
542
+ auto itView = viewIds.begin ();
543
+ std::advance (itView, i);
544
+ const IndexT viewId = *itView;
545
+
546
+ auto & observationsIt = observationsPerView.find (viewId);
547
+ if (observationsIt != observationsPerView.end ())
548
+ {
549
+ auto & observations = observationsIt->second .first ;
550
+ auto & landmarks = observationsIt->second .second ;
551
+
552
+ ALICEVISION_LOG_INFO (" Build nanoflann KdTree index for projected landmarks in 2D." );
553
+ ObservationsAdaptator data (observations);
554
+ KdTree2D tree (2 , data, nanoflann::KDTreeSingleIndexAdaptorParams (MAX_LEAF_ELEMENTS));
555
+ tree.buildIndex ();
556
+ ALICEVISION_LOG_INFO (" KdTree created for " << observations.size () << " points." );
557
+
558
+ // note that the observation is a neighbor to itself with zero distance, hence the +/- 1
559
+ size_t nbNeighbors_ = std::min (static_cast <size_t >(nbNeighbors2D), observations.size () - 1 ) + 1 ;
560
+ std::vector<double > means (observations.size ());
561
+ const std::size_t cacheSize = 1000 ;
562
+ accumulator_set<double , stats<tag::tail_quantile<right>, tag::mean>> acc (tag::tail<right>::cache_size =
563
+ cacheSize);
564
+ for (auto j = 0 ; j < observations.size (); j++)
565
+ {
566
+ const auto & obs = *observations[j];
567
+ std::vector<size_t > indices_ (nbNeighbors_);
568
+ std::vector<double > distances_ (nbNeighbors_);
569
+ tree.knnSearch (obs.x .data (), nbNeighbors_, &indices_[0 ], &distances_[0 ]);
570
+
571
+ std::transform (distances_.begin (), distances_.end (), distances_.begin (),
572
+ static_cast <double (*)(double )>(std::sqrt));
573
+ const auto & mean = std::accumulate (distances_.begin (), distances_.end (), 0.0 ) / (nbNeighbors_ - 1 );
574
+ means[j] = mean;
575
+ acc (mean);
576
+ }
577
+ double mean_max = std::numeric_limits<double >::max ();
578
+ if (percentile != 1 .f )
579
+ mean_max = quantile (acc, quantile_probability = percentile);
580
+ estimatedRadii_[i] = mean (acc);
581
+
582
+ std::vector<const Observation*> filteredObservations;
583
+ std::vector<Landmark*> filteredLandmarks;
584
+ filteredObservations.reserve (observations.size ());
585
+ filteredLandmarks.reserve (landmarks.size ());
586
+ for (auto j = 0 ; j < observations.size (); j++)
587
+ if (means[j] < mean_max)
588
+ {
589
+ filteredObservations.push_back (observations[j]);
590
+ filteredLandmarks.push_back (landmarks[j]);
591
+ }
592
+ filteredObservations.shrink_to_fit ();
593
+ filteredLandmarks.shrink_to_fit ();
594
+ observations = std::move (filteredObservations);
595
+ landmarks = std::move (filteredLandmarks);
596
+ }
597
+ }
598
+
599
+ for (auto & landmark : sfmData.getLandmarks ())
600
+ {
601
+ landmark.second .observations .clear ();
602
+ }
603
+
604
+ for (int i = 0 ; i < viewIds.size (); ++i)
605
+ {
606
+ auto itView = viewIds.begin ();
607
+ std::advance (itView, i);
608
+ const IndexT viewId = *itView;
609
+
610
+ if (estimatedRadii_[i] != -1 .)
611
+ estimatedRadii[viewId] = estimatedRadii_[i];
612
+
613
+ auto & observationsIt = observationsPerView.find (viewId);
614
+ if (observationsIt != observationsPerView.end ())
615
+ {
616
+ auto & observations = observationsIt->second .first ;
617
+ auto & landmarks = observationsIt->second .second ;
618
+ for (int j = 0 ; j < observations.size (); j++)
619
+ {
620
+ landmarks[j]->observations [viewId] = *observations[j];
621
+ }
622
+ }
623
+ }
624
+
625
+ return true ;
626
+ }
627
+
470
628
int aliceVision_main (int argc, char *argv[])
471
629
{
472
630
// command-line parameters
473
631
474
632
std::string inputSfmFilename;
475
633
std::string outputSfmFilename;
634
+ std::string outputRadiiFilename;
476
635
int maxNbObservationsPerLandmark = 2 ;
477
636
int minNbObservationsPerLandmark = 5 ;
478
637
double radiusScale = 2 ;
479
638
bool useFeatureScale = true ;
480
- int nbNeighbors = 10 ;
639
+ int nbNeighbors3D = 10 ;
481
640
double neighborsInfluence = 0.5 ;
482
641
int nbIterations = 5 ;
642
+ int nbNeighbors2D = 5 ;
643
+ float percentile = 0.95 ;
483
644
484
645
// user optional parameters
485
646
std::vector<std::string> featuresFolders;
@@ -503,19 +664,25 @@ int aliceVision_main(int argc, char *argv[])
503
664
" Scale factor applied to pixel size based radius filter applied to landmarks." )
504
665
(" useFeatureScale" , po::value<bool >(&useFeatureScale)->default_value (useFeatureScale),
505
666
" If true, use feature scale for computing pixel size. Otherwise, use a scale of 1 pixel." )
506
- (" nbNeighbors " , po::value<int >(&nbNeighbors )->default_value (nbNeighbors ),
667
+ (" nbNeighbors3D " , po::value<int >(&nbNeighbors3D )->default_value (nbNeighbors3D ),
507
668
" Number of neighbor landmarks used in making the decision for best observations." )
508
669
(" neighborsInfluence" , po::value<double >(&neighborsInfluence)->default_value (neighborsInfluence),
509
670
" Specifies how much influential the neighbors are in selecting the best observations."
510
671
" Between 0. and 1., the closer to 1., the more influencial the neighborhood is." )
511
672
(" nbIterations" , po::value<int >(&nbIterations)->default_value (nbIterations),
512
673
" Number of iterations to propagate neighbors information." )
674
+ (" nbNeighbors2D" , po::value<int >(&nbNeighbors2D)->default_value (nbNeighbors2D),
675
+ " Number of neighbor observations to be considered for the landmarks-based masking." )
676
+ (" percentile" , po::value<float >(&percentile)->default_value (percentile),
677
+ " TODO." )
513
678
(" featuresFolders,f" , po::value<std::vector<std::string>>(&featuresFolders)->multitoken (),
514
679
" Path to folder(s) containing the extracted features." )
515
680
(" matchesFolders,m" , po::value<std::vector<std::string>>(&matchesFolders)->multitoken (),
516
681
" Path to folder(s) in which computed matches are stored." )
517
682
(" describerTypes,d" , po::value<std::string>(&describerTypesName)->default_value (describerTypesName),
518
- feature::EImageDescriberType_informations ().c_str ());
683
+ feature::EImageDescriberType_informations ().c_str ())
684
+ (" outputRadiiFile" , po::value<std::string>(&outputRadiiFilename)->default_value (outputRadiiFilename),
685
+ " Output Radii file containing the estimated projection radius of observations per view." );
519
686
520
687
CmdLine cmdline (" AliceVision SfM filtering." ); // TODO add description
521
688
cmdline.add (requiredParams);
@@ -551,11 +718,30 @@ int aliceVision_main(int argc, char *argv[])
551
718
552
719
if (maxNbObservationsPerLandmark > 0 )
553
720
{
554
- ALICEVISION_LOG_INFO (" Filtering observations: started." );
555
- filterObservations (sfmData, maxNbObservationsPerLandmark, nbNeighbors , neighborsInfluence, nbIterations);
556
- ALICEVISION_LOG_INFO (" Filtering observations: done." );
721
+ ALICEVISION_LOG_INFO (" Filtering observations in 3D : started." );
722
+ filterObservations3D (sfmData, maxNbObservationsPerLandmark, nbNeighbors3D , neighborsInfluence, nbIterations);
723
+ ALICEVISION_LOG_INFO (" Filtering observations in 3D : done." );
557
724
}
558
725
726
+ if (nbNeighbors2D > 0 || percentile < 1 .f )
727
+ {
728
+ HashMap<IndexT, double > estimatedRadii;
729
+ ALICEVISION_LOG_INFO (" Filtering observations in 2D: started." );
730
+ filterObservations2D (sfmData, nbNeighbors2D, percentile, estimatedRadii);
731
+ ALICEVISION_LOG_INFO (" Filtering observations in 2D: done." );
732
+
733
+ if (outputRadiiFilename.empty ())
734
+ outputRadiiFilename = (fs::path (outputSfmFilename).parent_path () / " radii.txt" ).string ();
735
+ std::ofstream fs (outputRadiiFilename, std::ios::out);
736
+ if (!fs.is_open ())
737
+ ALICEVISION_LOG_WARNING (" Unable to create the radii file " << outputRadiiFilename);
738
+ else
739
+ {
740
+ for (const auto & radius : estimatedRadii)
741
+ fs << radius.first << " \t " << radius.second << std::endl;
742
+ fs.close ();
743
+ }
744
+ }
559
745
560
746
sfmDataIO::Save (sfmData, outputSfmFilename, sfmDataIO::ESfMData::ALL);
561
747
return EXIT_SUCCESS;
0 commit comments