Skip to content

Commit eb36bcc

Browse files
committed
[WIP][filterSfM] multiple improvements and fixes
- resolve duplicate features issue - do not normalize propagated neighbors scores - fix NaN MSE issue - fix radius estimation caused by quantile issue
1 parent 416e6a8 commit eb36bcc

File tree

1 file changed

+106
-48
lines changed

1 file changed

+106
-48
lines changed

src/software/pipeline/main_filterSfM.cpp

Lines changed: 106 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -221,63 +221,107 @@ using KdTree3D = nanoflann::KDTreeSingleIndexAdaptor<
221221
nanoflann::L2_Simple_Adaptor<double, LandmarksAdaptator>,
222222
LandmarksAdaptator, 3 /* dim */,size_t>;
223223

224-
class RadiusKnnSearch
224+
class PixSizeSearch
225225
{
226226
public:
227227
const double _radius_sq;
228-
const int _nb_neighbors;
229-
int nb_found = 0;
228+
const std::vector<double>& _pixSize;
229+
const double _pixSize_i;
230+
bool found = false;
230231

231-
inline RadiusKnnSearch(double radius, int k)
232+
inline PixSizeSearch(double radius, const std::vector<double>& pixSize, size_t i)
232233
: _radius_sq(radius * radius)
233-
, _nb_neighbors(k)
234+
, _pixSize(pixSize)
235+
, _pixSize_i(pixSize[i])
234236
{
235237
}
236238

237-
inline bool full() const { return nb_found == _nb_neighbors; }
239+
inline bool full() const { return found; }
238240

239-
inline bool addPoint(double dist, IndexT index)
241+
inline bool addPoint(double dist, size_t index)
240242
{
241-
if(dist < _radius_sq)
243+
// strict comparison because a point in the tree can be a neighbor of itself
244+
if(dist < _radius_sq && _pixSize[index] < _pixSize_i)
242245
{
243-
nb_found++;
244-
return nb_found < _nb_neighbors;
246+
found = true;
247+
return false;
245248
}
246249
return true;
247250
}
248251

249252
inline double worstDist() const { return _radius_sq; }
250253
};
251254

252-
class PixSizeSearch
255+
class KnnNonZeroSearch
253256
{
257+
254258
public:
255-
const double _radius_sq;
256-
const std::vector<double>& _pixSize;
257-
const double _pixSize_i;
258-
bool found = false;
259+
size_t* indices;
260+
double* dists;
261+
int capacity;
262+
int count;
263+
double _epsilon = 1e-6;
264+
265+
inline KnnNonZeroSearch(int capacity_)
266+
: indices(nullptr)
267+
, dists(nullptr)
268+
, capacity(capacity_)
269+
, count(0)
270+
{
271+
}
259272

260-
inline PixSizeSearch(double radius, const std::vector<double>& pixSize, size_t i)
261-
: _radius_sq(radius * radius)
262-
, _pixSize(pixSize)
263-
, _pixSize_i(pixSize[i])
273+
inline void init(size_t* indices_, double* dists_)
264274
{
275+
indices = indices_;
276+
dists = dists_;
277+
count = 0;
278+
if(capacity)
279+
dists[capacity - 1] = (std::numeric_limits<double>::max)();
265280
}
266281

267-
inline bool full() const { return found; }
282+
inline int size() const { return count; }
283+
284+
inline bool full() const { return count == capacity; }
268285

269286
inline bool addPoint(double dist, size_t index)
270287
{
271-
// strict comparison because a point in the tree can be a neighbor of itself
272-
if(dist < _radius_sq && _pixSize[index] < _pixSize_i)
288+
if(dist < _epsilon)
289+
return true;
290+
int i;
291+
bool isDuplicate = false;
292+
for(i = count; i > 0; --i)
273293
{
274-
found = true;
275-
return false;
294+
if(!(dists[i - 1] > dist))
295+
{
296+
isDuplicate = (dists[i - 1] == dist);
297+
break;
298+
}
276299
}
300+
if(isDuplicate)
301+
{
302+
if(indices[i - 1] > index)
303+
indices[i - 1] = index;
304+
}
305+
else
306+
{
307+
if(i < capacity)
308+
{
309+
if(i > 0)
310+
if(count < capacity)
311+
std::move_backward(&indices[i], &indices[count], &indices[count + 1]);
312+
else
313+
std::move_backward(&indices[i], &indices[capacity - 1], &indices[capacity]);
314+
dists[i] = dist;
315+
indices[i] = index;
316+
}
317+
if(count < capacity)
318+
count++;
319+
}
320+
// tell caller that the search shall continue
277321
return true;
278322
}
279323

280-
inline double worstDist() const { return _radius_sq; }
324+
inline double worstDist() const { return dists[capacity - 1]; }
281325
};
282326

283327
using ObservationsPerView = stl::flat_map<std::size_t, std::pair<std::vector<Observation>, std::vector<Landmark*>>>;
@@ -349,7 +393,7 @@ void filterLandmarks_step2(SfMData& sfmData,
349393

350394
ALICEVISION_LOG_INFO("Computing landmarks neighbors: started.");
351395
// note that the landmark is a neighbor to itself with zero distance, hence the +/- 1
352-
int nbNeighbors_ = std::min(params.nbNeighbors3D, static_cast<int>(landmarksData.size() - 1)) + 1;
396+
int nbNeighbors_ = params.nbNeighbors3D;
353397
// contains the observing view ids and neighbors for each landmark
354398
std::vector<std::pair<std::vector<IndexT>, std::vector<size_t>>> viewData(landmarksData.size());
355399
#pragma omp parallel for
@@ -369,9 +413,12 @@ void filterLandmarks_step2(SfMData& sfmData,
369413

370414
neighbors.resize(nbNeighbors_);
371415
std::vector<double> weights_(nbNeighbors_);
372-
tree.knnSearch(landmark.X.data(), nbNeighbors_, &neighbors[0], &weights_[0]);
373-
// a landmark is a neighbor to itself with zero distance, remove it
374-
neighbors.erase(neighbors.begin());
416+
KnnNonZeroSearch resultSet(nbNeighbors_);
417+
resultSet.init(&neighbors[0], &weights_[0]);
418+
tree.findNeighbors(resultSet, landmark.X.data());
419+
const auto& nbFound = resultSet.size();
420+
neighbors.resize(nbFound);
421+
weights_.resize(nbFound);
375422
}
376423
ALICEVISION_LOG_INFO("Computing landmarks neighbors: done.");
377424

@@ -601,31 +648,28 @@ void computeNeighborsInfo(std::vector<Landmark*>& landmarksData, const FilterPar
601648
KdTree3D tree(3, dataAdaptor, nanoflann::KDTreeSingleIndexAdaptorParams(MAX_LEAF_ELEMENTS));
602649
tree.buildIndex();
603650
ALICEVISION_LOG_INFO("KdTree created for " << landmarksData.size() << " points.");
604-
// note that the landmark is a neighbor to itself with zero distance, hence the +/- 1
605-
int nbNeighbors_ = std::min(params.nbNeighbors3D, static_cast<int>(landmarksData.size() - 1)) + 1;
651+
int nbNeighbors_ = params.nbNeighbors3D;
606652
#pragma omp parallel for
607653
for(auto i = 0; i < landmarksData.size(); i++)
608654
{
609655
const sfmData::Landmark& landmark = *landmarksData[i];
610656
auto& [indices_, weights_] = neighborsData[i];
611657
indices_.resize(nbNeighbors_);
612658
weights_.resize(nbNeighbors_);
613-
tree.knnSearch(landmark.X.data(), nbNeighbors_, &indices_[0], &weights_[0]);
614-
// a landmark is a neighbor to itself with zero distance, remove it
615-
indices_.erase(indices_.begin());
616-
weights_.erase(weights_.begin());
659+
KnnNonZeroSearch resultSet(nbNeighbors_);
660+
resultSet.init(&indices_[0], &weights_[0]);
661+
tree.findNeighbors(resultSet, landmark.X.data());
662+
const auto& nbFound = resultSet.size();
663+
indices_.resize(nbFound);
664+
weights_.resize(nbFound);
617665
// accumulator used for normalisation
618666
double total = 0.;
619667
for(auto& w : weights_)
620668
{
621669
// weight is the inverse of distance between a landmark and its neighbor
622670
w = 1. / std::sqrt(w);
623-
if(std::isinf(w))
624-
w = std::numeric_limits<double>::max();
625671
total += w;
626672
}
627-
if(std::isinf(total))
628-
total = std::numeric_limits<double>::max();
629673
// normalize weights
630674
for(auto& w : weights_)
631675
{
@@ -651,7 +695,7 @@ void computeNewScores(const std::vector<Landmark*>& landmarksData,
651695
// accumulator for normalisation
652696
double viewScores_total = 0.;
653697
auto& [indices_, weights_] = neighborsData[id];
654-
for(auto j = 0; j < params.nbNeighbors3D; j++)
698+
for(auto j = 0; j < indices_.size(); j++)
655699
{
656700
const auto& neighborId = indices_[j];
657701
const auto& neighborWeight = weights_[j];
@@ -689,7 +733,8 @@ void computeNewScores(const std::vector<Landmark*>& landmarksData,
689733
for(auto j = 0; j < viewScores_acc.size(); j++)
690734
{
691735
// normalize score and apply influence factor
692-
viewScores_acc[j] *= params.neighborsInfluence / viewScores_total;
736+
//viewScores_acc[j] *= params.neighborsInfluence / viewScores_total;
737+
viewScores_acc[j] *= params.neighborsInfluence;
693738
// combine weighted neighbor scores and the landmark's own scores
694739
viewScores_acc[j] += (1 - params.neighborsInfluence) * viewScores[j];
695740
}
@@ -742,7 +787,8 @@ void propagateNeighborsInfo(std::vector<Landmark*>& landmarksData,
742787
const auto& v = viewScoresData_t[id][j] - viewScoresData[id].second[j];
743788
error_j += v * v;
744789
}
745-
error_j /= viewScoresData_t[id].size();
790+
if(error_j > 0.)
791+
error_j /= viewScoresData_t[id].size();
746792
error += error_j;
747793
}
748794
// update scores
@@ -799,6 +845,9 @@ bool filterObservations3D(SfMData& sfmData, const FilterParams::FilterObservatio
799845
Observations filteredObservations;
800846
for(auto j = 0; j < params.maxNbObservationsPerLandmark; j++)
801847
{
848+
const auto& viewScore = viewScores[idx[j]];
849+
if(viewScore < 0.3)
850+
break;
802851
const auto& viewId = viewIds[idx[j]];
803852
filteredObservations[viewId] = landmark.observations[viewId];
804853
}
@@ -818,24 +867,31 @@ double filter2DView(SfMData& sfmData, const FilterParams::FilterObservations2DPa
818867
tree.buildIndex();
819868

820869
// note that the observation is a neighbor to itself with zero distance, hence the +/- 1
821-
size_t nbNeighbors_ = std::min(static_cast<size_t>(params.nbNeighbors2D), observations.size() - 1) + 1;
870+
size_t nbNeighbors_ = params.nbNeighbors2D;
822871
// average neighbors distance for each observation
823-
std::vector<double> means(observations.size());
872+
std::vector<double> means(observations.size(), std::numeric_limits<double>::max());
824873
const std::size_t cacheSize = 1000;
825874
// accumulator for quantile computation
826-
accumulator_set<double, stats<tag::tail_quantile<right>>> acc(tag::tail<right>::cache_size = cacheSize);
875+
accumulator_set<double, stats<tag::median, tag::tail_quantile<right>>> acc(tag::tail<right>::cache_size = cacheSize);
827876
for(auto j = 0; j < observations.size(); j++)
828877
{
829878
// Find neighbors and the corresponding distances
830879
const auto& obs = observations[j];
831880
std::vector<size_t> indices_(nbNeighbors_);
832881
std::vector<double> distances_(nbNeighbors_);
833-
tree.knnSearch(obs.x.data(), nbNeighbors_, &indices_[0], &distances_[0]);
882+
KnnNonZeroSearch resultSet(nbNeighbors_);
883+
resultSet.init(&indices_[0], &distances_[0]);
884+
tree.findNeighbors(resultSet, obs.x.data());
885+
const auto& nbFound = resultSet.size();
886+
if(nbFound == 0)
887+
continue;
888+
indices_.resize(nbFound);
889+
distances_.resize(nbFound);
834890
// returned distances are L2 -> apply squared root
835891
std::transform(distances_.begin(), distances_.end(), distances_.begin(),
836892
static_cast<double (*)(double)>(std::sqrt));
837893
// average distance
838-
const auto& mean = std::accumulate(distances_.begin(), distances_.end(), 0.0) / (nbNeighbors_ - 1);
894+
const auto& mean = std::accumulate(distances_.begin(), distances_.end(), 0.0) / nbFound;
839895
means[j] = mean;
840896
// update accumulator
841897
acc(mean);
@@ -846,14 +902,16 @@ double filter2DView(SfMData& sfmData, const FilterParams::FilterObservations2DPa
846902
mean_max = quantile(acc, quantile_probability = params.percentile);
847903
// estimated mask radius is the average of distance means
848904
// quantile is used to avoid outlier bias
849-
double radius = quantile(acc, quantile_probability = (1.f - params.percentile) * 0.5f);
905+
double radius = median(acc);
850906
// check if estimated radius is too large
851907
{
852908
const View& view = *(sfmData.getViews().at(viewId));
853909
double radiusMax =
854910
params.maskRadiusThreshold * 0.5 * (view.getImage().getWidth() + view.getImage().getHeight());
855911
if(radius > radiusMax)
856912
radius = radiusMax;
913+
if(mean_max > radiusMax)
914+
mean_max = radiusMax;
857915
}
858916

859917
// filter outlier observations

0 commit comments

Comments
 (0)