Skip to content

Commit

Permalink
Merge pull request #1315 from alicevision/dev/hdrMerge_autoRefLevel
Browse files Browse the repository at this point in the history
[hdr] Compute the center exposure of the hdr automatically
  • Loading branch information
fabiencastan authored Feb 14, 2023
2 parents 0f46c10 + 8fed92a commit d2ddcfa
Show file tree
Hide file tree
Showing 8 changed files with 637 additions and 278 deletions.
110 changes: 103 additions & 7 deletions src/aliceVision/hdr/brackets.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "brackets.hpp"

#include <fstream>

#include <aliceVision/numeric/numeric.hpp>

#include <boost/filesystem.hpp>
Expand Down Expand Up @@ -87,6 +89,7 @@ bool estimateBracketsFromSfmData(std::vector<std::vector<std::shared_ptr<sfmData
groups.push_back(group);
}

std::vector< std::vector<sfmData::ExposureSetting>> v_exposuresSetting;
for(auto & group : groups)
{
// Sort all images by exposure time
Expand All @@ -96,21 +99,112 @@ bool estimateBracketsFromSfmData(std::vector<std::vector<std::shared_ptr<sfmData
return true;
return (a->getCameraExposureSetting().getExposure() < b->getCameraExposureSetting().getExposure());
});
std::vector<sfmData::ExposureSetting> 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<std::shared_ptr<sfmData::View>> & out_targetViews, const std::vector<std::vector<std::shared_ptr<sfmData::View>>> & groups, int offsetRefBracketIndex)
void selectTargetViews(std::vector<std::shared_ptr<sfmData::View>> & out_targetViews, const std::vector<std::vector<std::shared_ptr<sfmData::View>>> & 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<std::string> 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<double> 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)
{
Expand All @@ -119,6 +213,8 @@ void selectTargetViews(std::vector<std::shared_ptr<sfmData::View>> & out_targetV

out_targetViews.push_back(group[targetIndex]);
}
return;
}

}
}
70 changes: 69 additions & 1 deletion src/aliceVision/hdr/brackets.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,8 +90,11 @@ bool estimateBracketsFromSfmData(std::vector<std::vector<std::shared_ptr<sfmData
* @param[out] targetViews: estimated target views
* @param[in] groups: groups of Views corresponding to multi-bracketing. Warning: Needs be sorted by exposure time.
* @param[in] offsetRefBracketIndex: 0 mean center bracket and you can choose +N/-N to select the reference bracket
* @param[in] targetIndexesFilename: in case offsetRefBracketIndex is out of range the number of views in a group target indexes can be read from a text file
* if the file cannot be read or does not contain the expected number of values (same as view group number) and
* if offsetRefBracketIndex is out of range the number of views then a clamped values of offsetRefBracketIndex is considered
*/
void selectTargetViews(std::vector<std::shared_ptr<sfmData::View>> & out_targetViews, const std::vector<std::vector<std::shared_ptr<sfmData::View>>>& groups, int offsetRefBracketIndex);
void selectTargetViews(std::vector<std::shared_ptr<sfmData::View>> & out_targetViews, const std::vector<std::vector<std::shared_ptr<sfmData::View>>>& groups, int offsetRefBracketIndex, const std::string& targetIndexesFilename = "", const double meanTargetedLuma = 0.4);

}
}
8 changes: 7 additions & 1 deletion src/aliceVision/hdr/hdrTestCommon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ bool extractSamplesGroups(std::vector<std::vector<ImageSample>>& out_samples,
imgReadOptions.rawColorInterpretation = image::ERawColorInterpretation::LibRawWhiteBalancing;
imgReadOptions.workingColorSpace = image::EImageColorSpace::LINEAR;

std::vector<IndexT> viewIds;
for (size_t id = 0; id < imagePaths[idGroup].size(); ++id)
{
viewIds.push_back(id);
}

std::vector<ImageSample> groupSamples;
if (!Sampling::extractSamplesFromImages(groupSamples, imagePaths[idGroup],
if (!Sampling::extractSamplesFromImages(groupSamples, imagePaths[idGroup], viewIds,
times[idGroup], width, height,
channelQuantization, imgReadOptions,
Sampling::Params{}))
Expand Down
Loading

0 comments on commit d2ddcfa

Please sign in to comment.