From 0aec7697a1dba3cf2e5ff234c296293f91e0be73 Mon Sep 17 00:00:00 2001 From: Fabien Servant Date: Fri, 25 Jul 2025 16:50:41 +0200 Subject: [PATCH] Feature matching chunks --- meshroom/aliceVision/FeatureMatching.py | 2 +- .../ImagePairListIO.cpp | 87 ++++++------------- .../ImagePairListIO.hpp | 32 +++---- .../ImagePairListIO_test.cpp | 25 +----- .../matchingImageCollection/pairBuilder.cpp | 26 +++--- .../matchingImageCollection/pairBuilder.hpp | 8 +- .../pairBuilder_test.cpp | 13 +-- .../pipeline/main_featureMatching.cpp | 85 +++++++++--------- .../pipeline/main_relativePoseEstimating.cpp | 2 +- 9 files changed, 109 insertions(+), 171 deletions(-) diff --git a/meshroom/aliceVision/FeatureMatching.py b/meshroom/aliceVision/FeatureMatching.py index b874f182b4..5d957c1898 100644 --- a/meshroom/aliceVision/FeatureMatching.py +++ b/meshroom/aliceVision/FeatureMatching.py @@ -8,7 +8,7 @@ class FeatureMatching(desc.AVCommandLineNode): commandLine = "aliceVision_featureMatching {allParams}" size = desc.DynamicNodeSize("input") parallelization = desc.Parallelization(blockSize=20) - commandLineRange = "--rangeStart {rangeStart} --rangeSize {rangeBlockSize}" + commandLineRange = "--rangeIteration {rangeIteration} --rangeBlocksCount {rangeBlocksCount}" category = "Sparse Reconstruction" documentation = """ diff --git a/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp b/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp index 9cd15baf39..7b05e965a2 100644 --- a/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp +++ b/src/aliceVision/matchingImageCollection/ImagePairListIO.cpp @@ -14,21 +14,21 @@ namespace aliceVision { namespace matchingImageCollection { -bool loadPairs(std::istream& stream, PairSet& pairs, int rangeStart, int rangeSize) + +bool loadPairsFromFile(const std::string& sFileName, PairSet& pairs) { + std::ifstream stream(sFileName); + if (!stream.is_open()) + { + ALICEVISION_LOG_WARNING("loadPairsFromFile: Impossible to read the specified file: \"" << sFileName << "\"."); + return false; + } + std::size_t nbLine = 0; std::string sValue; for (; std::getline(stream, sValue); ++nbLine) { - if (rangeStart != -1 && rangeSize != 0) - { - if (nbLine < rangeStart) - continue; - if (nbLine >= rangeStart + rangeSize) - break; - } - std::vector vec_str; boost::trim(sValue); boost::split(vec_str, sValue, boost::is_any_of("\t "), boost::token_compress_on); @@ -39,6 +39,7 @@ bool loadPairs(std::istream& stream, PairSet& pairs, int rangeStart, int rangeSi ALICEVISION_LOG_WARNING("loadPairs: Invalid input file."); return false; } + std::stringstream oss; oss.clear(); oss.str(vec_str[0]); @@ -49,32 +50,30 @@ bool loadPairs(std::istream& stream, PairSet& pairs, int rangeStart, int rangeSi oss.clear(); oss.str(vec_str[i]); oss >> J; - if (I == J) - { - ALICEVISION_LOG_WARNING("loadPairs: Invalid input file. Image " << I << " sees itself."); - return false; - } - Pair pairToInsert = (I < J) ? std::make_pair(I, J) : std::make_pair(J, I); - if (pairs.find(pairToInsert) != pairs.end()) - { - // There is no reason to have the same image pair twice in the list of image pairs - // to match. - ALICEVISION_LOG_WARNING("loadPairs: image pair (" << I << ", " << J << ") already added."); - } - ALICEVISION_LOG_INFO("loadPairs: image pair (" << I << ", " << J << ") added."); + + Pair pairToInsert = std::make_pair(I, J); pairs.insert(pairToInsert); } } + return true; } -void savePairs(std::ostream& stream, const PairSet& pairs) +bool savePairsToFile(const std::string& sFileName, const PairSet& pairs) { + std::ofstream outStream(sFileName); + if (!outStream.is_open()) + { + ALICEVISION_LOG_WARNING("savePairsToFile: Impossible to open the output specified file: \"" << sFileName << "\"."); + return false; + } + if (pairs.empty()) { - return; + return false; } - stream << pairs.begin()->first << " " << pairs.begin()->second; + + outStream << pairs.begin()->first << " " << pairs.begin()->second; IndexT previousIndex = pairs.begin()->first; // Pairs is sorted so we will always receive elements with the same first pair ID in @@ -83,47 +82,15 @@ void savePairs(std::ostream& stream, const PairSet& pairs) { if (it->first == previousIndex) { - stream << " " << it->second; + outStream << " " << it->second; } else { - stream << "\n" << it->first << " " << it->second; + outStream << "\n" << it->first << " " << it->second; previousIndex = it->first; } } - stream << "\n"; -} - -bool loadPairsFromFile(const std::string& sFileName, // filename of the list file, - PairSet& pairs, - int rangeStart, - int rangeSize) -{ - std::ifstream in(sFileName); - if (!in.is_open()) - { - ALICEVISION_LOG_WARNING("loadPairsFromFile: Impossible to read the specified file: \"" << sFileName << "\"."); - return false; - } - - if (!loadPairs(in, pairs, rangeStart, rangeSize)) - { - ALICEVISION_LOG_WARNING("loadPairsFromFile: Failed to read file: \"" << sFileName << "\"."); - return false; - } - return true; -} - -bool savePairsToFile(const std::string& sFileName, const PairSet& pairs) -{ - std::ofstream outStream(sFileName); - if (!outStream.is_open()) - { - ALICEVISION_LOG_WARNING("savePairsToFile: Impossible to open the output specified file: \"" << sFileName << "\"."); - return false; - } - - savePairs(outStream, pairs); + outStream << "\n"; return !outStream.bad(); } diff --git a/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp b/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp index 7ebd63d507..2707d4957f 100644 --- a/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp +++ b/src/aliceVision/matchingImageCollection/ImagePairListIO.hpp @@ -13,22 +13,22 @@ namespace aliceVision { namespace matchingImageCollection { -/// Load a set of PairSet from a stream -/// I J K L (pair that link I) -bool loadPairs(std::istream& stream, PairSet& pairs, int rangeStart = -1, int rangeSize = 0); - -/// Save a set of PairSet to a stream (one pair per line) -/// I J -/// I K -void savePairs(std::ostream& stream, const PairSet& pairs); - -/// Same as loadPairs, but loads from a given file -bool loadPairsFromFile(const std::string& sFileName, // filename of the list file, - PairSet& pairs, - int rangeStart = -1, - int rangeSize = 0); - -/// Same as savePairs, but saves to a given file +/** + * @Brief load pairs from file + * File format is reference matchImg1 matchImage2 ...\n + * @param sFileName input file path to load + * @param pairs the output set of pairs + * @return false if a problem is detected in the file +*/ +bool loadPairsFromFile(const std::string& sFileName, PairSet& pairs); + +/** + * @Brief Save pairs to file + * File format is reference matchImg1 matchImage2 ...\n + * @param sFileName input file path to save + * @param pairs the input set of pairs + * @return false if a problem is detected in the file +*/ bool savePairsToFile(const std::string& sFileName, const PairSet& pairs); } // namespace matchingImageCollection diff --git a/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp b/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp index b3c77b039b..63f5cd94a1 100644 --- a/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp +++ b/src/aliceVision/matchingImageCollection/ImagePairListIO_test.cpp @@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(read_write_pairs_to_file) PairSet pairSetGTsorted; pairSetGTsorted.insert(std::make_pair(0, 1)); - pairSetGTsorted.insert(std::make_pair(0, 2)); + pairSetGTsorted.insert(std::make_pair(2, 0)); pairSetGTsorted.insert(std::make_pair(1, 2)); BOOST_CHECK(savePairsToFile("pairsT_IO.txt", pairSetGT)); @@ -35,26 +35,3 @@ BOOST_AUTO_TEST_CASE(read_write_pairs_to_file) std::remove("pairsT_IO.txt"); } -BOOST_AUTO_TEST_CASE(save_pairs) -{ - PairSet pairs = {{0, 2}, {0, 4}, {0, 5}, {8, 2}, {0, 1}, {5, 9}}; - - std::stringstream output; - savePairs(output, pairs); - BOOST_CHECK_EQUAL(output.str(), std::string("0 1 2 4 5\n5 9\n8 2\n")); -} - -BOOST_AUTO_TEST_CASE(load_multiple_pairs_per_line) -{ - std::stringstream input; - input.str(R"( 0 2 4 5 - 0 1 - 5 9 -)"); - - PairSet loadedPairs; - BOOST_CHECK(loadPairs(input, loadedPairs)); - - PairSet expectedPairs = {{0, 2}, {0, 4}, {0, 5}, {0, 1}, {5, 9}}; - BOOST_CHECK(loadedPairs == expectedPairs); -} diff --git a/src/aliceVision/matchingImageCollection/pairBuilder.cpp b/src/aliceVision/matchingImageCollection/pairBuilder.cpp index 01bd528fec..6ab7425e39 100644 --- a/src/aliceVision/matchingImageCollection/pairBuilder.cpp +++ b/src/aliceVision/matchingImageCollection/pairBuilder.cpp @@ -19,29 +19,23 @@ namespace aliceVision { /// Generate all the (I,J) pairs of the upper diagonal of the NxN matrix -PairSet exhaustivePairs(const sfmData::Views& views, int rangeStart, int rangeSize) +PairSet exhaustivePairs(const std::set & viewIds) { PairSet pairs; - sfmData::Views::const_iterator itA = views.begin(); - sfmData::Views::const_iterator itAEnd = views.end(); - - // If we have a rangeStart, only compute the matching for (rangeStart, X). - if (rangeStart != -1 && rangeSize != 0) - { - if (rangeStart >= views.size()) - return pairs; - std::advance(itA, rangeStart); - itAEnd = views.begin(); - std::advance(itAEnd, std::min(std::size_t(rangeStart + rangeSize), views.size())); - } + auto itA = viewIds.begin(); + auto itAEnd = viewIds.end(); for (; itA != itAEnd; ++itA) { - sfmData::Views::const_iterator itB = itA; + auto itB = itA; std::advance(itB, 1); - for (; itB != views.end(); ++itB) - pairs.insert(std::make_pair(itA->first, itB->first)); + + for (; itB != viewIds.end(); ++itB) + { + pairs.insert(std::make_pair(*itA, *itB)); + } } + return pairs; } diff --git a/src/aliceVision/matchingImageCollection/pairBuilder.hpp b/src/aliceVision/matchingImageCollection/pairBuilder.hpp index 50f3b23f8c..c43c4355ac 100644 --- a/src/aliceVision/matchingImageCollection/pairBuilder.hpp +++ b/src/aliceVision/matchingImageCollection/pairBuilder.hpp @@ -13,7 +13,11 @@ namespace aliceVision { -/// Generate all the (I,J) pairs of the upper diagonal of the NxN matrix -PairSet exhaustivePairs(const sfmData::Views& views, int rangeStart = -1, int rangeSize = 0); +/** + * @brief Generate all the (I,J) pairs of the upper diagonal of the NxN matrix + * @param views the sfmData views indices list + * @return a generated set of pairs + */ +PairSet exhaustivePairs(const std::set & viewIds); }; // namespace aliceVision diff --git a/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp b/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp index 73c0de23c8..fada5aa86f 100644 --- a/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp +++ b/src/aliceVision/matchingImageCollection/pairBuilder_test.cpp @@ -34,20 +34,15 @@ bool checkPairOrder(const IterablePairs& pairs) BOOST_AUTO_TEST_CASE(matchingImageCollection_exhaustivePairs) { - sfmData::Views views; { // Empty - PairSet pairSet = exhaustivePairs(views); + std::set indices; + PairSet pairSet = exhaustivePairs(indices); BOOST_CHECK_EQUAL(0, pairSet.size()); } { - std::vector indexes = {{12, 54, 89, 65}}; - for (IndexT i : indexes) - { - views.emplace(i, std::make_shared("filepath", i)); - } - - PairSet pairSet = exhaustivePairs(views); + std::set indices = {12, 54, 89, 65}; + PairSet pairSet = exhaustivePairs(indices); BOOST_CHECK(checkPairOrder(pairSet)); BOOST_CHECK_EQUAL(6, pairSet.size()); BOOST_CHECK(pairSet.find(std::make_pair(12, 54)) != pairSet.end()); diff --git a/src/software/pipeline/main_featureMatching.cpp b/src/software/pipeline/main_featureMatching.cpp index 9a8edb634a..c52290c955 100644 --- a/src/software/pipeline/main_featureMatching.cpp +++ b/src/software/pipeline/main_featureMatching.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -101,8 +102,8 @@ int aliceVision_main(int argc, char** argv) std::string describerTypesName = feature::EImageDescriberType_enumToString(feature::EImageDescriberType::SIFT); float distRatio = 0.8f; std::vector predefinedPairList; - int rangeStart = -1; - int rangeSize = 0; + int rangeIteration = 0; + int rangeBlocksCount = 1; std::string nearestMatchingMethod = "ANN_L2"; robustEstimation::ERobustEstimator geometricEstimator = robustEstimation::ERobustEstimator::ACRANSAC; double geometricErrorMax = 0.0; //< the maximum reprojection error allowed for image matching with geometric validation @@ -180,10 +181,10 @@ int aliceVision_main(int argc, char** argv) "Export debug files (svg, dot).") ("maxMatches", po::value(&numMatchesToKeep)->default_value(numMatchesToKeep), "Maximum number pf matches to keep.") - ("rangeStart", po::value(&rangeStart)->default_value(rangeStart), - "Range image index start.") - ("rangeSize", po::value(&rangeSize)->default_value(rangeSize), - "Range size.") + ("rangeIteration", po::value(&rangeIteration)->default_value(rangeIteration), + "chunk index.") + ("rangeBlocksCount", po::value(&rangeBlocksCount)->default_value(rangeBlocksCount), + "Total number of chunks.") ("randomSeed", po::value(&randomSeed)->default_value(randomSeed), "This seed value will generate a sequence using a linear random generator. Set -1 to use a random seed."); // clang-format on @@ -243,38 +244,59 @@ int aliceVision_main(int argc, char** argv) // - Keep correspondences only if NearestNeighbor ratio is ok // from matching mode compute the pair list that have to be matched - PairSet pairs; - std::set filter; + PairSet allPairs; + // We assume that there is only one pair for (I,J) and (J,I) if (predefinedPairList.empty()) { - pairs = exhaustivePairs(sfmData.getViews(), rangeStart, rangeSize); + allPairs = exhaustivePairs(sfmData.getViewsKeys()); } else { for (const std::string& imagePairsFile : predefinedPairList) { ALICEVISION_LOG_INFO("Load pair list from file: " << imagePairsFile); - if (!matchingImageCollection::loadPairsFromFile(imagePairsFile, pairs, rangeStart, rangeSize)) + if (!matchingImageCollection::loadPairsFromFile(imagePairsFile, allPairs)) + { return EXIT_FAILURE; + } } } - if (pairs.empty()) + if (allPairs.empty()) { ALICEVISION_LOG_INFO("No image pair to match."); // if we only compute a selection of matches, we may have no match. - return rangeSize ? EXIT_SUCCESS : EXIT_FAILURE; + return rangeBlocksCount > 1 ? EXIT_SUCCESS : EXIT_FAILURE; } - ALICEVISION_LOG_INFO("Number of pairs: " << pairs.size()); + ALICEVISION_LOG_INFO("Number of pairs: " << allPairs.size()); + + int chunkStart, chunkEnd; + if (!rangeComputation(chunkStart, chunkEnd, rangeIteration, rangeBlocksCount, allPairs.size())) + { + ALICEVISION_LOG_INFO("Nothing to compute in this chunk"); + } + + ALICEVISION_LOG_INFO("A total of " << allPairs.size() << " pairs has to be processed."); + ALICEVISION_LOG_INFO("Current chunk will analyze pairs from " << chunkStart << " to " << chunkEnd << "."); // filter creation - for (const auto& pair : pairs) + // Keep only pairs in the chunk + int pos = 0; + PairSet pairs; + std::set filter; + for (const auto& pair : allPairs) { - filter.insert(pair.first); - filter.insert(pair.second); + if (pos >= chunkStart && pos < chunkEnd) + { + pairs.insert(pair); + filter.insert(pair.first); + filter.insert(pair.second); + } + + pos++; } PairwiseMatches mapPutativesMatches; @@ -355,7 +377,7 @@ int aliceVision_main(int argc, char** argv) { ALICEVISION_LOG_INFO("No putative feature matches."); // If we only compute a selection of matches, we may have no match. - return rangeSize ? EXIT_SUCCESS : EXIT_FAILURE; + return rangeBlocksCount > 1 ? EXIT_SUCCESS : EXIT_FAILURE; } if (geometricFilterType == EGeometricFilterType::HOMOGRAPHY_GROWING) @@ -373,10 +395,10 @@ int aliceVision_main(int argc, char** argv) } } - // when a range is specified, generate a file prefix to reflect the current iteration (rangeStart/rangeSize) + // when a range is specified, generate a file prefix to reflect the current iteration // => with matchFilePerImage: avoids overwriting files if a view is present in several iterations // => without matchFilePerImage: avoids overwriting the unique resulting file - const std::string filePrefix = rangeSize > 0 ? std::to_string(rangeStart / rangeSize) + "." : ""; + const std::string filePrefix = std::to_string(rangeIteration) + "."; ALICEVISION_LOG_INFO(std::to_string(mapPutativesMatches.size()) << " putative image pair matches"); @@ -387,33 +409,12 @@ int aliceVision_main(int argc, char** argv) // export putative matches if (savePutativeMatches) + { Save(mapPutativesMatches, (fs::path(matchesFolder) / "putativeMatches").string(), fileExtension, matchFilePerImage, filePrefix); + } ALICEVISION_LOG_INFO("Task (Regions Matching) done in (s): " + std::to_string(timer.elapsed())); - /* - // TODO: DELI - if(exportDebugFiles) - { - //-- export putative matches Adjacency matrix - PairwiseMatchingToAdjacencyMatrixSVG(sfmData.getViews().size(), - mapPutativesMatches, - (fs::path(matchesFolder) / "PutativeAdjacencyMatrix.svg").string()); - //-- export view pair graph once putative graph matches have been computed - { - std::set set_ViewIds; - - std::transform(sfmData.getViews().begin(), sfmData.getViews().end(), - std::inserter(set_ViewIds, set_ViewIds.begin()), stl::RetrieveKey()); - - graph::indexedGraph putativeGraph(set_ViewIds, getPairs(mapPutativesMatches)); - - graph::exportToGraphvizData( - (fs::path(matchesFolder) / "putative_matches.dot").string(), - putativeGraph.g); - } - } - */ #ifdef ALICEVISION_DEBUG_MATCHING { diff --git a/src/software/pipeline/main_relativePoseEstimating.cpp b/src/software/pipeline/main_relativePoseEstimating.cpp index f5218cdbd9..89457ddcf5 100644 --- a/src/software/pipeline/main_relativePoseEstimating.cpp +++ b/src/software/pipeline/main_relativePoseEstimating.cpp @@ -224,7 +224,7 @@ int aliceVision_main(int argc, char** argv) PairSet pairs; ALICEVISION_LOG_INFO("Load pair list from file: " << imagePairsFile); - if (!matchingImageCollection::loadPairsFromFile(imagePairsFile, pairs, 0, -1)) + if (!matchingImageCollection::loadPairsFromFile(imagePairsFile, pairs)) { return EXIT_FAILURE; }