From 80fbb544245bac6a21c1ed64f24b05154fb16849 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Sun, 22 Sep 2019 16:50:29 +0200 Subject: [PATCH 01/11] [sfm] retrieve markers ID and store it in landmark color --- src/aliceVision/sfm/CMakeLists.txt | 1 + .../sfm/pipeline/ReconstructionEngine.cpp | 77 +++++++++++++++++++ .../sfm/pipeline/ReconstructionEngine.hpp | 9 +++ .../ReconstructionEngine_sequentialSfM.cpp | 6 ++ src/aliceVision/sfmData/SfMData.hpp | 27 +++++++ src/software/pipeline/main_incrementalSfM.cpp | 13 ++-- 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp diff --git a/src/aliceVision/sfm/CMakeLists.txt b/src/aliceVision/sfm/CMakeLists.txt index 5edb444e49..89c707ea08 100644 --- a/src/aliceVision/sfm/CMakeLists.txt +++ b/src/aliceVision/sfm/CMakeLists.txt @@ -38,6 +38,7 @@ set(sfm_files_sources pipeline/localization/SfMLocalizer.cpp pipeline/localization/SfMLocalizationSingle3DTrackObservationDatabase.cpp pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp + pipeline/ReconstructionEngine.cpp pipeline/RigSequence.cpp pipeline/RelativePoseInfo.cpp pipeline/structureFromKnownPoses/StructureEstimationFromKnownPoses.cpp diff --git a/src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp b/src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp new file mode 100644 index 0000000000..613e087df6 --- /dev/null +++ b/src/aliceVision/sfm/pipeline/ReconstructionEngine.cpp @@ -0,0 +1,77 @@ +// This file is part of the AliceVision project. +// Copyright (c) 2019 AliceVision contributors. +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ReconstructionEngine.hpp" + +#include +#include + + +namespace aliceVision { +namespace sfm { + + +void retrieveMarkersId(sfmData::SfMData& sfmData) +{ + std::set allMarkerDescTypes; +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) + allMarkerDescTypes.insert(feature::EImageDescriberType::CCTAG3); + allMarkerDescTypes.insert(feature::EImageDescriberType::CCTAG4); +#endif + if (allMarkerDescTypes.empty()) + return; + + std::set usedDescTypes = sfmData.getLandmarkDescTypes(); + + std::vector markerDescTypes; + std::set_intersection(allMarkerDescTypes.begin(), allMarkerDescTypes.end(), + usedDescTypes.begin(), usedDescTypes.end(), + std::back_inserter(markerDescTypes)); + + std::set markerDescTypes_set(markerDescTypes.begin(), markerDescTypes.end()); + + if(markerDescTypes.empty()) + return; + + // load the corresponding view regions + feature::RegionsPerView regionPerView; + std::set filter; + // It could be optimized by loading only the minimal number of desc files, + // but as we are only retrieving them for markers, the performance impact is limited. + if (!sfm::loadRegionsPerView(regionPerView, sfmData, sfmData.getFeaturesFolders(), markerDescTypes, filter)) + { + ALICEVISION_THROW_ERROR("Error while loading markers regions."); + } + for (auto& landmarkIt : sfmData.getLandmarks()) + { + auto& landmark = landmarkIt.second; + if (landmark.observations.empty()) + continue; + if (markerDescTypes_set.find(landmark.descType) == markerDescTypes_set.end()) + continue; + landmark.rgb = image::BLACK; + + const auto obs = landmark.observations.begin(); + const feature::Regions& regions = regionPerView.getRegions(obs->first, landmark.descType); + const feature::CCTAG_Regions& cctagRegions = dynamic_cast(regions); + const auto& d = cctagRegions.Descriptors()[obs->second.id_feat]; + for (int i = 0; i < d.size(); ++i) + { + if (d[i] == 255) + { + ALICEVISION_LOG_TRACE("Found marker: " << i << " (landmarkId: " << landmarkIt.first << ")."); + landmark.rgb.r() = i; + break; + } + } + } +} + + +} // namespace sfm +} // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp b/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp index b076880e0f..5d51961fa7 100644 --- a/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp +++ b/src/aliceVision/sfm/pipeline/ReconstructionEngine.hpp @@ -15,6 +15,9 @@ namespace aliceVision { namespace sfm { +void retrieveMarkersId(sfmData::SfMData& sfmData); + + /** * @brief Basic Reconstruction Engine. * Process Function handle the reconstruction. @@ -68,6 +71,11 @@ class ReconstructionEngine sfmData::colorizeTracks(_sfmData); } + void retrieveMarkersId() + { + aliceVision::sfm::retrieveMarkersId(_sfmData); + } + protected: /// Output folder where outputs will be stored std::string _outputFolder; @@ -75,5 +83,6 @@ class ReconstructionEngine sfmData::SfMData _sfmData; }; + } // namespace sfm } // namespace aliceVision diff --git a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp index caf4b01ecc..36ea1efbfa 100644 --- a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp +++ b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp @@ -722,6 +722,12 @@ void ReconstructionEngine_sequentialSfM::exportStatistics(double reconstructionT << "\t- elapsed time: " << reconstructionTime << std::endl << "\t- residual RMSE: " << residual); + std::map descTypeUsage = _sfmData.getLandmarkDescTypesUsages(); + for(const auto& d: descTypeUsage) + { + ALICEVISION_LOG_INFO(" - # " << EImageDescriberType_enumToString(d.first) << ": " << d.second); + } + // residual histogram Histogram residualHistogram; computeResidualsHistogram(&residualHistogram); diff --git a/src/aliceVision/sfmData/SfMData.hpp b/src/aliceVision/sfmData/SfMData.hpp index c4cc864626..13c1e6404b 100644 --- a/src/aliceVision/sfmData/SfMData.hpp +++ b/src/aliceVision/sfmData/SfMData.hpp @@ -297,6 +297,33 @@ class SfMData return _rigs.at(view.getRigId()); } + std::set getLandmarkDescTypes() const + { + std::set output; + for (auto s : getLandmarks()) + { + output.insert(s.second.descType); + } + return output; + } + + std::map getLandmarkDescTypesUsages() const + { + std::map output; + for (auto s : getLandmarks()) + { + if (output.find(s.second.descType) == output.end()) + { + output[s.second.descType] = 1; + } + else + { + ++output[s.second.descType]; + } + } + return output; + } + /** * @brief Get the median Exposure Value (Ev) of * @return diff --git a/src/software/pipeline/main_incrementalSfM.cpp b/src/software/pipeline/main_incrementalSfM.cpp index ad334dd225..90f4407bec 100644 --- a/src/software/pipeline/main_incrementalSfM.cpp +++ b/src/software/pipeline/main_incrementalSfM.cpp @@ -298,16 +298,17 @@ int main(int argc, char **argv) if(!sfmEngine.process()) return EXIT_FAILURE; - // get the color for the 3D points - sfmEngine.colorize(); - // set featuresFolders and matchesFolders relative paths { - sfmEngine.getSfMData().addFeaturesFolders(featuresFolders); - sfmEngine.getSfMData().addMatchesFolders(matchesFolders); - sfmEngine.getSfMData().setAbsolutePath(outputSfM); + sfmEngine.getSfMData().addFeaturesFolders(featuresFolders); + sfmEngine.getSfMData().addMatchesFolders(matchesFolders); + sfmEngine.getSfMData().setAbsolutePath(outputSfM); } + // get the color for the 3D points + sfmEngine.colorize(); + sfmEngine.retrieveMarkersId(); + ALICEVISION_LOG_INFO("Structure from motion took (s): " + std::to_string(timer.elapsed())); ALICEVISION_LOG_INFO("Generating HTML report..."); From da5c13d18fb82af7c44806888bd1b10f04e2854a Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Sun, 22 Sep 2019 17:10:11 +0200 Subject: [PATCH 02/11] [software] sfmTransform: new option to align on specific markers --- src/aliceVision/sfm/utils/alignment.cpp | 59 ++++++++++++ src/aliceVision/sfm/utils/alignment.hpp | 21 ++++ src/software/utils/main_sfmTransform.cpp | 117 ++++++++++++++++++++--- 3 files changed, 186 insertions(+), 11 deletions(-) diff --git a/src/aliceVision/sfm/utils/alignment.cpp b/src/aliceVision/sfm/utils/alignment.cpp index c9b3327b2c..d6a2a59379 100644 --- a/src/aliceVision/sfm/utils/alignment.cpp +++ b/src/aliceVision/sfm/utils/alignment.cpp @@ -267,5 +267,64 @@ void computeNewCoordinateSystemFromLandmarks(const sfmData::SfMData& sfmData, out_t = - out_S * out_R * meanPoints; } + +bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmData, + const feature::EImageDescriberType& imageDescriberType, + const std::vector& markers, + bool withScaling, + double& out_S, + Mat3& out_R, + Vec3& out_t + ) +{ + std::vector landmarksIds(markers.size(), -1); + + int maxLandmarkIdx = 0; + for (const auto& landmarkIt : sfmData.getLandmarks()) + { + if(landmarkIt.first > maxLandmarkIdx) + maxLandmarkIdx = landmarkIt.first; + if(landmarkIt.second.descType != imageDescriberType) + continue; + for (int i = 0; i < markers.size(); ++i) + { + if (landmarkIt.second.rgb.r() == markers[i].first) + { + landmarksIds[i] = landmarkIt.first; + } + } + } + + for (int i = 0; i < landmarksIds.size(); ++i) + { + int landmarkId = landmarksIds[i]; + if (landmarkId == -1) + { + ALICEVISION_LOG_ERROR("Failed to find marker: " << int(markers[i].first)); + ALICEVISION_THROW_ERROR("Failed to find marker: " << int(markers[i].first)); + } + } + + Mat ptsSrc = Mat3X(3, markers.size()); + Mat ptsDst = Mat3X(3, markers.size()); + for (std::size_t i = 0; i < markers.size(); ++i) + { + ptsSrc.col(i) = sfmData.getLandmarks().at(landmarksIds[i]).X; + ptsDst.col(i) = markers[i].second; + } + + if (markers.size() == 1) + { + out_S = 1; + out_R = Mat3::Identity(); + out_t = ptsDst.col(0) - ptsSrc.col(0); + return true; + } + + const Mat4 RTS = Eigen::umeyama(ptsSrc, ptsDst, withScaling); + + return geometry::decomposeRTS(RTS, out_S, out_t, out_R); +} + } // namespace sfm } // namespace aliceVision diff --git a/src/aliceVision/sfm/utils/alignment.hpp b/src/aliceVision/sfm/utils/alignment.hpp index ef33999328..5d85fc42bb 100644 --- a/src/aliceVision/sfm/utils/alignment.hpp +++ b/src/aliceVision/sfm/utils/alignment.hpp @@ -156,5 +156,26 @@ void computeNewCoordinateSystemFromSingleCamera(const sfmData::SfMData& sfmData, Mat3& out_R, Vec3& out_t); +using MarkerWithCoord = std::pair; + +/** +* @brief Compute a new coordinate system so that markers are aligned with the target coordinates. +* +* @param[in] sfmData +* @param[in] imageDescriberType +* @param[in] markers: markers id associated to a target 3D coordinate +* @param[in] withScaling +* @param[out] out_S scale +* @param[out] out_R rotation +* @param[out] out_t translation +*/ +bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmData, + const feature::EImageDescriberType& imageDescriberType, + const std::vector& markers, + bool withScaling, + double& out_S, + Mat3& out_R, + Vec3& out_t); + } // namespace sfm } // namespace aliceVision diff --git a/src/software/utils/main_sfmTransform.cpp b/src/software/utils/main_sfmTransform.cpp index 35b3ebb343..9425a02fbb 100644 --- a/src/software/utils/main_sfmTransform.cpp +++ b/src/software/utils/main_sfmTransform.cpp @@ -12,11 +12,15 @@ #include #include +#include +#include +#include #include #include #include + // These constants define the current software version. // They must be updated when the command line is changed. #define ALICEVISION_SOFTWARE_VERSION_MAJOR 1 @@ -35,6 +39,7 @@ enum class EAlignmentMethod: unsigned char , AUTO_FROM_CAMERAS , AUTO_FROM_LANDMARKS , FROM_SINGLE_CAMERA + , FROM_MARKERS }; /** @@ -50,6 +55,7 @@ std::string EAlignmentMethod_enumToString(EAlignmentMethod alignmentMethod) case EAlignmentMethod::AUTO_FROM_CAMERAS: return "auto_from_cameras"; case EAlignmentMethod::AUTO_FROM_LANDMARKS: return "auto_from_landmarks"; case EAlignmentMethod::FROM_SINGLE_CAMERA: return "from_single_camera"; + case EAlignmentMethod::FROM_MARKERS: return "from_markers"; } throw std::out_of_range("Invalid EAlignmentMethod enum"); } @@ -67,7 +73,8 @@ EAlignmentMethod EAlignmentMethod_stringToEnum(const std::string& alignmentMetho if(method == "transformation") return EAlignmentMethod::TRANSFOMATION; if(method == "auto_from_cameras") return EAlignmentMethod::AUTO_FROM_CAMERAS; if(method == "auto_from_landmarks") return EAlignmentMethod::AUTO_FROM_LANDMARKS; - if(method == "from_single_camera") return EAlignmentMethod::FROM_SINGLE_CAMERA; + if(method == "from_single_camera") return EAlignmentMethod::FROM_SINGLE_CAMERA; + if(method == "from_markers") return EAlignmentMethod::FROM_MARKERS; throw std::out_of_range("Invalid SfM alignment method : " + alignmentMethod); } @@ -93,6 +100,29 @@ static bool parseAlignScale(const std::string& alignScale, double& S, Mat3& R, V return true; } + +inline std::istream& operator>>(std::istream& in, sfm::MarkerWithCoord& marker) +{ + std::string token; + in >> token; + std::vector markerCoord; + boost::split(markerCoord, token, boost::algorithm::is_any_of(":=")); + if(markerCoord.size() != 2) + throw std::invalid_argument("Failed to parse MarkerWithCoord from: " + token); + marker.first = boost::lexical_cast(markerCoord.front()); + + std::vector coord; + boost::split(coord, markerCoord.back(), boost::algorithm::is_any_of(",;_")); + if (coord.size() != 3) + throw std::invalid_argument("Failed to parse Marker coordinates from: " + markerCoord.back()); + + for (int i = 0; i < 3; ++i) + { + marker.second(i) = boost::lexical_cast(coord[i]); + } + return in; +} + int main(int argc, char **argv) { // command-line parameters @@ -107,6 +137,10 @@ int main(int argc, char **argv) std::string transform; std::string landmarksDescriberTypesName; double userScale = 1; + bool applyScale = true; + bool applyRotation = true; + bool applyTranslation = true; + std::vector markers; po::options_description allParams("AliceVision sfmTransform"); @@ -134,7 +168,16 @@ int main(int argc, char **argv) "Use all of them if empty\n" + feature::EImageDescriberType_informations()).c_str()) ("scale", po::value(&userScale)->default_value(userScale), - "Additional scale to apply."); + "Additional scale to apply.") + ("applyScale", po::value(&applyScale)->default_value(applyScale), + "Apply scale transformation.") + ("applyRotation", po::value(&applyRotation)->default_value(applyRotation), + "Apply rotation transformation.") + ("applyTranslation", po::value(&applyTranslation)->default_value(applyTranslation), + "Apply translation transformation.") + ("markers", po::value>(&markers)->multitoken(), + "Markers ID and target coordinates 'ID:x,y,z'.") + ; po::options_description logParams("Log parameters"); logParams.add_options() @@ -186,6 +229,12 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + if (alignmentMethod == EAlignmentMethod::FROM_MARKERS && markers.empty()) + { + ALICEVISION_LOG_ERROR("Missing --markers option"); + return EXIT_FAILURE; + } + // Load input scene sfmData::SfMData sfmDataIn; if(!sfmDataIO::Load(sfmDataIn, sfmDataFilename, sfmDataIO::ESfMData::ALL)) @@ -219,23 +268,69 @@ int main(int argc, char **argv) break; case EAlignmentMethod::FROM_SINGLE_CAMERA: - sfm::computeNewCoordinateSystemFromSingleCamera(sfmDataIn,transform, S, R, t); + sfm::computeNewCoordinateSystemFromSingleCamera(sfmDataIn, transform, S, R, t); break; - } - { - std::stringstream ss; - ss << "Transformation:" << std::endl; - ss << "\t- Scale: " << S << std::endl; - ss << "\t- Rotation:\n" << R << std::endl; - ss << "\t- Translate: " << t.transpose() << std::endl; - ALICEVISION_LOG_INFO(ss.str()); + case EAlignmentMethod::FROM_MARKERS: + { + std::vector markersDescTypes = { +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) + feature::EImageDescriberType::CCTAG3, feature::EImageDescriberType::CCTAG4 +#endif + }; + std::set usedDescTypes = sfmDataIn.getLandmarkDescTypes(); + + std::vector usedMarkersDescTypes; + std::set_intersection( + usedDescTypes.begin(), usedDescTypes.end(), + markersDescTypes.begin(), markersDescTypes.end(), + std::back_inserter(usedMarkersDescTypes) + ); + std::vector inDescTypes = feature::EImageDescriberType_stringToEnums(landmarksDescriberTypesName); + + std::vector vDescTypes; + std::set_intersection( + usedMarkersDescTypes.begin(), usedMarkersDescTypes.end(), + inDescTypes.begin(), inDescTypes.end(), + std::back_inserter(vDescTypes) + ); + if (vDescTypes.size() != 1) + { + ALICEVISION_LOG_ERROR("Alignment from markers: Invalid number of image describer types: " << vDescTypes.size()); + for (auto d : vDescTypes) + { + ALICEVISION_LOG_ERROR(" - " << feature::EImageDescriberType_enumToString(d)); + } + return EXIT_FAILURE; + } + const bool success = sfm::computeNewCoordinateSystemFromSpecificMarkers(sfmDataIn, vDescTypes.front(), markers, applyScale, S, R, t); + if (!success) + { + ALICEVISION_LOG_ERROR("Failed to find a valid transformation for these " << markers.size() << " markers."); + return EXIT_FAILURE; + } + break; + } } // apply user scale S *= userScale; t *= userScale; + if (!applyScale) + S = 1; + if (!applyRotation) + R = Mat3::Identity(); + if (!applyTranslation) + t = Vec3::Zero(); + + { + ALICEVISION_LOG_INFO("Transformation:" << std::endl + << "\t- Scale: " << S << std::endl + << "\t- Rotation:\n" << R << std::endl + << "\t- Translate: " << t.transpose()); + } + sfm::applyTransform(sfmDataIn, S, R, t); ALICEVISION_LOG_INFO("Save into '" << outSfMDataFilename << "'"); From b6d35f34d234ae5d662692d014310b1c2d05bed6 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Sun, 22 Sep 2019 17:10:43 +0200 Subject: [PATCH 03/11] [feature] fix minor cctag warning --- src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp b/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp index bbe93a0120..f8441d3f36 100644 --- a/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp +++ b/src/aliceVision/feature/cctag/ImageDescriber_CCTAG.hpp @@ -16,7 +16,7 @@ #include namespace cctag { - class Parameters; // Hidden implementation + struct Parameters; // Hidden implementation } namespace aliceVision { From ca907161021bd49d471497daaedf5e372dcb8bf2 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Sun, 22 Sep 2019 17:11:43 +0200 Subject: [PATCH 04/11] [mesh] texturing: throw an exception if the ref input is empty --- src/aliceVision/mesh/Texturing.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aliceVision/mesh/Texturing.cpp b/src/aliceVision/mesh/Texturing.cpp index 28113b9774..c9fa2e8f07 100644 --- a/src/aliceVision/mesh/Texturing.cpp +++ b/src/aliceVision/mesh/Texturing.cpp @@ -881,6 +881,9 @@ void Texturing::loadFromOBJ(const std::string& filename, bool flipNormals) void Texturing::remapVisibilities(EVisibilityRemappingMethod remappingMethod, const Mesh& refMesh, const mesh::PointsVisibility& refPointsVisibilities) { + if (refPointsVisibilities.empty()) + throw std::runtime_error("Texturing: Cannot remap visibilities as there is no reference points."); + assert(pointsVisibilities == nullptr); pointsVisibilities = new mesh::PointsVisibility(); From 053b1610979c4fe698a5d98145b3b5b3dc0f8658 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Sun, 22 Sep 2019 23:57:23 +0200 Subject: [PATCH 05/11] [software] sfmTransform: simplify parsing of command line args --- src/software/utils/main_sfmTransform.cpp | 30 ++++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/software/utils/main_sfmTransform.cpp b/src/software/utils/main_sfmTransform.cpp index 9425a02fbb..bacf13de7e 100644 --- a/src/software/utils/main_sfmTransform.cpp +++ b/src/software/utils/main_sfmTransform.cpp @@ -78,6 +78,16 @@ EAlignmentMethod EAlignmentMethod_stringToEnum(const std::string& alignmentMetho throw std::out_of_range("Invalid SfM alignment method : " + alignmentMethod); } + +inline std::istream& operator>>(std::istream& in, EAlignmentMethod& alignment) +{ + std::string token; + in >> token; + alignment = EAlignmentMethod_stringToEnum(token); + return in; +} + + static bool parseAlignScale(const std::string& alignScale, double& S, Mat3& R, Vec3& t) { double rx, ry, rz, rr; @@ -130,7 +140,8 @@ int main(int argc, char **argv) std::string verboseLevel = system::EVerboseLevel_enumToString(system::Logger::getDefaultVerboseLevel()); std::string sfmDataFilename; std::string outSfMDataFilename; - std::string alignmentMethodName; + EAlignmentMethod alignmentMethod = EAlignmentMethod::AUTO_FROM_CAMERAS; + // user optional parameters @@ -149,15 +160,17 @@ int main(int argc, char **argv) ("input,i", po::value(&sfmDataFilename)->required(), "SfMData file to align.") ("output,o", po::value(&outSfMDataFilename)->required(), - "Output SfMData scene.") - ("method", po::value(&alignmentMethodName)->required(), - "Transform method:\n" - "\t- transformation: Apply a given transformation\n" - "\t- auto_from_cameras: Use cameras\n" - "\t- auto_from_landmarks: Use landmarks\n"); + "Output SfMData scene."); po::options_description optionalParams("Optional parameters"); optionalParams.add_options() + ("method", po::value(&alignmentMethod)->default_value(alignmentMethod), + "Transform Method:\n" + "\t- transformation: Apply a given transformation\n" + "\t- auto_from_cameras: Use cameras\n" + "\t- auto_from_landmarks: Use landmarks\n" + "\t- from_single_camera: Use camera specified by --tranformation\n" + "\t- from_markers: Use markers specified by --markers\n") ("transformation", po::value(&transform)->default_value(transform), "required only for 'transformation' and 'single camera' methods:\n" "Transformation: Align [X,Y,Z] to +Y-axis, rotate around Y by R deg, scale by S; syntax: X,Y,Z;R;S\n" @@ -217,9 +230,6 @@ int main(int argc, char **argv) // set verbose level system::Logger::get()->setLogLevel(verboseLevel); - // set alignment method - const EAlignmentMethod alignmentMethod = EAlignmentMethod_stringToEnum(alignmentMethodName); - if(transform.empty() && ( alignmentMethod == EAlignmentMethod::TRANSFOMATION || alignmentMethod == EAlignmentMethod::FROM_SINGLE_CAMERA) From 81183934e0584761ddda73385c2503767ab0c694 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 23 Sep 2019 10:30:15 +0200 Subject: [PATCH 06/11] [software] SfMAlignment: support multiple matching methods for alignment: views ids, poses ids, filepath pattern, specific metadata, markers ids - SfMAlignment: provides an option to align based on markers ID - SfMAlignment: provides an option to align based on poseId - SfMAlignment: provides an option to align views based on file pattern matching (regex) - SfMAlignment: provides an option to align views based on specific metadata matching --- .../feature/imageDescriberCommon.hpp | 9 + src/aliceVision/sfm/utils/alignment.cpp | 421 ++++++++++++++++-- src/aliceVision/sfm/utils/alignment.hpp | 59 ++- src/software/utils/main_sfmAlignment.cpp | 126 +++++- 4 files changed, 586 insertions(+), 29 deletions(-) diff --git a/src/aliceVision/feature/imageDescriberCommon.hpp b/src/aliceVision/feature/imageDescriberCommon.hpp index a8eba511d5..d4b3c0c33a 100644 --- a/src/aliceVision/feature/imageDescriberCommon.hpp +++ b/src/aliceVision/feature/imageDescriberCommon.hpp @@ -67,6 +67,15 @@ EImageDescriberType EImageDescriberType_stringToEnum(const std::string& imageDes */ std::vector EImageDescriberType_stringToEnums(const std::string& describerMethods); +inline bool isMarker(EImageDescriberType imageDescriberType) +{ +#if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_CCTAG) + return imageDescriberType == EImageDescriberType::CCTAG3 || imageDescriberType == EImageDescriberType::CCTAG4; +#else + return false; +#endif +} + /** * @brief getStrongSupportCoeff * @param imageDescriberType diff --git a/src/aliceVision/sfm/utils/alignment.cpp b/src/aliceVision/sfm/utils/alignment.cpp index d6a2a59379..da1f126131 100644 --- a/src/aliceVision/sfm/utils/alignment.cpp +++ b/src/aliceVision/sfm/utils/alignment.cpp @@ -15,13 +15,69 @@ #include #include +#include + namespace bacc = boost::accumulators; namespace aliceVision { namespace sfm { -bool computeSimilarity(const sfmData::SfMData& sfmDataA, + +bool computeSimilarityFromCommonViews(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector>& commonViewIds, + double* out_S, + Mat3* out_R, + Vec3* out_t) +{ + assert(out_S != nullptr); + assert(out_R != nullptr); + assert(out_t != nullptr); + + if (commonViewIds.empty()) + { + ALICEVISION_LOG_WARNING("Cannot compute similarities without common view."); + return false; + } + + // Move input point in appropriate container + Mat xA(3, commonViewIds.size()); + Mat xB(3, commonViewIds.size()); + for (std::size_t i = 0; i < commonViewIds.size(); ++i) + { + auto viewIdPair = commonViewIds[i]; + xA.col(i) = sfmDataA.getPose(sfmDataA.getView(viewIdPair.first)).getTransform().center(); + xB.col(i) = sfmDataB.getPose(sfmDataB.getView(viewIdPair.second)).getTransform().center(); + } + + if (commonViewIds.size() == 1) + { + *out_S = 1.0; + *out_R = Mat3::Identity(); + *out_t = xB.col(0) - xA.col(0); + return true; + } + + // Compute rigid transformation p'i = S R pi + t + double S; + Vec3 t; + Mat3 R; + std::vector inliers; + + if (!aliceVision::geometry::ACRansac_FindRTS(xA, xB, S, t, R, inliers, true)) + return false; + + ALICEVISION_LOG_DEBUG("There are " << commonViewIds.size() << " common cameras and " << inliers.size() << " were used to compute the similarity transform."); + + *out_S = S; + *out_R = R; + *out_t = t; + + return true; +} + +bool computeSimilarityFromCommonCameras_viewId(const sfmData::SfMData& sfmDataA, const sfmData::SfMData& sfmDataB, double* out_S, Mat3* out_R, @@ -33,39 +89,354 @@ bool computeSimilarity(const sfmData::SfMData& sfmDataA, std::vector commonViewIds; getCommonViewsWithPoses(sfmDataA, sfmDataB, commonViewIds); - if(commonViewIds.size() < 2) - { - ALICEVISION_LOG_WARNING("Cannot compute similarities. Need at least 2 common views."); - return false; - } ALICEVISION_LOG_DEBUG("Found " << commonViewIds.size() << " common views."); - // Move input point in appropriate container - Mat xA(3, commonViewIds.size()); - Mat xB(3, commonViewIds.size()); - for(std::size_t i = 0; i < commonViewIds.size(); ++i) + std::vector> commonViewIds_pairs; + for (IndexT id : commonViewIds) { - IndexT viewId = commonViewIds[i]; - xA.col(i) = sfmDataA.getAbsolutePose(sfmDataA.getViews().at(viewId)->getPoseId()).getTransform().center(); - xB.col(i) = sfmDataB.getAbsolutePose(sfmDataB.getViews().at(viewId)->getPoseId()).getTransform().center(); + commonViewIds_pairs.push_back(std::make_pair(id, id)); } + return computeSimilarityFromCommonViews(sfmDataA, sfmDataB, commonViewIds_pairs, out_S, out_R, out_t); +} + +bool computeSimilarityFromCommonCameras_poseId( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + double* out_S, + Mat3* out_R, + Vec3* out_t) +{ + assert(out_S != nullptr); + assert(out_R != nullptr); + assert(out_t != nullptr); + + std::vector commonPoseIds; + getCommonPoseId(sfmDataA, sfmDataB, commonPoseIds); + ALICEVISION_LOG_DEBUG("Found " << commonPoseIds.size() << " common views."); + if (commonPoseIds.empty()) + { + ALICEVISION_LOG_WARNING("Cannot compute similarities without common pose."); + return false; + } + + // Move input point in appropriate container + Mat xA(3, commonPoseIds.size()); + Mat xB(3, commonPoseIds.size()); + for (std::size_t i = 0; i < commonPoseIds.size(); ++i) + { + IndexT poseId = commonPoseIds[i]; + xA.col(i) = sfmDataA.getAbsolutePose(poseId).getTransform().center(); + xB.col(i) = sfmDataB.getAbsolutePose(poseId).getTransform().center(); + } + if (commonPoseIds.size() == 1) + { + *out_S = 1.0; + *out_R = Mat3::Identity(); + *out_t = xB.col(0) - xA.col(0); + return true; + } + + // Compute rigid transformation p'i = S R pi + t + double S; + Vec3 t; + Mat3 R; + std::vector inliers; + + if (!aliceVision::geometry::ACRansac_FindRTS(xA, xB, S, t, R, inliers, true)) + return false; + + ALICEVISION_LOG_DEBUG("There are " << commonPoseIds.size() << " common camera poses and " << inliers.size() << " were used to compute the similarity transform."); + + *out_S = S; + *out_R = R; + *out_t = t; + + return true; +} + + +std::map retrieveMatchingFilepath( + const sfmData::SfMData& sfmData, + const std::string& filePatternMatching) +{ + std::set duplicates; + std::map uniqueFileparts; + for (auto& viewIt : sfmData.getViews()) + { + const std::string& imagePath = viewIt.second->getImagePath(); + std::string cumulatedValues; + if (filePatternMatching.empty()) + { + cumulatedValues = imagePath; + } + else + { + std::regex re(filePatternMatching); + std::smatch matches; + if (std::regex_match(imagePath, matches, re)) + { + for(int i = 1; i < matches.size(); ++i) + { + const std::ssub_match& submatch = matches[i]; + cumulatedValues += submatch.str(); + } + } + } + ALICEVISION_LOG_TRACE("retrieveMatchingFilepath: " << imagePath << " -> " << cumulatedValues); + auto& it = uniqueFileparts.find(cumulatedValues); + if (it != uniqueFileparts.end()) + { + duplicates.insert(cumulatedValues); + } + else + { + uniqueFileparts[cumulatedValues] = viewIt.first; + } + } + for (const std::string& d : duplicates) + { + uniqueFileparts.erase(d); + } + return uniqueFileparts; +} + +void matchViewsByFilePattern( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + std::vector>& out_commonViewIds) +{ + out_commonViewIds.clear(); + std::map filepathValuesA = retrieveMatchingFilepath(sfmDataA, filePatternMatching); + std::map filepathValuesB = retrieveMatchingFilepath(sfmDataB, filePatternMatching); + + using P = std::pair; + std::vector

commonMetadataValues; + std::set_intersection( + filepathValuesA.begin(), filepathValuesA.end(), // already sorted + filepathValuesB.begin(), filepathValuesB.end(), // already sorted + std::back_inserter(commonMetadataValues), + [](const P& p1, const P& p2) { + return p1.first < p2.first; + } + ); + + for (const P& p : commonMetadataValues) + { + out_commonViewIds.emplace_back(filepathValuesA.at(p.first), filepathValuesB.at(p.first)); + } +} + + +bool computeSimilarityFromCommonCameras_imageFileMatching( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + double* out_S, + Mat3* out_R, + Vec3* out_t) +{ + assert(out_S != nullptr); + assert(out_R != nullptr); + assert(out_t != nullptr); + + std::vector> commonViewIds; + matchViewsByFilePattern(sfmDataA, sfmDataB, filePatternMatching, commonViewIds); + + ALICEVISION_LOG_DEBUG("Found " << commonViewIds.size() << " common views."); + + return computeSimilarityFromCommonViews(sfmDataA, sfmDataB, commonViewIds, out_S, out_R, out_t); +} + + + +std::map retrieveUniqueMetadataValues( + const sfmData::SfMData& sfmData, + const std::vector& metadataList) +{ + std::set duplicates; + std::map uniqueMetadataValues; + for (auto& viewIt : sfmData.getViews()) + { + const std::map& m = viewIt.second->getMetadata(); + std::string cumulatedValues; + for (const std::string& k : metadataList) + { + const auto mIt = m.find(k); + if (mIt != m.end()) + cumulatedValues += mIt->second; + } + ALICEVISION_LOG_TRACE("retrieveUniqueMetadataValues: " << viewIt.second->getImagePath() << " -> " << cumulatedValues); + auto& it = uniqueMetadataValues.find(cumulatedValues); + if (it != uniqueMetadataValues.end()) + { + duplicates.insert(cumulatedValues); + } + else + { + uniqueMetadataValues[cumulatedValues] = viewIt.first; + } + } + for (const std::string& d: duplicates) + { + uniqueMetadataValues.erase(d); + } + return uniqueMetadataValues; +} + +void matchViewsByMetadataMatching( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + std::vector>& out_commonViewIds) +{ + out_commonViewIds.clear(); + std::map metadataValuesA = retrieveUniqueMetadataValues(sfmDataA, metadataList); + std::map metadataValuesB = retrieveUniqueMetadataValues(sfmDataB, metadataList); + + using P = std::pair; + std::vector

commonMetadataValues; + std::set_intersection( + metadataValuesA.begin(), metadataValuesA.end(), // already sorted + metadataValuesB.begin(), metadataValuesB.end(), // already sorted + std::back_inserter(commonMetadataValues), + [](const P& p1, const P& p2) { + return p1.first < p2.first; + } + ); + for (const P& p : commonMetadataValues) + { + out_commonViewIds.emplace_back(metadataValuesA.at(p.first), metadataValuesB.at(p.first)); + } +} + +bool computeSimilarityFromCommonCameras_metadataMatching( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + double* out_S, + Mat3* out_R, + Vec3* out_t) +{ + assert(out_S != nullptr); + assert(out_R != nullptr); + assert(out_t != nullptr); + + std::vector> commonViewIds; + matchViewsByMetadataMatching(sfmDataA, sfmDataB, metadataList, commonViewIds); + + ALICEVISION_LOG_DEBUG("Found " << commonViewIds.size() << " common views."); + + return computeSimilarityFromCommonViews(sfmDataA, sfmDataB, commonViewIds, out_S, out_R, out_t); +} + + +/** + * @return map, landmarkId> + */ +std::map, IndexT> getUniqueMarkers(const sfmData::SfMData& sfmDataA) +{ + std::set> duplicates; + std::map, IndexT> markers; + + for (const auto& landmarkIt : sfmDataA.getLandmarks()) + { + if (isMarker(landmarkIt.second.descType)) + { + const auto p = std::make_pair(landmarkIt.second.descType, landmarkIt.second.rgb.r()); + if (markers.find(p) == markers.end()) + { + markers[p] = landmarkIt.first; + } + else + { + duplicates.insert(p); // multiple usages + } + } + } + for (const auto& d : duplicates) + { + markers.erase(d); + } + return markers; +} + + +bool computeSimilarityFromCommonMarkers( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + double* out_S, + Mat3* out_R, + Vec3* out_t) +{ + assert(out_S != nullptr); + assert(out_R != nullptr); + assert(out_t != nullptr); + + std::map, IndexT> markers_A = getUniqueMarkers(sfmDataA); + std::map, IndexT> markers_B = getUniqueMarkers(sfmDataB); + + using P = std::pair, IndexT>; + std::vector

commonMarkers; + std::set_intersection( + markers_A.begin(), markers_A.end(), // already sorted + markers_B.begin(), markers_B.end(), // already sorted + std::back_inserter(commonMarkers), + [](const P& p1, const P& p2) { + return p1.first < p2.first; + } + ); + + ALICEVISION_LOG_DEBUG("Found " << commonMarkers.size() << " common markers."); + if (commonMarkers.empty()) + { + ALICEVISION_LOG_WARNING("Cannot compute similarities without common marker."); + return false; + } + + std::map> commonLandmarks; + for (auto& m : commonMarkers) + { + auto& markerLandmarks = commonLandmarks[m.first.second]; + markerLandmarks.first = markers_A.at(m.first); + markerLandmarks.second = markers_B.at(m.first); + } + + // Move input point in appropriate container + Mat xA(3, commonLandmarks.size()); + Mat xB(3, commonLandmarks.size()); + auto it = commonLandmarks.begin(); + for (std::size_t i = 0; i < commonLandmarks.size(); ++i, ++it) + { + const std::pair& commonLandmark = it->second; + xA.col(i) = sfmDataA.getLandmarks().at(commonLandmark.first).X; + xB.col(i) = sfmDataB.getLandmarks().at(commonLandmark.second).X; + } + + if (commonLandmarks.size() == 1) + { + *out_S = 1.0; + *out_R = Mat3::Identity(); + *out_t = xB.col(0) - xA.col(0); + return true; + } - // Compute rigid transformation p'i = S R pi + t - double S; - Vec3 t; - Mat3 R; - std::vector inliers; + // Compute rigid transformation p'i = S R pi + t + double S; + Vec3 t; + Mat3 R; + std::vector inliers; - if(!aliceVision::geometry::ACRansac_FindRTS(xA, xB, S, t, R, inliers, true)) - return false; + if (!aliceVision::geometry::ACRansac_FindRTS(xA, xB, S, t, R, inliers, true)) + return false; - ALICEVISION_LOG_DEBUG("There are " << commonViewIds.size() << " common cameras and " << inliers.size() << " were used to compute the similarity transform."); + ALICEVISION_LOG_DEBUG("There are " << commonLandmarks.size() << " common markers and " << inliers.size() << " were used to compute the similarity transform."); - *out_S = S; - *out_R = R; - *out_t = t; + *out_S = S; + *out_R = R; + *out_t = t; - return true; + return true; } void computeNewCoordinateSystemFromCameras(const sfmData::SfMData& sfmData, diff --git a/src/aliceVision/sfm/utils/alignment.hpp b/src/aliceVision/sfm/utils/alignment.hpp index 5d85fc42bb..8960809f35 100644 --- a/src/aliceVision/sfm/utils/alignment.hpp +++ b/src/aliceVision/sfm/utils/alignment.hpp @@ -45,8 +45,22 @@ inline void getCommonViewsWithPoses(const sfmData::SfMData& sfmDataA, } } +inline void getCommonPoseId(const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + std::vector& outIndexes) +{ + for (const auto& poseA : sfmDataA.getPoses()) + { + if (sfmDataB.getPoses().find(poseA.first) != sfmDataB.getPoses().end()) + { + outIndexes.push_back(poseA.first); + } + } +} + + /** - * @brief Compute a 5DOF rigid transform between the two set of cameras. + * @brief Compute a 5DOF rigid transform between the two set of cameras based on common viewIds. * * @param[in] sfmDataA * @param[in] sfmDataB @@ -55,12 +69,53 @@ inline void getCommonViewsWithPoses(const sfmData::SfMData& sfmDataA, * @param[out] out_t output translation vector * @return true if it finds a similarity transformation */ -bool computeSimilarity(const sfmData::SfMData& sfmDataA, +bool computeSimilarityFromCommonCameras_viewId(const sfmData::SfMData& sfmDataA, const sfmData::SfMData& sfmDataB, double* out_S, Mat3* out_R, Vec3* out_t); +/** +* @brief Compute a 5DOF rigid transform between the two set of cameras based on common poseIds. +* +* @param[in] sfmDataA +* @param[in] sfmDataB +* @param[out] out_S output scale factor +* @param[out] out_R output rotation 3x3 matrix +* @param[out] out_t output translation vector +* @return true if it finds a similarity transformation +*/ +bool computeSimilarityFromCommonCameras_poseId( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + double* out_S, + Mat3* out_R, + Vec3* out_t); + +bool computeSimilarityFromCommonCameras_imageFileMatching( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + double* out_S, + Mat3* out_R, + Vec3* out_t); + +bool computeSimilarityFromCommonCameras_metadataMatching( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + double* out_S, + Mat3* out_R, + Vec3* out_t); + + +bool computeSimilarityFromCommonMarkers( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + double* out_S, + Mat3* out_R, + Vec3* out_t); + /** * @brief Apply a transformation the given SfMData diff --git a/src/software/utils/main_sfmAlignment.cpp b/src/software/utils/main_sfmAlignment.cpp index 1cff1311b1..50e0707e13 100644 --- a/src/software/utils/main_sfmAlignment.cpp +++ b/src/software/utils/main_sfmAlignment.cpp @@ -26,6 +26,65 @@ using namespace aliceVision::sfm; namespace po = boost::program_options; + +/** +* @brief Alignment method enum +*/ +enum class EAlignmentMethod : unsigned char +{ + FROM_CAMERAS_VIEWID = 0 + , FROM_CAMERAS_POSEID + , FROM_CAMERAS_FILEPATH + , FROM_CAMERAS_METADATA + , FROM_MARKERS +}; + +/** +* @brief Convert an EAlignmentMethod enum to its corresponding string +* @param[in] alignmentMethod The given EAlignmentMethod enum +* @return string +*/ +std::string EAlignmentMethod_enumToString(EAlignmentMethod alignmentMethod) +{ + switch (alignmentMethod) + { + case EAlignmentMethod::FROM_CAMERAS_VIEWID: return "from_cameras_viewid"; + case EAlignmentMethod::FROM_CAMERAS_POSEID: return "from_cameras_poseid"; + case EAlignmentMethod::FROM_CAMERAS_FILEPATH: return "from_cameras_filepath"; + case EAlignmentMethod::FROM_CAMERAS_METADATA: return "from_cameras_metadata"; + case EAlignmentMethod::FROM_MARKERS: return "from_markers"; + } + throw std::out_of_range("Invalid EAlignmentMethod enum"); +} + +/** +* @brief Convert a string to its corresponding EAlignmentMethod enum +* @param[in] alignmentMethod The given string +* @return EAlignmentMethod enum +*/ +EAlignmentMethod EAlignmentMethod_stringToEnum(const std::string& alignmentMethod) +{ + std::string method = alignmentMethod; + std::transform(method.begin(), method.end(), method.begin(), ::tolower); //tolower + + if (method == "from_cameras_viewid") return EAlignmentMethod::FROM_CAMERAS_VIEWID; + if (method == "from_cameras_poseid") return EAlignmentMethod::FROM_CAMERAS_POSEID; + if (method == "from_cameras_filepath") return EAlignmentMethod::FROM_CAMERAS_FILEPATH; + if (method == "from_cameras_metadata") return EAlignmentMethod::FROM_CAMERAS_METADATA; + if (method == "from_markers") return EAlignmentMethod::FROM_MARKERS; + throw std::out_of_range("Invalid SfM alignment method : " + alignmentMethod); +} + +inline std::istream& operator>>(std::istream& in, EAlignmentMethod& alignment) +{ + std::string token; + in >> token; + alignment = EAlignmentMethod_stringToEnum(token); + return in; +} + + + int main(int argc, char **argv) { // command-line parameters @@ -34,6 +93,12 @@ int main(int argc, char **argv) std::string sfmDataFilename; std::string outSfMDataFilename; std::string sfmDataReferenceFilename; + bool applyScale = true; + bool applyRotation = true; + bool applyTranslation = true; + EAlignmentMethod alignmentMethod = EAlignmentMethod::FROM_CAMERAS_VIEWID; + std::string fileMatchingPattern; + std::vector metadataMatchingList = {"Make", "Model", "Exif:BodySerialNumber" , "Exif:LensSerialNumber" }; po::options_description allParams("AliceVision sfmAlignment"); @@ -46,12 +111,33 @@ int main(int argc, char **argv) ("reference,r", po::value(&sfmDataReferenceFilename)->required(), "Path to the scene used as the reference coordinate system."); + po::options_description optionalParams("Optional parameters"); + optionalParams.add_options() + ("method", po::value(&alignmentMethod)->default_value(alignmentMethod), + "Alignment Method:\n" + "\t- from_cameras_viewid: Align cameras with same view Id\n" + "\t- from_cameras_poseid: Align cameras with same pose Id\n" + "\t- from_cameras_filepath: Align cameras with a filepath matching, using --fileMatchingPattern\n" + "\t- from_cameras_metadata: Align cameras with matching metadata, using --metadataMatchingList\n" + "\t- from_markers: Align from markers with the same Id\n") + ("fileMatchingPattern", po::value(&fileMatchingPattern)->default_value(fileMatchingPattern), + "Matching pattern for the from_cameras_filepath method.\n") + ("metadataMatchingList", po::value>(&metadataMatchingList)->multitoken()->default_value(metadataMatchingList), + "List of metadata that should match to create the correspondences.\n") + ("applyScale", po::value(&applyScale)->default_value(applyScale), + "Apply scale transformation.") + ("applyRotation", po::value(&applyRotation)->default_value(applyRotation), + "Apply rotation transformation.") + ("applyTranslation", po::value(&applyTranslation)->default_value(applyTranslation), + "Apply translation transformation.") + ; + po::options_description logParams("Log parameters"); logParams.add_options() ("verboseLevel,v", po::value(&verboseLevel)->default_value(verboseLevel), "verbosity level (fatal, error, warning, info, debug, trace)."); - allParams.add(requiredParams).add(logParams); + allParams.add(requiredParams).add(optionalParams).add(logParams); po::variables_map vm; try @@ -105,7 +191,36 @@ int main(int argc, char **argv) double S; Mat3 R; Vec3 t; - bool hasValidSimilarity = sfm::computeSimilarity(sfmDataIn, sfmDataInRef, &S, &R, &t); + bool hasValidSimilarity = false; + + switch(alignmentMethod) + { + case EAlignmentMethod::FROM_CAMERAS_VIEWID: + { + hasValidSimilarity = sfm::computeSimilarityFromCommonCameras_viewId(sfmDataIn, sfmDataInRef, &S, &R, &t); + break; + } + case EAlignmentMethod::FROM_CAMERAS_POSEID: + { + hasValidSimilarity = sfm::computeSimilarityFromCommonCameras_poseId(sfmDataIn, sfmDataInRef, &S, &R, &t); + break; + } + case EAlignmentMethod::FROM_CAMERAS_FILEPATH: + { + hasValidSimilarity = sfm::computeSimilarityFromCommonCameras_imageFileMatching(sfmDataIn, sfmDataInRef, fileMatchingPattern, &S, &R, &t); + break; + } + case EAlignmentMethod::FROM_CAMERAS_METADATA: + { + hasValidSimilarity = sfm::computeSimilarityFromCommonCameras_metadataMatching(sfmDataIn, sfmDataInRef, metadataMatchingList, &S, &R, &t); + break; + } + case EAlignmentMethod::FROM_MARKERS: + { + hasValidSimilarity = sfm::computeSimilarityFromCommonMarkers(sfmDataIn, sfmDataInRef, &S, &R, &t); + break; + } + } if(!hasValidSimilarity) { @@ -126,6 +241,13 @@ int main(int argc, char **argv) ALICEVISION_LOG_INFO(ss.str()); } + if (!applyScale) + S = 1; + if (!applyRotation) + R = Mat3::Identity(); + if (!applyTranslation) + t = Vec3::Zero(); + sfm::applyTransform(sfmDataIn, S, R, t); ALICEVISION_LOG_INFO("Save into '" << outSfMDataFilename << "'"); From 0fef959480d6cb5a98584c92b1293eef9d87d9d9 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 23 Sep 2019 23:41:56 +0200 Subject: [PATCH 07/11] [sfm] alignment: can only compute similarity from reconstructed views Filter out matching views without pose. --- src/aliceVision/sfm/utils/alignment.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/aliceVision/sfm/utils/alignment.cpp b/src/aliceVision/sfm/utils/alignment.cpp index da1f126131..15db212967 100644 --- a/src/aliceVision/sfm/utils/alignment.cpp +++ b/src/aliceVision/sfm/utils/alignment.cpp @@ -35,23 +35,32 @@ bool computeSimilarityFromCommonViews(const sfmData::SfMData& sfmDataA, assert(out_R != nullptr); assert(out_t != nullptr); - if (commonViewIds.empty()) + std::vector> reconstructedCommonViewIds; + for (const auto& c : commonViewIds) + { + if (sfmDataA.isPoseAndIntrinsicDefined(c.first) && sfmDataB.isPoseAndIntrinsicDefined(c.second)) + { + reconstructedCommonViewIds.emplace_back(c); + } + } + + if (reconstructedCommonViewIds.empty()) { ALICEVISION_LOG_WARNING("Cannot compute similarities without common view."); return false; } // Move input point in appropriate container - Mat xA(3, commonViewIds.size()); - Mat xB(3, commonViewIds.size()); - for (std::size_t i = 0; i < commonViewIds.size(); ++i) + Mat xA(3, reconstructedCommonViewIds.size()); + Mat xB(3, reconstructedCommonViewIds.size()); + for (std::size_t i = 0; i < reconstructedCommonViewIds.size(); ++i) { - auto viewIdPair = commonViewIds[i]; + auto viewIdPair = reconstructedCommonViewIds[i]; xA.col(i) = sfmDataA.getPose(sfmDataA.getView(viewIdPair.first)).getTransform().center(); xB.col(i) = sfmDataB.getPose(sfmDataB.getView(viewIdPair.second)).getTransform().center(); } - if (commonViewIds.size() == 1) + if (reconstructedCommonViewIds.size() == 1) { *out_S = 1.0; *out_R = Mat3::Identity(); @@ -68,7 +77,7 @@ bool computeSimilarityFromCommonViews(const sfmData::SfMData& sfmDataA, if (!aliceVision::geometry::ACRansac_FindRTS(xA, xB, S, t, R, inliers, true)) return false; - ALICEVISION_LOG_DEBUG("There are " << commonViewIds.size() << " common cameras and " << inliers.size() << " were used to compute the similarity transform."); + ALICEVISION_LOG_DEBUG("There are " << reconstructedCommonViewIds.size() << " common cameras and " << inliers.size() << " were used to compute the similarity transform."); *out_S = S; *out_R = R; From 23d4d8173bf241270a7d137ee10250fcc10a44b2 Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Mon, 23 Sep 2019 23:43:57 +0200 Subject: [PATCH 08/11] [software] new SfMTransform software to retrieve poses and intrinsics from another reconstruction based on matching views --- src/aliceVision/sfm/utils/alignment.hpp | 14 ++ src/software/utils/CMakeLists.txt | 12 ++ src/software/utils/main_sfmTransfer.cpp | 255 ++++++++++++++++++++++++ 3 files changed, 281 insertions(+) create mode 100644 src/software/utils/main_sfmTransfer.cpp diff --git a/src/aliceVision/sfm/utils/alignment.hpp b/src/aliceVision/sfm/utils/alignment.hpp index 8960809f35..da49f78023 100644 --- a/src/aliceVision/sfm/utils/alignment.hpp +++ b/src/aliceVision/sfm/utils/alignment.hpp @@ -59,6 +59,20 @@ inline void getCommonPoseId(const sfmData::SfMData& sfmDataA, } +void matchViewsByFilePattern( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::string& filePatternMatching, + std::vector>& out_commonViewIds); + + +void matchViewsByMetadataMatching( + const sfmData::SfMData& sfmDataA, + const sfmData::SfMData& sfmDataB, + const std::vector& metadataList, + std::vector>& out_commonViewIds); + + /** * @brief Compute a 5DOF rigid transform between the two set of cameras based on common viewIds. * diff --git a/src/software/utils/CMakeLists.txt b/src/software/utils/CMakeLists.txt index 4c5d080c5f..ce5deecbcc 100644 --- a/src/software/utils/CMakeLists.txt +++ b/src/software/utils/CMakeLists.txt @@ -107,6 +107,18 @@ alicevision_add_software(aliceVision_utils_sfmAlignment ${Boost_LIBRARIES} ) +# SfM transfer +alicevision_add_software(aliceVision_utils_sfmTransfer + SOURCE main_sfmTransfer.cpp + FOLDER ${FOLDER_SOFTWARE_UTILS} + LINKS aliceVision_system + aliceVision_feature + aliceVision_sfm + aliceVision_sfmData + aliceVision_sfmDataIO + ${Boost_LIBRARIES} +) + # SfM transform alicevision_add_software(aliceVision_utils_sfmTransform SOURCE main_sfmTransform.cpp diff --git a/src/software/utils/main_sfmTransfer.cpp b/src/software/utils/main_sfmTransfer.cpp new file mode 100644 index 0000000000..de7f2fb86b --- /dev/null +++ b/src/software/utils/main_sfmTransfer.cpp @@ -0,0 +1,255 @@ +// This file is part of the AliceVision project. +// Copyright (c) 2016 AliceVision contributors. +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +// These constants define the current software version. +// They must be updated when the command line is changed. +#define ALICEVISION_SOFTWARE_VERSION_MAJOR 1 +#define ALICEVISION_SOFTWARE_VERSION_MINOR 0 + +using namespace aliceVision; +using namespace aliceVision::sfm; + +namespace po = boost::program_options; + + +/** +* @brief Matching Views method enum +*/ +enum class EMatchingMethod : unsigned char +{ + FROM_VIEWID = 0 + , FROM_FILEPATH + , FROM_METADATA +}; + +/** +* @brief Convert an EMatchingMethod enum to its corresponding string +* @param[in] matchingMethod The given EMatchingMethod enum +* @return string +*/ +std::string EMatchingMethod_enumToString(EMatchingMethod alignmentMethod) +{ + switch (alignmentMethod) + { + case EMatchingMethod::FROM_VIEWID: return "from_viewid"; + case EMatchingMethod::FROM_FILEPATH: return "from_filepath"; + case EMatchingMethod::FROM_METADATA: return "from_metadata"; + } + throw std::out_of_range("Invalid EMatchingMethod enum"); +} + +/** +* @brief Convert a string to its corresponding EMatchingMethod enum +* @param[in] matchingMethod The given string +* @return EMatchingMethod enum +*/ +EMatchingMethod EMatchingMethod_stringToEnum(const std::string& alignmentMethod) +{ + std::string method = alignmentMethod; + std::transform(method.begin(), method.end(), method.begin(), ::tolower); //tolower + + if (method == "from_viewid") return EMatchingMethod::FROM_VIEWID; + if (method == "from_filepath") return EMatchingMethod::FROM_FILEPATH; + if (method == "from_metadata") return EMatchingMethod::FROM_METADATA; + throw std::out_of_range("Invalid SfM alignment method : " + alignmentMethod); +} + +inline std::istream& operator>>(std::istream& in, EMatchingMethod& alignment) +{ + std::string token; + in >> token; + alignment = EMatchingMethod_stringToEnum(token); + return in; +} + + + +int main(int argc, char **argv) +{ + // command-line parameters + + std::string verboseLevel = system::EVerboseLevel_enumToString(system::Logger::getDefaultVerboseLevel()); + std::string sfmDataFilename; + std::string outSfMDataFilename; + std::string sfmDataReferenceFilename; + bool transferPoses = true; + bool transferIntrinsics = true; + EMatchingMethod matchingMethod = EMatchingMethod::FROM_VIEWID; + std::string fileMatchingPattern; + std::vector metadataMatchingList = { "Make", "Model", "Exif:BodySerialNumber" , "Exif:LensSerialNumber" }; + + po::options_description allParams("AliceVision sfmAlignment"); + + po::options_description requiredParams("Required parameters"); + requiredParams.add_options() + ("input,i", po::value(&sfmDataFilename)->required(), + "SfMData file to align.") + ("output,o", po::value(&outSfMDataFilename)->required(), + "Output SfMData scene.") + ("reference,r", po::value(&sfmDataReferenceFilename)->required(), + "Path to the scene used as the reference coordinate system."); + + po::options_description optionalParams("Optional parameters"); + optionalParams.add_options() + ("method", po::value(&matchingMethod)->default_value(matchingMethod), + "Matching Method:\n" + "\t- from_viewid: Align cameras with same view Id\n" + "\t- from_filepath: Align cameras with a filepath matching, using --fileMatchingPattern\n" + "\t- from_metadata: Align cameras with matching metadata, using --metadataMatchingList\n") + ("fileMatchingPattern", po::value(&fileMatchingPattern)->default_value(fileMatchingPattern), + "Matching pattern for the from_filepath method.\n") + ("metadataMatchingList", po::value>(&metadataMatchingList)->multitoken()->default_value(metadataMatchingList), + "List of metadata that should match to create the correspondences.\n") + ("transferPoses", po::value(&transferPoses)->default_value(transferPoses), + "Transfer poses.") + ("transferIntrinsics", po::value(&transferIntrinsics)->default_value(transferIntrinsics), + "Transfer intrinsics.") + ; + + po::options_description logParams("Log parameters"); + logParams.add_options() + ("verboseLevel,v", po::value(&verboseLevel)->default_value(verboseLevel), + "verbosity level (fatal, error, warning, info, debug, trace)."); + + allParams.add(requiredParams).add(optionalParams).add(logParams); + + po::variables_map vm; + try + { + po::store(po::parse_command_line(argc, argv, allParams), vm); + + if (vm.count("help") || (argc == 1)) + { + ALICEVISION_COUT(allParams); + return EXIT_SUCCESS; + } + po::notify(vm); + } + catch (boost::program_options::required_option& e) + { + ALICEVISION_CERR("ERROR: " << e.what()); + ALICEVISION_COUT("Usage:\n\n" << allParams); + return EXIT_FAILURE; + } + catch (boost::program_options::error& e) + { + ALICEVISION_CERR("ERROR: " << e.what()); + ALICEVISION_COUT("Usage:\n\n" << allParams); + return EXIT_FAILURE; + } + + ALICEVISION_COUT("Program called with the following parameters:"); + ALICEVISION_COUT(vm); + + // set verbose level + system::Logger::get()->setLogLevel(verboseLevel); + + // Load input scene + sfmData::SfMData sfmDataIn; + if (!sfmDataIO::Load(sfmDataIn, sfmDataFilename, sfmDataIO::ESfMData::ALL)) + { + ALICEVISION_LOG_ERROR("The input SfMData file '" << sfmDataFilename << "' cannot be read"); + return EXIT_FAILURE; + } + + // Load reference scene + sfmData::SfMData sfmDataInRef; + if (!sfmDataIO::Load(sfmDataInRef, sfmDataReferenceFilename, sfmDataIO::ESfMData::ALL)) + { + ALICEVISION_LOG_ERROR("The reference SfMData file '" << sfmDataReferenceFilename << "' cannot be read"); + return EXIT_FAILURE; + } + + ALICEVISION_LOG_INFO("Search similarity transformation."); + + std::vector> commonViewIds; + switch (matchingMethod) + { + case EMatchingMethod::FROM_VIEWID: + { + std::vector commonViewIdsTmp; + getCommonViews(sfmDataIn, sfmDataInRef, commonViewIdsTmp); + for (IndexT id : commonViewIdsTmp) + { + commonViewIds.push_back(std::make_pair(id, id)); + } + break; + } + case EMatchingMethod::FROM_FILEPATH: + { + sfm::matchViewsByFilePattern(sfmDataIn, sfmDataInRef, fileMatchingPattern, commonViewIds); + break; + } + case EMatchingMethod::FROM_METADATA: + { + sfm::matchViewsByMetadataMatching(sfmDataIn, sfmDataInRef, metadataMatchingList, commonViewIds); + break; + } + } + ALICEVISION_LOG_DEBUG("Found " << commonViewIds.size() << " common views."); + + if (commonViewIds.empty()) + { + ALICEVISION_LOG_ERROR("Failed to find matching Views between the 2 SfmData."); + return EXIT_FAILURE; + } + + if (!transferPoses && !transferIntrinsics) + { + ALICEVISION_LOG_ERROR("Nothing to do."); + } + else + { + for (const auto& matchingViews: commonViewIds) + { + if(!sfmDataIn.isPoseAndIntrinsicDefined(matchingViews.first) && + sfmDataInRef.isPoseAndIntrinsicDefined(matchingViews.second)) + { + // Missing pose in sfmDataIn and valid pose in sfmDataInRef, + // so we can transfer the pose. + + auto& viewA = sfmDataIn.getView(matchingViews.first); + const auto& viewB = sfmDataInRef.getView(matchingViews.second); + if (viewA.isPartOfRig() || viewB.isPartOfRig()) + { + ALICEVISION_LOG_DEBUG("Rig poses are not yet supported in SfMTransfer."); + continue; + } + + if (transferPoses) + { + sfmDataIn.getPoses()[viewA.getPoseId()] = sfmDataInRef.getPoses().at(viewB.getPoseId()); + } + if (transferIntrinsics) + { + sfmDataIn.getIntrinsicPtr(viewA.getIntrinsicId())->assign(*sfmDataInRef.getIntrinsicPtr(viewB.getIntrinsicId())); + } + } + } + } + + ALICEVISION_LOG_INFO("Save into '" << outSfMDataFilename << "'"); + // Export the SfMData scene in the expected format + if (!sfmDataIO::Save(sfmDataIn, outSfMDataFilename, sfmDataIO::ESfMData::ALL)) + { + ALICEVISION_LOG_ERROR("An error occurred while trying to save '" << outSfMDataFilename << "'"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} From de1c3e3ad7544e5b553efd04c9929522bdba6dfb Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Tue, 24 Sep 2019 21:25:13 +0200 Subject: [PATCH 09/11] [sfm] alignment: build fixes for g++ --- src/aliceVision/sfm/utils/alignment.cpp | 44 ++++++++++++++++++++---- src/aliceVision/sfm/utils/alignment.hpp | 10 +++++- src/software/utils/main_sfmAlignment.cpp | 4 +++ src/software/utils/main_sfmTransfer.cpp | 4 +++ src/software/utils/main_sfmTransform.cpp | 31 ++++------------- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/aliceVision/sfm/utils/alignment.cpp b/src/aliceVision/sfm/utils/alignment.cpp index 15db212967..92f691f6cc 100644 --- a/src/aliceVision/sfm/utils/alignment.cpp +++ b/src/aliceVision/sfm/utils/alignment.cpp @@ -14,6 +14,10 @@ #include #include +#include +#include +#include + #include #include @@ -24,6 +28,34 @@ namespace aliceVision { namespace sfm { +std::istream& operator>>(std::istream& in, MarkerWithCoord& marker) +{ + std::string token; + in >> token; + std::vector markerCoord; + boost::split(markerCoord, token, boost::algorithm::is_any_of(":=")); + if(markerCoord.size() != 2) + throw std::invalid_argument("Failed to parse MarkerWithCoord from: " + token); + marker.id = boost::lexical_cast(markerCoord.front()); + + std::vector coord; + boost::split(coord, markerCoord.back(), boost::algorithm::is_any_of(",;_")); + if (coord.size() != 3) + throw std::invalid_argument("Failed to parse Marker coordinates from: " + markerCoord.back()); + + for (int i = 0; i < 3; ++i) + { + marker.coord(i) = boost::lexical_cast(coord[i]); + } + return in; +} + +std::ostream& operator<<(std::ostream& os, const MarkerWithCoord& marker) +{ + os << marker.id << ":" << marker.coord(0) << "," << marker.coord(1) << "," << marker.coord(2); + return os; +} + bool computeSimilarityFromCommonViews(const sfmData::SfMData& sfmDataA, const sfmData::SfMData& sfmDataB, const std::vector>& commonViewIds, @@ -192,7 +224,7 @@ std::map retrieveMatchingFilepath( } } ALICEVISION_LOG_TRACE("retrieveMatchingFilepath: " << imagePath << " -> " << cumulatedValues); - auto& it = uniqueFileparts.find(cumulatedValues); + auto it = uniqueFileparts.find(cumulatedValues); if (it != uniqueFileparts.end()) { duplicates.insert(cumulatedValues); @@ -276,7 +308,7 @@ std::map retrieveUniqueMetadataValues( cumulatedValues += mIt->second; } ALICEVISION_LOG_TRACE("retrieveUniqueMetadataValues: " << viewIt.second->getImagePath() << " -> " << cumulatedValues); - auto& it = uniqueMetadataValues.find(cumulatedValues); + auto it = uniqueMetadataValues.find(cumulatedValues); if (it != uniqueMetadataValues.end()) { duplicates.insert(cumulatedValues); @@ -668,7 +700,7 @@ bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmDa continue; for (int i = 0; i < markers.size(); ++i) { - if (landmarkIt.second.rgb.r() == markers[i].first) + if (landmarkIt.second.rgb.r() == markers[i].id) { landmarksIds[i] = landmarkIt.first; } @@ -680,8 +712,8 @@ bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmDa int landmarkId = landmarksIds[i]; if (landmarkId == -1) { - ALICEVISION_LOG_ERROR("Failed to find marker: " << int(markers[i].first)); - ALICEVISION_THROW_ERROR("Failed to find marker: " << int(markers[i].first)); + ALICEVISION_LOG_ERROR("Failed to find marker: " << int(markers[i].id)); + ALICEVISION_THROW_ERROR("Failed to find marker: " << int(markers[i].id)); } } @@ -690,7 +722,7 @@ bool computeNewCoordinateSystemFromSpecificMarkers(const sfmData::SfMData& sfmDa for (std::size_t i = 0; i < markers.size(); ++i) { ptsSrc.col(i) = sfmData.getLandmarks().at(landmarksIds[i]).X; - ptsDst.col(i) = markers[i].second; + ptsDst.col(i) = markers[i].coord; } if (markers.size() == 1) diff --git a/src/aliceVision/sfm/utils/alignment.hpp b/src/aliceVision/sfm/utils/alignment.hpp index da49f78023..74483df741 100644 --- a/src/aliceVision/sfm/utils/alignment.hpp +++ b/src/aliceVision/sfm/utils/alignment.hpp @@ -225,7 +225,15 @@ void computeNewCoordinateSystemFromSingleCamera(const sfmData::SfMData& sfmData, Mat3& out_R, Vec3& out_t); -using MarkerWithCoord = std::pair; +struct MarkerWithCoord +{ + int id; + Vec3 coord; +}; + +std::istream& operator>>(std::istream& in, MarkerWithCoord& marker); + +std::ostream& operator<<(std::ostream& os, const MarkerWithCoord& marker); /** * @brief Compute a new coordinate system so that markers are aligned with the target coordinates. diff --git a/src/software/utils/main_sfmAlignment.cpp b/src/software/utils/main_sfmAlignment.cpp index 50e0707e13..4a5bc0dc95 100644 --- a/src/software/utils/main_sfmAlignment.cpp +++ b/src/software/utils/main_sfmAlignment.cpp @@ -83,6 +83,10 @@ inline std::istream& operator>>(std::istream& in, EAlignmentMethod& alignment) return in; } +inline std::ostream& operator<<(std::ostream& os, EAlignmentMethod e) +{ + return os << EAlignmentMethod_enumToString(e); +} int main(int argc, char **argv) diff --git a/src/software/utils/main_sfmTransfer.cpp b/src/software/utils/main_sfmTransfer.cpp index de7f2fb86b..1e7125c74b 100644 --- a/src/software/utils/main_sfmTransfer.cpp +++ b/src/software/utils/main_sfmTransfer.cpp @@ -77,6 +77,10 @@ inline std::istream& operator>>(std::istream& in, EMatchingMethod& alignment) return in; } +inline std::ostream& operator<<(std::ostream& os, EMatchingMethod e) +{ + return os << EMatchingMethod_enumToString(e); +} int main(int argc, char **argv) diff --git a/src/software/utils/main_sfmTransform.cpp b/src/software/utils/main_sfmTransform.cpp index bacf13de7e..ba81895aed 100644 --- a/src/software/utils/main_sfmTransform.cpp +++ b/src/software/utils/main_sfmTransform.cpp @@ -12,9 +12,6 @@ #include #include -#include -#include -#include #include #include @@ -87,6 +84,12 @@ inline std::istream& operator>>(std::istream& in, EAlignmentMethod& alignment) return in; } +inline std::ostream& operator<<(std::ostream& os, EAlignmentMethod e) +{ + return os << EAlignmentMethod_enumToString(e); +} + + static bool parseAlignScale(const std::string& alignScale, double& S, Mat3& R, Vec3& t) { @@ -111,28 +114,6 @@ static bool parseAlignScale(const std::string& alignScale, double& S, Mat3& R, V } -inline std::istream& operator>>(std::istream& in, sfm::MarkerWithCoord& marker) -{ - std::string token; - in >> token; - std::vector markerCoord; - boost::split(markerCoord, token, boost::algorithm::is_any_of(":=")); - if(markerCoord.size() != 2) - throw std::invalid_argument("Failed to parse MarkerWithCoord from: " + token); - marker.first = boost::lexical_cast(markerCoord.front()); - - std::vector coord; - boost::split(coord, markerCoord.back(), boost::algorithm::is_any_of(",;_")); - if (coord.size() != 3) - throw std::invalid_argument("Failed to parse Marker coordinates from: " + markerCoord.back()); - - for (int i = 0; i < 3; ++i) - { - marker.second(i) = boost::lexical_cast(coord[i]); - } - return in; -} - int main(int argc, char **argv) { // command-line parameters From 141c4ad609146cf850aa9546d1aa4b1e5f45164c Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Tue, 24 Sep 2019 21:33:12 +0200 Subject: [PATCH 10/11] [sfm] bug fix: tracksPerView should have a key for each view In many places, we assume that tracksPerView has a key for each view. So this commit adds an initialization step to ensure that it is the case (instead of checking if the key exists all over the place). --- .../ReconstructionEngine_sequentialSfM.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp index 36ea1efbfa..8ee7354b95 100644 --- a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp +++ b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp @@ -246,6 +246,13 @@ std::size_t ReconstructionEngine_sequentialSfM::fuseMatchesIntoTracks() // build tracks with STL compliant type tracksBuilder.exportToSTL(_map_tracks); ALICEVISION_LOG_DEBUG("Build tracks per view"); + + // Init tracksPerView to have an entry in the map for each view (even if there is no track at all) + for(const auto& viewIt: _sfmData.views) + { + // create an entry in the map + _map_tracksPerView[viewIt.first]; + } track::tracksUtilsMap::computeTracksPerView(_map_tracks, _map_tracksPerView); ALICEVISION_LOG_DEBUG("Build tracks pyramid per view"); computeTracksPyramidPerView( @@ -849,11 +856,7 @@ bool ReconstructionEngine_sequentialSfM::findConnectedViews( const bool isIntrinsicsReconstructed = reconstructedIntrinsics.count(intrinsicId); // Compute 2D - 3D possible content - aliceVision::track::TracksPerView::const_iterator tracksIdsIt = _map_tracksPerView.find(viewId); - if(tracksIdsIt == _map_tracksPerView.end()) - continue; - - const aliceVision::track::TrackIdSet& set_tracksIds = tracksIdsIt->second; + const aliceVision::track::TrackIdSet& set_tracksIds = _map_tracksPerView.at(viewId); if (set_tracksIds.empty()) continue; From b3762bc3457a6c71f84a90548932f6b01f6d764e Mon Sep 17 00:00:00 2001 From: Fabien Castan Date: Tue, 24 Sep 2019 21:44:21 +0200 Subject: [PATCH 11/11] [sfm] fix: this is not an error if we have a calibration in input --- .../pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp index 8ee7354b95..815631ccb4 100644 --- a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp +++ b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp @@ -659,7 +659,8 @@ bool ReconstructionEngine_sequentialSfM::bundleAdjustment(std::set& newR // - the number of cameras to refine cannot be < to the number of newly added cameras (set to 'refine' by default) if((nbRefinedPoses <= newReconstructedViews.size()) && _sfmData.getRigs().empty()) { - throw std::runtime_error("The local bundle adjustment refinement has not been done: the new cameras are not connected to the rest of the graph."); + ALICEVISION_LOG_INFO("Local bundle adjustment: the new cameras are not connected to the rest of the graph" + " (nbRefinedPoses: " << nbRefinedPoses << ", newReconstructedViews.size(): " << newReconstructedViews.size() << ")."); } }