Skip to content

Commit

Permalink
[HDR Calibration and Merging]
Browse files Browse the repository at this point in the history
Compute reference index for merging within the merging node after reading luminance statistics provided by the calibration node through a text file.
  • Loading branch information
demoulinv committed Jan 5, 2023
1 parent d58300a commit 6e2e7cb
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 67 deletions.
58 changes: 50 additions & 8 deletions src/aliceVision/hdr/brackets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ bool estimateBracketsFromSfmData(std::vector<std::vector<std::shared_ptr<sfmData
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, const std::string& targetIndexesFilename)
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)
{
// If targetIndexesFilename cannot be opened or is not valid target views are derived from the middle exposed views
// For odd number, there is no ambiguity on the middle image.
Expand All @@ -118,16 +118,58 @@ void selectTargetViews(std::vector<std::shared_ptr<sfmData::View>> & out_targetV
{
ALICEVISION_LOG_INFO("Use offsetRefBracketIndex parameter");
}
else // try to use the index in the file
else // try to use the luminance statistics of the LDR images stored in the file
{
ALICEVISION_LOG_INFO("offsetRefBracketIndex parameter out of range, read file containing the target index");
std::ifstream file(targetIndexesFilename);
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: " << targetIndexesFilename);
ALICEVISION_THROW_ERROR("Failed to open file: " << lumaStatFilepath);
std::vector<std::string> lines;
std::string line;
if (!std::getline(file, line))
ALICEVISION_THROW_ERROR("Failed to read from file: " << targetIndexesFilename);
targetIndex = atoi(line.c_str());
while (std::getline(file, line))
{
lines.push_back(line);
}
if ((lines.size() < 2) || (atoi(lines[0].c_str()) != groups.size()) || (atoi(lines[1].c_str()) < groups[0].size()))
{
ALICEVISION_THROW_ERROR("File: " << lumaStatFilepath << " is not a valid file");
}
int nbGroup = atoi(lines[0].c_str());
int nbExp = atoi(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[2 + j * nbExp + i]);
int exposure, nbItem;
double lumaMean, lumaMin, lumaMax;
if (!(iss >> exposure >> nbItem >> lumaMean >> lumaMin >> lumaMax))
{
ALICEVISION_THROW_ERROR("File: " << lumaStatFilepath << " is not a valid file");
}
lumaMeanMean += lumaMean;
}
v_lumaMeanMean.push_back(lumaMeanMean / nbGroup);
}

double minDiffWithLumaTarget = 1000.0;
targetIndex = 0;

for (int k = 0; k < v_lumaMeanMean.size(); ++k)
{
const double diffWithLumaTarget = fabs(v_lumaMeanMean[k] - meanTargetedLuma);
if (diffWithLumaTarget < minDiffWithLumaTarget)
{
minDiffWithLumaTarget = diffWithLumaTarget;
targetIndex = k;
}
}

ALICEVISION_LOG_INFO("offsetRefBracketIndex parameter automaticaly set to " << targetIndex);
}

for (auto& group : groups)
Expand Down
2 changes: 1 addition & 1 deletion src/aliceVision/hdr/brackets.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ bool estimateBracketsFromSfmData(std::vector<std::vector<std::shared_ptr<sfmData
* 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, const std::string& targetIndexesFilename = "");
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);

}
}
89 changes: 39 additions & 50 deletions src/software/pipeline/main_LdrToHdrCalibration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,54 +111,40 @@ inline std::istream& operator>>(std::istream& in, ECalibrationMethod& calibratio
return in;
}

int computeMergingRefIndex(const std::vector<double>& groupedExposure, const std::vector<hdr::ImageSample>& samples, const double meanTargetedLuma)
struct luminanceInfo
{
double meanLum;
double minLum;
double maxLum;
int itemNb;
};

void computeLuminanceStat(const std::vector<double>& groupedExposure, const std::vector<hdr::ImageSample>& samples, std::map<int, luminanceInfo>& luminanceInfos)
{
std::map<int, std::pair<int, double>> meanLum;
std::map<int, std::pair<double, double>> rangeLum;
for (int i = 0; i < groupedExposure.size(); i++)
{
meanLum[(int)(groupedExposure[i] * 1000)].first = 0;
meanLum[(int)(groupedExposure[i] * 1000)].second = 0.0;
rangeLum[(int)(groupedExposure[i] * 1000)].first = 1000.0;
rangeLum[(int)(groupedExposure[i] * 1000)].second = 0.0;
luminanceInfos[(int)(groupedExposure[i] * 1000)].itemNb = 0;
luminanceInfos[(int)(groupedExposure[i] * 1000)].meanLum = 0.0;
luminanceInfos[(int)(groupedExposure[i] * 1000)].minLum = 1000.0;
luminanceInfos[(int)(groupedExposure[i] * 1000)].maxLum = 0.0;
}
for (int i = 0; i < samples.size(); i++)
{
for (int j = 0; j < samples[i].descriptions.size(); j++)
{
double lum = image::Rgb2GrayLinear(samples[i].descriptions[j].mean[0], samples[i].descriptions[j].mean[1], samples[i].descriptions[j].mean[2]);
meanLum[(int)(samples[i].descriptions[j].exposure * 1000)].second += lum;
meanLum[(int)(samples[i].descriptions[j].exposure * 1000)].first++;
if (lum < rangeLum[(int)(samples[i].descriptions[j].exposure * 1000)].first)
luminanceInfos[(int)(samples[i].descriptions[j].exposure * 1000)].meanLum += lum;
luminanceInfos[(int)(samples[i].descriptions[j].exposure * 1000)].itemNb++;
if (lum < luminanceInfos[(int)(samples[i].descriptions[j].exposure * 1000)].minLum)
{
rangeLum[(int)(samples[i].descriptions[j].exposure * 1000)].first = lum;
luminanceInfos[(int)(samples[i].descriptions[j].exposure * 1000)].minLum = lum;
}
if (lum > rangeLum[(int)(samples[i].descriptions[j].exposure * 1000)].second)
if (lum > luminanceInfos[(int)(samples[i].descriptions[j].exposure * 1000)].maxLum)
{
rangeLum[(int)(samples[i].descriptions[j].exposure * 1000)].second = lum;
luminanceInfos[(int)(samples[i].descriptions[j].exposure * 1000)].maxLum = lum;
}
}
}

double minDiffWithLumaTarget = 1000.0;
int minDiffWithLumaTargetIdx = 0;

int k = 0;
for (const auto& n : meanLum)
{
double lumaMean = n.second.second / (double)n.second.first;

ALICEVISION_LOG_DEBUG('[' << n.first << "] : mean = " << lumaMean << " ; min = " << rangeLum[k].first << " ; max = " << rangeLum[k].second);

if (fabs(lumaMean - meanTargetedLuma) < minDiffWithLumaTarget)
{
minDiffWithLumaTarget = fabs(lumaMean - meanTargetedLuma);
minDiffWithLumaTargetIdx = k;
}
++k;
}

return minDiffWithLumaTargetIdx;
}

int aliceVision_main(int argc, char** argv)
Expand All @@ -171,7 +157,6 @@ int aliceVision_main(int argc, char** argv)
int nbBrackets = 0;
int channelQuantizationPower = 10;
size_t maxTotalPoints = 1000000;
double meanTargetedLumaForMerging = 0.4;

// Command line parameters

Expand All @@ -198,8 +183,6 @@ int aliceVision_main(int argc, char** argv)
("maxTotalPoints", po::value<size_t>(&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).")
("meanTargetedLumaForMerging", po::value<double>(&meanTargetedLumaForMerging)->default_value(meanTargetedLumaForMerging),
"Mean expected luminance after merging step. Must be in the range [0, 1].")
;

CmdLine cmdline("This program recovers the Camera Response Function (CRF) from samples extracted from LDR images with multi-bracketing.\n"
Expand Down Expand Up @@ -273,7 +256,7 @@ int aliceVision_main(int argc, char** argv)
std::vector<std::vector<hdr::ImageSample>> calibrationSamples;
hdr::rgbCurve calibrationWeight(channelQuantization);
std::vector<std::vector<double>> groupedExposures;
std::vector<int> refIndexes;
std::vector<std::map<int, luminanceInfo>> v_luminanceInfos;

if(calibrationMethod == ECalibrationMethod::LINEAR)
{
Expand Down Expand Up @@ -343,7 +326,9 @@ int aliceVision_main(int argc, char** argv)

sampling.analyzeSource(samples, channelQuantization, group_pos);

refIndexes.push_back(computeMergingRefIndex(groupedExposures[group_pos], samples, meanTargetedLumaForMerging));
std::map<int, luminanceInfo> luminanceInfos;
computeLuminanceStat(groupedExposures[group_pos], samples, luminanceInfos);
v_luminanceInfos.push_back(luminanceInfos);

++group_pos;
}
Expand Down Expand Up @@ -448,23 +433,27 @@ int aliceVision_main(int argc, char** argv)
response.write(outputResponsePath);
response.writeHtml(htmlOutput, "response");

double meanIndex = 0.0;
for (int i = 0; i < refIndexes.size(); i++)
{
meanIndex += static_cast<double>(refIndexes[i]);
}
const int refIndex = static_cast<int>(std::round(meanIndex /= refIndexes.size()));

const std::string expRefIndexFilename = (fs::path(outputResponsePath).parent_path() / (std::string("exposureRefIndex") + std::string(".txt"))).string();
std::ofstream file(expRefIndexFilename);
const std::string lumastatFilename = (fs::path(outputResponsePath).parent_path() / (std::string("luminanceStatistics") + std::string(".txt"))).string();
std::ofstream file(lumastatFilename);
if (!file)
{
ALICEVISION_LOG_WARNING("Unable to create file " << expRefIndexFilename << " for storing the exposure reference Index");
ALICEVISION_LOG_WARNING("You will have to set manually the dedicated paramater at merging stage.");
ALICEVISION_LOG_ERROR("Unable to create file " << lumastatFilename << " for storing luminance statistics");
return EXIT_FAILURE;
}
else
{
file << std::to_string(refIndex) + "\n";
file << v_luminanceInfos.size() << std::endl;
file << v_luminanceInfos[0].size() << std::endl;

for (int i = 0; i < v_luminanceInfos.size(); ++i)
{
for (auto it = v_luminanceInfos[i].begin(); it != v_luminanceInfos[i].end(); it++)
{
file << it->first << " " << (it->second).itemNb << " " << (it->second).meanLum / (it->second).itemNb << " ";
file << (it->second).minLum << " " << (it->second).maxLum << std::endl;
}
}

}

return EXIT_SUCCESS;
Expand Down
19 changes: 11 additions & 8 deletions src/software/pipeline/main_LdrToHdrMerge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ 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
int offsetRefBracketIndex = 1000; // By default, use the automatic selection
double meanTargetedLumaForMerging = 0.4;

hdr::EFunctionType fusionWeightFunction = hdr::EFunctionType::GAUSSIAN;
float highlightCorrectionFactor = 0.0f;
Expand Down Expand Up @@ -87,6 +88,8 @@ int aliceVision_main(int argc, char** argv)
"Weight function used to fuse all LDR images together (gaussian, triangle, plateau).")
("offsetRefBracketIndex", po::value<int>(&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<double>(&meanTargetedLumaForMerging)->default_value(meanTargetedLumaForMerging),
"Mean expected luminance after merging step. Must be in the range [0, 1].")
("highlightTargetLux", po::value<float>(&highlightTargetLux)->default_value(highlightTargetLux),
"Highlights maximum luminance.")
("highlightCorrectionFactor", po::value<float>(&highlightCorrectionFactor)->default_value(highlightCorrectionFactor),
Expand All @@ -95,9 +98,9 @@ int aliceVision_main(int argc, char** argv)
("storageDataType", po::value<image::EStorageDataType>(&storageDataType)->default_value(storageDataType),
("Storage data type: " + image::EStorageDataType_informations()).c_str())
("rangeStart", po::value<int>(&rangeStart)->default_value(rangeStart),
"Range image index start.")
"Range image index start.")
("rangeSize", po::value<int>(&rangeSize)->default_value(rangeSize),
"Range size.");
"Range size.");

CmdLine cmdline("This program merges LDR images into HDR images.\n"
"AliceVision LdrToHdrMerge");
Expand Down Expand Up @@ -183,19 +186,19 @@ int aliceVision_main(int argc, char** argv)
const int targetIndex = middleIndex + offsetRefBracketIndex;
const bool isOffsetRefBracketIndexValid = (targetIndex >= 0) && (targetIndex < usedNbBrackets);

const fs::path targetIndexFilepath(fs::path(inputResponsePath).parent_path() / (std::string("exposureRefIndex.txt")));
const fs::path lumaStatFilepath(fs::path(inputResponsePath).parent_path() / (std::string("luminanceStatistics.txt")));

if (!fs::is_regular_file(targetIndexFilepath) && !isOffsetRefBracketIndexValid)
if (!fs::is_regular_file(lumaStatFilepath) && !isOffsetRefBracketIndexValid)
{
ALICEVISION_LOG_ERROR("Unable to open the file " << targetIndexFilepath.string() << " with the selection of exposures. This file is needed to select the optimal exposure for the creation of HDR images.");
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;
}

hdr::selectTargetViews(targetViews, groupedViews, offsetRefBracketIndex, targetIndexFilepath.string());
hdr::selectTargetViews(targetViews, groupedViews, offsetRefBracketIndex, lumaStatFilepath.string(), meanTargetedLumaForMerging);

if (targetViews.empty() && !isOffsetRefBracketIndexValid)
{
ALICEVISION_LOG_ERROR("File " << targetIndexFilepath.string() << " is not valid. This file is required to select the optimal exposure for the creation of HDR images.");
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;
}
}
Expand Down

0 comments on commit 6e2e7cb

Please sign in to comment.