Skip to content

Commit 0278a7b

Browse files
committed
[WIP][filterSfM] multiple improvements
- add option to propagate neighbor observations - use Jaccard index in step 2
1 parent 5c5b7d4 commit 0278a7b

File tree

1 file changed

+52
-58
lines changed

1 file changed

+52
-58
lines changed

src/software/pipeline/main_filterSfM.cpp

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct FilterParams
6161
struct FilterLandmarksStep2Params
6262
{
6363
bool enabled = true;
64-
int maxNbObservationsPerLandmark = 2;
64+
float minScore = .3f;
6565
int nbNeighbors3D = 10;
6666
} step2;
6767
struct FilterLandmarksStep3Params
@@ -77,6 +77,9 @@ struct FilterParams
7777
int maxNbObservationsPerLandmark = 2;
7878
bool propagationEnabled = true;
7979
int nbNeighbors3D = 10;
80+
bool observationsPropagationEnabled = true;
81+
int observationsPropagationFrequency = 1;
82+
int observationsPropagationCount = 1;
8083
double neighborsInfluence = 0.5;
8184
int nbIterations = 5;
8285
bool dampingEnabled = true;
@@ -97,7 +100,7 @@ std::istream& operator>>(std::istream& in, FilterParams& params)
97100
in >> token;
98101
std::vector<std::string> splitParams;
99102
boost::split(splitParams, token, boost::algorithm::is_any_of(":"));
100-
if(splitParams.size() != 21)
103+
if(splitParams.size() != 24)
101104
throw std::invalid_argument("Failed to parse FilterParams from: \n" + token);
102105
int i = 0;
103106

@@ -107,7 +110,7 @@ std::istream& operator>>(std::istream& in, FilterParams& params)
107110
params.filterLandmarks.step1.minNbObservationsPerLandmark = boost::lexical_cast<int>(splitParams[i++]);
108111

109112
params.filterLandmarks.step2.enabled = boost::to_lower_copy(splitParams[i++]) == "true";
110-
params.filterLandmarks.step2.maxNbObservationsPerLandmark = boost::lexical_cast<int>(splitParams[i++]);
113+
params.filterLandmarks.step2.minScore = boost::lexical_cast<float>(splitParams[i++]);
111114
params.filterLandmarks.step2.nbNeighbors3D = boost::lexical_cast<int>(splitParams[i++]);
112115

113116
params.filterLandmarks.step3.enabled = boost::to_lower_copy(splitParams[i++]) == "true";
@@ -118,6 +121,9 @@ std::istream& operator>>(std::istream& in, FilterParams& params)
118121
params.filterObservations3D.maxNbObservationsPerLandmark = boost::lexical_cast<int>(splitParams[i++]);
119122
params.filterObservations3D.propagationEnabled = boost::to_lower_copy(splitParams[i++]) == "true";
120123
params.filterObservations3D.nbNeighbors3D = boost::lexical_cast<int>(splitParams[i++]);
124+
params.filterObservations3D.observationsPropagationEnabled = boost::to_lower_copy(splitParams[i++]) == "true";
125+
params.filterObservations3D.observationsPropagationFrequency = boost::lexical_cast<int>(splitParams[i++]);
126+
params.filterObservations3D.observationsPropagationCount = boost::lexical_cast<int>(splitParams[i++]);
121127
params.filterObservations3D.neighborsInfluence = boost::lexical_cast<double>(splitParams[i++]);
122128
params.filterObservations3D.nbIterations = boost::lexical_cast<int>(splitParams[i++]);
123129
params.filterObservations3D.dampingEnabled = boost::to_lower_copy(splitParams[i++]) == "true";
@@ -436,40 +442,24 @@ void filterLandmarks_step2(SfMData& sfmData,
436442
#pragma omp parallel for reduction(+ : nbToRemove)
437443
for(auto i = 0; i < landmarksData.size(); i++)
438444
{
439-
int score = 0;
445+
float score = 0.f;
440446
auto& [viewIds, neighbors] = viewData[i];
441447
for(auto j = 0; j < neighbors.size(); j++)
442448
{
443449
int scoreNeighbor = 0;
444450
const auto& neighborId = neighbors[j];
445451
const auto& viewIds_neighbor = viewData[neighborId].first;
446-
// loop over common views
447-
auto viewIds_it = viewIds.begin();
448-
auto viewIds_neighbor_it = viewIds_neighbor.begin();
449-
while(viewIds_it != viewIds.end() && viewIds_neighbor_it != viewIds_neighbor.end())
450-
{
451-
if(*viewIds_it < *viewIds_neighbor_it)
452-
{
453-
++viewIds_it;
454-
}
455-
else
456-
{
457-
// if same view
458-
if(!(*viewIds_neighbor_it < *viewIds_it))
459-
{
460-
scoreNeighbor++;
461-
if(scoreNeighbor == params.maxNbObservationsPerLandmark)
462-
{
463-
score++;
464-
break;
465-
}
466-
++viewIds_it;
467-
}
468-
++viewIds_neighbor_it;
469-
}
470-
}
452+
453+
std::vector<IndexT> viewIds_intersection;
454+
std::set_intersection(viewIds.begin(), viewIds.end(), viewIds_neighbor.begin(), viewIds_neighbor.end(),
455+
std::back_inserter(viewIds_intersection));
456+
float nbIntersection = static_cast<float>(viewIds_intersection.size());
457+
// Jaccard index or Intersection over Union (IoU): #overlap / #union
458+
score += nbIntersection / ((viewIds.size() + viewIds_neighbor.size()) - nbIntersection);
471459
}
472-
if(score < params.nbNeighbors3D / 2)
460+
//score /= neighbors.size();
461+
score /= params.nbNeighbors3D;
462+
if(score < params.minScore)
473463
{
474464
toRemove[i] = true;
475465
nbToRemove++;
@@ -686,10 +676,10 @@ void computeNeighborsInfo(std::vector<Landmark*>& landmarksData, const FilterPar
686676
ALICEVISION_LOG_INFO("Computing landmark neighbors and distance-based weights: done");
687677
}
688678

689-
void augmentInitialObservations(std::vector<std::pair<std::vector<IndexT>, std::vector<double>>>& viewScoresData,
679+
void propagateNeighborsObservations(std::vector<std::pair<std::vector<IndexT>, std::vector<double>>>& viewScoresData,
690680
std::vector<std::pair<std::vector<size_t>, std::vector<double>>>& neighborsData)
691681
{
692-
ALICEVISION_LOG_INFO("Augment initial observations based on neighbors observations: started");
682+
ALICEVISION_LOG_INFO("Augment observations with neighbors observations: started");
693683
std::vector<std::vector<IndexT>> viewScoresData_t(viewScoresData.size());
694684
// for each landmark, identify the new observations coming from its neighborhood
695685
#pragma omp parallel for
@@ -712,12 +702,14 @@ void augmentInitialObservations(std::vector<std::pair<std::vector<IndexT>, std::
712702
std::back_inserter(viewIds_toAdd));
713703
viewIds_t = std::move(viewIds_toAdd);
714704
}
705+
double nbAdded = 0;
715706
// for each landmark, add the previously identified observations
716-
#pragma omp parallel for
707+
#pragma omp parallel for reduction(+ : nbAdded)
717708
for(int id = 0; id < viewScoresData.size(); id++)
718709
{
719710
auto& [viewIds, viewScores] = viewScoresData[id];
720711
const auto& viewIds_toAdd = viewScoresData_t[id];
712+
nbAdded += static_cast<double>(viewIds_toAdd.size()) / viewIds.size();
721713
viewIds.insert(viewIds.end(), viewIds_toAdd.begin(), viewIds_toAdd.end());
722714
viewScores.insert(viewScores.end(), viewIds_toAdd.size(), 0.);
723715

@@ -737,21 +729,24 @@ void augmentInitialObservations(std::vector<std::pair<std::vector<IndexT>, std::
737729
viewScores[j] = scores_temp[idx[j]];
738730
}
739731
}
740-
ALICEVISION_LOG_INFO("Augment initial observations based on neighbors observations: done");
732+
ALICEVISION_LOG_INFO(
733+
"On average, observations per landmark are augmented by: " << nbAdded * 100. / viewScoresData.size() << " %.");
734+
ALICEVISION_LOG_INFO("Augment observations with neighbors observations: done");
741735
}
742736

743-
void computeNewScores(const std::vector<Landmark*>& landmarksData,
737+
void propagateNeighborsScores(const std::vector<Landmark*>& landmarksData,
744738
const FilterParams::FilterObservations3DParams& params,
745739
const std::vector<std::pair<std::vector<size_t>, std::vector<double>>>& neighborsData,
746740
const std::vector<std::pair<std::vector<IndexT>, std::vector<double>>>& viewScoresData,
747741
std::vector<std::vector<double>>& viewScoresData_t)
748742
{
743+
ALICEVISION_LOG_INFO("Propagating neighbors observation scores: started");
749744
#pragma omp parallel for
750745
for(auto id = 0; id < landmarksData.size(); id++)
751746
{
752747
const auto& [viewIds, viewScores] = viewScoresData[id];
753748
auto& viewScores_acc = viewScoresData_t[id];
754-
// initialize to zero, will first contain the weighted average scores of neighbors
749+
// initialize to zero, will contain the weighted average scores of neighbors
755750
viewScores_acc.assign(viewScores.size(), 0.);
756751
// accumulator for normalisation
757752
double viewScores_total = 0.;
@@ -789,19 +784,16 @@ void computeNewScores(const std::vector<Landmark*>& landmarksData,
789784
}
790785
}
791786
}
792-
// if common views with neighbor landmarks
793-
//if(viewScores_total != 0.)
794787
for(auto j = 0; j < viewScores_acc.size(); j++)
795788
{
796-
// normalize score and apply influence factor
797-
//viewScores_acc[j] *= params.neighborsInfluence / viewScores_total;
789+
// apply influence factor
798790
viewScores_acc[j] *= params.neighborsInfluence;
799791
// combine weighted neighbor scores and the landmark's own scores
800792
viewScores_acc[j] += (1 - params.neighborsInfluence) * viewScores[j];
801793
}
802794

803795
// dampen scores of non-chosen observations
804-
if(params.dampingEnabled && viewScores_acc.size() <= params.maxNbObservationsPerLandmark)
796+
if(params.dampingEnabled && viewScores_acc.size() > params.maxNbObservationsPerLandmark)
805797
{
806798
// sort by descending view score order
807799
std::vector<size_t> idx(viewScores_acc.size());
@@ -820,19 +812,27 @@ void computeNewScores(const std::vector<Landmark*>& landmarksData,
820812
viewScores_acc[j] /= viewScores_total;
821813
}
822814
}
815+
ALICEVISION_LOG_INFO("Propagating neighbors observation scores: done");
823816
}
824817

825818
void propagateNeighborsInfo(std::vector<Landmark*>& landmarksData,
826819
const FilterParams::FilterObservations3DParams& params,
827820
std::vector<std::pair<std::vector<size_t>, std::vector<double>>>& neighborsData,
828821
std::vector<std::pair<std::vector<IndexT>, std::vector<double>>>& viewScoresData)
829822
{
830-
ALICEVISION_LOG_INFO("Propagating neighbors observation scores: started");
831823
// new view scores at iteration t
832824
std::vector<std::vector<double>> viewScoresData_t(landmarksData.size());
825+
int observationsPropagationCount =
826+
params.observationsPropagationCount == 0 ? params.nbIterations : params.observationsPropagationCount;
833827
for(auto i = 0; i < params.nbIterations; i++)
834828
{
835-
computeNewScores(landmarksData, params, neighborsData, viewScoresData, viewScoresData_t);
829+
if(params.observationsPropagationEnabled && (i % params.observationsPropagationFrequency == 0) &&
830+
observationsPropagationCount > 0)
831+
{
832+
propagateNeighborsObservations(viewScoresData, neighborsData);
833+
--observationsPropagationCount;
834+
}
835+
propagateNeighborsScores(landmarksData, params, neighborsData, viewScoresData, viewScoresData_t);
836836

837837
// Mean Squared Error
838838
double error = 0.;
@@ -858,7 +858,6 @@ void propagateNeighborsInfo(std::vector<Landmark*>& landmarksData,
858858
error /= landmarksData.size();
859859
ALICEVISION_LOG_INFO("MSE at iteration " << i << ": " << error);
860860
}
861-
ALICEVISION_LOG_INFO("Propagating neighbors observation scores: done");
862861
}
863862

864863
void removeNonObservedLandmarks(SfMData& sfmData)
@@ -901,7 +900,6 @@ bool filterObservations3D(SfMData& sfmData, const FilterParams::FilterObservatio
901900
// contains the neighbor landmarks ids with their corresponding weights
902901
std::vector<std::pair<std::vector<size_t>, std::vector<double>>> neighborsData(landmarksData.size());
903902
computeNeighborsInfo(landmarksData, params, neighborsData);
904-
augmentInitialObservations(viewScoresData, neighborsData);
905903
propagateNeighborsInfo(landmarksData, params, neighborsData, viewScoresData);
906904
}
907905

@@ -913,24 +911,17 @@ bool filterObservations3D(SfMData& sfmData, const FilterParams::FilterObservatio
913911
auto& [viewIds, viewScores] = viewScoresData[i];
914912
const auto& nbObservations = viewIds.size();
915913

916-
//// check number of observations
917-
//if(landmark.observations.size() <= params.maxNbObservationsPerLandmark)
918-
// continue;
919-
920914
// sort by descending view score order
921915
std::vector<size_t> idx(nbObservations);
922916
std::iota(idx.begin(), idx.end(), 0);
923917
std::stable_sort(idx.begin(), idx.end(), [&v = viewScores](size_t i1, size_t i2) { return v[i1] > v[i2]; });
924918

925919
// keep only observations with best scores
926920
Observations filteredObservations;
927-
//double threshold = 0.1 / params.maxNbObservationsPerLandmark;
928-
for(auto j = 0; j < params.maxNbObservationsPerLandmark; j++)
921+
size_t maxNbObservationsPerLandmark =
922+
std::min(static_cast<size_t>(params.maxNbObservationsPerLandmark), landmark.observations.size());
923+
for(auto j = 0; j < maxNbObservationsPerLandmark; j++)
929924
{
930-
//const auto& viewScore = viewScores[idx[j]];
931-
/*if(viewScore < threshold)
932-
break;*/
933-
934925
// add observation only if it's an original observation and not augmented
935926
const auto& viewId = viewIds[idx[j]];
936927
const auto& obsIt = landmark.observations.find(viewId);
@@ -954,7 +945,7 @@ double filter2DView(SfMData& sfmData, const FilterParams::FilterObservations2DPa
954945
KdTree2D tree(2, data, nanoflann::KDTreeSingleIndexAdaptorParams(MAX_LEAF_ELEMENTS));
955946
tree.buildIndex();
956947

957-
size_t nbNeighbors_ = params.nbNeighbors2D;
948+
int nbNeighbors_ = params.nbNeighbors2D;
958949
// average neighbors distance for each observation
959950
std::vector<double> means(observations.size(), std::numeric_limits<double>::max());
960951
const std::size_t cacheSize = 1000;
@@ -1106,7 +1097,7 @@ int aliceVision_main(int argc, char *argv[])
11061097
" * Min Nb of Observations: Minimum number of observations required to keep a landmark.\n"
11071098
" * Step 2 parameters:\n"
11081099
" * Enabled: Remove landmarks with dissimilar observations of 3D landmark neighbors.\n"
1109-
" * Max Nb of Observations: Maximum number of allowed observations per landmark.\n"
1100+
" * Min Score: Minimum similarity score between the observations of the landmark and of its neighbors.\n"
11101101
" * Nb of Neighbors in 3D: Number of neighbor landmarks used in making the decision for best observations.\n"
11111102
" * Step 3 parameters:\n"
11121103
" * Enabled: Remove landmarks with worse resolution than neighbors.\n"
@@ -1116,8 +1107,11 @@ int aliceVision_main(int argc, char *argv[])
11161107
"Filter Observations 3D parameters:\n"
11171108
" * Enabled: Select best observations for observation consistency between 3D neighboring landmarks.\n"
11181109
" * Max Nb of Observations: Maximum number of allowed observations per landmark.\n"
1119-
" * Enable Neighbors Influence: nable propagating neighbors' scores iteratively.\n"
1110+
" * Enable Neighbors Influence: Enable propagating neighbors' info (scores, observations) iteratively.\n"
11201111
" * Nb of Neighbors in 3D: Number of neighbor landmarks used in making the decision for best observations.\n"
1112+
" * Enable Propagating Observations: Propagate neighbors observations before propagating the scores.\n"
1113+
" * Frequency: Specifies every how many iterations should the observations be propagated.\n"
1114+
" * Count: Maximum number of times the observations are propagated (0 for no limit).\n"
11211115
" * Neighbors Influence: Specifies how much influential the neighbors are in selecting the best observations.\n"
11221116
" Between 0. and 1., the closer to 1., the more influencial the neighborhood is.\n"
11231117
" * Nb of Iterations: Number of iterations to propagate neighbors information.\n"

0 commit comments

Comments
 (0)