diff --git a/meshroom/aliceVision/ImportAlembic.py b/meshroom/aliceVision/ImportAlembic.py index 55a1754ba0..2f35820477 100644 --- a/meshroom/aliceVision/ImportAlembic.py +++ b/meshroom/aliceVision/ImportAlembic.py @@ -1,4 +1,4 @@ -__version__ = "1.0" +__version__ = "1.1" from meshroom.core import desc from meshroom.core.utils import VERBOSE_LEVEL @@ -19,18 +19,20 @@ class ImportAlembic(desc.AVCommandLineNode): description="The external Alembic file to import.", value="", ), - desc.File( - name="imagesDir", - label="Images Directory", - description="Directory containing the images.", - value="", + desc.FloatParam( + name="framerate", + label="Frame rate", + description="Alembic frame rate to compute frame id from time", + value=24.0, + range=(10.0, 50.0, 1.0), ), - desc.ChoiceParam( - name="extension", - label="Images Extension", - description="File extension for the images in the directory to be taken into account.", - value=".exr", - values=[".exr", ".jpg", ".png"], + desc.IntParam( + name="imageWidth", + label="Image(s) Width", + description="Alembic does not export the camera resolutions. \n" + "Setup the image width for all images, the height will depend on the sensor size ratio.", + value=1920, + range=(640, 10000, 10), ), desc.ChoiceParam( name="verboseLevel", @@ -46,6 +48,6 @@ class ImportAlembic(desc.AVCommandLineNode): name="output", label="SfMData", description="SfMData file populated with the camera poses from the external Alembic file.", - value="{nodeCacheFolder}/importedAbc.sfm", + value="{nodeCacheFolder}/importedAbc.abc", ), ] diff --git a/meshroom/aliceVision/SfMPoseInjecting.py b/meshroom/aliceVision/SfMPoseInjecting.py index f8e4502e6e..037e4ad029 100644 --- a/meshroom/aliceVision/SfMPoseInjecting.py +++ b/meshroom/aliceVision/SfMPoseInjecting.py @@ -4,6 +4,7 @@ from meshroom.core.utils import VERBOSE_LEVEL import json +import pathlib class SfMPoseInjecting(desc.AVCommandLineNode): @@ -23,9 +24,17 @@ class SfMPoseInjecting(desc.AVCommandLineNode): desc.File( name="posesFilename", label="Poses", - description="Input JSON file containing the poses.", + description="Input file containing the poses (Json or ABC).", value="", ), + desc.FloatParam( + name="framerate", + label="Frame rate", + description="Alembic frame rate to compute frame id from time", + value=24.0, + range=(10.0, 50.0, 1.0), + enabled=lambda node: pathlib.Path(node.posesFilename.value).suffix.lower() == ".abc" + ), desc.ChoiceParam( name="rotationFormat", label="Rotation Format", @@ -33,6 +42,7 @@ class SfMPoseInjecting(desc.AVCommandLineNode): " - EulerZXY: Euler rotation in degrees (Y*X*Z)", values=["EulerZXY"], value="EulerZXY", + enabled=lambda node: pathlib.Path(node.posesFilename.value).suffix.lower() == ".json" ), desc.ChoiceParam( name="verboseLevel", diff --git a/meshroom/aliceVision/SfmBootstrapping.py b/meshroom/aliceVision/SfmBootstrapping.py index 9d7da89762..6d552fa2a1 100644 --- a/meshroom/aliceVision/SfmBootstrapping.py +++ b/meshroom/aliceVision/SfmBootstrapping.py @@ -1,4 +1,4 @@ -__version__ = "4.1" +__version__ = "4.2" from meshroom.core import desc from meshroom.core.utils import VERBOSE_LEVEL @@ -21,8 +21,8 @@ class SfMBootStrapping(desc.AVCommandLineNode): desc.ChoiceParam( name="method", label="Method", - description="Bootstrapping method: classic (epipolar geometry), mesh (3D mesh constraints), or depth (depth map information).", - values=["classic", "mesh", "depth"], + description="Bootstrapping method: classic (epipolar geometry), mesh (3D mesh constraints), mesh_single (mesh without visual parallax), or depth (depth map information).", + values=["classic", "mesh", "mesh_single", "depth"], value="classic", ), desc.File( @@ -36,7 +36,7 @@ class SfMBootStrapping(desc.AVCommandLineNode): label="Mesh File", description="Mesh file (*.obj).", value="", - enabled=lambda node: node.method.value == "mesh" + enabled=lambda node: node.method.value.startswith("mesh") ), desc.File( name="pairs", diff --git a/src/aliceVision/dataio/json.cpp b/src/aliceVision/dataio/json.cpp index 8219fc0be2..74fa954ea6 100644 --- a/src/aliceVision/dataio/json.cpp +++ b/src/aliceVision/dataio/json.cpp @@ -16,6 +16,9 @@ std::vector readJsons(std::istream& is, boost::system::error while (true) { totalRead = p.write_some(content, ec); + + //remove processed string from content + content = content.substr(totalRead); // If the parser did not find a value, then it won't // find anything more. @@ -28,9 +31,6 @@ std::vector readJsons(std::istream& is, boost::system::error // content jvs.push_back(p.release()); p.reset(); - - //remove processed string from content - content = content.substr(totalRead); } } diff --git a/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp b/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp index 352f15755c..37e3eff16e 100644 --- a/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp +++ b/src/aliceVision/sfm/bundle/BundleAdjustmentCeres.cpp @@ -630,7 +630,7 @@ void BundleAdjustmentCeres::addLandmarksToProblem(const sfmData::SfMData& sfmDat problem.AddResidualBlock(costFunction, nullptr, params); } - if (!refineStructure || landmark.state == EEstimatorParameterState::CONSTANT) + if (!refineStructure || landmark.state == EEstimatorParameterState::CONSTANT || landmark.isPrecise()) { // set the whole landmark parameter block as constant. _statistics.addState(EParameter::LANDMARK, EEstimatorParameterState::CONSTANT); diff --git a/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.cpp b/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.cpp index f66934ec10..133137adf4 100644 --- a/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.cpp +++ b/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.cpp @@ -159,11 +159,6 @@ bool ExpansionChunk::process(sfmData::SfMData & sfmData, const track::TracksHand { return false; } - - if (_pointFetcherHandler) - { - setConstraints(sfmData, tracksHandler, validViewIds); - } _historyHandler->saveState(sfmData); @@ -174,18 +169,17 @@ bool ExpansionChunk::process(sfmData::SfMData & sfmData, const track::TracksHand bool ExpansionChunk::triangulate(sfmData::SfMData & sfmData, const track::TracksHandler & tracksHandler, const std::set & viewIds) { - ALICEVISION_LOG_INFO("ExpansionChunk::triangulate start"); - SfmTriangulation triangulation(_triangulationMinPoints, _maxTriangulationError); - - + ALICEVISION_LOG_INFO("ExpansionChunk::triangulate start"); const bool enableMultiviewTriangulation = true; + const size_t minPoints = _triangulationHandler->getMinObservations(); + if (enableMultiviewTriangulation) { std::set evaluatedTracks; std::map outputLandmarks; std::mt19937 randomNumberGenerator; - if (!triangulation.process(sfmData, tracksHandler.getAllTracks(), tracksHandler.getTracksPerView(), + if (!_triangulationHandler->process(sfmData, tracksHandler.getAllTracks(), tracksHandler.getTracksPerView(), randomNumberGenerator, viewIds, evaluatedTracks, outputLandmarks, false)) { @@ -204,7 +198,7 @@ bool ExpansionChunk::triangulate(sfmData::SfMData & sfmData, const track::Tracks landmarks.erase(pl.first); } - if (landmark.getObservations().size() < _triangulationMinPoints) + if (landmark.getObservations().size() < minPoints) { continue; } @@ -232,7 +226,51 @@ bool ExpansionChunk::triangulate(sfmData::SfMData & sfmData, const track::Tracks std::set evaluatedTracks; std::map outputLandmarks; std::mt19937 randomNumberGenerator; - if (!triangulation.process(sfmData, tracksHandler.getAllTracks(), tracksHandler.getTracksPerView(), + if (!_triangulationHandler->process(sfmData, tracksHandler.getAllTracks(), tracksHandler.getTracksPerView(), + randomNumberGenerator, viewIds, + evaluatedTracks, outputLandmarks, true)) + { + return false; + } + + auto & landmarks = sfmData.getLandmarks(); + ALICEVISION_LOG_INFO("Existing landmarks : " << landmarks.size()); + + for (const auto & pl : outputLandmarks) + { + const auto & landmark = pl.second; + + if (landmarks.find(pl.first) != landmarks.end()) + { + if (!_ignoreMultiviewOnPrior) + { + continue; + } + } + + if (landmark.getObservations().size() < minPoints) + { + continue; + } + + if (!SfmTriangulation::checkChierality(sfmData, landmark)) + { + continue; + } + + landmarks.insert(pl); + } + + ALICEVISION_LOG_INFO("New landmarks count : " << landmarks.size()); + ALICEVISION_LOG_INFO("ExpansionChunk::triangulate end"); + } + + if (_enableMeshPrior) + { + std::set evaluatedTracks; + std::map outputLandmarks; + std::mt19937 randomNumberGenerator; + if (!_triangulationHandler->process(sfmData, tracksHandler.getAllTracks(), tracksHandler.getTracksPerView(), randomNumberGenerator, viewIds, evaluatedTracks, outputLandmarks, true)) { @@ -254,7 +292,7 @@ bool ExpansionChunk::triangulate(sfmData::SfMData & sfmData, const track::Tracks } } - if (landmark.getObservations().size() < _triangulationMinPoints) + if (landmark.getObservations().size() < minPoints) { continue; } @@ -285,7 +323,7 @@ void ExpansionChunk::addPose(sfmData::SfMData & sfmData, IndexT viewId, const Ei void ExpansionChunk::setConstraints(sfmData::SfMData & sfmData, const track::TracksHandler & tracksHandler, const std::set & viewIds) { - ALICEVISION_LOG_INFO("ExpansionChunk::setConstraints start"); + /*ALICEVISION_LOG_INFO("ExpansionChunk::setConstraints start"); const track::TracksMap & tracks = tracksHandler.getAllTracks(); const track::TracksPerView & tracksPerView = tracksHandler.getTracksPerView(); @@ -399,7 +437,7 @@ void ExpansionChunk::setConstraints(sfmData::SfMData & sfmData, const track::Tra } ALICEVISION_LOG_INFO("ExpansionChunk::setConstraints added " << constraints.size() << " constraints"); - ALICEVISION_LOG_INFO("ExpansionChunk::setConstraints end"); + ALICEVISION_LOG_INFO("ExpansionChunk::setConstraints end");*/ } } // namespace sfm diff --git a/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.hpp b/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.hpp index 7e1c35589f..75c1b5dc2c 100644 --- a/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.hpp +++ b/src/aliceVision/sfm/pipeline/expanding/ExpansionChunk.hpp @@ -11,8 +11,8 @@ #include #include #include -#include #include +#include namespace aliceVision { namespace sfm { @@ -45,7 +45,7 @@ class ExpansionChunk } /** - * brief setup the expansion history handler + * @brief setup the expansion history handler * @param expansionHistory a shared ptr */ void setExpansionHistoryHandler(ExpansionHistory::sptr & expansionHistory) @@ -54,16 +54,7 @@ class ExpansionChunk } /** - * brief setup the point fetcher handler - * @param pointFetcher a unique ptr. the Ownership will be taken - */ - void setPointFetcherHandler(PointFetcher::uptr & pointFetcherHandler) - { - _pointFetcherHandler = std::move(pointFetcherHandler); - } - - /** - * brief setup the point fetcher handler + * @brief setup the Resection handler * @param resectionHandler a unique ptr. the Ownership will be taken */ void setResectionHandler(SfmResection::uptr & resectionHandler) @@ -72,21 +63,12 @@ class ExpansionChunk } /** - * @brief set the minimal number of points to enable triangulation of a track - * @param count the number of points - */ - void setTriangulationMinPoints(size_t count) - { - _triangulationMinPoints = count; - } - - /** - * @brief set the maximal reprojection error in the triangulation process. - * @param count the number of points + * @brief setup the Triangulation handler + * @param triangulationHandler a unique ptr. the Ownership will be taken */ - void setTriangulationMaxError(double error) + void setTriangulationHandler(SfmTriangulation::uptr & triangulationHandler) { - _maxTriangulationError = error; + _triangulationHandler = std::move(triangulationHandler); } /** @@ -159,16 +141,15 @@ class ExpansionChunk private: SfmBundle::uptr _bundleHandler; ExpansionHistory::sptr _historyHandler; - PointFetcher::uptr _pointFetcherHandler; std::set _ignoredViews; SfmResection::uptr _resectionHandler; + SfmTriangulation::uptr _triangulationHandler; private: - size_t _triangulationMinPoints = 2; double _minTriangulationAngleDegrees = 3.0; - double _maxTriangulationError = 8.0; size_t _weakResectionSize = 100; bool _enableDepthPrior = true; + bool _enableMeshPrior = true; bool _ignoreMultiviewOnPrior = false; }; diff --git a/src/aliceVision/sfm/pipeline/expanding/ExpansionIteration.hpp b/src/aliceVision/sfm/pipeline/expanding/ExpansionIteration.hpp index e8d615e3c8..6698b1eb27 100644 --- a/src/aliceVision/sfm/pipeline/expanding/ExpansionIteration.hpp +++ b/src/aliceVision/sfm/pipeline/expanding/ExpansionIteration.hpp @@ -42,7 +42,7 @@ class ExpansionIteration } /** - * brief setup the expansion history handler + * @brief setup the expansion history handler * @param expansionHistory a shared ptr */ void setExpansionHistoryHandler(ExpansionHistory::sptr & expansionHistory) @@ -51,7 +51,7 @@ class ExpansionIteration } /** - * brief setup the expansion history handler + * @brief setup the expansion history handler * @param expansionPolicy a unique ptr. Ownership will be taken */ void setExpansionPolicyHandler(ExpansionPolicy::uptr & expansionPolicy) @@ -60,7 +60,7 @@ class ExpansionIteration } /** - * brief setup the expansion chunk handler + * @brief setup the expansion chunk handler * @param expansionChunk a unique ptr. Ownership will be taken */ void setExpansionChunkHandler(ExpansionChunk::uptr & expansionChunk) diff --git a/src/aliceVision/sfm/pipeline/expanding/ExpansionProcess.hpp b/src/aliceVision/sfm/pipeline/expanding/ExpansionProcess.hpp index 35d4ca1926..9a3004356c 100644 --- a/src/aliceVision/sfm/pipeline/expanding/ExpansionProcess.hpp +++ b/src/aliceVision/sfm/pipeline/expanding/ExpansionProcess.hpp @@ -40,7 +40,7 @@ class ExpansionProcess{ } /** - * brief setup the expansion history handler + * @brief setup the expansion history handler * @param expansionHistory a shared ptr */ void setExpansionHistoryHandler(ExpansionHistory::sptr & expansionHistory) @@ -49,7 +49,7 @@ class ExpansionProcess{ } /** - * brief setup the expansion iteration handler + * @brief setup the expansion iteration handler * @param expansionIteration a unique ptr. Ownership will be taken */ void setExpansionIterationHandler(ExpansionIteration::uptr & expansionIteration) @@ -58,7 +58,7 @@ class ExpansionProcess{ } /** - * brief setup the expansion iteration post process handler + * @brief setup the expansion iteration post process handler * @param expansionPostProcess a unique ptr. Ownership will be taken */ void setExpansionIterationPostProcessHandler(ExpansionPostProcess::uptr & expansionPostProcess) diff --git a/src/aliceVision/sfm/pipeline/expanding/LbaPolicy.hpp b/src/aliceVision/sfm/pipeline/expanding/LbaPolicy.hpp index 9024fb0daa..2bd8b85ada 100644 --- a/src/aliceVision/sfm/pipeline/expanding/LbaPolicy.hpp +++ b/src/aliceVision/sfm/pipeline/expanding/LbaPolicy.hpp @@ -30,7 +30,7 @@ class LbaPolicy virtual bool build(sfmData::SfMData & sfmData, const track::TracksHandler & tracksHandler, const std::set & viewIds) = 0; /** - * brief setup the expansion history handler + * @brief setup the expansion history handler * @param expansionHistory a shared ptr */ void setExpansionHistoryHandler(ExpansionHistory::sptr & expansionHistory) diff --git a/src/aliceVision/sfm/pipeline/expanding/PointFetcher.hpp b/src/aliceVision/sfm/pipeline/expanding/PointFetcher.hpp index 089cc7f1ce..23a47f8cd5 100644 --- a/src/aliceVision/sfm/pipeline/expanding/PointFetcher.hpp +++ b/src/aliceVision/sfm/pipeline/expanding/PointFetcher.hpp @@ -18,6 +18,7 @@ class PointFetcher { public: using uptr = std::unique_ptr; + using sptr = std::shared_ptr; public: /** diff --git a/src/aliceVision/sfm/pipeline/expanding/SfmBundle.hpp b/src/aliceVision/sfm/pipeline/expanding/SfmBundle.hpp index 432d10cc09..5358d87fbd 100644 --- a/src/aliceVision/sfm/pipeline/expanding/SfmBundle.hpp +++ b/src/aliceVision/sfm/pipeline/expanding/SfmBundle.hpp @@ -32,7 +32,7 @@ class SfmBundle bool process(sfmData::SfMData & sfmData, const track::TracksHandler & tracksHandler, const std::set & viewIds); /** - * brief setup the expansion chunk handler + * @brief setup the expansion chunk handler * @param expansionChunk a unique ptr. Ownership will be taken */ void setLbaPolicyHandler(LbaPolicy::uptr & lbaPolicy) @@ -106,7 +106,7 @@ class SfmBundle double _maxReprojectionError = 4.0; double _minAngleForLandmark = 2.0; double _maxConstraintDistance = 1.0; - size_t _minTrackLength = 2; + size_t _minTrackLength = 1; size_t _minPointsPerPose = 30; size_t _bundleAdjustmentMaxOutlier = 50; size_t _minNbCamerasToRefinePrincipalPoint = 3; diff --git a/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.cpp b/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.cpp index 1ce0c43c27..2286c43b16 100644 --- a/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.cpp +++ b/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.cpp @@ -86,9 +86,19 @@ bool SfmTriangulation::process( sfmData::Landmark result; if (useDepthPrior) { - if (!processTrackWithPrior(sfmData, track, randomNumberGenerator, trackViewsFiltered, result)) + if (_pointFetcherHandler) { - continue; + if (!processTrackWithPointFetcher(sfmData, track, randomNumberGenerator, trackViewsFiltered, result)) + { + continue; + } + } + else + { + if (!processTrackWithPrior(sfmData, track, randomNumberGenerator, trackViewsFiltered, result)) + { + continue; + } } } else @@ -284,6 +294,108 @@ bool SfmTriangulation::processTrackWithPrior( return true; } +bool SfmTriangulation::processTrackWithPointFetcher( + const sfmData::SfMData & sfmData, + const track::Track & track, + std::mt19937 &randomNumberGenerator, + const std::set & viewIds, + sfmData::Landmark & result + ) +{ + size_t bestInliersCount = 0; + std::vector> possibleParameters; + + //For each observed view in the track + for (auto referenceViewId : viewIds) + { + if (track.featPerView.find(referenceViewId) == track.featPerView.end()) + { + continue; + } + + //Look if this observation has an associated depth + const auto & refTrackItem = track.featPerView.at(referenceViewId); + + const sfmData::View & v = sfmData.getView(referenceViewId); + const sfmData::CameraPose & cp = sfmData.getAbsolutePose(v.getPoseId()); + const camera::IntrinsicBase & intrinsics = *sfmData.getIntrinsics().at(v.getIntrinsicId()); + + _pointFetcherHandler->setPose(cp.getTransform()); + + Vec3 point, normal; + if (!_pointFetcherHandler->pickPointAndNormal(point, normal, intrinsics, refTrackItem.coords)) + { + continue; + } + + possibleParameters.push_back(std::make_pair(point, normal)); + } + + + + //Consider each point + for (int idRef = 0; idRef < possibleParameters.size(); idRef++) + { + const Vec3 & refpt = possibleParameters[idRef].first; + + //Make sure this point is not dependent on parallax + //As it does not need parallax to estimate its depth + sfmData::Landmark landmark; + landmark.setParallaxRobust(true); + landmark.X = refpt; + landmark.descType = track.descType; + landmark.setIsPrecise(true); + + //For each observed view in the track + for (auto viewId: viewIds) + { + if (track.featPerView.find(viewId) == track.featPerView.end()) + { + continue; + } + + const auto & trackItem = track.featPerView.at(viewId); + + const sfmData::View & view = sfmData.getView(viewId); + const camera::IntrinsicBase & intrinsic = sfmData.getIntrinsic(view.getIntrinsicId()); + const geometry::Pose3 pose = sfmData.getPose(view).getTransform(); + + const Vec2 est = intrinsic.transformProject(pose, refpt.homogeneous(), true); + const Vec2 mes = trackItem.coords.cast(); + double err = (est - mes).norm() / trackItem.scale; + + //Use _maxError as threshold for inliers/outlier detection + if (err > _maxError) + { + continue; + } + + //Create and associated an observation to the landmark + sfmData::Observation & o = landmark.getObservations()[viewId]; + o.setFeatureId(trackItem.featureId); + o.setScale(trackItem.scale); + o.setCoordinates(trackItem.coords); + o.setDepth(trackItem.depth); + } + + //Store the landmark if it's better than the previously estimated one + int count = landmark.getObservations().size(); + if (count > bestInliersCount) + { + bestInliersCount = count; + result = landmark; + } + } + + //One inlier is the reference, so we need at least 2 inliers + if (bestInliersCount < 2) + { + return false; + } + + return true; +} + bool SfmTriangulation::checkChierality(const sfmData::SfMData & sfmData, const sfmData::Landmark & landmark) { for (const auto & pRefObs : landmark.getObservations()) diff --git a/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.hpp b/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.hpp index 4830e45dec..bfdf897c3c 100644 --- a/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.hpp +++ b/src/aliceVision/sfm/pipeline/expanding/SfmTriangulation.hpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace aliceVision { namespace sfm { @@ -19,6 +20,9 @@ namespace sfm { */ class SfmTriangulation { +public: + using uptr = std::unique_ptr; + public: SfmTriangulation(size_t minObservations, double maxError) : _minObservations(minObservations), @@ -50,6 +54,24 @@ class SfmTriangulation bool useDepthPrior ); + /** + * @brief setup the point fetcher handler + * @param pointFetcher a unique ptr. the Ownership will be taken + */ + void setPointFetcherHandler(PointFetcher::uptr & pointFetcherHandler) + { + _pointFetcherHandler = std::move(pointFetcherHandler); + } + + /** + * @brief Retrieve the requested minimal number of observations per triangulation + * @return a positive number which is the minimal number of observations per point. + */ + size_t getMinObservations() const + { + return _minObservations; + } + public: /** * Check that all observation of a given landmark are physically possible @@ -102,7 +124,27 @@ class SfmTriangulation sfmData::Landmark & result ); + /** + * Process triangulation of a track with Point fetcher enabled + * @param sfmData the actual state of the sfm + * @param track the track of interest + * @param randomNumberGenerator random number generator object + * @param viewIds the set of view ids to process. Only tracks observed in these views will be considered + * @param result the output landmark + * @return false if a critical error occurred + */ + bool processTrackWithPointFetcher( + const sfmData::SfMData & sfmData, + const track::Track & track, + std::mt19937 &randomNumberGenerator, + const std::set & viewIds, + sfmData::Landmark & result + ); + private: + PointFetcher::uptr _pointFetcherHandler; + +private: const size_t _minObservations; const double _maxError; }; diff --git a/src/aliceVision/sfmData/Landmark.hpp b/src/aliceVision/sfmData/Landmark.hpp index ef1535a588..637155bb2b 100644 --- a/src/aliceVision/sfmData/Landmark.hpp +++ b/src/aliceVision/sfmData/Landmark.hpp @@ -85,9 +85,22 @@ class Landmark */ void setParallaxRobust(bool parallaxRobust) { _parallaxRobust = parallaxRobust; } + /** + * @brief Is this landmark precisely located + * @return true if this landmark has this special property + */ + bool isPrecise() const { return _isPrecise; } + + /** + * @brief decide if this landmark is robust even if its parallax is low + * @param isPrecise True if robust to lack of parallax + */ + void setIsPrecise(bool isPrecise) { _isPrecise = isPrecise; } + private: Observations _observations; bool _parallaxRobust = false; + bool _isPrecise = false; }; } // namespace sfmData diff --git a/src/aliceVision/sfmDataIO/ExternalAlembicImporter.cpp b/src/aliceVision/sfmDataIO/ExternalAlembicImporter.cpp index 6ffbaf25f7..2ff51d42d8 100644 --- a/src/aliceVision/sfmDataIO/ExternalAlembicImporter.cpp +++ b/src/aliceVision/sfmDataIO/ExternalAlembicImporter.cpp @@ -13,10 +13,14 @@ #include #include +#include +#include +#include + namespace aliceVision { namespace sfmDataIO { -ExternalAlembicImporter::ExternalAlembicImporter(const std::string& filename) +ExternalAlembicImporter::ExternalAlembicImporter(const std::string& filename, double framerate, unsigned int imageWidth) { Alembic::AbcCoreFactory::IFactory factory; Alembic::AbcCoreFactory::IFactory::CoreType coreType; @@ -29,105 +33,280 @@ ExternalAlembicImporter::ExternalAlembicImporter(const std::string& filename) _rootEntity = archive.getTop(); _filename = filename; + _framerate = framerate; + _imageWidth = imageWidth; +} + +bool ExternalAlembicImporter::populateSfM(sfmData::SfMData& sfmdata) +{ + std::vector cameras; + + // Go through the hierarchy and find ICamera objects + populateCameras(cameras, _rootEntity); + + ALICEVISION_LOG_INFO("Found " << cameras.size() << " different camera(s)."); + + //Loop over all cameras + for (auto cam : cameras) + { + // Get all time samples + std::set samplesXform; + if (!collectXformTimes(samplesXform, cam.getParent())) + { + return false; + } + + // Get camera time samples + std::set samplesCameras; + if (!collectCameraTimes(samplesCameras, cam)) + { + return false; + } + + // Union of all sets + std::set samples; + samples = samplesCameras; + samples.insert(samplesXform.begin(), samplesXform.end()); + + ALICEVISION_LOG_INFO("Found " << samples.size() << " samples for camera."); + + // Add all poses + for (const auto & sample : samples) + { + Imath::M44d mat = computeTransform(cam.getParent(), sample); + + addPose(sfmdata, sample, mat); + addCamera(sfmdata, sample, cam); + addView(sfmdata, sample); + } + } + + return true; +} + +void ExternalAlembicImporter::addPose(sfmData::SfMData& sfmdata, Alembic::Abc::chrono_t sample, const Imath::M44d & mat) +{ + const size_t frame = std::round(sample * _framerate); + + Mat4 vision_T_gl = Mat4::Identity(); + vision_T_gl(1, 1) = -1.0; + vision_T_gl(2, 2) = -1.0; + + // Convert to Eigen, transposing + // (look like ABC transform is a left to right operation) + + Mat4 world_T_camera = Mat4::Identity(); + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + world_T_camera(i, j) = mat[j][i]; + } + } + + // world_T_camera is in GL coordinates (Y up, Z back) + // we want camera_T_world = world_T_camera^-1 in vision coordinates (Y down, Z front) + Mat4 camera_T_world = (vision_T_gl * world_T_camera * vision_T_gl).inverse(); + + // Assign pose to camera + geometry::Pose3 pose(camera_T_world); + sfmdata.getPoses().assign(frame, sfmData::CameraPose(pose)); } -void ExternalAlembicImporter::populateSfM(sfmData::SfMData& sfmdata, const std::vector & files) +void ExternalAlembicImporter::addCamera(sfmData::SfMData& sfmdata, Alembic::Abc::chrono_t sample, Alembic::AbcGeom::ICamera iCamera) { - Alembic::Abc::M44d identity; - visitObject(_rootEntity, identity, sfmdata, files); + Alembic::Abc::ISampleSelector ss(sample); + Alembic::AbcGeom::ICameraSchema cs = iCamera.getSchema(); + Alembic::AbcGeom::CameraSample camSample = cs.getValue(ss); + + size_t frame = std::round(sample * _framerate); + + const int w = _imageWidth; + const double cmToMm = 10.0; + double happ = camSample.getHorizontalAperture() * cmToMm; + double vapp = camSample.getVerticalAperture() * cmToMm; + + const double dw = static_cast(w); + const double dh = dw * vapp / happ; + const int h = static_cast(std::round(dh)); + + + auto cam = camera::createPinhole( + camera::EDISTORTION::DISTORTION_NONE, + camera::EUNDISTORTION::UNDISTORTION_NONE, + w, h, + 1.0, 1.0, + 0.0, 0.0 + ); + + + + cam->setSensorWidth(happ); + cam->setSensorHeight(vapp); + cam->setFocalLength(camSample.getFocalLength(), 1.0, false); + + Vec2 offset; + const double mmToPix = dw / happ; + offset.x() = camSample.getHorizontalFilmOffset() * cmToMm * mmToPix; + offset.y() = - camSample.getVerticalFilmOffset() * cmToMm * mmToPix; + cam->setOffset(offset); + + sfmdata.getIntrinsics().emplace(frame, cam); +} + +void ExternalAlembicImporter::addView(sfmData::SfMData& sfmdata, Alembic::Abc::chrono_t sample) +{ + const size_t frame = std::round(sample * _framerate); + + const auto & intrinsics = sfmdata.getIntrinsics(); + auto it = intrinsics.find(frame); + if (it == intrinsics.end()) + { + // should not happen + ALICEVISION_LOG_ERROR("Intrinsic not found."); + return; + } + + const auto & intrinsic = it->second; + + std::stringstream filename; + filename << std::setfill('0') << std::setw(12) << frame << ".exr"; + + sfmData::View view(filename.str(), frame, frame, frame, intrinsic->w(), intrinsic->h()); + view.setFrameId(frame); + + sfmdata.getViews().assign(frame, view); } -void ExternalAlembicImporter::visitObject(Alembic::Abc::IObject iObj, const Alembic::Abc::M44d & mat, sfmData::SfMData& sfmdata, const std::vector & files) +Imath::M44d ExternalAlembicImporter::computeTransform(Alembic::Abc::IObject iObj, double time) { + Imath::M44d worldMatrix; + worldMatrix.makeIdentity(); - const Alembic::Abc::MetaData& md = iObj.getMetaData(); + Alembic::Abc::ISampleSelector ss(time); + Alembic::Abc::IObject current = iObj; - if (Alembic::AbcGeom::IXform::matches(md)) + // Walk up the hierarchy + while (current.valid()) { - Alembic::AbcGeom::IXform xform(iObj, Alembic::Abc::kWrapExisting); - Alembic::AbcGeom::IXformSchema schema = xform.getSchema(); + const Alembic::Abc::MetaData& md = current.getMetaData(); + + // If this is an IXform, accumulate its transform + if (Alembic::AbcGeom::IXform::matches(md)) { + + Alembic::AbcGeom::IXform xform(current, Alembic::Abc::kWrapExisting); + Alembic::AbcGeom::XformSample sample; + xform.getSchema().get(sample, ss); - Alembic::AbcGeom::XformSample xsample; + // Multiply in parent-first order + worldMatrix = sample.getMatrix() * worldMatrix; + } + + // Move to parent + current = current.getParent(); + } + + return worldMatrix; +} + +void ExternalAlembicImporter::populateCameras(std::vector & cameras, Alembic::Abc::IObject iObj) +{ + cameras.clear(); + + std::list stack; + stack.push_back(iObj); + + while (!stack.empty()) + { + Alembic::Abc::IObject currentObject = stack.back(); + stack.pop_back(); - if (schema.getNumSamples() == 1) + const Alembic::Abc::MetaData& md = currentObject.getMetaData(); + + if (Alembic::AbcGeom::ICamera::matches(md)) { - ALICEVISION_THROW_ERROR("Non implemented path."); + Alembic::AbcGeom::ICamera camera(currentObject, Alembic::Abc::kWrapExisting); + cameras.push_back(camera); } - - if (schema.getNumSamples() != files.size()) + + for (std::size_t i = 0; i < currentObject.getNumChildren(); i++) { - ALICEVISION_THROW_ERROR("Incompatible number of files wrt abc samples.") + Alembic::Abc::IObject child = currentObject.getChild(i); + if (child.valid()) + { + stack.push_back(child); + } } + } +} - Mat4 M = Mat4::Identity(); - M(1, 1) = -1.0; - M(2, 2) = -1.0; +bool ExternalAlembicImporter::collectXformTimes(std::set & samples, Alembic::AbcGeom::IObject iObject) +{ + Alembic::Abc::IObject current = iObject; - for (Alembic::Abc::index_t frame = 0; frame < xform.getSchema().getNumSamples(); ++frame) - { - xform.getSchema().get(xsample, Alembic::Abc::ISampleSelector(frame)); - const auto & currentMat = xsample.getMatrix(); - const auto newMat = mat * xsample.getMatrix(); + samples.clear(); + + // Walk up the hierarchy + while (current.valid()) + { + const Alembic::Abc::MetaData& md = current.getMetaData(); + + // If this is an IXform, accumulate its transform + if (Alembic::AbcGeom::IXform::matches(md)) { - Mat4 T = Mat4::Identity(); - for (int i = 0; i < 4; i++) + Alembic::AbcGeom::IXform xform(current, Alembic::Abc::kWrapExisting); + Alembic::AbcGeom::IXformSchema schema = xform.getSchema(); + Alembic::Abc::TimeSamplingPtr timeSampling = schema.getTimeSampling(); + + for (Alembic::Abc::index_t frame = 0; frame < schema.getNumSamples(); ++frame) { - for (int j = 0; j < 4; j++) + Alembic::Abc::chrono_t time = timeSampling->getSampleTime(frame); + + // Check that the sample is a frame + double framePos = time * _framerate; + double frameCheck = std::round(framePos) - framePos; + if (std::abs(frameCheck) > 1e-6) { - T(i, j) = newMat[j][i]; + ALICEVISION_LOG_ERROR("The framerate seems off (non integer frame)."); + return false; } - } - Mat4 T2 = (M * T * M).inverse(); - geometry::Pose3 pose(T2); - - Alembic::AbcGeom::ICamera camera(xform.getChild(0), Alembic::Abc::kWrapExisting); - Alembic::AbcGeom::ICameraSchema cs = camera.getSchema(); - Alembic::AbcGeom::CameraSample camSample; - camSample = cs.getValue(Alembic::Abc::ISampleSelector(frame)); - - /*std::cout << " ---- " << std::endl; - std::cout << camSample.getFocalLength() << std::endl; - std::cout << camSample.getHorizontalAperture() << std::endl; - std::cout << camSample.getVerticalAperture() << std::endl; - std::cout << camSample.getLensSqueezeRatio() << std::endl; - std::cout << camSample.getHorizontalFilmOffset() << std::endl; - std::cout << camSample.getVerticalFilmOffset() << std::endl;*/ - - int w = 0; - int h = 0; - image::readImageMetadata(files[frame], w, h); - - auto cam = camera::createPinhole( - camera::EDISTORTION::DISTORTION_NONE, - camera::EUNDISTORTION::UNDISTORTION_NONE, - w, h, - 1.0, 1.0, - 0.0, 0.0 - ); - - const double dw = static_cast(w); - const double dh = static_cast(h); - double happ = camSample.getHorizontalAperture(); - double vapp = camSample.getVerticalAperture(); - const double sensorWidthPix = std::max(dw, dh); - - cam->setSensorWidth(happ / (dw * 0.1 / sensorWidthPix)); - cam->setSensorHeight(vapp / (dh * 0.1 / sensorWidthPix)); - cam->setFocalLength(camSample.getFocalLength(), 1.0, false); - - sfmdata.getIntrinsics().emplace(frame, cam); - sfmdata.getPoses().assign(frame, sfmData::CameraPose(pose)); - sfmdata.getViews().emplace(frame, std::make_shared(files[frame], frame, frame, frame, w, h)); + samples.insert(time); + } } + + // Move to parent + current = current.getParent(); } - // Recurse - for (std::size_t i = 0; i < iObj.getNumChildren(); i++) + return true; +} + +bool ExternalAlembicImporter::collectCameraTimes(std::set & samples, Alembic::AbcGeom::ICamera iCamera) +{ + Alembic::AbcGeom::ICameraSchema schema = iCamera.getSchema(); + Alembic::Abc::TimeSamplingPtr timeSampling = schema.getTimeSampling(); + + for (Alembic::Abc::index_t frame = 0; frame < schema.getNumSamples(); ++frame) { - visitObject(iObj.getChild(i), mat, sfmdata, files); + Alembic::Abc::chrono_t time = timeSampling->getSampleTime(frame); + + // Check that the sample is a frame + double framePos = time * _framerate; + double frameCheck = std::round(framePos) - framePos; + if (std::abs(frameCheck) > 1e-6) + { + ALICEVISION_LOG_ERROR("The framerate seems off (non integer frame)."); + return false; + } + + samples.insert(time); } + + + return true; } + } // namespace sfmDataIO } // namespace aliceVision diff --git a/src/aliceVision/sfmDataIO/ExternalAlembicImporter.hpp b/src/aliceVision/sfmDataIO/ExternalAlembicImporter.hpp index ed113acfe6..51a69e4270 100644 --- a/src/aliceVision/sfmDataIO/ExternalAlembicImporter.hpp +++ b/src/aliceVision/sfmDataIO/ExternalAlembicImporter.hpp @@ -11,7 +11,7 @@ #include #include #include - +#include namespace aliceVision { namespace sfmDataIO { @@ -19,20 +19,75 @@ namespace sfmDataIO { class ExternalAlembicImporter { public: - explicit ExternalAlembicImporter(const std::string& filename); + explicit ExternalAlembicImporter(const std::string& filename, double framerate = 24.0, unsigned int imageWidth = 1920); /** * @brief populate a SfMData from the alembic file * @param[out] sfmData The output SfMData - * @param[in] files the input list of images files + * @return true on success of function */ - void populateSfM(sfmData::SfMData& sfmdata, const std::vector & files); + bool populateSfM(sfmData::SfMData& sfmdata); + +private: + /** + * @brief Find all ICamera objects in the alembic file and store them in the "cameras" vector + * @param cameras the output vector of ICamera + * @param iObj the node of the alembic file where we want to start going through + */ + void populateCameras(std::vector & cameras, Alembic::Abc::IObject iObj); + + /** + * @brief Compute full pose of the object and all its parents + * @param iObj the object of interest + * @param time the current time of interest + * @return a 4x4 matrix which is the transform from the object coordinates to the world coordinates frame + */ + Imath::M44d computeTransform(Alembic::AbcGeom::IObject iObj, double time); + + /** + * @brief collect all time samples in the object and its parents to build a list of unique states + * @param samples the output set of samples found + * @param iObject the object of interest + * @return return False if some sample time is off with the framerate + */ + bool collectXformTimes(std::set & samples, Alembic::AbcGeom::IObject iObject); - void visitObject(Alembic::Abc::IObject iObj, const Alembic::Abc::M44d & mat, sfmData::SfMData& sfmdata, const std::vector & files); + /** + * @brief collect all time samples in the camera to build a list of unique states + * @param samples the output set of samples found + * @param iCamera the object of interest + * @return return False if some sample time is off with the framerate + */ + bool collectCameraTimes(std::set & samples, Alembic::AbcGeom::ICamera iCamera); + + /** + * @brief add found pose to the sfmdata + * @param sfmdata the output sfmdata + * @param sample the sample time + * @param mat the input matrix to use + */ + void addPose(sfmData::SfMData& sfmdata, Alembic::Abc::chrono_t sample, const Imath::M44d & mat); + + /** + * @brief add found camera to the sfmdata + * @param sfmdata the output sfmdata + * @param sample the sample time + * @param iCamera the input camera + */ + void addCamera(sfmData::SfMData& sfmdata, Alembic::Abc::chrono_t sample, Alembic::AbcGeom::ICamera iCamera); + + /** + * @brief add view to the sfmdata + * @param sfmdata the output sfmdata + * @param sample the sample time + */ + void addView(sfmData::SfMData& sfmdata, Alembic::Abc::chrono_t sample); private: Alembic::Abc::IObject _rootEntity; std::string _filename; + double _framerate; + unsigned int _imageWidth; }; } // namespace sfmDataIO diff --git a/src/software/pipeline/main_sfmBootstrapping.cpp b/src/software/pipeline/main_sfmBootstrapping.cpp index 643f9e1d2e..273e9d84a7 100644 --- a/src/software/pipeline/main_sfmBootstrapping.cpp +++ b/src/software/pipeline/main_sfmBootstrapping.cpp @@ -44,7 +44,7 @@ // These constants define the current software version. // They must be updated when the command line is changed. #define ALICEVISION_SOFTWARE_VERSION_MAJOR 4 -#define ALICEVISION_SOFTWARE_VERSION_MINOR 1 +#define ALICEVISION_SOFTWARE_VERSION_MINOR 2 using namespace aliceVision; @@ -55,7 +55,8 @@ enum EBOOTSTRAPMETHOD { CLASSIC = (1u << 0), MESH = (1u << 1), - DEPTH = (1u << 2) + DEPTH = (1u << 2), + MESH_SINGLE = (1u << 3), }; inline EBOOTSTRAPMETHOD EBOOTSTRAPMETHOD_stringToEnum(const std::string& method) @@ -73,6 +74,11 @@ inline EBOOTSTRAPMETHOD EBOOTSTRAPMETHOD_stringToEnum(const std::string& method) return EBOOTSTRAPMETHOD::MESH; } + if (type == "mesh_single") + { + return EBOOTSTRAPMETHOD::MESH_SINGLE; + } + if (type == "depth") { return EBOOTSTRAPMETHOD::DEPTH; @@ -140,7 +146,9 @@ bool landmarksFromMesh( //Create a Landmark with a unique observation sfmData::Landmark l; l.X = point; - l.descType = feature::EImageDescriberType::SIFT; + l.descType = track.descType; + l.setParallaxRobust(true); + sfmData::Observations & observations = l.getObservations(); observations[referenceViewId] = sfmData::Observation(refpt, featureId, scale); landmarks[trackId] = l; @@ -234,7 +242,7 @@ bool processMesh(sfmData::SfMData & sfmData, return false; } - if (!firstViewFilters.empty()) + if (firstViewFilters.empty()) { ALICEVISION_LOG_ERROR("No known pose for mesh"); return false; @@ -284,6 +292,35 @@ bool processMesh(sfmData::SfMData & sfmData, return true; } +bool processMeshSingle(sfmData::SfMData & sfmData, + const track::TracksHandler & tracksHandler, + const std::set & firstViewFilters, + const std::string & meshFilename) +{ + //Load mesh in the mesh intersection object + if (meshFilename.empty()) + { + ALICEVISION_LOG_ERROR("No mesh file given"); + return false; + } + + if (firstViewFilters.empty()) + { + ALICEVISION_LOG_ERROR("No known pose for mesh"); + return false; + } + + sfmData::Landmarks landmarks; + if (!landmarksFromMesh(landmarks, sfmData, meshFilename, firstViewFilters, tracksHandler)) + { + return false; + } + + sfmData.getLandmarks() = landmarks; + + return true; +} + bool processDepth(sfmData::SfMData & sfmData, const track::TracksHandler & tracksHandler, const std::vector &reconstructedPairs, @@ -515,6 +552,9 @@ int aliceVision_main(int argc, char** argv) firstViewFilters, meshFilename, minAngleHard, minAngleSoft, maxAngle); break; + case MESH_SINGLE: + ret = processMeshSingle(sfmData, tracksHandler, firstViewFilters, meshFilename); + break; case DEPTH: ret = processDepth(sfmData, tracksHandler, reconstructedPairs, minAngleHard, minAngleSoft, maxAngle); diff --git a/src/software/pipeline/main_sfmExpanding.cpp b/src/software/pipeline/main_sfmExpanding.cpp index fcfcd3c6d0..0c11f5e1e1 100644 --- a/src/software/pipeline/main_sfmExpanding.cpp +++ b/src/software/pipeline/main_sfmExpanding.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -181,7 +182,7 @@ int aliceVision_main(int argc, char** argv) return EXIT_FAILURE; } - if (sfmData.getValidViews().size() < 2) + if (sfmData.getValidViews().size() < 1) { ALICEVISION_LOG_INFO("Expansion requires that some views are already defined."); return EXIT_SUCCESS; @@ -258,16 +259,17 @@ int aliceVision_main(int argc, char** argv) sfmResectionHandler->setMaxIterations(localizerEstimatorMaxIterations); sfmResectionHandler->setValidationPolicy(resectionValidationPolicy); + sfm::SfmTriangulation::uptr sfmTriangulationHandler = std::make_unique(minNbObservationsForTriangulation, maxTriangulationError); + sfmTriangulationHandler->setPointFetcherHandler(pointFetcherHandler); + sfm::ExpansionChunk::uptr expansionChunk = std::make_unique(); expansionChunk->setBundleHandler(sfmBundle); expansionChunk->setExpansionHistoryHandler(expansionHistory); expansionChunk->setResectionHandler(sfmResectionHandler); - expansionChunk->setTriangulationMaxError(maxTriangulationError); - expansionChunk->setTriangulationMinPoints(minNbObservationsForTriangulation); + expansionChunk->setTriangulationHandler(sfmTriangulationHandler); expansionChunk->setEnableDepthPrior(enableDepthPrior); expansionChunk->setIgnoreMultiviewOnPrior(ignoreMultiviewOnPrior); expansionChunk->setMinAngleTriangulation(minAngleForTriangulation); - expansionChunk->setPointFetcherHandler(pointFetcherHandler); expansionChunk->setWeakResectionSize(weakResectionSize); sfm::ExpansionPolicy::uptr expansionPolicy; diff --git a/src/software/utils/CMakeLists.txt b/src/software/utils/CMakeLists.txt index c6d9ed9161..b06f038b14 100644 --- a/src/software/utils/CMakeLists.txt +++ b/src/software/utils/CMakeLists.txt @@ -340,6 +340,7 @@ if (ALICEVISION_BUILD_SFM) aliceVision_sfm Boost::program_options Boost::json + Alembic::Alembic ) # SfM Survey Injecting diff --git a/src/software/utils/main_importAlembic.cpp b/src/software/utils/main_importAlembic.cpp index 073659c9f3..a00c6e0162 100644 --- a/src/software/utils/main_importAlembic.cpp +++ b/src/software/utils/main_importAlembic.cpp @@ -21,7 +21,7 @@ // 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 +#define ALICEVISION_SOFTWARE_VERSION_MINOR 1 using namespace aliceVision; namespace po = boost::program_options; @@ -31,25 +31,29 @@ int aliceVision_main(int argc, char** argv) // command-line parameters std::string abcFilename; std::string sfmDataOutputFilename; - std::string imagesDir; - std::string extension; + double frameRate = 24.0; + int imageWidth = 640; // clang-format off po::options_description requiredParams("Required parameters"); requiredParams.add_options() ("input,i", po::value(&abcFilename)->required(), "The external Alembic file to import.") - ("imagesDir", po::value(&imagesDir)->required(), - "Directory with images.") - ("extension", po::value(&extension)->required(), - "File extension for the images in the directory to be taken into account (should include the period, e.g. '.jpg').") ("output,o", po::value(&sfmDataOutputFilename)->required(), "SfMData file populated with the camera poses from the external Alembic file."); + + po::options_description optionalParams("Optional parameters"); + optionalParams.add_options() + ("imageWidth", po::value(&imageWidth)->default_value(imageWidth), + "Alembic does not export the camera resolutions. Setup the image width for all images, the height will depend on the sensor size ratio.") + ("framerate", po::value(&frameRate)->default_value(frameRate), + "Alembic frame rate to compute frame id from time."); // clang-format on CmdLine cmdline("AliceVision Alembic importer"); cmdline.add(requiredParams); + cmdline.add(optionalParams); if (!cmdline.execute(argc, argv)) { return EXIT_FAILURE; @@ -59,35 +63,12 @@ int aliceVision_main(int argc, char** argv) HardwareContext hwc = cmdline.getHardwareContext(); omp_set_num_threads(hwc.getMaxThreads()); - if (!std::filesystem::exists(imagesDir)) - { - ALICEVISION_LOG_ERROR("Images directory does not exist."); - return EXIT_FAILURE; - } - - if (!std::filesystem::is_directory(imagesDir)) - { - ALICEVISION_LOG_ERROR("Images directory value is not a directory."); - return EXIT_FAILURE; - } - - std::vector files; - for (const auto & item: std::filesystem::directory_iterator(imagesDir)) - { - const std::string itemExtension = item.path().extension().string(); - if (boost::algorithm::to_lower_copy(itemExtension) == extension) - { - files.push_back(std::filesystem::canonical(item.path()).string()); - } - } - std::sort(files.begin(), files.end()); - std::unique_ptr importer; try { - importer = std::make_unique(abcFilename); + importer = std::make_unique(abcFilename, frameRate, imageWidth); } catch(...) { @@ -96,7 +77,11 @@ int aliceVision_main(int argc, char** argv) } sfmData::SfMData sfmData; - importer->populateSfM(sfmData, files); + if (!importer->populateSfM(sfmData)) + { + ALICEVISION_LOG_ERROR("Failed to import Alembic file."); + return EXIT_FAILURE; + } if (!sfmDataIO::save(sfmData, sfmDataOutputFilename, sfmDataIO::ESfMData(sfmDataIO::ALL))) { diff --git a/src/software/utils/main_sfmPoseInjecting.cpp b/src/software/utils/main_sfmPoseInjecting.cpp index 9909dc3237..9193e11251 100644 --- a/src/software/utils/main_sfmPoseInjecting.cpp +++ b/src/software/utils/main_sfmPoseInjecting.cpp @@ -13,10 +13,13 @@ #include #include +#include +#include #include #include +#include #include // These constants define the current software version. @@ -171,13 +174,46 @@ bool getPosesFromJson(const std::string& posesFilename, ERotationFormat format, return true; } +/** + * @brief Get a set of poses from an alembic file (assumes the file format is ok). + * @param posesFilename the input ABC filename + * @param readPose the output poses vector + * @param frameRate the file framerate used to estimate frame id from samples timecodes. + * @return false if the process failed, true otherwise + */ +bool getPosesFromAlembic(const std::string& posesFilename, std::vector& readPoses, double frameRate) +{ + sfmDataIO::ExternalAlembicImporter importer(posesFilename, frameRate); + + sfmData::SfMData sfmData; + importer.populateSfM(sfmData); + + readPoses.clear(); + for (const auto &[id, v]: sfmData.getViews().valueRange()) + { + if (!sfmData.isPoseAndIntrinsicDefined(id)) + { + continue; + } + + PoseInput pi; + pi.frameId = v.getFrameId(); + pi.T = sfmData.getAbsolutePose(v.getPoseId()).getTransform().getHomogeneous(); + + readPoses.push_back(pi); + } + + return true; +} + int aliceVision_main(int argc, char** argv) { // command-line parameters std::string sfmDataFilename; std::string sfmDataOutputFilename; std::string posesFilename; - ERotationFormat format; + ERotationFormat format = ERotationFormat::EulerZXY; + double frameRate = 24.0; // clang-format off po::options_description requiredParams("Required parameters"); @@ -185,14 +221,16 @@ int aliceVision_main(int argc, char** argv) ("input,i", po::value(&sfmDataFilename)->required(), "Input SfMData file.") ("output,o", po::value(&sfmDataOutputFilename)->required(), - "SfMData output file with the injected poses.") - ("rotationFormat,r", po::value(&format)->required(), - "Rotation format for the input poses: EulerZXY."); + "SfMData output file with the injected poses."); po::options_description optionalParams("Optional parameters"); optionalParams.add_options() + ("rotationFormat,r", po::value(&format)->default_value(format), + "Rotation format for the input poses: EulerZXY.") + ("framerate", po::value(&frameRate)->default_value(frameRate), + "Alembic frame rate to compute frame id from time.") ("posesFilename,p", po::value(&posesFilename)->default_value(posesFilename), - "JSON file containing the poses to inject."); + "File containing the poses to inject."); // clang-format on CmdLine cmdline("AliceVision SfM Pose injecting"); @@ -224,9 +262,30 @@ int aliceVision_main(int argc, char** argv) } std::vector readPoses; - if (!getPosesFromJson(posesFilename, format, readPoses)) + + std::string ext = std::filesystem::path(posesFilename).extension().string(); + boost::to_lower(ext); + if (ext == ".json") + { + ALICEVISION_LOG_INFO("Json file input detected."); + if (!getPosesFromJson(posesFilename, format, readPoses)) + { + ALICEVISION_LOG_ERROR("Cannot read the poses"); + return EXIT_FAILURE; + } + } + else if (ext == ".abc") + { + ALICEVISION_LOG_INFO("Alembic file input detected."); + if (!getPosesFromAlembic(posesFilename, readPoses, frameRate)) + { + ALICEVISION_LOG_ERROR("Cannot read the poses"); + return EXIT_FAILURE; + } + } + else { - ALICEVISION_LOG_ERROR("Cannot read the poses"); + ALICEVISION_LOG_ERROR("Input file format not recognized."); return EXIT_FAILURE; } @@ -237,6 +296,7 @@ int aliceVision_main(int argc, char** argv) { if (pview->getFrameId() == rpose.frameId) { + ALICEVISION_LOG_INFO("Assigning view " << id << "(frame " << rpose.frameId << ")"); geometry::Pose3 pose(rpose.T); sfmData::CameraPose cpose(pose, false); cpose.setRemovable(false);