diff --git a/src/aliceVision/hdr/brackets.cpp b/src/aliceVision/hdr/brackets.cpp index 3b9e49d703..f3e6df4215 100644 --- a/src/aliceVision/hdr/brackets.cpp +++ b/src/aliceVision/hdr/brackets.cpp @@ -1,5 +1,7 @@ #include "brackets.hpp" +#include + #include #include @@ -87,6 +89,7 @@ bool estimateBracketsFromSfmData(std::vector> v_exposuresSetting; for(auto & group : groups) { // Sort all images by exposure time @@ -96,21 +99,112 @@ bool estimateBracketsFromSfmData(std::vectorgetCameraExposureSetting().getExposure() < b->getCameraExposureSetting().getExposure()); }); + std::vector exposuresSetting; + for (auto& v : group) + { + exposuresSetting.push_back(v->getCameraExposureSetting()); + } + v_exposuresSetting.push_back(exposuresSetting); + } + + // Check exposure consistency between group + if (v_exposuresSetting.size() > 1) + { + for (int g = 1 ; g < v_exposuresSetting.size(); ++g) + { + for (int e = 0; e < v_exposuresSetting[g].size(); ++e) + { + if (!(v_exposuresSetting[g][e] == v_exposuresSetting[g - 1][e])) + { + ALICEVISION_LOG_WARNING("Non consistant exposures between poses have been detected. Most likely the dataset has been captured with an automatic exposure mode enabled. Final result can be impacted."); + g = v_exposuresSetting.size(); + break; + } + } + } } return true; } -void selectTargetViews(std::vector> & out_targetViews, const std::vector>> & groups, int offsetRefBracketIndex) +void selectTargetViews(std::vector> & out_targetViews, const std::vector>> & groups, int offsetRefBracketIndex, const std::string& lumaStatFilepath, const double meanTargetedLuma) { - for(auto & group : groups) + // If targetIndexesFilename cannot be opened or is not valid an error is thrown + // For odd number, there is no ambiguity on the middle image. + // For even number, we arbitrarily choose the more exposed view (as we usually have more under-exposed images than over-exposed). + const int viewNumberPerGroup = groups[0].size(); + const int middleIndex = viewNumberPerGroup / 2; + int targetIndex = middleIndex + offsetRefBracketIndex; + + out_targetViews.clear(); + + if ((targetIndex >= 0) && (targetIndex < viewNumberPerGroup)) + { + ALICEVISION_LOG_INFO("Use offsetRefBracketIndex parameter"); + } + else // try to use the luminance statistics of the LDR images stored in the file { - // Target views are the middle exposed views - // For add number, there is no ambiguity on the middle image. - // For even number, we arbitrarily choose the more exposed view (as we usually have more under-exposed images than over-exposed). - const int middleIndex = int(group.size()) / 2; - const int targetIndex = clamp(middleIndex + offsetRefBracketIndex, 0, int(group.size()) - 1); + ALICEVISION_LOG_INFO("offsetRefBracketIndex parameter out of range, read file containing luminance statistics to compute an estimation"); + std::ifstream file(lumaStatFilepath); + if (!file) + ALICEVISION_THROW_ERROR("Failed to open file: " << lumaStatFilepath); + std::vector lines; + std::string line; + while (std::getline(file, line)) + { + lines.push_back(line); + } + if ((lines.size() < 3) || (std::stoi(lines[0].c_str()) != groups.size()) || (std::stoi(lines[1].c_str()) < groups[0].size())) + { + ALICEVISION_THROW_ERROR("File: " << lumaStatFilepath << " is not a valid file"); + } + int nbGroup = std::stoi(lines[0].c_str()); + int nbExp = std::stoi(lines[1].c_str()); + std::vector v_lumaMeanMean; + + for (int i = 0; i < nbExp; ++i) + { + double lumaMeanMean = 0.0; + for (int j = 0; j < nbGroup; ++j) + { + std::istringstream iss(lines[3 + j * nbExp + i]); + aliceVision::IndexT srcId; + int nbItem; + double exposure, lumaMean, lumaMin, lumaMax; + if (!(iss >> srcId >> exposure >> nbItem >> lumaMean >> lumaMin >> lumaMax)) + { + ALICEVISION_THROW_ERROR("File: " << lumaStatFilepath << " is not a valid file"); + } + lumaMeanMean += lumaMean; + } + v_lumaMeanMean.push_back(lumaMeanMean / nbGroup); + } + + // adjust last index to avoid non increasing luminance curve due to saturation in highlights + int lastIdx = v_lumaMeanMean.size() - 1; + while ((lastIdx > 1) && ((v_lumaMeanMean[lastIdx] < v_lumaMeanMean[lastIdx - 1]) || (v_lumaMeanMean[lastIdx] < v_lumaMeanMean[lastIdx - 2]))) + { + lastIdx--; + } + + double minDiffWithLumaTarget = 1000.0; + targetIndex = 0; + + for (int k = 0; k < lastIdx; ++k) + { + const double diffWithLumaTarget = (v_lumaMeanMean[k] > meanTargetedLuma) ? (v_lumaMeanMean[k] - meanTargetedLuma) : (meanTargetedLuma - v_lumaMeanMean[k]); + if (diffWithLumaTarget < minDiffWithLumaTarget) + { + minDiffWithLumaTarget = diffWithLumaTarget; + targetIndex = k; + } + } + ALICEVISION_LOG_INFO("offsetRefBracketIndex parameter automaticaly set to " << targetIndex - middleIndex); + } + + for (auto& group : groups) + { //Set the ldr ancestors id for (auto v : group) { @@ -119,6 +213,8 @@ void selectTargetViews(std::vector> & out_targetV out_targetViews.push_back(group[targetIndex]); } + return; } + } } diff --git a/src/aliceVision/hdr/brackets.hpp b/src/aliceVision/hdr/brackets.hpp index 490868bc99..756a0ac92a 100644 --- a/src/aliceVision/hdr/brackets.hpp +++ b/src/aliceVision/hdr/brackets.hpp @@ -11,6 +11,71 @@ namespace aliceVision { namespace hdr { +enum class ECalibrationMethod +{ + LINEAR, + DEBEVEC, + GROSSBERG, + LAGUERRE, +}; + +/** + * @brief convert an enum ECalibrationMethod to its corresponding string + * @param ECalibrationMethod + * @return String + */ +inline std::string ECalibrationMethod_enumToString(const ECalibrationMethod calibrationMethod) +{ + switch (calibrationMethod) + { + case ECalibrationMethod::LINEAR: + return "linear"; + case ECalibrationMethod::DEBEVEC: + return "debevec"; + case ECalibrationMethod::GROSSBERG: + return "grossberg"; + case ECalibrationMethod::LAGUERRE: + return "laguerre"; + } + throw std::out_of_range("Invalid method name enum"); +} + +/** + * @brief convert a string calibration method name to its corresponding enum ECalibrationMethod + * @param ECalibrationMethod + * @return String + */ +inline ECalibrationMethod ECalibrationMethod_stringToEnum(const std::string& calibrationMethodName) +{ + std::string methodName = calibrationMethodName; + std::transform(methodName.begin(), methodName.end(), methodName.begin(), ::tolower); + + if (methodName == "linear") + return ECalibrationMethod::LINEAR; + if (methodName == "debevec") + return ECalibrationMethod::DEBEVEC; + if (methodName == "grossberg") + return ECalibrationMethod::GROSSBERG; + if (methodName == "laguerre") + return ECalibrationMethod::LAGUERRE; + + throw std::out_of_range("Invalid method name : '" + calibrationMethodName + "'"); +} + +inline std::ostream& operator<<(std::ostream& os, ECalibrationMethod calibrationMethodName) +{ + os << ECalibrationMethod_enumToString(calibrationMethodName); + return os; +} + +inline std::istream& operator>>(std::istream& in, ECalibrationMethod& calibrationMethod) +{ + std::string token; + in >> token; + calibrationMethod = ECalibrationMethod_stringToEnum(token); + return in; +} + /** * @brief Estimate brackets information from sfm data * @param[out] groups: estimated groups @@ -25,8 +90,11 @@ bool estimateBracketsFromSfmData(std::vector> & out_targetViews, const std::vector>>& groups, int offsetRefBracketIndex); +void selectTargetViews(std::vector> & out_targetViews, const std::vector>>& groups, int offsetRefBracketIndex, const std::string& targetIndexesFilename = "", const double meanTargetedLuma = 0.4); } } diff --git a/src/aliceVision/hdr/hdrTestCommon.hpp b/src/aliceVision/hdr/hdrTestCommon.hpp index ebfa9fccb3..43c49e9278 100644 --- a/src/aliceVision/hdr/hdrTestCommon.hpp +++ b/src/aliceVision/hdr/hdrTestCommon.hpp @@ -38,8 +38,14 @@ bool extractSamplesGroups(std::vector>& out_samples, imgReadOptions.rawColorInterpretation = image::ERawColorInterpretation::LibRawWhiteBalancing; imgReadOptions.workingColorSpace = image::EImageColorSpace::LINEAR; + std::vector viewIds; + for (size_t id = 0; id < imagePaths[idGroup].size(); ++id) + { + viewIds.push_back(id); + } + std::vector groupSamples; - if (!Sampling::extractSamplesFromImages(groupSamples, imagePaths[idGroup], + if (!Sampling::extractSamplesFromImages(groupSamples, imagePaths[idGroup], viewIds, times[idGroup], width, height, channelQuantization, imgReadOptions, Sampling::Params{})) diff --git a/src/aliceVision/hdr/sampling.cpp b/src/aliceVision/hdr/sampling.cpp index 4aafeaf797..db9245aaa5 100644 --- a/src/aliceVision/hdr/sampling.cpp +++ b/src/aliceVision/hdr/sampling.cpp @@ -65,7 +65,8 @@ std::istream & operator>>(std::istream& is, ImageSample & s) std::ostream & operator<<(std::ostream& os, const PixelDescription & p) { - os.write((const char *)&p.exposure, sizeof(p.exposure)); + os.write((const char*)&p.srcId, sizeof(p.srcId)); + os.write((const char*)&p.exposure, sizeof(p.exposure)); os.write((const char *)&p.mean.r(), sizeof(p.mean.r())); os.write((const char *)&p.mean.g(), sizeof(p.mean.g())); os.write((const char *)&p.mean.b(), sizeof(p.mean.b())); @@ -78,7 +79,8 @@ std::ostream & operator<<(std::ostream& os, const PixelDescription & p) std::istream & operator>>(std::istream& is, PixelDescription & p) { - is.read((char *)&p.exposure, sizeof(p.exposure)); + is.read((char*)&p.srcId, sizeof(p.srcId)); + is.read((char*)&p.exposure, sizeof(p.exposure)); is.read((char *)&p.mean.r(), sizeof(p.mean.r())); is.read((char *)&p.mean.g(), sizeof(p.mean.g())); is.read((char *)&p.mean.b(), sizeof(p.mean.b())); @@ -146,7 +148,7 @@ void square(image::Image & dest, const Eigen::Matrix& out_samples, const std::vector& imagePaths, const std::vector& times, const size_t imageWidth, const size_t imageHeight, const size_t channelQuantization, const image::ImageReadOptions & imgReadOptions, const Sampling::Params params) +bool Sampling::extractSamplesFromImages(std::vector& out_samples, const std::vector& imagePaths, const std::vector& viewIds, const std::vector& times, const size_t imageWidth, const size_t imageHeight, const size_t channelQuantization, const image::ImageReadOptions & imgReadOptions, const Sampling::Params params, const bool simplified) { const int radiusp1 = params.radius + 1; const int diameter = (params.radius * 2) + 1; @@ -184,46 +186,98 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c throw std::runtime_error(ss.str()); } - #pragma omp parallel for - for (int idx = 0; idx < vec_blocks.size(); ++idx) + if (simplified) { - int cx = vec_blocks[idx].first; - int cy = vec_blocks[idx].second; + // Luminance statistics are calculated from a subsampled square, centered and rotated by 45°. + // 2 vertices of this square are the centers of the longest sides of the image. + // Such a shape is suitable for both fisheye and classic images. - int blockWidth = ((img.Width() - cx) > params.blockSize) ? params.blockSize : img.Width() - cx; - int blockHeight = ((img.Height() - cy) > params.blockSize) ? params.blockSize : img.Height() - cy; + const int H = imageHeight; + const int W = imageWidth; + const int hH = imageHeight / 2; + const int hW = imageWidth / 2; - auto blockInput = img.block(cy, cx, blockHeight, blockWidth); - auto blockOutput = samples.block(cy, cx, blockHeight, blockWidth); + const int a1 = (H <= W) ? hW : hH; + const int a2 = (H <= W) ? hW : W - hH; + const int a3 = (H <= W) ? H - hW : hH; + const int a4 = (H <= W) ? hW + H : W + hH; - // Stats for deviation - Image> imgIntegral, imgIntegralSquare; - Image imgSquare; + // All rows must be considered if image orientation is landscape (H < W) + // Only imgW rows centered on imgH/2 must be considered if image orientation is portrait (H > W) + const int rmin = (H <= W) ? 0 : (H - W) / 2; + const int rmax = (H <= W) ? H : (H + W) / 2; - square(imgSquare, blockInput); - integral(imgIntegral, blockInput); - integral(imgIntegralSquare, imgSquare); + const int sampling = 16; - for(int y = radiusp1; y < imgIntegral.Height() - params.radius; ++y) + #pragma omp parallel for + for (int r = rmin; r < rmax; r = r + sampling) { - for(int x = radiusp1; x < imgIntegral.Width() - params.radius; ++x) + const int cmin = (r < hH) ? a1 - r : r - a3; + const int cmax = (r < hH) ? a2 + r : a4 - r; + + for (int c = cmin; c < cmax; c = c + sampling) { - image::Rgb S1 = imgIntegral(y + params.radius, x + params.radius) + imgIntegral(y - radiusp1, x - radiusp1) - imgIntegral(y + params.radius, x - radiusp1) - imgIntegral(y - radiusp1, x + params.radius); - image::Rgb S2 = imgIntegralSquare(y + params.radius, x + params.radius) + imgIntegralSquare(y - radiusp1, x - radiusp1) - imgIntegralSquare(y + params.radius, x - radiusp1) - imgIntegralSquare(y - radiusp1, x + params.radius); - PixelDescription pd; - + + pd.srcId = viewIds[idBracket]; pd.exposure = exposure; - pd.mean.r() = blockInput(y,x).r(); - pd.mean.g() = blockInput(y,x).g(); - pd.mean.b() = blockInput(y,x).b(); - pd.variance.r() = (S2.r() - (S1.r()*S1.r()) / area) / area; - pd.variance.g() = (S2.g() - (S1.g()*S1.g()) / area) / area; - pd.variance.b() = (S2.b() - (S1.b()*S1.b()) / area) / area; - - blockOutput(y, x).x = cx + x; - blockOutput(y, x).y = cy + y; - blockOutput(y, x).descriptions.push_back(pd); + pd.mean.r() = img(r, c).r(); + pd.mean.g() = img(r, c).g(); + pd.mean.b() = img(r, c).b(); + pd.variance.r() = 0.0; + pd.variance.g() = 0.0; + pd.variance.b() = 0.0; + + samples(r, c).x = c; + samples(r, c).y = r; + samples(r, c).descriptions.push_back(pd); + } + } + } + else + { + #pragma omp parallel for + for (int idx = 0; idx < vec_blocks.size(); ++idx) + { + int cx = vec_blocks[idx].first; + int cy = vec_blocks[idx].second; + + int blockWidth = ((img.Width() - cx) > params.blockSize) ? params.blockSize : img.Width() - cx; + int blockHeight = ((img.Height() - cy) > params.blockSize) ? params.blockSize : img.Height() - cy; + + auto blockInput = img.block(cy, cx, blockHeight, blockWidth); + auto blockOutput = samples.block(cy, cx, blockHeight, blockWidth); + + // Stats for deviation + Image> imgIntegral, imgIntegralSquare; + Image imgSquare; + + square(imgSquare, blockInput); + integral(imgIntegral, blockInput); + integral(imgIntegralSquare, imgSquare); + + for (int y = radiusp1; y < imgIntegral.Height() - params.radius; ++y) + { + for (int x = radiusp1; x < imgIntegral.Width() - params.radius; ++x) + { + image::Rgb S1 = imgIntegral(y + params.radius, x + params.radius) + imgIntegral(y - radiusp1, x - radiusp1) - imgIntegral(y + params.radius, x - radiusp1) - imgIntegral(y - radiusp1, x + params.radius); + image::Rgb S2 = imgIntegralSquare(y + params.radius, x + params.radius) + imgIntegralSquare(y - radiusp1, x - radiusp1) - imgIntegralSquare(y + params.radius, x - radiusp1) - imgIntegralSquare(y - radiusp1, x + params.radius); + + PixelDescription pd; + + pd.srcId = viewIds[idBracket]; + pd.exposure = exposure; + pd.mean.r() = blockInput(y, x).r(); + pd.mean.g() = blockInput(y, x).g(); + pd.mean.b() = blockInput(y, x).b(); + pd.variance.r() = (S2.r() - (S1.r() * S1.r()) / area) / area; + pd.variance.g() = (S2.g() - (S1.g() * S1.g()) / area) / area; + pd.variance.b() = (S2.b() - (S1.b() * S1.b()) / area) / area; + + blockOutput(y, x).x = cx + x; + blockOutput(y, x).y = cy + y; + blockOutput(y, x).descriptions.push_back(pd); + } } } } @@ -235,124 +289,127 @@ bool Sampling::extractSamplesFromImages(std::vector& out_samples, c return false; } - // Create samples image - #pragma omp parallel for - for (int y = params.radius; y < samples.Height() - params.radius; ++y) + if (!simplified) { - for (int x = params.radius; x < samples.Width() - params.radius; ++x) + // Create samples image + #pragma omp parallel for + for (int y = params.radius; y < samples.Height() - params.radius; ++y) { - ImageSample & sample = samples(y, x); - if (sample.descriptions.size() < 2) - { - continue; - } - - int last_ok = 0; - - // Make sure we don't have a patch with high variance on any bracket. - // If the variance is too high somewhere, ignore the whole coordinate samples - bool valid = true; - const float maxVariance = 0.05f; - for (int k = 0; k < sample.descriptions.size(); ++k) + for (int x = params.radius; x < samples.Width() - params.radius; ++x) { - if (sample.descriptions[k].variance.r() > maxVariance || - sample.descriptions[k].variance.g() > maxVariance || - sample.descriptions[k].variance.b() > maxVariance) + ImageSample& sample = samples(y, x); + if (sample.descriptions.size() < 2) { - valid = false; - break; + continue; } - } - if (!valid) - { - sample.descriptions.clear(); - continue; - } + int last_ok = 0; - // Makes sure the curve is monotonic - int firstvalid = -1; - int lastvalid = 0; - for (std::size_t k = 1; k < sample.descriptions.size(); ++k) - { - bool valid = false; - - // Threshold on the max values, to avoid using fully saturated pixels - // TODO: on RAW images, values can be higher. May need to be computed dynamically? - const float maxValue = 0.99f; - if(sample.descriptions[k].mean.r() > maxValue || - sample.descriptions[k].mean.g() > maxValue || - sample.descriptions[k].mean.b() > maxValue) + // Make sure we don't have a patch with high variance on any bracket. + // If the variance is too high somewhere, ignore the whole coordinate samples + bool valid = true; + const float maxVariance = 0.05f; + for (int k = 0; k < sample.descriptions.size(); ++k) { - continue; + if (sample.descriptions[k].variance.r() > maxVariance || + sample.descriptions[k].variance.g() > maxVariance || + sample.descriptions[k].variance.b() > maxVariance) + { + valid = false; + break; + } } - // Ensures that at least one channel is strictly increasing with increasing exposure - // TODO: check "exposure" params, we may have the same exposure multiple times - const float minIncreaseRatio = 1.004f; - if(sample.descriptions[k].mean.r() > minIncreaseRatio * sample.descriptions[k - 1].mean.r() || - sample.descriptions[k].mean.g() > minIncreaseRatio * sample.descriptions[k - 1].mean.g() || - sample.descriptions[k].mean.b() > minIncreaseRatio * sample.descriptions[k - 1].mean.b()) + if (!valid) { - valid = true; + sample.descriptions.clear(); + continue; } - // Ensures that the values of each channel are increasing with increasing exposure - if (sample.descriptions[k].mean.r() < sample.descriptions[k - 1].mean.r() || - sample.descriptions[k].mean.g() < sample.descriptions[k - 1].mean.g() || - sample.descriptions[k].mean.b() < sample.descriptions[k - 1].mean.b()) + // Makes sure the curve is monotonic + int firstvalid = -1; + int lastvalid = 0; + for (std::size_t k = 1; k < sample.descriptions.size(); ++k) { - valid = false; - } + bool valid = false; + + // Threshold on the max values, to avoid using fully saturated pixels + // TODO: on RAW images, values can be higher. May need to be computed dynamically? + const float maxValue = 0.99f; + if (sample.descriptions[k].mean.r() > maxValue || + sample.descriptions[k].mean.g() > maxValue || + sample.descriptions[k].mean.b() > maxValue) + { + continue; + } - // If we have enough information to analyze the chrominance - const float minGlobalValue = 0.1f; - if(sample.descriptions[k - 1].mean.norm() > minGlobalValue) - { - // Check that both colors are similars - const float n1 = sample.descriptions[k - 1].mean.norm(); - const float n2 = sample.descriptions[k].mean.norm(); - const float dot = sample.descriptions[k - 1].mean.dot(sample.descriptions[k].mean); - const float cosa = dot / (n1*n2); - - const float maxCosa = 0.95f; // ~ 18deg - if(cosa < maxCosa) + // Ensures that at least one channel is strictly increasing with increasing exposure + // TODO: check "exposure" params, we may have the same exposure multiple times + const float minIncreaseRatio = 1.004f; + if (sample.descriptions[k].mean.r() > minIncreaseRatio * sample.descriptions[k - 1].mean.r() || + sample.descriptions[k].mean.g() > minIncreaseRatio * sample.descriptions[k - 1].mean.g() || + sample.descriptions[k].mean.b() > minIncreaseRatio * sample.descriptions[k - 1].mean.b()) + { + valid = true; + } + + // Ensures that the values of each channel are increasing with increasing exposure + if (sample.descriptions[k].mean.r() < sample.descriptions[k - 1].mean.r() || + sample.descriptions[k].mean.g() < sample.descriptions[k - 1].mean.g() || + sample.descriptions[k].mean.b() < sample.descriptions[k - 1].mean.b()) { valid = false; } - } - if (valid) - { - if (firstvalid < 0) + // If we have enough information to analyze the chrominance + const float minGlobalValue = 0.1f; + if (sample.descriptions[k - 1].mean.norm() > minGlobalValue) { - firstvalid = int(k) - 1; + // Check that both colors are similars + const float n1 = sample.descriptions[k - 1].mean.norm(); + const float n2 = sample.descriptions[k].mean.norm(); + const float dot = sample.descriptions[k - 1].mean.dot(sample.descriptions[k].mean); + const float cosa = dot / (n1 * n2); + + const float maxCosa = 0.95f; // ~ 18deg + if (cosa < maxCosa) + { + valid = false; + } } - lastvalid = int(k); - } - else - { - if (lastvalid != 0) + + if (valid) { - break; + if (firstvalid < 0) + { + firstvalid = int(k) - 1; + } + lastvalid = int(k); + } + else + { + if (lastvalid != 0) + { + break; + } } } - } - if (lastvalid == 0 || firstvalid < 0) - { - sample.descriptions.clear(); - continue; - } + if (lastvalid == 0 || firstvalid < 0) + { + sample.descriptions.clear(); + continue; + } - if (firstvalid > 0 || lastvalid < int(sample.descriptions.size()) - 1) - { - std::vector replace; - for (int pos = firstvalid; pos <= lastvalid; ++pos) + if (firstvalid > 0 || lastvalid < int(sample.descriptions.size()) - 1) { - replace.push_back(sample.descriptions[pos]); + std::vector replace; + for (int pos = firstvalid; pos <= lastvalid; ++pos) + { + replace.push_back(sample.descriptions[pos]); + } + sample.descriptions = replace; } - sample.descriptions = replace; } } } diff --git a/src/aliceVision/hdr/sampling.hpp b/src/aliceVision/hdr/sampling.hpp index 4992a5c8c9..c5ca4fb717 100644 --- a/src/aliceVision/hdr/sampling.hpp +++ b/src/aliceVision/hdr/sampling.hpp @@ -24,6 +24,7 @@ struct UniqueDescriptor struct PixelDescription { + aliceVision::IndexT srcId = 0; float exposure; image::Rgb mean; image::Rgb variance; @@ -66,7 +67,7 @@ class Sampling void filter(size_t maxTotalPoints); void extractUsefulSamples(std::vector & out_samples, const std::vector & samples, int imageIndex) const; - static bool extractSamplesFromImages(std::vector& out_samples, const std::vector & imagePaths, const std::vector& times, const size_t imageWidth, const size_t imageHeight, const size_t channelQuantization, const image::ImageReadOptions & imgReadOptions, const Params params); + static bool extractSamplesFromImages(std::vector& out_samples, const std::vector & imagePaths, const std::vector& viewIds, const std::vector& times, const size_t imageWidth, const size_t imageHeight, const size_t channelQuantization, const image::ImageReadOptions & imgReadOptions, const Params params, const bool simplified = false); private: MapSampleRefList _positions; diff --git a/src/software/pipeline/main_LdrToHdrCalibration.cpp b/src/software/pipeline/main_LdrToHdrCalibration.cpp index c4e62d4333..fdc2af1997 100644 --- a/src/software/pipeline/main_LdrToHdrCalibration.cpp +++ b/src/software/pipeline/main_LdrToHdrCalibration.cpp @@ -33,6 +33,7 @@ #include #include +#include // These constants define the current software version. @@ -41,73 +42,100 @@ #define ALICEVISION_SOFTWARE_VERSION_MINOR 1 using namespace aliceVision; +using namespace aliceVision::hdr; namespace po = boost::program_options; namespace fs = boost::filesystem; -enum class ECalibrationMethod +struct luminanceInfo { - LINEAR, - DEBEVEC, - GROSSBERG, - LAGUERRE, + double exposure = 0.0; + double meanLum = 0.0; + double minLum = 1e6; + double maxLum = 0.0; + int itemNb = 0; + + luminanceInfo() = default; }; -/** - * @brief convert an enum ECalibrationMethod to its corresponding string - * @param ECalibrationMethod - * @return String - */ -inline std::string ECalibrationMethod_enumToString(const ECalibrationMethod calibrationMethod) +void computeLuminanceStatFromSamples(const std::vector& samples, std::map& luminanceInfos) { - switch(calibrationMethod) + luminanceInfo lumaInfo; + luminanceInfos.clear(); + + for (int i = 0; i < samples.size(); i++) { - case ECalibrationMethod::LINEAR: - return "linear"; - case ECalibrationMethod::DEBEVEC: - return "debevec"; - case ECalibrationMethod::GROSSBERG: - return "grossberg"; - case ECalibrationMethod::LAGUERRE: - return "laguerre"; + for (int j = 0; j < samples[i].descriptions.size(); j++) + { + const IndexT key = samples[i].descriptions[j].srcId; + + const double lum = image::Rgb2GrayLinear(samples[i].descriptions[j].mean[0], samples[i].descriptions[j].mean[1], samples[i].descriptions[j].mean[2]); + + if (luminanceInfos.find(key) == luminanceInfos.end()) + { + luminanceInfos[key] = lumaInfo; + luminanceInfos[key].exposure = samples[i].descriptions[j].exposure; + } + + luminanceInfos[key].meanLum += lum; + luminanceInfos[key].itemNb++; + if (lum < luminanceInfos[key].minLum) + { + luminanceInfos[key].minLum = lum; + } + if (lum > luminanceInfos[key].maxLum) + { + luminanceInfos[key].maxLum = lum; + } + } } - throw std::out_of_range("Invalid method name enum"); } -/** - * @brief convert a string calibration method name to its corresponding enum ECalibrationMethod - * @param ECalibrationMethod - * @return String - */ -inline ECalibrationMethod ECalibrationMethod_stringToEnum(const std::string& calibrationMethodName) +void computeLuminanceInfoFromImage(image::Image& image, luminanceInfo& lumaInfo) { - std::string methodName = calibrationMethodName; - std::transform(methodName.begin(), methodName.end(), methodName.begin(), ::tolower); - - if(methodName == "linear") - return ECalibrationMethod::LINEAR; - if(methodName == "debevec") - return ECalibrationMethod::DEBEVEC; - if(methodName == "grossberg") - return ECalibrationMethod::GROSSBERG; - if(methodName == "laguerre") - return ECalibrationMethod::LAGUERRE; - - throw std::out_of_range("Invalid method name : '" + calibrationMethodName + "'"); -} + // Luminance statistics are calculated from a subsampled square, centered and rotated by 45°. + // 2 vertices of this square are the centers of the longest sides of the image. + // Such a shape is suitable for both fisheye and classic images. -inline std::ostream& operator<<(std::ostream& os, ECalibrationMethod calibrationMethodName) -{ - os << ECalibrationMethod_enumToString(calibrationMethodName); - return os; -} + double meanLuminance = 0.0; + double maxLuminance = 0.0; + double minLuminance = 1000.0; + int sampleNb = 0; -inline std::istream& operator>>(std::istream& in, ECalibrationMethod& calibrationMethod) -{ - std::string token; - in >> token; - calibrationMethod = ECalibrationMethod_stringToEnum(token); - return in; + const int imgH = image.Height(); + const int imgW = image.Width(); + + const int a1 = (imgH <= imgW) ? imgW / 2 : imgH / 2; + const int a2 = (imgH <= imgW) ? imgW / 2 : imgW - (imgH / 2); + const int a3 = (imgH <= imgW) ? imgH - (imgW / 2) : imgH / 2; + const int a4 = (imgH <= imgW) ? (imgW / 2) + imgH : imgW + (imgH / 2); + + // All rows must be considered if image orientation is landscape + // Only imgW rows centered on imgH/2 must be considered if image orientation is portrait + const int rmin = (imgH <= imgW) ? 0 : (imgH - imgW) / 2; + const int rmax = (imgH <= imgW) ? imgH : (imgH + imgW) / 2; + + const int sampling = 16; + + for (int r = rmin; r < rmax; r = r + sampling) + { + const int cmin = (r < imgH / 2) ? a1 - r : r - a3; + const int cmax = (r < imgH / 2) ? a2 + r : a4 - r; + + for (int c = cmin; c < cmax; c = c + sampling) + { + double luma = image::Rgb2GrayLinear(image(r, c)[0], image(r, c)[1], image(r, c)[2]); + meanLuminance += luma; + minLuminance = (luma < minLuminance) ? luma : minLuminance; + maxLuminance = (luma > maxLuminance) ? luma : maxLuminance; + sampleNb++; + } + } + + lumaInfo.itemNb = sampleNb; + lumaInfo.minLum = minLuminance; + lumaInfo.maxLum = maxLuminance; + lumaInfo.meanLum = meanLuminance; } int aliceVision_main(int argc, char** argv) @@ -115,11 +143,13 @@ int aliceVision_main(int argc, char** argv) std::string sfmInputDataFilename; std::string samplesFolder; std::string outputResponsePath; - ECalibrationMethod calibrationMethod = ECalibrationMethod::LINEAR; + ECalibrationMethod calibrationMethod = ECalibrationMethod::DEBEVEC; std::string calibrationWeightFunction = "default"; int nbBrackets = 0; int channelQuantizationPower = 10; + image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB; size_t maxTotalPoints = 1000000; + bool byPass = false; // Command line parameters @@ -138,14 +168,18 @@ int aliceVision_main(int argc, char** argv) "Name of method used for camera calibration: linear, debevec, grossberg, laguerre.") ("calibrationWeight,w", po::value(&calibrationWeightFunction)->default_value(calibrationWeightFunction), "Weight function used to calibrate camera response (default depends on the calibration method, gaussian, " - "triangle, plateau).") + "triangle, plateau).") ("nbBrackets,b", po::value(&nbBrackets)->default_value(nbBrackets), "bracket count per HDR image (0 means automatic).") + ("byPass", po::value(&byPass)->default_value(byPass), + "bypass HDR creation and use a single bracket as input for next steps") ("channelQuantizationPower", po::value(&channelQuantizationPower)->default_value(channelQuantizationPower), "Quantization level like 8 bits or 10 bits.") + ("workingColorSpace", po::value(&workingColorSpace)->default_value(workingColorSpace), + ("Working color space: " + image::EImageColorSpace_informations()).c_str()) ("maxTotalPoints", po::value(&maxTotalPoints)->default_value(maxTotalPoints), - "Max number of points used from the sampling. This ensures that the number of pixels values extracted by the sampling " - "can be managed by the calibration step (in term of computation time and memory usage).") + "Max number of points used from the sampling. This ensures that the number of pixels values extracted by the sampling " + "can be managed by the calibration step (in term of computation time and memory usage).") ; CmdLine cmdline("This program recovers the Camera Response Function (CRF) from samples extracted from LDR images with multi-bracketing.\n" @@ -216,29 +250,41 @@ int aliceVision_main(int argc, char** argv) } } + std::vector> v_luminanceInfos; std::vector> calibrationSamples; hdr::rgbCurve calibrationWeight(channelQuantization); + std::vector> groupedExposures; - if(calibrationMethod == ECalibrationMethod::LINEAR) + if (samplesFolder.empty()) { - ALICEVISION_LOG_INFO("No calibration needed in Linear."); - if(!samplesFolder.empty()) - { - ALICEVISION_LOG_WARNING("The provided input sampling folder will not be used."); - } + ALICEVISION_LOG_ERROR("A folder with selected samples is required to calibrate the Camera Response Function (CRF) and/or estimate the hdr output exposure level."); + return EXIT_FAILURE; } else { - if(samplesFolder.empty()) + // Build camera exposure table + for (int i = 0; i < groupedViews.size(); ++i) { - ALICEVISION_LOG_ERROR("A folder with selected samples is required to calibrate the Camera Response Function (CRF)."); - return EXIT_FAILURE; + const std::vector>& group = groupedViews[i]; + std::vector exposuresSetting; + + for (int j = 0; j < group.size(); ++j) + { + const sfmData::ExposureSetting exp = group[j]->getCameraExposureSetting(); + exposuresSetting.push_back(exp); + } + if (!sfmData::hasComparableExposures(exposuresSetting)) + { + ALICEVISION_THROW_ERROR("Camera exposure settings are inconsistent."); + } + groupedExposures.push_back(getExposures(exposuresSetting)); } + size_t group_pos = 0; hdr::Sampling sampling; ALICEVISION_LOG_INFO("Analyzing samples for each group"); - for(auto & group : groupedViews) + for (auto& group : groupedViews) { // Read from file const std::string samplesFilepath = (fs::path(samplesFolder) / (std::to_string(group_pos) + "_samples.dat")).string(); @@ -250,7 +296,7 @@ int aliceVision_main(int argc, char** argv) } std::size_t size; - fileSamples.read((char *)&size, sizeof(size)); + fileSamples.read((char*)&size, sizeof(size)); std::vector samples(size); for (std::size_t i = 0; i < size; ++i) @@ -260,50 +306,57 @@ int aliceVision_main(int argc, char** argv) sampling.analyzeSource(samples, channelQuantization, group_pos); - ++group_pos; - } + std::map luminanceInfos; + computeLuminanceStatFromSamples(samples, luminanceInfos); - // We need to trim samples list - sampling.filter(maxTotalPoints); + v_luminanceInfos.push_back(luminanceInfos); - ALICEVISION_LOG_INFO("Extracting samples for each group"); - group_pos = 0; + ++group_pos; + } - std::size_t total = 0; - for(auto & group : groupedViews) + if (!byPass) { - // Read from file - const std::string samplesFilepath = (fs::path(samplesFolder) / (std::to_string(group_pos) + "_samples.dat")).string(); - std::ifstream fileSamples(samplesFilepath, std::ios::binary); - if (!fileSamples.is_open()) - { - ALICEVISION_LOG_ERROR("Impossible to read samples from file " << samplesFilepath); - return EXIT_FAILURE; - } + // We need to trim samples list + sampling.filter(maxTotalPoints); - std::size_t size = 0; - fileSamples.read((char *)&size, sizeof(size)); + ALICEVISION_LOG_INFO("Extracting samples for each group"); + group_pos = 0; - std::vector samples(size); - for (int i = 0; i < size; ++i) + std::size_t total = 0; + for (auto& group : groupedViews) { - fileSamples >> samples[i]; + // Read from file + const std::string samplesFilepath = (fs::path(samplesFolder) / (std::to_string(group_pos) + "_samples.dat")).string(); + std::ifstream fileSamples(samplesFilepath, std::ios::binary); + if (!fileSamples.is_open()) + { + ALICEVISION_LOG_ERROR("Impossible to read samples from file " << samplesFilepath); + return EXIT_FAILURE; + } + + std::size_t size = 0; + fileSamples.read((char*)&size, sizeof(size)); + + std::vector samples(size); + for (int i = 0; i < size; ++i) + { + fileSamples >> samples[i]; + } + + std::vector out_samples; + sampling.extractUsefulSamples(out_samples, samples, group_pos); + + calibrationSamples.push_back(out_samples); + + ++group_pos; } - std::vector out_samples; - sampling.extractUsefulSamples(out_samples, samples, group_pos); - - calibrationSamples.push_back(out_samples); - - ++group_pos; - } - - // Define calibration weighting curve from name - boost::algorithm::to_lower(calibrationWeightFunction); - if(calibrationWeightFunction == "default") - { - switch(calibrationMethod) + // Define calibration weighting curve from name + boost::algorithm::to_lower(calibrationWeightFunction); + if (calibrationWeightFunction == "default") { + switch (calibrationMethod) + { case ECalibrationMethod::DEBEVEC: calibrationWeightFunction = hdr::EFunctionType_enumToString(hdr::EFunctionType::TRIANGLE); break; @@ -313,35 +366,19 @@ int aliceVision_main(int argc, char** argv) default: calibrationWeightFunction = hdr::EFunctionType_enumToString(hdr::EFunctionType::GAUSSIAN); break; + } } + calibrationWeight.setFunction(hdr::EFunctionType_stringToEnum(calibrationWeightFunction)); } - calibrationWeight.setFunction(hdr::EFunctionType_stringToEnum(calibrationWeightFunction)); } - ALICEVISION_LOG_INFO("Start calibration"); - hdr::rgbCurve response(channelQuantization); - - // Build camera exposure table - std::vector> groupedExposures; - for(int i = 0; i < groupedViews.size(); ++i) + if (!byPass) { - const std::vector>& group = groupedViews[i]; - std::vector exposuresSetting; + ALICEVISION_LOG_INFO("Start calibration"); + hdr::rgbCurve response(channelQuantization); - for(int j = 0; j < group.size(); ++j) - { - const sfmData::ExposureSetting exp = group[j]->getCameraExposureSetting(/*group[0]->getMetadataISO(), group[0]->getMetadataFNumber()*/); - exposuresSetting.push_back(exp); - } - if(!sfmData::hasComparableExposures(exposuresSetting)) + switch (calibrationMethod) { - ALICEVISION_THROW_ERROR("Camera exposure settings are inconsistent."); - } - groupedExposures.push_back(getExposures(exposuresSetting)); - } - - switch(calibrationMethod) - { case ECalibrationMethod::LINEAR: { // set the response function to linear @@ -374,13 +411,54 @@ int aliceVision_main(int argc, char** argv) calibration.process(calibrationSamples, groupedExposures, channelQuantization, false, response); break; } + } + + const std::string methodName = ECalibrationMethod_enumToString(calibrationMethod); + const std::string htmlOutput = (fs::path(outputResponsePath).parent_path() / (std::string("response_") + methodName + std::string(".html"))).string(); + + response.write(outputResponsePath); + response.writeHtml(htmlOutput, "response"); } - const std::string methodName = ECalibrationMethod_enumToString(calibrationMethod); - const std::string htmlOutput = (fs::path(outputResponsePath).parent_path() / (std::string("response_") + methodName + std::string(".html"))).string(); + const std::string lumastatFilename = (fs::path(outputResponsePath).parent_path() / "luminanceStatistics.txt").string(); + std::ofstream file(lumastatFilename); + if (!file) + { + ALICEVISION_LOG_ERROR("Unable to create file " << lumastatFilename << " for storing luminance statistics"); + return EXIT_FAILURE; + } + + file << v_luminanceInfos.size() << std::endl; + if (!v_luminanceInfos.empty()) + { + file << v_luminanceInfos[0].size() << std::endl; + file << "# viewId ; exposure ; sampleNumber ; meanLuminance ; minLuminance ; maxLuminance" << std::endl; - response.write(outputResponsePath); - response.writeHtml(htmlOutput, "response"); + for (int i = 0; i < v_luminanceInfos.size(); ++i) + { + while (!v_luminanceInfos[i].empty()) + { + // search min exposure + IndexT srcIdWithMinimalExposure = UndefinedIndexT; + double exposureMin = 1e9; + for (auto it = v_luminanceInfos[i].begin(); it != v_luminanceInfos[i].end(); it++) + { + if ((it->second).exposure < exposureMin) + { + exposureMin = (it->second).exposure; + srcIdWithMinimalExposure = it->first; + } + } + // write in file + file << srcIdWithMinimalExposure << " "; + file << v_luminanceInfos[i][srcIdWithMinimalExposure].exposure << " " << v_luminanceInfos[i][srcIdWithMinimalExposure].itemNb << " "; + file << v_luminanceInfos[i][srcIdWithMinimalExposure].meanLum / v_luminanceInfos[i][srcIdWithMinimalExposure].itemNb << " "; + file << v_luminanceInfos[i][srcIdWithMinimalExposure].minLum << " " << v_luminanceInfos[i][srcIdWithMinimalExposure].maxLum << std::endl; + // erase from map + v_luminanceInfos[i].erase(srcIdWithMinimalExposure); + } + } + } return EXIT_SUCCESS; } diff --git a/src/software/pipeline/main_LdrToHdrMerge.cpp b/src/software/pipeline/main_LdrToHdrMerge.cpp index 879c9c3bf5..b84091ae0c 100644 --- a/src/software/pipeline/main_LdrToHdrMerge.cpp +++ b/src/software/pipeline/main_LdrToHdrMerge.cpp @@ -54,8 +54,9 @@ int aliceVision_main(int argc, char** argv) int nbBrackets = 3; bool byPass = false; int channelQuantizationPower = 10; + int offsetRefBracketIndex = 1000; // By default, use the automatic selection + double meanTargetedLumaForMerging = 0.4; image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB; - int offsetRefBracketIndex = 0; hdr::EFunctionType fusionWeightFunction = hdr::EFunctionType::GAUSSIAN; float highlightCorrectionFactor = 0.0f; @@ -90,6 +91,8 @@ int aliceVision_main(int argc, char** argv) "Weight function used to fuse all LDR images together (gaussian, triangle, plateau).") ("offsetRefBracketIndex", po::value(&offsetRefBracketIndex)->default_value(offsetRefBracketIndex), "Zero to use the center bracket. +N to use a more exposed bracket or -N to use a less exposed backet.") + ("meanTargetedLumaForMerging", po::value(&meanTargetedLumaForMerging)->default_value(meanTargetedLumaForMerging), + "Mean expected luminance after merging step when input LDR images are decoded in sRGB color space. Must be in the range [0, 1].") ("highlightTargetLux", po::value(&highlightTargetLux)->default_value(highlightTargetLux), "Highlights maximum luminance.") ("highlightCorrectionFactor", po::value(&highlightCorrectionFactor)->default_value(highlightCorrectionFactor), @@ -98,9 +101,9 @@ int aliceVision_main(int argc, char** argv) ("storageDataType", po::value(&storageDataType)->default_value(storageDataType), ("Storage data type: " + image::EStorageDataType_informations()).c_str()) ("rangeStart", po::value(&rangeStart)->default_value(rangeStart), - "Range image index start.") + "Range image index start.") ("rangeSize", po::value(&rangeSize)->default_value(rangeSize), - "Range size."); + "Range size."); CmdLine cmdline("This program merges LDR images into HDR images.\n" "AliceVision LdrToHdrMerge"); @@ -154,6 +157,7 @@ int aliceVision_main(int argc, char** argv) return EXIT_FAILURE; } + std::size_t usedNbBrackets; { std::set sizeOfGroups; for(auto& group : groupedViews) @@ -162,7 +166,7 @@ int aliceVision_main(int argc, char** argv) } if(sizeOfGroups.size() == 1) { - std::size_t usedNbBrackets = *sizeOfGroups.begin(); + usedNbBrackets = *sizeOfGroups.begin(); if(usedNbBrackets == 1) { ALICEVISION_LOG_INFO("No multi-bracketing."); @@ -178,22 +182,48 @@ int aliceVision_main(int argc, char** argv) } } std::vector> targetViews; - hdr::selectTargetViews(targetViews, groupedViews, offsetRefBracketIndex); + + if (!byPass) + { + const int middleIndex = usedNbBrackets / 2; + const int targetIndex = middleIndex + offsetRefBracketIndex; + const bool isOffsetRefBracketIndexValid = (targetIndex >= 0) && (targetIndex < usedNbBrackets); + + const fs::path lumaStatFilepath(fs::path(inputResponsePath).parent_path() / (std::string("luminanceStatistics.txt"))); + + if (!fs::is_regular_file(lumaStatFilepath) && !isOffsetRefBracketIndexValid) + { + ALICEVISION_LOG_ERROR("Unable to open the file " << lumaStatFilepath.string() << " with luminance statistics. This file is needed to select the optimal exposure for the creation of HDR images."); + return EXIT_FAILURE; + } + + // Adjust the targeted luminance level by removing the corresponding gamma if the working color space is not sRGB. + if (workingColorSpace != image::EImageColorSpace::SRGB) + { + meanTargetedLumaForMerging = std::pow((meanTargetedLumaForMerging + 0.055) / 1.055, 2.2); + } + hdr::selectTargetViews(targetViews, groupedViews, offsetRefBracketIndex, lumaStatFilepath.string(), meanTargetedLumaForMerging); + + if ((targetViews.empty() || targetViews.size() != groupedViews.size()) && !isOffsetRefBracketIndexValid) + { + ALICEVISION_LOG_ERROR("File " << lumaStatFilepath.string() << " is not valid. This file is required to select the optimal exposure for the creation of HDR images."); + return EXIT_FAILURE; + } + } // Define range to compute if(rangeStart != -1) { - if(rangeStart < 0 || rangeSize < 0 || - rangeStart > groupedViews.size()) - { - ALICEVISION_LOG_ERROR("Range is incorrect"); - return EXIT_FAILURE; - } + if(rangeStart < 0 || rangeSize < 0 || rangeStart > groupedViews.size()) + { + ALICEVISION_LOG_ERROR("Range is incorrect"); + return EXIT_FAILURE; + } - if(rangeStart + rangeSize > groupedViews.size()) - { - rangeSize = groupedViews.size() - rangeStart; - } + if(rangeStart + rangeSize > groupedViews.size()) + { + rangeSize = groupedViews.size() - rangeStart; + } } else { @@ -211,7 +241,20 @@ int aliceVision_main(int argc, char** argv) // Export a new sfmData with HDR images as new Views. for(std::size_t g = 0; g < groupedViews.size(); ++g) { - std::shared_ptr hdrView = std::make_shared(*targetViews[g]); + std::shared_ptr hdrView; + if (groupedViews[g].size() == 1) + { + hdrView = std::make_shared(*groupedViews[g][0]); + } + else if (targetViews.empty()) + { + ALICEVISION_LOG_ERROR("Target view for HDR merging has not been computed"); + return EXIT_FAILURE; + } + else + { + hdrView = std::make_shared(*targetViews[g]); + } if(!byPass) { const std::string hdrImagePath = getHdrImagePath(outputPath, g); diff --git a/src/software/pipeline/main_LdrToHdrSampling.cpp b/src/software/pipeline/main_LdrToHdrSampling.cpp index e412d64a91..1d98092bd5 100644 --- a/src/software/pipeline/main_LdrToHdrSampling.cpp +++ b/src/software/pipeline/main_LdrToHdrSampling.cpp @@ -44,6 +44,7 @@ #define ALICEVISION_SOFTWARE_VERSION_MINOR 1 using namespace aliceVision; +using namespace aliceVision::hdr; namespace po = boost::program_options; namespace fs = boost::filesystem; @@ -54,8 +55,10 @@ int aliceVision_main(int argc, char** argv) std::string outputFolder; int nbBrackets = 0; int channelQuantizationPower = 10; + ECalibrationMethod calibrationMethod = ECalibrationMethod::DEBEVEC; image::EImageColorSpace workingColorSpace = image::EImageColorSpace::SRGB; hdr::Sampling::Params params; + bool byPass = false; bool debug = false; int rangeStart = -1; @@ -74,10 +77,14 @@ int aliceVision_main(int argc, char** argv) optionalParams.add_options() ("nbBrackets,b", po::value(&nbBrackets)->default_value(nbBrackets), "bracket count per HDR image (0 means automatic).") + ("byPass", po::value(&byPass)->default_value(byPass), + "bypass HDR creation and use a single bracket as input for next steps") ("channelQuantizationPower", po::value(&channelQuantizationPower)->default_value(channelQuantizationPower), "Quantization level like 8 bits or 10 bits.") ("workingColorSpace", po::value(&workingColorSpace)->default_value(workingColorSpace), ("Working color space: " + image::EImageColorSpace_informations()).c_str()) + ("calibrationMethod,m", po::value(&calibrationMethod)->default_value(calibrationMethod), + "Name of method used for camera calibration: linear, debevec, grossberg, laguerre.") ("blockSize", po::value(¶ms.blockSize)->default_value(params.blockSize), "Size of the image tile to extract a sample.") ("radius", po::value(¶ms.radius)->default_value(params.radius), @@ -105,7 +112,6 @@ int aliceVision_main(int argc, char** argv) HardwareContext hwc = cmdline.getHardwareContext(); omp_set_num_threads(hwc.getMaxThreads()); - const std::size_t channelQuantization = std::pow(2, channelQuantizationPower); // Read sfm data @@ -190,6 +196,7 @@ int aliceVision_main(int argc, char** argv) std::vector paths; std::vector exposuresSetting; + std::vector viewIds; image::ERawColorInterpretation rawColorInterpretation = image::ERawColorInterpretation::LibRawWhiteBalancing; std::string colorProfileFileName = ""; @@ -198,6 +205,7 @@ int aliceVision_main(int argc, char** argv) { paths.push_back(v->getImagePath()); exposuresSetting.push_back(v->getCameraExposureSetting()); + viewIds.push_back(v->getViewId()); const std::string rawColorInterpretation_str = v->getRawColorInterpretation(); rawColorInterpretation = image::ERawColorInterpretation_stringToEnum(rawColorInterpretation_str); @@ -216,8 +224,10 @@ int aliceVision_main(int argc, char** argv) imgReadOptions.rawColorInterpretation = rawColorInterpretation; imgReadOptions.colorProfileFileName = colorProfileFileName; + const bool simplifiedSampling = byPass || (calibrationMethod == ECalibrationMethod::LINEAR); + std::vector out_samples; - const bool res = hdr::Sampling::extractSamplesFromImages(out_samples, paths, exposures, width, height, channelQuantization, imgReadOptions, params); + const bool res = hdr::Sampling::extractSamplesFromImages(out_samples, paths, viewIds, exposures, width, height, channelQuantization, imgReadOptions, params, simplifiedSampling); if (!res) { ALICEVISION_LOG_ERROR("Error while extracting samples from group " << groupIdx);