diff --git a/src/aliceVision/colorHarmonization/CommonDataByPair.hpp b/src/aliceVision/colorHarmonization/CommonDataByPair.hpp index 3ea30fb5d5..fbb7013d2b 100644 --- a/src/aliceVision/colorHarmonization/CommonDataByPair.hpp +++ b/src/aliceVision/colorHarmonization/CommonDataByPair.hpp @@ -47,7 +47,7 @@ class CommonDataByPair */ template< typename ImageType > static void computeHisto( - Histogram< double > & histo, + utils::Histogram< double > & histo, const image::Image< unsigned char >& mask, std::size_t channelIndex, const image::Image< ImageType >& image ) diff --git a/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp b/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp index a40175dd5d..c816caf9c1 100644 --- a/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp +++ b/src/aliceVision/colorHarmonization/gainOffsetConstraintBuilder_test.cpp @@ -48,7 +48,7 @@ class normal_distribution BOOST_AUTO_TEST_CASE(ColorHarmonisation_Simple_offset) { - Histogram< double > histo( 0, 256, 255); + utils::Histogram< double > histo( 0, 256, 255); for (std::size_t i=0; i < 6000; i++) { histo.Add(normal_distribution(127, 10)()); @@ -106,8 +106,8 @@ BOOST_AUTO_TEST_CASE(ColorHarmonisation_Simple_offset) { BOOST_AUTO_TEST_CASE(ColorHarmonisation_Offset_gain) { - Histogram< double > histo_ref( 0, 256, 255); - Histogram< double > histo_offset_gain( 0, 256, 255); + utils::Histogram< double > histo_ref( 0, 256, 255); + utils::Histogram< double > histo_offset_gain( 0, 256, 255); const double GAIN = 3.0; const double OFFSET = 160; //const double GAIN = 2.0; diff --git a/src/aliceVision/dataio/ImageFeed.cpp b/src/aliceVision/dataio/ImageFeed.cpp index 6d03d3c97b..06d6cb00e3 100644 --- a/src/aliceVision/dataio/ImageFeed.cpp +++ b/src/aliceVision/dataio/ImageFeed.cpp @@ -225,7 +225,7 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin folder = bf::path(imagePath).parent_path().string(); ALICEVISION_LOG_DEBUG("filePattern: " << filePattern); std::string regexStr = filePattern; - re = filterToRegex(regexStr); + re = utils::filterToRegex(regexStr); } else { diff --git a/src/aliceVision/image/convertion.hpp b/src/aliceVision/image/convertion.hpp index 0828a19a7b..68e2b07493 100644 --- a/src/aliceVision/image/convertion.hpp +++ b/src/aliceVision/image/convertion.hpp @@ -13,50 +13,98 @@ namespace aliceVision{ namespace image { -template // The factor comes from http://www.easyrgb.com/ // RGB to XYZ : Y is the luminance channel // var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722 -inline T Rgb2Gray(const T r,const T g, const T b) { - return r * 0.2126 + g * 0.7152 + b * 0.0722; +inline float Rgb2GrayLinear(const float r, const float g, const float b) +{ + return r * 0.2126f + g * 0.7152f + b * 0.0722f; } -template -inline void Convert(const Tin& valin, Tout& out) { +inline float Rgb2Gray(const float r, const float g, const float b) +{ + return r * 0.299f + g * 0.587f + b * 0.114f; +} + +template +inline void Convert(const Tin& valin, Tout& out) +{ out = static_cast(valin); } template<> -inline void Convert( - const unsigned char& valin, RGBColor& valOut) +inline void Convert(const unsigned char& valin, RGBColor& valOut) { valOut = RGBColor(valin); } template<> -inline void Convert( - const RGBColor& valin, unsigned char& valOut) +inline void Convert(const RGBColor& valin, unsigned char& valOut) +{ + valOut = static_cast(Rgb2GrayLinear(valin.r(), valin.g(), valin.b())); +} + +template<> +inline void Convert(const unsigned char& valin, RGBAColor& valOut) +{ + valOut = RGBAColor(valin, valin, valin, 255); +} + +template<> +inline void Convert(const RGBAColor& valin, unsigned char& valOut) +{ + valOut = static_cast((valin.a() / 255.f) * Rgb2GrayLinear(valin.r(), valin.g(), valin.b())); +} + +template<> +inline void Convert(const RGBAColor& valin, RGBColor& valOut) +{ + valOut = RGBColor(static_cast((valin.a() / 255.f) * valin.r()), + static_cast((valin.a() / 255.f) * valin.g()), + static_cast((valin.a() / 255.f) * valin.b())); +} + +template<> +inline void Convert(const RGBColor& valin, RGBAColor& valOut) +{ + valOut = RGBAColor(valin.r(), valin.g(), valin.b(), static_cast(255)); +} + +template<> +inline void Convert(const float& valin, RGBfColor& valOut) +{ + valOut = RGBfColor(valin); +} + +template<> +inline void Convert(const RGBfColor& valin, float& valOut) +{ + valOut = Rgb2GrayLinear(valin.r(), valin.g(), valin.b()); +} + +template<> +inline void Convert(const float& valin, RGBAfColor& valOut) +{ + valOut = RGBAfColor(valin); +} + +template<> +inline void Convert(const RGBAfColor& valin, float& valOut) { - valOut = static_cast(0.3 * valin.r() + 0.59 * valin.g() + 0.11 * valin.b()); + valOut = Rgb2GrayLinear(valin.a() * valin.r(), valin.a() * valin.g(), valin.a() * valin.b()); } template<> -inline void Convert( - const RGBAColor& valin, unsigned char& valOut) +inline void Convert(const RGBAfColor& valin, RGBfColor& valOut) { - valOut = static_cast( - (valin.a()/255.f) * - (0.3 * valin.r() + 0.59 * valin.g() + 0.11 * valin.b())); + valOut = RGBfColor(valin.a() * valin.r(), valin.a() * valin.g(), valin.a() * valin.b()); } template<> -inline void Convert( - const RGBAColor& valin, RGBColor& valOut) +inline void Convert(const RGBfColor& valin, RGBAfColor& valOut) { - valOut = RGBColor( - static_cast ((valin.a()/255.f) * valin.r()), - static_cast ((valin.a()/255.f) * valin.g()), - static_cast ((valin.a()/255.f) * valin.b())); + // alpha 1 by default + valOut = RGBAfColor(valin.r(), valin.g(), valin.b()); } template diff --git a/src/aliceVision/mvsUtils/MultiViewParams.cpp b/src/aliceVision/mvsUtils/MultiViewParams.cpp index 04f2217198..8489798190 100644 --- a/src/aliceVision/mvsUtils/MultiViewParams.cpp +++ b/src/aliceVision/mvsUtils/MultiViewParams.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -73,17 +74,26 @@ MultiViewParams::MultiViewParams(const sfmData::SfMData& sfmData, else if(_imagesFolder != "/" && !_imagesFolder.empty() && fs::is_directory(_imagesFolder) && !fs::is_empty(_imagesFolder)) { // find folder file extension - const fs::recursive_directory_iterator end; - const auto findIt = std::find_if(fs::recursive_directory_iterator(_imagesFolder), end, - [&view](const fs::directory_entry& e) { - return (e.path().stem() == std::to_string(view.getViewId()) && - (imageIO::isSupportedUndistortFormat(e.path().extension().string()))); - }); - - if(findIt == end) - throw std::runtime_error("Cannot find image file " + std::to_string(view.getViewId()) + " in folder " + _imagesFolder); + std::vector paths = utils::getFilesPathsFromFolder(_imagesFolder, + [&view](const fs::path& path) + { + return (path.stem() == std::to_string(view.getViewId()) && (imageIO::isSupportedUndistortFormat(path.extension().string()))); + } + ); + + // if path was not found + if(paths.empty()) + { + throw std::runtime_error("Cannot find image file coresponding to the view '" + + std::to_string(view.getViewId()) + "' in folder '" + _imagesFolder + "'."); + } + else if(paths.size() > 1) + { + throw std::runtime_error("Ambiguous case: Multiple image file found for the view '" + + std::to_string(view.getViewId()) + "' in folder '" + _imagesFolder + "'."); + } - path = _imagesFolder + std::to_string(view.getViewId()) + findIt->path().extension().string(); + path = _imagesFolder + std::to_string(view.getViewId()) + fs::path(paths[0]).extension().string(); } dimensions.emplace(view.getWidth(), view.getHeight()); diff --git a/src/aliceVision/sfm/generateReport.cpp b/src/aliceVision/sfm/generateReport.cpp index cea7ea7af1..f1fbe2184a 100644 --- a/src/aliceVision/sfm/generateReport.cpp +++ b/src/aliceVision/sfm/generateReport.cpp @@ -152,7 +152,7 @@ bool generateSfMReport(const sfmData::SfMData& sfmData, htmlDocStream.pushInfo(os.str()); const double maxRange = *max_element(residuals.begin(), residuals.end()); - Histogram histo(0.0, maxRange, 100); + utils::Histogram histo(0.0, maxRange, 100); histo.Add(residuals.begin(), residuals.end()); svg::svgHisto svg_Histo; diff --git a/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp b/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp index 0a5cfb4018..c7b278d1bb 100644 --- a/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp +++ b/src/aliceVision/sfm/pipeline/global/GlobalSfMRotationAveragingSolver.cpp @@ -245,7 +245,7 @@ void GlobalSfMRotationAveragingSolver::TripletRotationRejection( if (!vec_errToIdentityPerTriplet.empty()) { - Histogram histo(0.0f, *max_element(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()), 20); + utils::Histogram histo(0.0f, *max_element(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()), 20); histo.Add(vec_errToIdentityPerTriplet.begin(), vec_errToIdentityPerTriplet.end()); ALICEVISION_LOG_DEBUG(histo.ToString()); } diff --git a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp index 4fbe9e7cc8..1204d079a7 100644 --- a/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp +++ b/src/aliceVision/sfm/pipeline/sequential/ReconstructionEngine_sequentialSfM.cpp @@ -737,7 +737,7 @@ void ReconstructionEngine_sequentialSfM::exportStatistics(double reconstructionT } // residual histogram - Histogram residualHistogram; + utils::Histogram residualHistogram; { BoxStats residualStats; computeResidualsHistogram(_sfmData, residualStats, &residualHistogram); @@ -753,7 +753,7 @@ void ReconstructionEngine_sequentialSfM::exportStatistics(double reconstructionT } // tracks lengths histogram - Histogram observationsLengthHistogram; + utils::Histogram observationsLengthHistogram; { BoxStats observationsLengthStats; int overallNbObservations = 0; @@ -765,7 +765,7 @@ void ReconstructionEngine_sequentialSfM::exportStatistics(double reconstructionT } // nb landmarks per view histogram - Histogram landmarksPerViewHistogram; + utils::Histogram landmarksPerViewHistogram; { BoxStats landmarksPerViewStats; computeLandmarksPerViewHistogram(_sfmData, landmarksPerViewStats, &landmarksPerViewHistogram); @@ -1150,7 +1150,7 @@ bool ReconstructionEngine_sequentialSfM::makeInitialPair3D(const Pair& currentPa } // save outlier residual information - Histogram residualHistogram; + utils::Histogram residualHistogram; BoxStats residualStats; computeResidualsHistogram(_sfmData, residualStats, &residualHistogram); ALICEVISION_LOG_DEBUG("MSE Residual initial pair inlier: " << residualStats.mean); diff --git a/src/aliceVision/sfm/sfmStatistics.cpp b/src/aliceVision/sfm/sfmStatistics.cpp index b140296cdc..000e4a42e6 100644 --- a/src/aliceVision/sfm/sfmStatistics.cpp +++ b/src/aliceVision/sfm/sfmStatistics.cpp @@ -16,14 +16,14 @@ namespace aliceVision { namespace sfm { -void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, Histogram* out_histogram, const std::set& specificViews) +void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* out_histogram, const std::set& specificViews) { { // Init output params out_stats = BoxStats(); if (out_histogram) { - *out_histogram = Histogram(); + *out_histogram = utils::Histogram(); } } if (sfmData.getLandmarks().empty()) @@ -61,20 +61,20 @@ void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats if (out_histogram) { - *out_histogram = Histogram(0.0, std::ceil(out_stats.max), std::ceil(out_stats.max)*2); + *out_histogram = utils::Histogram(0.0, std::ceil(out_stats.max), std::ceil(out_stats.max)*2); out_histogram->Add(vec_residuals.begin(), vec_residuals.end()); } } -void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, int& overallNbObservations, Histogram* out_histogram, const std::set& specificViews) +void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, int& overallNbObservations, utils::Histogram* out_histogram, const std::set& specificViews) { { // Init output params out_stats = BoxStats(); if (out_histogram) { - *out_histogram = Histogram(); + *out_histogram = utils::Histogram(); } } if (sfmData.getLandmarks().empty()) @@ -116,19 +116,19 @@ void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxSta if (out_histogram) { - *out_histogram = Histogram(out_stats.min, out_stats.max + 1, out_stats.max - out_stats.min + 1); + *out_histogram = utils::Histogram(out_stats.min, out_stats.max + 1, out_stats.max - out_stats.min + 1); out_histogram->Add(nbObservations.begin(), nbObservations.end()); } } -void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, Histogram* out_histogram) +void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* out_histogram) { { // Init output params out_stats = BoxStats(); if (out_histogram) { - *out_histogram = Histogram(); + *out_histogram = utils::Histogram(); } } if(sfmData.getLandmarks().empty()) @@ -161,7 +161,7 @@ void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats< if (out_histogram) { //*out_histogram = Histogram(0, sfmData.getViews().size(), sfmData.getViews().size()); - *out_histogram = Histogram(out_stats.min, (out_stats.max + 1), 10); + *out_histogram = utils::Histogram(out_stats.min, (out_stats.max + 1), 10); out_histogram->Add(nbLandmarksPerViewVec.begin(), nbLandmarksPerViewVec.end()); } } @@ -258,14 +258,14 @@ void computeFeatMatchPerView(const sfmData::SfMData& sfmData, std::vector& out_stats, Histogram* out_histogram, const std::set& specificViews) +void computeScaleHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* out_histogram, const std::set& specificViews) { { // Init output params out_stats = BoxStats(); if (out_histogram) { - *out_histogram = Histogram(); + *out_histogram = utils::Histogram(); } } if(sfmData.getLandmarks().empty()) @@ -297,7 +297,7 @@ void computeScaleHistogram(const sfmData::SfMData& sfmData, BoxStats& ou if (out_histogram) { size_t maxValue = std::ceil(out_stats.max); - *out_histogram = Histogram(0.0, double(maxValue), maxValue +1); + *out_histogram = utils::Histogram(0.0, double(maxValue), maxValue +1); out_histogram->Add(vec_scaleObservations.begin(), vec_scaleObservations.end()); } } @@ -348,7 +348,7 @@ void computeResidualsPerView(const sfmData::SfMData& sfmData, int& nbViews, std: continue; const std::vector& residuals = it->second; BoxStats residualStats(residuals.begin(), residuals.end()); - Histogram residual_histogram = Histogram(residualStats.min, residualStats.max+1, residualStats.max - residualStats.min +1); + utils::Histogram residual_histogram = utils::Histogram(residualStats.min, residualStats.max+1, residualStats.max - residualStats.min +1); residual_histogram.Add(residuals.begin(), residuals.end()); nbResidualsPerViewMin[viewIdx] = residualStats.min; @@ -399,7 +399,7 @@ void computeObservationsLengthsPerView(const sfmData::SfMData& sfmData, int& nbV const IndexT viewId = viewKeys[viewIdx]; const std::vector& nbObservations = observationLengthsPerView[viewId]; BoxStats observationsLengthsStats(nbObservations.begin(), nbObservations.end()); - Histogram observationsLengths_histogram(observationsLengthsStats.min, observationsLengthsStats.max + 1, observationsLengthsStats.max - observationsLengthsStats.min + 1); + utils::Histogram observationsLengths_histogram(observationsLengthsStats.min, observationsLengthsStats.max + 1, observationsLengthsStats.max - observationsLengthsStats.min + 1); observationsLengths_histogram.Add(nbObservations.begin(), nbObservations.end()); nbObservationsLengthsPerViewMin[viewIdx] = observationsLengthsStats.min; diff --git a/src/aliceVision/sfm/sfmStatistics.hpp b/src/aliceVision/sfm/sfmStatistics.hpp index f80bdfe05e..6483137ea1 100644 --- a/src/aliceVision/sfm/sfmStatistics.hpp +++ b/src/aliceVision/sfm/sfmStatistics.hpp @@ -25,7 +25,7 @@ namespace sfm { * @param[out] out_histogram : histogram of the number of points for each residual value (0-4 px) * @param[in] specificViews: Limit stats to specific views. If empty, compute stats for all views. */ -void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, Histogram* out_histogram, const std::set& specificViews = std::set()); +void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* out_histogram, const std::set& specificViews = std::set()); /** * @brief Compute histogram of observations lengths @@ -34,7 +34,7 @@ void computeResidualsHistogram(const sfmData::SfMData& sfmData, BoxStats * @param[out] observationsLengthHistogram : histogram of the number of points for each observation length * @param[in] specificViews: Limit stats to specific views. If empty, compute stats for all views */ -void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, int& overallNbObservations, Histogram* observationsLengthHistogram, const std::set& specificViews = std::set()); +void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, int& overallNbObservations, utils::Histogram* observationsLengthHistogram, const std::set& specificViews = std::set()); /** * @brief Compute histogram of the number of landmarks per view @@ -42,7 +42,7 @@ void computeObservationsLengthsHistogram(const sfmData::SfMData& sfmData, BoxSta * @param[out] out_stats: stats containing the landmarks * @param[out] landmarksPerViewHistogram: histogram of the number of landmarks for each view */ -void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, Histogram* landmarksPerViewHistogram); +void computeLandmarksPerViewHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* landmarksPerViewHistogram); /** * @brief Compute landmarks per view @@ -66,7 +66,7 @@ void computeFeatMatchPerView(const sfmData::SfMData& sfmData, std::vector& out_stats, Histogram* scaleHistogram, const std::set& specificViews = std::set()); +void computeScaleHistogram(const sfmData::SfMData& sfmData, BoxStats& out_stats, utils::Histogram* scaleHistogram, const std::set& specificViews = std::set()); /** * @brief Compute different stats of residuals per view diff --git a/src/aliceVision/sfmDataIO/viewIO.cpp b/src/aliceVision/sfmDataIO/viewIO.cpp index 7a55f4733f..4acce0433f 100644 --- a/src/aliceVision/sfmDataIO/viewIO.cpp +++ b/src/aliceVision/sfmDataIO/viewIO.cpp @@ -10,6 +10,7 @@ #include #include #include +#include "aliceVision/utils/filesIO.hpp" #include #include @@ -270,33 +271,12 @@ std::shared_ptr getViewIntrinsic( return intrinsic; } -boost::filesystem::path viewPathFromFolders(const sfmData::View& view, const std::vector& folders) +std::vector viewPathsFromFolders(const sfmData::View& view, const std::vector& folders) { - boost::filesystem::path path = ""; - for(const std::string& folder : folders) - { - path = viewPathFromFolder(view, folder); - if(!path.empty()) - { - break; - } - } - - return path; -} - -boost::filesystem::path viewPathFromFolder(const sfmData::View& view, const std::string& folder) -{ - const fs::recursive_directory_iterator end; - const auto findIt = std::find_if(fs::recursive_directory_iterator(folder), end, - [&view](const fs::directory_entry& e) - { - const fs::path stem = e.path().stem(); - return (stem == std::to_string(view.getViewId()) || stem == fs::path(view.getImagePath()).stem()); - } - ); - - return (findIt != end) ? findIt->path() : ""; + return utils::getFilesPathsFromFolders(folders, [&view](const boost::filesystem::path& path) { + const boost::filesystem::path stem = path.stem(); + return (stem == std::to_string(view.getViewId()) || stem == fs::path(view.getImagePath()).stem()); + }); } } // namespace sfmDataIO diff --git a/src/aliceVision/sfmDataIO/viewIO.hpp b/src/aliceVision/sfmDataIO/viewIO.hpp index ad0448b4c1..6658f03496 100644 --- a/src/aliceVision/sfmDataIO/viewIO.hpp +++ b/src/aliceVision/sfmDataIO/viewIO.hpp @@ -83,22 +83,13 @@ std::shared_ptr getViewIntrinsic( double defaultPPx = -1, double defaultPPy = -1); /** - * @brief Allows you to retrieve the image file path corresponding to a view by searching through a list of folders. - * Filename must be the same or equal to the image uid. - * @param[in] the view - * @param[in] the folder list - * @return the path to the corresponding view if found in the folders, otherwise returns an empty path (""). - */ -boost::filesystem::path viewPathFromFolders(const sfmData::View& view, const std::vector& folders); - -/** - * @brief Allows you to retrieve the image file path corresponding to a view by searching in a folder. - Filename must be the same or equal to the image uid. - * @param[in] the view - * @param[in] the folder path - * @return the path to the corresponding view if found in the folder, otherwise returns an empty path (""). - */ -boost::filesystem::path viewPathFromFolder(const sfmData::View& view, const std::string& folder); +* @brief Allows you to retrieve the files paths corresponding to a view by searching through a list of folders. +* Filename must be the same or equal to the viewId. +* @param[in] the view +* @param[in] the folder list +* @return the list of paths to the corresponding view if found in the folders, otherwise returns an empty list. +*/ +std::vector viewPathsFromFolders(const sfmData::View& view, const std::vector& folders); } // namespace sfmDataIO } // namespace aliceVision diff --git a/src/aliceVision/utils/CMakeLists.txt b/src/aliceVision/utils/CMakeLists.txt index 203bbac973..e7ffd4220c 100644 --- a/src/aliceVision/utils/CMakeLists.txt +++ b/src/aliceVision/utils/CMakeLists.txt @@ -2,6 +2,7 @@ set(utils_files_headers Histogram.hpp regexFilter.hpp + filesIO.hpp ) alicevision_add_interface(aliceVision_utils diff --git a/src/aliceVision/utils/Histogram.hpp b/src/aliceVision/utils/Histogram.hpp index af89f27e03..651300d3d6 100644 --- a/src/aliceVision/utils/Histogram.hpp +++ b/src/aliceVision/utils/Histogram.hpp @@ -13,7 +13,7 @@ #include namespace aliceVision { - +namespace utils { // A histogram class. // The Histogram object can keep a tally of values // within a range, the range is arranged into some @@ -139,4 +139,5 @@ class Histogram size_t overflow, underflow; //count under/over flow }; -} +} // namespace utils +} // namespace aliceVision diff --git a/src/aliceVision/utils/filesIO.hpp b/src/aliceVision/utils/filesIO.hpp new file mode 100644 index 0000000000..d1f7fd5351 --- /dev/null +++ b/src/aliceVision/utils/filesIO.hpp @@ -0,0 +1,65 @@ +// This file is part of the AliceVision project. +// Copyright (c) 2020 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 "boost/filesystem.hpp" + +#include +#include +#include + +namespace fs = boost::filesystem; + +namespace aliceVision { +namespace utils { +/** + * @brief Allows to retrieve the files paths that validates a specific predicate by searching in a folder. + * @param[in] the folders path + * @param[in] the predicate + * @return the paths list to the corresponding files if they validate the predicate, otherwise it returns an empty list. + */ +inline std::vector getFilesPathsFromFolder(const std::string& folder, + const std::function& predicate) +{ + // Get all files paths in folder + std::vector paths; + + // If the path isn't a folder path + if(!fs::is_directory(folder)) + throw std::invalid_argument("The path '" + folder + "' is not a valid folder path."); + + for(const auto& pathIt : fs::directory_iterator(folder)) + { + const fs::path path = pathIt.path(); + if(is_regular_file(path) && predicate(path)) + paths.push_back(path.generic_string()); + } + + return paths; +} + +/** + * @brief Allows to retrieve the files paths that validates a specific predicate by searching through a list of folders. + * @param[in] the folders paths list + * @param[in] the predicate + * @return the paths list to the corresponding files if they validate the predicate, otherwise it returns an empty list. + */ +inline std::vector getFilesPathsFromFolders(const std::vector& folders, + const std::function& predicate) +{ + std::vector paths; + for(const std::string& folder : folders) + { + const std::vector subPaths = getFilesPathsFromFolder(folder, predicate); + paths.insert(paths.end(), subPaths.begin(), subPaths.end()); + } + + return paths; +} + +} // namespace utils +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/utils/regexFilter.hpp b/src/aliceVision/utils/regexFilter.hpp index 628f3d08b9..2129e86b19 100644 --- a/src/aliceVision/utils/regexFilter.hpp +++ b/src/aliceVision/utils/regexFilter.hpp @@ -13,12 +13,15 @@ #include #include +namespace aliceVision { +namespace utils +{ /** * @brief Create regex from a string filter (supported regex: '#' matches a single digit, '@' one or more digits, '?' one character and '*' zero or more) * @param[in] str - Input string filter * @return the resulting regex */ -std::regex filterToRegex(std::string str) +inline std::regex filterToRegex(std::string str) { boost::replace_all(str, ".", "\\."); // escape "." boost::replace_all(str, "@", "[0-9]+"); // one @ correspond to one or more digits @@ -43,10 +46,13 @@ std::regex filterToRegex(std::string str) * @param[in] - Vector that contains strings * @param[in] filter - String represent the filter to be applied */ -void filterStrings(std::vector& strVec, const std::string& filter) +inline void filterStrings(std::vector& strVec, const std::string& filter) { const std::regex regex = filterToRegex(filter); const auto begin = strVec.begin(); const auto end = strVec.end(); strVec.erase(std::remove_if(begin, end, [®ex](const std::string& str) { return !std::regex_match(str, regex); }), end); -} \ No newline at end of file +} + +} // namespace utils +} // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/utils/regexFilter_test.cpp b/src/aliceVision/utils/regexFilter_test.cpp index 72d73745c5..d7fadade7c 100644 --- a/src/aliceVision/utils/regexFilter_test.cpp +++ b/src/aliceVision/utils/regexFilter_test.cpp @@ -14,6 +14,7 @@ #include #include +using namespace aliceVision; BOOST_AUTO_TEST_CASE(utils_regexMatching) { @@ -31,44 +32,44 @@ BOOST_AUTO_TEST_CASE(utils_regexMatching) { std::vector test; test.assign(exemplePaths.begin(), exemplePaths.end()); - filterStrings(test, "C:/Users/*####.jpg"); + utils::filterStrings(test, "C:/Users/*####.jpg"); BOOST_CHECK_EQUAL(test.size(), 12); } { std::vector test; test.assign(exemplePaths.begin(), exemplePaths.end()); - filterStrings(test, "C:/Users/*000#.jpg"); + utils::filterStrings(test, "C:/Users/*000#.jpg"); BOOST_CHECK_EQUAL(test.size(), 9); } { std::vector test; test.assign(exemplePaths.begin(), exemplePaths.end()); - filterStrings(test, "C:/Users/*000#.jpg"); + utils::filterStrings(test, "C:/Users/*000#.jpg"); BOOST_CHECK_EQUAL(test.size(), 9); } { std::vector test; test.assign(exemplePaths.begin(), exemplePaths.end()); const std::vector testDesired{"C:/Users/test00.exr", "C:/Users/test01.exr", "C:/Users/test02.exr", "C:/Users/test03.exr"}; - filterStrings(test, "C:/Users/*#?.exr"); + utils::filterStrings(test, "C:/Users/*#?.exr"); BOOST_CHECK_EQUAL(test, testDesired); } { std::vector test; test.assign(exemplePaths.begin(), exemplePaths.end()); - filterStrings(test, "C:/Users/*_####.???"); + utils::filterStrings(test, "C:/Users/*_####.???"); BOOST_CHECK_EQUAL(test.size(), 12); } { std::vector test; test.assign(exemplePaths.begin(), exemplePaths.end()); - filterStrings(test, "C:/Users/#####.???"); + utils::filterStrings(test, "C:/Users/#####.???"); BOOST_CHECK_EQUAL(test.size(), 0); } { std::vector test; test.assign(exemplePaths.begin(), exemplePaths.end()); - filterStrings(test, "C:/Users/@.???"); + utils::filterStrings(test, "C:/Users/@.???"); BOOST_CHECK_EQUAL(test.size(), 4); } } \ No newline at end of file diff --git a/src/software/convert/main_convertSfMFormat.cpp b/src/software/convert/main_convertSfMFormat.cpp index 672ec8f291..a8d1d0f7af 100644 --- a/src/software/convert/main_convertSfMFormat.cpp +++ b/src/software/convert/main_convertSfMFormat.cpp @@ -150,7 +150,7 @@ int aliceVision_main(int argc, char **argv) imageWhiteRegexList.reserve(imageWhiteList.size()); for (const std::string& exp : imageWhiteList) { - imageWhiteRegexList.emplace_back(filterToRegex(exp)); + imageWhiteRegexList.emplace_back(utils::filterToRegex(exp)); } std::vector viewsToRemove; diff --git a/src/software/export/main_exportAnimatedCamera.cpp b/src/software/export/main_exportAnimatedCamera.cpp index c3048c308b..dc6cf10120 100644 --- a/src/software/export/main_exportAnimatedCamera.cpp +++ b/src/software/export/main_exportAnimatedCamera.cpp @@ -143,7 +143,7 @@ int aliceVision_main(int argc, char** argv) // regex filter if(!viewFilter.empty()) { - const std::regex regexFilter = filterToRegex(viewFilter); + const std::regex regexFilter = utils::filterToRegex(viewFilter); if(!std::regex_match(view.getImagePath(), regexFilter)) continue; } diff --git a/src/software/pipeline/main_prepareDenseScene.cpp b/src/software/pipeline/main_prepareDenseScene.cpp index a92930bbd7..65f56bd2ff 100644 --- a/src/software/pipeline/main_prepareDenseScene.cpp +++ b/src/software/pipeline/main_prepareDenseScene.cpp @@ -177,13 +177,20 @@ bool prepareDenseScene(const SfMData& sfmData, { if(!imagesFolders.empty()) { - const fs::path path = viewPathFromFolders(*view, imagesFolders); + std::vector paths = sfmDataIO::viewPathsFromFolders(*view, imagesFolders); // if path was not found - if(path.empty()) - throw std::runtime_error("Cannot find view " + std::to_string(view->getViewId()) + " image file in given folder(s)"); - - srcImage = path.string(); + if(paths.empty()) + { + throw std::runtime_error("Cannot find view '" + std::to_string(view->getViewId()) + "' image file in given folder(s)"); + } + else if(paths.size() > 1) + { + throw std::runtime_error( "Ambiguous case: Multiple source image files found in given folder(s) for the view '" + + std::to_string(view->getViewId()) + "'."); + } + + srcImage = paths[0]; } const std::string dstColorImage = (fs::path(outFolder) / (baseFilename + "." + image::EImageFileType_enumToString(outputFileType))).string(); const IntrinsicBase* cam = iterIntrinsic->second.get(); diff --git a/src/software/utils/main_imageProcessing.cpp b/src/software/utils/main_imageProcessing.cpp index 660011581b..7961cbafa9 100644 --- a/src/software/utils/main_imageProcessing.cpp +++ b/src/software/utils/main_imageProcessing.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -32,28 +33,227 @@ using namespace aliceVision; namespace po = boost::program_options; namespace fs = boost::filesystem; +struct SharpenParams +{ + bool enabled; + int width; + float contrast; + float threshold; +}; + +std::istream& operator>>(std::istream& in, SharpenParams& sParams) +{ + std::string token; + in >> token; + std::vector splitParams; + boost::split(splitParams, token, boost::algorithm::is_any_of(":")); + if(splitParams.size() != 4) + throw std::invalid_argument("Failed to parse SharpenParams from: " + token); + sParams.enabled = boost::to_lower_copy(splitParams[0]) == "true"; + sParams.width = boost::lexical_cast(splitParams[1]); + sParams.contrast = boost::lexical_cast(splitParams[2]); + sParams.threshold = boost::lexical_cast(splitParams[3]); + + return in; +} + +inline std::ostream& operator<<(std::ostream& os, const SharpenParams& sParams) +{ + os << sParams.enabled << ":" << sParams.width << ":" << sParams.contrast << ":"<< sParams.threshold; + return os; +} + +struct BilateralFilterParams +{ + bool enabled; + int distance; + float sigmaColor; + float sigmaSpace; +}; + +std::istream& operator>>(std::istream& in, BilateralFilterParams& bfParams) +{ + std::string token; + in >> token; + std::vector splitParams; + boost::split(splitParams, token, boost::algorithm::is_any_of(":")); + if(splitParams.size() != 4) + throw std::invalid_argument("Failed to parse BilateralFilterParams from: " + token); + bfParams.enabled = boost::to_lower_copy(splitParams[0]) == "true"; + bfParams.distance = boost::lexical_cast(splitParams[1]); + bfParams.sigmaColor = boost::lexical_cast(splitParams[2]); + bfParams.sigmaSpace = boost::lexical_cast(splitParams[3]); + + return in; +} + +inline std::ostream& operator<<(std::ostream& os, const BilateralFilterParams& bfParams) +{ + os << bfParams.enabled << ":" << bfParams.distance << ":" << bfParams.sigmaColor << ":" << bfParams.sigmaSpace; + return os; +} + +struct ClaheFilterParams +{ + bool enabled; + float clipLimit; + int tileGridSize; +}; + +std::istream& operator>>(std::istream& in, ClaheFilterParams& cfParams) +{ + std::string token; + in >> token; + std::vector splitParams; + boost::split(splitParams, token, boost::algorithm::is_any_of(":")); + if(splitParams.size() != 3) + throw std::invalid_argument("Failed to parse ClaheFilterParams from: " + token); + cfParams.enabled = boost::to_lower_copy(splitParams[0]) == "true"; + cfParams.clipLimit = boost::lexical_cast(splitParams[1]); + cfParams.tileGridSize = boost::lexical_cast(splitParams[2]); + + return in; +} + +inline std::ostream& operator<<(std::ostream& os, const ClaheFilterParams& cfParams) +{ + os << cfParams.enabled << ":" << cfParams.clipLimit << ":" << cfParams.tileGridSize; + return os; +} + +enum class ENoiseMethod { uniform, gaussian, salt }; + +inline std::string ENoiseMethod_enumToString(ENoiseMethod noiseMethod) +{ + switch(noiseMethod) + { + case ENoiseMethod::uniform: return "uniform"; + case ENoiseMethod::gaussian: return "gaussian"; + case ENoiseMethod::salt: return "salt"; + } + throw std::invalid_argument("Invalid ENoiseMethod Enum"); +} + +inline ENoiseMethod ENoiseMethod_stringToEnum(std::string noiseMethod) +{ + boost::to_lower(noiseMethod); + if(noiseMethod == "uniform") return ENoiseMethod::uniform; + if(noiseMethod == "gaussian") return ENoiseMethod::gaussian; + if(noiseMethod == "salt") return ENoiseMethod::salt; + + throw std::invalid_argument("Unrecognized noise method '" + noiseMethod + "'"); +} + +struct NoiseFilterParams +{ + bool enabled; + ENoiseMethod method; + float A; + float B; + bool mono; +}; + +std::istream& operator>>(std::istream& in, NoiseFilterParams& nfParams) +{ + std::string token; + in >> token; + std::vector splitParams; + boost::split(splitParams, token, boost::algorithm::is_any_of(":")); + if(splitParams.size() != 5) + throw std::invalid_argument("Failed to parse NoiseFilterParams from: " + token); + nfParams.enabled = boost::to_lower_copy(splitParams[0]) == "true"; + nfParams.method = ENoiseMethod_stringToEnum(splitParams[1]); + nfParams.A = boost::lexical_cast(splitParams[2]); + nfParams.B = boost::lexical_cast(splitParams[3]); + nfParams.mono = boost::to_lower_copy(splitParams[4]) == "true"; + return in; +} + +inline std::ostream& operator<<(std::ostream& os, const NoiseFilterParams& nfParams) +{ + os << nfParams.enabled << ":" << ENoiseMethod_enumToString(nfParams.method) << ":" << nfParams.A << ":" << nfParams.B + << ":" << nfParams.mono; + return os; +} + +enum class EImageFormat { RGBA, RGB, Grayscale }; + +inline std::string EImageFormat_enumToString(EImageFormat imageFormat) +{ + switch(imageFormat) + { + case EImageFormat::RGBA: return "rgba"; + case EImageFormat::RGB: return "rgb"; + case EImageFormat::Grayscale: return "grayscale"; + } + throw std::invalid_argument("Invalid EImageFormat Enum"); +} + +inline EImageFormat EImageFormat_stringToEnum(std::string imageFormat) +{ + boost::to_lower(imageFormat); + if(imageFormat == "rgba") return EImageFormat::RGBA; + if(imageFormat == "rgb") return EImageFormat::RGB; + if(imageFormat == "grayscale") return EImageFormat::Grayscale; + + throw std::invalid_argument("Unrecognized image format '" + imageFormat + "'"); +} + +inline std::ostream& operator<<(std::ostream& os, EImageFormat e) +{ + return os << EImageFormat_enumToString(e); +} + +inline std::istream& operator>>(std::istream& in, EImageFormat& e) +{ + std::string token; + in >> token; + e = EImageFormat_stringToEnum(token); + return in; +} + struct ProcessingParams { bool reconstructedViewsOnly = false; bool keepImageFilename = false; bool exposureCompensation = false; + EImageFormat outputFormat = EImageFormat::RGBA; float scaleFactor = 1.0f; float contrast = 1.0f; int medianFilter = 0; bool fillHoles = false; - int sharpenWidth = 1; - float sharpenContrast = 1.f; - float sharpenThreshold = 0.f; + SharpenParams sharpen = + { + false, // enable + 3, // width + 1.0f, // contrast + 0.0f // threshold + }; + + BilateralFilterParams bilateralFilter = + { + false, // enable + 0, // distance + 0.0f, // sigmaColor + 0.0f // sigmaSpace + }; - bool bilateralFilter = false; - int bilateralFilterDistance = 0; - float bilateralFilterSigmaColor = 0.0f; - float bilateralFilterSigmaSpace = 0.0f; + ClaheFilterParams claheFilter = + { + false, // enable + 4.0f, // clipLimit + 8 // tileGridSize + }; + + NoiseFilterParams noise = { + false, // enable + ENoiseMethod::uniform, // method + 0.0f, // A + 1.0f, // B + true // mono + }; - bool claheFilter = false; - float claheClipLimit = 4.0f; - int claheTileGridSize = 8; }; #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) @@ -149,24 +349,24 @@ void processImage(image::Image& image, const ProcessingParams image.swap(filtered); } - if (pParams.sharpenWidth >= 3.f && pParams.sharpenContrast > 0.f) + if(pParams.sharpen.enabled) { image::Image filtered(image.Width(), image.Height()); const oiio::ImageBuf inBuf(oiio::ImageSpec(image.Width(), image.Height(), nchannels, oiio::TypeDesc::FLOAT), image.data()); oiio::ImageBuf outBuf(oiio::ImageSpec(image.Width(), image.Height(), nchannels, oiio::TypeDesc::FLOAT), filtered.data()); - oiio::ImageBufAlgo::unsharp_mask(outBuf, inBuf, "gaussian", pParams.sharpenWidth, pParams.sharpenContrast, pParams.sharpenThreshold); + oiio::ImageBufAlgo::unsharp_mask(outBuf, inBuf, "gaussian", pParams.sharpen.width, pParams.sharpen.contrast, pParams.sharpen.threshold); image.swap(filtered); } - if (pParams.bilateralFilter) + if (pParams.bilateralFilter.enabled) { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) // Create temporary OpenCV Mat (keep only 3 Channels) to handled Eigen data of our image cv::Mat openCVMatIn = imageRGBAToCvMatBGR(image); cv::Mat openCVMatOut(image.Width(), image.Height(), CV_32FC3); - cv::bilateralFilter(openCVMatIn, openCVMatOut, pParams.bilateralFilterDistance, pParams.bilateralFilterSigmaColor, pParams.bilateralFilterSigmaSpace); + cv::bilateralFilter(openCVMatIn, openCVMatOut, pParams.bilateralFilter.distance, pParams.bilateralFilter.sigmaColor, pParams.bilateralFilter.sigmaSpace); // Copy filtered data from openCV Mat(3 channels) to our image(keep the alpha channel unfiltered) cvMatBGRToImageRGBA(openCVMatOut, image); @@ -177,7 +377,7 @@ void processImage(image::Image& image, const ProcessingParams } // Contrast Limited Adaptive Histogram Equalization - if(pParams.claheFilter) + if(pParams.claheFilter.enabled) { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) // Convert alicevision::image to BGR openCV Mat @@ -199,7 +399,7 @@ void processImage(image::Image& image, const ProcessingParams // apply Clahe algorithm to the L channel { - const cv::Ptr clahe = cv::createCLAHE(pParams.claheClipLimit, cv::Size(pParams.claheTileGridSize, pParams.claheTileGridSize)); + const cv::Ptr clahe = cv::createCLAHE(pParams.claheFilter.clipLimit, cv::Size(pParams.claheFilter.tileGridSize, pParams.claheFilter.tileGridSize)); clahe->apply(L, L); } @@ -233,6 +433,79 @@ void processImage(image::Image& image, const ProcessingParams image.swap(filtered); } + + if(pParams.noise.enabled) + { + oiio::ImageBuf inBuf(oiio::ImageSpec(image.Width(), image.Height(), nchannels, oiio::TypeDesc::FLOAT), image.data()); + oiio::ImageBufAlgo::noise(inBuf, ENoiseMethod_enumToString(pParams.noise.method), pParams.noise.A, pParams.noise.B, pParams.noise.mono); + } +} + +void saveImage(image::Image& image, const std::string& inputPath, const std::string& outputPath, + const std::vector& metadataFolders, + const EImageFormat& outputFormat) +{ + // Read metadata path + std::string metadataFilePath; + + const std::string filename = fs::path(inputPath).filename().string(); + // If metadataFolders is specified + if(!metadataFolders.empty()) + { + // The file must match the file name and extension to be used as a metadata replacement. + const std::vector metadataFilePaths = utils::getFilesPathsFromFolders( + metadataFolders, [&filename](const boost::filesystem::path& path) + { + return path.filename().string() == filename; + } + ); + + if(metadataFilePaths.size() > 1) + { + ALICEVISION_LOG_ERROR("Ambiguous case: Multiple path corresponding to this file was found for metadata replacement."); + throw std::invalid_argument("Ambiguous case: Multiple path corresponding to this file was found for metadata replacement"); + } + + if(metadataFilePaths.empty()) + { + ALICEVISION_LOG_WARNING("Metadata folders was specified but there is no matching for this image: "<< filename + << ". The default metadata will be used instead for this image."); + metadataFilePath = inputPath; + } + else + { + ALICEVISION_LOG_TRACE("Metadata path found for the current image: " << filename); + metadataFilePath = metadataFilePaths[0]; + } + } + else + { + // Metadata are extracted from the original images + metadataFilePath = inputPath; + } + + const oiio::ParamValueList metadata = image::readImageMetadata(metadataFilePath); + + // Save image + ALICEVISION_LOG_TRACE("Export image: '" << outputPath << "'."); + + if(outputFormat == EImageFormat::Grayscale) + { + image::Image outputImage; + image::ConvertPixelType(image, &outputImage); + image::writeImage(outputPath, outputImage, image::EImageColorSpace::AUTO, metadata); + } + else if(outputFormat == EImageFormat::RGB) + { + image::Image outputImage; + image::ConvertPixelType(image, &outputImage); + image::writeImage(outputPath, outputImage, image::EImageColorSpace::AUTO, metadata); + } + else + { + // Already in RGBAf + image::writeImage(outputPath, image, image::EImageColorSpace::AUTO, metadata); + } } int aliceVision_main(int argc, char * argv[]) @@ -240,7 +513,9 @@ int aliceVision_main(int argc, char * argv[]) std::string verboseLevel = system::EVerboseLevel_enumToString(system::Logger::getDefaultVerboseLevel()); std::string inputExpression; std::vector inputFolders; + std::vector metadataFolders; std::string outputPath; + EImageFormat outputFormat = EImageFormat::RGBA; std::string extension; ProcessingParams pParams; @@ -262,6 +537,8 @@ int aliceVision_main(int argc, char * argv[]) po::options_description optionalParams("Optional parameters"); optionalParams.add_options() + ("metadataFolders", po::value>(&metadataFolders)->multitoken(), + "Use images metadata from specific folder(s) instead of those specified in the input images.") ("reconstructedViewsOnly", po::value(&pParams.reconstructedViewsOnly)->default_value(pParams.reconstructedViewsOnly), "Process only recontructed views or all views.") ("scaleFactor", po::value(&pParams.scaleFactor)->default_value(pParams.scaleFactor), @@ -275,31 +552,41 @@ int aliceVision_main(int argc, char * argv[]) ("medianFilter", po::value(&pParams.medianFilter)->default_value(pParams.medianFilter), "Median Filter (0: no filter).") - ("sharpenWidth", po::value(&pParams.sharpenWidth)->default_value(pParams.sharpenWidth), - "Sharpen kernel width (<3: no sharpening).") - ("sharpenContrast", po::value(&pParams.sharpenContrast)->default_value(pParams.sharpenContrast), - "Sharpen contrast value (0.0: no sharpening).") - ("sharpenThreshold", po::value(&pParams.sharpenThreshold)->default_value(pParams.sharpenThreshold), - "Threshold for minimal variation for contrast to avoid sharpening of small noise (0.0: no noise threshold).") + ("sharpenFilter", po::value(&pParams.sharpen)->default_value(pParams.sharpen), + "Sharpen Filter parameters:\n" + " * Enabled: Use Sharpen.\n" + " * Width: Sharpen kernel width.\n" + " * Contrast: Sharpen contrast value.\n " + " * Threshold: Threshold for minimal variation for contrast to avoid sharpening of small noise (0.0: no noise threshold).") ("fillHoles", po::value(&pParams.fillHoles)->default_value(pParams.fillHoles), "Fill Holes.") - ("bilateralFilter", po::value(&pParams.bilateralFilter)->default_value(pParams.bilateralFilter), - "use bilateral Filter") - ("bilateralFilterDistance", po::value(&pParams.bilateralFilterDistance)->default_value(pParams.bilateralFilterDistance), - "Diameter of each pixel neighborhood that is used during filtering (if <=0 is computed proportionaly from sigmaSpace).") - ("bilateralFilterSigmaSpace",po::value(&pParams.bilateralFilterSigmaSpace)->default_value(pParams.bilateralFilterSigmaSpace), - "Filter sigma in the coordinate space.") - ("bilateralFilterSigmaColor",po::value(&pParams.bilateralFilterSigmaColor)->default_value(pParams.bilateralFilterSigmaColor), - "Filter sigma in the color space.") - - ("claheFilter", po::value(&pParams.claheFilter)->default_value(pParams.claheFilter), - "Use Contrast Limited Adaptive Histogram Equalization (CLAHE) Filter.") - ("claheClipLimit", po::value(&pParams.claheClipLimit)->default_value(pParams.claheClipLimit), - "Sets Threshold For Contrast Limiting.") - ("claheTileGridSize", po::value(&pParams.claheTileGridSize)->default_value(pParams.claheTileGridSize), - "Sets Size Of Grid For Histogram Equalization. Input Image Will Be Divided Into Equally Sized Rectangular Tiles.") + ("bilateralFilter", po::value(&pParams.bilateralFilter)->default_value(pParams.bilateralFilter), + "Bilateral Filter parameters:\n" + " * Enabled: Use bilateral Filter.\n" + " * Distance: Diameter of each pixel neighborhood that is used during filtering (if <=0 is computed proportionaly from sigmaSpace).\n" + " * SigmaSpace: Filter sigma in the coordinate space.\n " + " * SigmaColor: Filter sigma in the color space.") + + ("claheFilter", po::value(&pParams.claheFilter)->default_value(pParams.claheFilter), + "Sharpen Filter parameters:\n" + " * Enabled: Use Contrast Limited Adaptive Histogram Equalization (CLAHE).\n" + " * ClipLimit: Sets Threshold For Contrast Limiting.\n" + " * TileGridSize: Sets Size Of Grid For Histogram Equalization. Input Image Will Be Divided Into Equally Sized Rectangular Tiles.") + + ("noiseFilter", po::value(&pParams.noise)->default_value(pParams.noise), + "Noise Filter parameters:\n" + " * Enabled: Add Noise.\n" + " * method: There are several noise types to choose from:\n" + " - uniform: adds noise values uninformly distributed on range [A,B).\n" + " - gaussian: adds Gaussian (normal distribution) noise values with mean value A and standard deviation B.\n" + " - salt: changes to value A a portion of pixels given by B.\n" + " * A, B: parameters that have a different interpretation depending on the method chosen.\n" + " * mono: If is true, a single noise value will be applied to all channels otherwise a separate noise value will be computed for each channel.") + + ("outputFormat", po::value(&outputFormat)->default_value(outputFormat), + "Output image format (rgba, rgb, grayscale)") ("extension", po::value(&extension)->default_value(extension), "Output image extension (like exr, or empty to keep the source file format.") @@ -351,7 +638,7 @@ int aliceVision_main(int argc, char * argv[]) } #if !ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) - if(pParams.bilateralFilter || pParams.claheFilter) + if(pParams.bilateralFilter.enabled || pParams.claheFilter.enabled) { ALICEVISION_LOG_ERROR("Invalid option: BilateralFilter and claheFilter can't be used without openCV !"); return EXIT_FAILURE; @@ -392,17 +679,25 @@ int aliceVision_main(int argc, char * argv[]) // if inputFolders are used if(checkInputFolders) { - const std::string foundViewPath = sfmDataIO::viewPathFromFolders(view, inputFolders).generic_string(); + const std::vector foundViewPaths = sfmDataIO::viewPathsFromFolders(view, inputFolders); + // Checks if a file associated with a given view is found in the inputfolders - if(foundViewPath.empty()) + if(foundViewPaths.empty()) + { + ALICEVISION_LOG_ERROR("Some views from SfmData cannot be found in folders passed in the parameters.\n" + << "Use only SfmData input, use reconstructedViewsOnly or specify the correct inputFolders."); + return EXIT_FAILURE; + } + else if(foundViewPaths.size() > 1) { - ALICEVISION_LOG_ERROR("Some views from SfmData cannot be found in folders passed in the parameters."); - ALICEVISION_LOG_ERROR("Use only SfmData input, use reconstructedViewsOnly or specify the correct inputFolders."); + ALICEVISION_LOG_ERROR("Ambiguous case: Multiple paths found in input folders for the corresponding view '" << view.getViewId() << "'.\n" + << "Make sure that only one match can be found in input folders."); return EXIT_FAILURE; } + // Add to ViewPaths the new associated path - ViewPaths.insert({view.getViewId(), foundViewPath}); - ALICEVISION_LOG_TRACE("New path found for the view " << view.getViewId() << " '" << foundViewPath << "'"); + ALICEVISION_LOG_TRACE("New path found for the view " << view.getViewId() << " '" << foundViewPaths[0] << "'"); + ViewPaths.insert({view.getViewId(), foundViewPaths[0]}); } else { @@ -419,12 +714,17 @@ int aliceVision_main(int argc, char * argv[]) const IndexT viewId = viewIt.first; const std::string viewPath = viewIt.second; sfmData::View& view = sfmData.getView(viewId); - ALICEVISION_LOG_INFO(++i << "/" << size << " - Process view '" << viewId << "'"); + + const fs::path fsPath = viewPath; + const std::string fileExt = fsPath.extension().string(); + const std::string outputExt = extension.empty() ? fileExt : (std::string(".") + extension); + const std::string outputfilePath = (fs::path(outputPath) / (std::to_string(viewId) + outputExt)).generic_string(); + + ALICEVISION_LOG_INFO(++i << "/" << size << " - Process view '" << viewId << "'."); // Read original image image::Image image; image::readImage(viewPath, image, image::EImageColorSpace::LINEAR); - const oiio::ParamValueList metadata = image::readImageMetadata(viewPath); // If exposureCompensation is needed for sfmData files if (pParams.exposureCompensation) @@ -444,16 +744,10 @@ int aliceVision_main(int argc, char * argv[]) processImage(image, pParams); // Save the image - const std::string ext = extension.empty() ? fs::path(viewPath).extension().string() : (std::string(".") + extension); - - // Analyze output path - const std::string outputImagePath = (fs::path(outputPath) / (std::to_string(viewId) + ext)).generic_string(); - - ALICEVISION_LOG_TRACE("Export image: '" << outputImagePath << "'."); - image::writeImage(outputImagePath, image, image::EImageColorSpace::AUTO, metadata); + saveImage(image, viewPath, outputfilePath, metadataFolders, outputFormat); // Update view for this modification - view.setImagePath(outputImagePath); + view.setImagePath(outputfilePath); view.setWidth(image.Width()); view.setHeight(image.Height()); } @@ -466,11 +760,11 @@ int aliceVision_main(int argc, char * argv[]) } } - const std::string outputSfmData = (fs::path(outputPath) / fs::path(inputExpression).filename()).string(); // Save sfmData with modified path to images - if(!sfmDataIO::Save(sfmData, outputSfmData, sfmDataIO::ALL)) + const std::string sfmfilePath = (fs::path(outputPath) / fs::path(inputExpression).filename()).generic_string(); + if(!sfmDataIO::Save(sfmData, sfmfilePath, sfmDataIO::ESfMData(sfmDataIO::ALL))) { - ALICEVISION_LOG_ERROR("The output SfMData file '" << outputPath << "' cannot be written."); + ALICEVISION_LOG_ERROR("The output SfMData file '" << sfmfilePath << "' cannot be written."); return EXIT_FAILURE; } } @@ -482,23 +776,10 @@ int aliceVision_main(int argc, char * argv[]) // If sfmInputDataFilename is empty use imageFolders instead if(inputExpression.empty()) { - for(const std::string& folder : inputFolders) - { - // If one of the paths isn't a folder path - if(!fs::is_directory(folder)) - { - ALICEVISION_LOG_ERROR("the path '" << folder << "' is not a valid folder path."); - return EXIT_FAILURE; - } - - for(fs::directory_entry& entry : fs::directory_iterator(folder)) - { - const std::string entryPath = entry.path().generic_string(); - const std::string ext = entry.path().extension().string(); - if(image::isSupported(ext)) - filesStrPaths.push_back(entryPath); - } - } + // Get supported files + filesStrPaths = utils::getFilesPathsFromFolders(inputFolders, [](const boost::filesystem::path& path) { + return image::isSupported(path.extension().string()); + }); } else { @@ -514,18 +795,15 @@ int aliceVision_main(int argc, char * argv[]) } else { - ALICEVISION_LOG_INFO("Working directory Path '" + inputPath.parent_path().string() + "'."); - // Iterate over files in directory - for(fs::directory_entry& entry : fs::directory_iterator(inputPath.parent_path())) - { - const std::string entryPath = entry.path().generic_string(); - const std::string ext = entry.path().extension().string(); - if(image::isSupported(ext)) - filesStrPaths.push_back(entryPath); - } - - // Regex filtering files paths - filterStrings(filesStrPaths, inputExpression); + ALICEVISION_LOG_INFO("Working directory Path '" + inputPath.parent_path().generic_string() + "'."); + + const std::regex regex = utils::filterToRegex(inputExpression); + // Get supported files in inputPath directory which matches our regex filter + filesStrPaths = utils::getFilesPathsFromFolder(inputPath.parent_path().generic_string(), + [®ex](const boost::filesystem::path& path) { + return image::isSupported(path.extension().string()) && std::regex_match(path.generic_string(), regex); + } + ); } } @@ -546,24 +824,22 @@ int aliceVision_main(int argc, char * argv[]) for(const std::string& inputFilePath : filesStrPaths) { const fs::path path = fs::path(inputFilePath); - const std::string fileName = path.stem().string(); + const std::string filename = path.stem().string(); const std::string fileExt = path.extension().string(); const std::string outputExt = extension.empty() ? fileExt : (std::string(".") + extension); - const std::string outputFilePath = (fs::path(outputPath) / (fileName + outputExt)).string(); + const std::string outputFilePath = (fs::path(outputPath) / (filename + outputExt)).generic_string(); - ALICEVISION_LOG_INFO(++i << "/" << size << " - Process image '" << fileName << fileExt << "'."); + ALICEVISION_LOG_INFO(++i << "/" << size << " - Process image '" << filename << fileExt << "'."); // Read original image image::Image image; image::readImage(inputFilePath, image, image::EImageColorSpace::LINEAR); - const oiio::ParamValueList metadata = image::readImageMetadata(inputFilePath); // Image processing processImage(image, pParams); // Save the image - ALICEVISION_LOG_TRACE("Export image: '" << outputFilePath << "'."); - image::writeImage(outputFilePath, image, image::EImageColorSpace::AUTO, metadata); + saveImage(image, inputFilePath, outputFilePath, metadataFolders, outputFormat); } } diff --git a/src/software/utils/precisionEvaluationToGt.hpp b/src/software/utils/precisionEvaluationToGt.hpp index 6e1e4eb9bf..8a61d4a042 100644 --- a/src/software/utils/precisionEvaluationToGt.hpp +++ b/src/software/utils/precisionEvaluationToGt.hpp @@ -218,7 +218,7 @@ inline void EvaluteToGT( htmlDocStream->pushInfo( htmlDocument::htmlMarkup("pre", os.str())); const double maxRange = *std::max_element(vec_baselineErrors.begin(), vec_baselineErrors.end()); - Histogram baselineHistogram(0.0, maxRange, 50); + utils::Histogram baselineHistogram(0.0, maxRange, 50); baselineHistogram.Add(vec_baselineErrors.begin(), vec_baselineErrors.end()); svg::svgHisto svg_BaselineHistogram; @@ -277,7 +277,7 @@ inline void EvaluteToGT( htmlDocStream->pushInfo( htmlDocument::htmlMarkup("pre", os.str())); const double maxRangeAngular = *std::max_element(vec_angularErrors.begin(), vec_angularErrors.end()); - Histogram angularHistogram(0.0, maxRangeAngular, 50); + utils::Histogram angularHistogram(0.0, maxRangeAngular, 50); angularHistogram.Add(vec_angularErrors.begin(), vec_angularErrors.end()); svg::svgHisto svg_AngularHistogram; diff --git a/src/software/utils/sfmColorHarmonize/colorHarmonizeEngineGlobal.cpp b/src/software/utils/sfmColorHarmonize/colorHarmonizeEngineGlobal.cpp index 292a94e9ef..e5a824ba5a 100644 --- a/src/software/utils/sfmColorHarmonize/colorHarmonizeEngineGlobal.cpp +++ b/src/software/utils/sfmColorHarmonize/colorHarmonizeEngineGlobal.cpp @@ -290,8 +290,8 @@ bool ColorHarmonizationEngineGlobal::Process() readImage(p_imaNames.first, imageI, image::EImageColorSpace::LINEAR); readImage(p_imaNames.second, imageJ, image::EImageColorSpace::LINEAR); - Histogram< double > histoI( minvalue, maxvalue, bin); - Histogram< double > histoJ( minvalue, maxvalue, bin); + utils::Histogram< double > histoI( minvalue, maxvalue, bin); + utils::Histogram< double > histoJ( minvalue, maxvalue, bin); int channelIndex = 0; // RED channel colorHarmonization::CommonDataByPair::computeHisto( histoI, maskI, channelIndex, imageI ); @@ -300,7 +300,7 @@ bool ColorHarmonizationEngineGlobal::Process() edgeR = relativeColorHistogramEdge(map_cameraNodeToCameraIndex[viewI], map_cameraNodeToCameraIndex[viewJ], histoI.GetHist(), histoJ.GetHist()); - histoI = histoJ = Histogram< double >( minvalue, maxvalue, bin); + histoI = histoJ = utils::Histogram(minvalue, maxvalue, bin); channelIndex = 1; // GREEN channel colorHarmonization::CommonDataByPair::computeHisto( histoI, maskI, channelIndex, imageI ); colorHarmonization::CommonDataByPair::computeHisto( histoJ, maskJ, channelIndex, imageJ ); @@ -308,7 +308,7 @@ bool ColorHarmonizationEngineGlobal::Process() edgeG = relativeColorHistogramEdge(map_cameraNodeToCameraIndex[viewI], map_cameraNodeToCameraIndex[viewJ], histoI.GetHist(), histoJ.GetHist()); - histoI = histoJ = Histogram< double >( minvalue, maxvalue, bin); + histoI = histoJ = utils::Histogram(minvalue, maxvalue, bin); channelIndex = 2; // BLUE channel colorHarmonization::CommonDataByPair::computeHisto( histoI, maskI, channelIndex, imageI ); colorHarmonization::CommonDataByPair::computeHisto( histoJ, maskJ, channelIndex, imageJ );