diff --git a/src/aliceVision/hdr/brackets.cpp b/src/aliceVision/hdr/brackets.cpp index f647bacbd5..d9fca34f48 100644 --- a/src/aliceVision/hdr/brackets.cpp +++ b/src/aliceVision/hdr/brackets.cpp @@ -38,8 +38,9 @@ bool estimateBracketsFromSfmData(std::vectorgetImage().getMetadataFNumber()); double exp = view->getImage().getCameraExposureSetting().getExposure(); std::string path = view->getImage().getImagePath(); + int64_t timestamp = view->getImage().getMetadataDateTimestamp(); - LuminanceInfo li(viewIt.first, path, exp); + LuminanceInfo li(viewIt.first, path, exp, timestamp); luminances.push_back(li); } @@ -303,6 +304,97 @@ std::vector> splitMonotonics(const std::vector> splitTimes(const std::vector & luminanceInfos, int maxDeltaInABurst = 2) +{ + std::vector> splitted; + + if (luminanceInfos.size() == 0) + { + return splitted; + } + + // Split the luminanceInfos into groups which have close capture time (burst detection) + std::vector normedTimes(luminanceInfos); + // Sort luminanceinfos by timestamp + std::sort(normedTimes.begin(), + normedTimes.end(), + [](const LuminanceInfo& a, const LuminanceInfo& b) -> bool { + return (a.mtimestamp < b.mtimestamp); + }); + + const int histoSize = maxDeltaInABurst + 2; + std::vector histo(histoSize, 0); + for (int i = normedTimes.size() - 1; i >= 1; i--) + { + normedTimes[i].mtimestamp -= normedTimes[i - 1].mtimestamp; + histo[std::min(maxDeltaInABurst + 1, (int)normedTimes[i].mtimestamp)]++; + } + normedTimes[0].mtimestamp = 0; + + // Check for outliers at both sides of the sequence. + // At the beginning an item is an outlier if the next one has been captured more than maxDeltaInABurst seconds after. + // At the end an item is an outlier if the previous one has been captured more than maxDeltaInABurst seconds before. + int firstIndexValid = 0; + while (normedTimes[firstIndexValid + 1].mtimestamp > maxDeltaInABurst && firstIndexValid < normedTimes.size() - 1) + { + firstIndexValid++; + } + if (firstIndexValid == normedTimes.size() - 1) + { + return splitted; + } + int LastIndexValid = normedTimes.size() - 1; + while (normedTimes[LastIndexValid].mtimestamp > maxDeltaInABurst && LastIndexValid > firstIndexValid) + { + LastIndexValid--; + } + if (LastIndexValid == firstIndexValid) + { + return splitted; + } + + int outlierNumber = firstIndexValid + (normedTimes.size() - 1 - LastIndexValid); + + int nbItemLow = 0; + for (int i = 0; i <= maxDeltaInABurst ; i++) + { + nbItemLow += histo[i]; + } + const int nbItemHigh = histo.back(); + + int nbGroup; + int groupSize; + if (nbItemLow % (nbItemHigh + 1 - outlierNumber) == 0) + { + nbGroup = nbItemHigh + 1 - outlierNumber; + groupSize = (luminanceInfos.size() - outlierNumber) / nbGroup; + std::vector group; + std::map mexposure; + for (int i = firstIndexValid; i <= LastIndexValid ; i++) + { + group.push_back(normedTimes[i]); + mexposure[normedTimes[i].mexposure] = 0; + if (i % groupSize == groupSize - 1) + { + if (mexposure.size() == groupSize) + { + // All group's exposures are different from each others + splitted.push_back(group); + group.clear(); + mexposure.clear(); + } + else + { + splitted.clear(); + break; + } + } + } + } + + return splitted; +} + /** * @brief assume ref is smaller than larger * Try to find a subpart of larger which has the same set of exposures that smaller @@ -349,11 +441,15 @@ std::vector> estimateGroups(const std::vector //Split and order the items using path std::vector> splitted = splitBasedir(luminanceInfos); - //Create monotonic groups + //Create groups std::vector> monotonics; for (const auto & luminanceInfoOneDir : splitted) { - std::vector> lmonotonics = splitMonotonics(luminanceInfoOneDir); + std::vector> lmonotonics = splitTimes(luminanceInfoOneDir); + if (lmonotonics.empty()) + { + lmonotonics = splitMonotonics(luminanceInfoOneDir); + } monotonics.insert(monotonics.end(), lmonotonics.begin(), lmonotonics.end()); } @@ -426,7 +522,6 @@ std::vector> estimateGroups(const std::vector continue; } - //Compare with all valid monotonics int offset = -1; for (const auto& monotonic : monotonics) @@ -484,7 +579,9 @@ std::vector> estimateGroups(const std::vector groups.push_back(group); } - ALICEVISION_LOG_INFO("Groups found : " << monotonics.size()); + const int groupNumber = monotonics.size(); + const int outlierNumber = luminanceInfos.size() - groupNumber * bestBracketCount; + ALICEVISION_LOG_INFO(groupNumber << " group(s) of " << bestBracketCount << " bracket(s) and " << outlierNumber << " outlier(s) found."); return groups; } diff --git a/src/aliceVision/hdr/brackets.hpp b/src/aliceVision/hdr/brackets.hpp index f36b8db9c2..ab5a309a9d 100644 --- a/src/aliceVision/hdr/brackets.hpp +++ b/src/aliceVision/hdr/brackets.hpp @@ -17,13 +17,15 @@ struct LuminanceInfo aliceVision::IndexT mviewId; std::string mpath; float mexposure; + int64_t mtimestamp; LuminanceInfo() = default; - LuminanceInfo(aliceVision::IndexT vid, const std::string & path, float exposure): + LuminanceInfo(aliceVision::IndexT vid, const std::string & path, float exposure, int64_t timestamp): mviewId(vid), mpath(path), - mexposure(exposure) + mexposure(exposure), + mtimestamp(timestamp) { } @@ -100,6 +102,14 @@ inline std::istream& operator>>(std::istream& in, ECalibrationMethod& calibratio /** * @brief Estimate the brackets information from the SfM data + * The bracket groups are estimated using the capturing time metadata of each picture. + * if the duration between 2 consecutive pictures is less than 2.5 seconds the 2 pictures are considered to be part of the + * same brackecting group.In case several single pictures, located at the beginning or at the end of the image set after + * it has been sorted, they are considered as outliers and they won't be added to any returned group. + * With the remaining pictures, a check is done to be sure that every groups contain the same number of pictures. If this + * is not the case, this first tentative of splitting aborts. A second method is then applied. It consists to group pictures + * with monotonic exposure variation after sorting them in the alphabetic order. Outliers can be removed at the beginiing + * and at the end of the sorted pictures or between 2 groups. * @param[out] groups the estimated groups * @param[in] sfmData SfM data to estimate the brackets from * @param[in] countBrackets the number of brackets (optional, 0 means that it will be guessed)