diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 1aa0ab9e99..719886bd70 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -101,9 +101,9 @@ jobs: meshroom_avBranch=$(git ls-remote --heads https://github.com/alicevision/Meshroom.git $GITHUB_HEAD_REF | cut -f 1) if [ $meshroom_avBranch != "" ]; then git checkout $meshroom_avBranch; echo "Use Meshroom/$GITHUB_HEAD_REF"; fi export MESHROOM_INSTALL_DIR=$PWD - export PYTHONPATH=$PWD:${PYTHONPATH} + export PYTHONPATH=$PWD:${ALICEVISION_ROOT}:${PYTHONPATH} export PATH=$PATH:${ALICEVISION_ROOT}/bin - export LD_LIBRARY_PATH=${ALICEVISION_ROOT}/lib64:${DEPS_INSTALL_DIR}/lib64:${DEPS_INSTALL_DIR}/lib:${LD_LIBRARY_PATH} + export LD_LIBRARY_PATH=${ALICEVISION_ROOT}/lib:${ALICEVISION_ROOT}/lib64:${DEPS_INSTALL_DIR}/lib64:${DEPS_INSTALL_DIR}/lib:${LD_LIBRARY_PATH} mkdir ./outputData cd bin/ python3 --version @@ -120,7 +120,8 @@ jobs: cd SfM_quality_evaluation/ # checkout a specific commit to ensure repeatability git checkout 36e3bf2d05c64d1726cb4a0e770923794f203f98 - export LD_LIBRARY_PATH=${ALICEVISION_ROOT}/lib64:${DEPS_INSTALL_DIR}/lib64:${DEPS_INSTALL_DIR}/lib:${LD_LIBRARY_PATH} + export PYTHONPATH=${ALICEVISION_ROOT}:${PYTHONPATH} + export LD_LIBRARY_PATH=${ALICEVISION_ROOT}/lib:${ALICEVISION_ROOT}/lib64:${DEPS_INSTALL_DIR}/lib64:${DEPS_INSTALL_DIR}/lib:${LD_LIBRARY_PATH} echo "ldd aliceVision_cameraInit" ldd ${ALICEVISION_ROOT}/bin/aliceVision_cameraInit python --version diff --git a/src/aliceVision/hdr/Brackets.i b/src/aliceVision/hdr/Brackets.i index 7bdcb1ee01..6267be082d 100644 --- a/src/aliceVision/hdr/Brackets.i +++ b/src/aliceVision/hdr/Brackets.i @@ -4,8 +4,19 @@ // 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/. + %include %{ #include -%} \ No newline at end of file + +using namespace aliceVision; +using namespace aliceVision::hdr; +%} + + +%include "std_vector.i" + +%template(vectorli) std::vector; +%template(vvectori) std::vector>; + diff --git a/src/aliceVision/hdr/Hdr.i b/src/aliceVision/hdr/Hdr.i index dcabcf1695..8718495f79 100644 --- a/src/aliceVision/hdr/Hdr.i +++ b/src/aliceVision/hdr/Hdr.i @@ -6,4 +6,6 @@ %module (module="pyalicevision") hdr -%include \ No newline at end of file +%include +%include + diff --git a/src/aliceVision/hdr/brackets.cpp b/src/aliceVision/hdr/brackets.cpp index 9ea739ae2b..c76dff2a95 100644 --- a/src/aliceVision/hdr/brackets.cpp +++ b/src/aliceVision/hdr/brackets.cpp @@ -24,30 +24,23 @@ bool estimateBracketsFromSfmData(std::vector> viewsOrderedByName; + std::set fnumbers; + std::vector luminances; for (auto& viewIt : sfmData.getViews()) { - viewsOrderedByName.push_back(viewIt.second); - } - - std::sort(viewsOrderedByName.begin(), - viewsOrderedByName.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { - if (a == nullptr || b == nullptr) - return true; - - std::filesystem::path path_a(a->getImage().getImagePath()); - std::filesystem::path path_b(b->getImage().getImagePath()); - - return (path_a.stem().string() < path_b.stem().string()); - }); + IndexT id = viewIt.first; + auto view = viewIt.second; + if (view ==nullptr) + { + continue; + } - // Print a warning if the aperture changes. - std::set fnumbers; - for (auto& view : viewsOrderedByName) - { fnumbers.insert(view->getImage().getMetadataFNumber()); + double exp = view->getImage().getCameraExposureSetting().getExposure(); + std::string path = view->getImage().getImagePath(); + + LuminanceInfo li(viewIt.first, path, exp); + luminances.push_back(li); } if (fnumbers.size() != 1) @@ -61,119 +54,25 @@ bool estimateBracketsFromSfmData(std::vector> group; - double lastExposure = std::numeric_limits::min(); - for (auto& view : viewsOrderedByName) + std::vector> groupsids; + + if (countBrackets == 0) { - if (countBrackets > 0) - { - group.push_back(view); - if (group.size() == countBrackets) - { - groups.push_back(group); - group.clear(); - } - } - else - { - // Automatically determines the number of brackets - double exp = view->getImage().getCameraExposureSetting().getExposure(); - if (exp <= lastExposure) - { - groups.push_back(group); - group.clear(); - } - - lastExposure = exp; - group.push_back(view); - } - } - - if (!group.empty()) - { - groups.push_back(group); - } - - // Vote for the best bracket count - std::map counters; - for (const auto& group : groups) - { - size_t bracketCount = group.size(); - if (counters.find(bracketCount) != counters.end()) - { - counters[bracketCount]++; - } - else - { - counters[bracketCount] = 1; - } - } - - int maxSize = 0; - int bestBracketCount = 0; - for (const auto& item : counters) - { - if (item.second > maxSize) - { - maxSize = item.second; - bestBracketCount = item.first; - } - else if (item.second == maxSize && item.first > bestBracketCount) - { - // If two brackets size have the same vote number, - // keep the larger one (this avoids keeping only the outlier) - bestBracketCount = item.first; - } - } - - // Only keep groups with the majority bracket size - auto groupIt = groups.begin(); - while (groupIt != groups.end()) - { - if (groupIt->size() != bestBracketCount) - { - groupIt = groups.erase(groupIt); - } - else - { - groupIt++; - } - } - - std::vector> v_exposuresSetting; - for (auto& group : groups) + groupsids = estimateGroups(luminances); + } + else { - // Sort all images by exposure time - std::sort(group.begin(), group.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool { - if (a == nullptr || b == nullptr) - return true; - return (a->getImage().getCameraExposureSetting().getExposure() < b->getImage().getCameraExposureSetting().getExposure()); - }); - - std::vector exposuresSetting; - for (auto& v : group) - { - exposuresSetting.push_back(v->getImage().getCameraExposureSetting()); - } - v_exposuresSetting.push_back(exposuresSetting); + groupsids = divideGroups(luminances, countBrackets); } - // Check exposure consistency between group - if (v_exposuresSetting.size() > 1) + for (const auto & group : groupsids) { - for (int g = 1; g < v_exposuresSetting.size(); ++g) + std::vector> gview; + for (const auto & id : group) { - 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; - } - } + gview.push_back(sfmData.getViews().at(id)); } + groups.push_back(gview); } return true; @@ -285,5 +184,382 @@ int selectTargetViews(std::vector>& targetViews, return targetIndex; } +std::vector> splitBasedir(const std::vector & luminanceInfos) +{ + std::vector> splitted; + + if (luminanceInfos.size() == 0) + { + return splitted; + } + + //Ignore non existing files + //Remove relative paths + //Remove symlinks + //This will enable correct path comparison + std::vector correctedPaths; + for (const auto item : luminanceInfos) + { + if (!std::filesystem::exists(item.mpath)) + { + continue; + } + + LuminanceInfo corrected = item; + corrected.mpath = std::filesystem::canonical(item.mpath).string(); + + correctedPaths.push_back(corrected); + } + + //Sort luminanceinfos by names + std::sort(correctedPaths.begin(), + correctedPaths.end(), + [](const LuminanceInfo& a, const LuminanceInfo& b) -> bool { + return (a.mpath < b.mpath); + }); + + //Split items per base directory + std::vector current; + for (int index = 0; index < correctedPaths.size(); index++) + { + if (index == 0) + { + current.push_back(correctedPaths[index]); + continue; + } + + std::filesystem::path pathCur(correctedPaths[index].mpath); + std::filesystem::path pathPrev(correctedPaths[index - 1].mpath); + + if (pathCur.parent_path() != pathPrev.parent_path()) + { + splitted.push_back(current); + current.clear(); + } + + current.push_back(correctedPaths[index]); + } + splitted.push_back(current); + + return splitted; +} + +std::vector> splitMonotonics(const std::vector & luminanceInfos) +{ + std::vector> splitted; + + if (luminanceInfos.size() == 0) + { + return splitted; + } + + //Split the luminanceInfos into groups which have monotonic values + //(either increasing or decreasing) + std::vector current; + current.push_back(luminanceInfos[0]); + for (int index = 1; index < luminanceInfos.size(); index++) + { + float val = luminanceInfos[index].mexposure; + float prev = luminanceInfos[index - 1].mexposure; + + + if (val == prev) + { + splitted.push_back(current); + + current.clear(); + current.push_back(luminanceInfos[index]); + continue; + } + + if (index + 1 == luminanceInfos.size()) + { + current.push_back(luminanceInfos[index]); + continue; + } + + float next = luminanceInfos[index + 1].mexposure; + + //If sign is negative, then the function is not locally monotonic + float sign = (next - val) * (val - prev); + + if (sign < 0) + { + current.push_back(luminanceInfos[index]); + splitted.push_back(current); + current.clear(); + //Extremity is added on both groups + current.push_back(luminanceInfos[index]); + } + else + { + current.push_back(luminanceInfos[index]); + } + } + + //Close the last group + splitted.push_back(current); + + 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 +* @param smaller the set to compare +* @param larger the set where the subpart should be +* @return the index of the subpart or -1 if not found +*/ +int extractIndex(const std::vector & smaller, const std::vector & larger) +{ + int largerSize = larger.size(); + int smallerSize = smaller.size(); + int diff = largerSize - smallerSize; + + //For all continuous subparts of the erased sequence + for (int indexStart = 0; indexStart < diff; indexStart++) + { + //Check that the subpart is the same set of exposures + bool allCorrect = true; + for (int pos = 0; pos < smallerSize; pos++) + { + if (smaller[pos].mexposure != larger[indexStart + pos].mexposure) + { + allCorrect = false; + } + } + + if (allCorrect) + { + return indexStart; + } + } + + return -1; +} + + +std::vector> estimateGroups(const std::vector & luminanceInfos) +{ + std::vector> groups; + if (luminanceInfos.size() == 0) + { + return groups; + } + + //Split and order the items using path + std::vector> splitted = splitBasedir(luminanceInfos); + //Create monotonic groups + std::vector> monotonics; + for (const auto & luminanceInfoOneDir : splitted) + { + std::vector> lmonotonics = splitMonotonics(luminanceInfoOneDir); + monotonics.insert(monotonics.end(), lmonotonics.begin(), lmonotonics.end()); + } + + //Sort the voters groups by exposure increasing + for (auto & group : monotonics) + { + std::sort(group.begin(), + group.end(), + [](const LuminanceInfo& a, const LuminanceInfo& b) { + return (a.mexposure < b.mexposure); + }); + } + + // Vote for the best bracket count + std::map counters; + for (const auto& group : monotonics) + { + size_t bracketCount = group.size(); + if (counters.find(bracketCount) != counters.end()) + { + counters[bracketCount]++; + } + else + { + counters[bracketCount] = 1; + } + } + + int maxSize = 0; + int bestBracketCount = 0; + for (const auto& item : counters) + { + if (item.second > maxSize) + { + maxSize = item.second; + bestBracketCount = item.first; + } + else if (item.second == maxSize && item.first > bestBracketCount) + { + // If two brackets size have the same vote number, + // keep the larger one (this avoids keeping only the outlier) + bestBracketCount = item.first; + } + } + + // Only keep voters with the majority bracket size + auto groupIt = monotonics.begin(); + std::vector> eraseds; + while (groupIt != monotonics.end()) + { + if (groupIt->size() != bestBracketCount) + { + eraseds.push_back(*groupIt); + groupIt = monotonics.erase(groupIt); + } + else + { + groupIt++; + } + } + + //Try to push back erased + for (const auto & erased: eraseds) + { + //If erased is larger than the most voted, then + //Maybe it contains outliers. Try to find a correct subpart + int diff = int(erased.size()) - bestBracketCount; + if (diff < 0) + { + continue; + } + + + //Compare with all valid monotonics + int offset = -1; + for (const auto& monotonic : monotonics) + { + offset = extractIndex(monotonic, erased); + if (offset >= 0) + { + break; + } + } + + //If something found, put it back on list of monotonics + if (offset >= 0) + { + std::vector subpart; + for (int index = 0; index < bestBracketCount; index++) + { + subpart.push_back(erased[offset + index]); + } + monotonics.push_back(subpart); + } + } + + //check coherency + bool coherency = true; + for (int idref = 1; idref < monotonics.size(); ++idref) + { + const int idprev = idref - 1; + for (int idExposure = 0; idExposure < monotonics[idref].size(); ++idExposure) + { + if (!(monotonics[idref][idExposure].mexposure == monotonics[idprev][idExposure].mexposure)) + { + 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."); + coherency = false; + + break; + } + } + + if (!coherency) + { + break; + } + } + + for (const auto & monotonic : monotonics) + { + std::vector group; + for (const auto & li : monotonic) + { + group.push_back(li.mviewId); + } + groups.push_back(group); + } + + ALICEVISION_LOG_INFO("Groups found : " << monotonics.size()); + + return groups; +} + +std::vector> divideGroups(const std::vector & luminanceInfos, unsigned bracketSize) +{ + std::vector> groups; + if (luminanceInfos.size() == 0) + { + return groups; + } + + //Split and order the items using path + std::vector> splitted = splitBasedir(luminanceInfos); + + std::vector> divided; + + for (const auto & item : splitted) + { + if (item.size() % bracketSize != 0) + { + ALICEVISION_LOG_ERROR("Input bracket size is not compatible with the number of images"); + return groups; + } + + //For each group of bracketSize items + for (int index = 0; index < item.size(); index += bracketSize) + { + //Create a new set + std::vector part; + for (int bracket = 0; bracket < bracketSize; bracket++) + { + part.push_back(item[index + bracket]); + } + + divided.push_back(part); + } + } + + //check coherency + bool coherency = true; + for (int idref = 1; idref < divided.size(); ++idref) + { + const int idprev = idref - 1; + for (int idExposure = 0; idExposure < divided[idref].size(); ++idExposure) + { + if (!(divided[idref][idExposure].mexposure == divided[idprev][idExposure].mexposure)) + { + 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."); + coherency = false; + + break; + } + } + + if (!coherency) + { + break; + } + } + + for (const auto & item : divided) + { + std::vector group; + for (const auto & li : item) + { + group.push_back(li.mviewId); + } + groups.push_back(group); + } + + return groups; +} + } // namespace hdr } // namespace aliceVision \ No newline at end of file diff --git a/src/aliceVision/hdr/brackets.hpp b/src/aliceVision/hdr/brackets.hpp index 8e37430dfb..f36b8db9c2 100644 --- a/src/aliceVision/hdr/brackets.hpp +++ b/src/aliceVision/hdr/brackets.hpp @@ -11,6 +11,24 @@ namespace aliceVision { namespace hdr { + +struct LuminanceInfo +{ + aliceVision::IndexT mviewId; + std::string mpath; + float mexposure; + + LuminanceInfo() = default; + + LuminanceInfo(aliceVision::IndexT vid, const std::string & path, float exposure): + mviewId(vid), + mpath(path), + mexposure(exposure) + { + + } +}; + enum class ECalibrationMethod { LINEAR, @@ -84,7 +102,7 @@ inline std::istream& operator>>(std::istream& in, ECalibrationMethod& calibratio * @brief Estimate the brackets information from the SfM data * @param[out] groups the estimated groups * @param[in] sfmData SfM data to estimate the brackets from - * @param[in] countBrackets the number of brackets + * @param[in] countBrackets the number of brackets (optional, 0 means that it will be guessed) * @return false if an error occurs (e.g. an invalid SfMData file has been provided), true otherwise */ bool estimateBracketsFromSfmData(std::vector>>& groups, @@ -110,5 +128,22 @@ int selectTargetViews(std::vector>& const std::string& targetIndexesFilename, const double meanTargetedLuma = 0.4); + + +/** + * @brief compute a set of group of viewids + * Each group should be the brackets of the same image + * @param luminanceInfos the input information about each image +*/ +std::vector> estimateGroups(const std::vector & luminanceInfos); + +/** + * @brief build a set of group of viewids + * Each group should be the brackets of the same image + * @param luminanceInfos the input information about each image + * @param bracketSize the number of brackets per hdr image (not estimated) +*/ +std::vector> divideGroups(const std::vector & luminanceInfos, unsigned bracketSize); + } // namespace hdr } // namespace aliceVision