diff --git a/modules/photo/src/hdr_common.cpp b/modules/photo/src/hdr_common.cpp index 70b24322b1..b2f2def688 100644 --- a/modules/photo/src/hdr_common.cpp +++ b/modules/photo/src/hdr_common.cpp @@ -59,16 +59,17 @@ void checkImageDimensions(const std::vector& images) } } -Mat triangleWeights() +Mat triangleWeights(int length) { // hat function - Mat w(LDR_SIZE, 1, CV_32F); - int half = LDR_SIZE / 2; - int maxVal = LDR_SIZE - 1; + CV_Assert(length >= 2); + Mat w(length, 1, CV_32F); + int half = length / 2; + int maxVal = length - 1; float epsilon = 1e-6f; w.at(0) = epsilon; - w.at(LDR_SIZE-1) = epsilon; - for (int i = 1; i < LDR_SIZE-1; i++){ + w.at(length - 1) = epsilon; + for (int i = 1; i < length - 1; i++){ w.at(i) = (i < half) ? static_cast(i) : static_cast(maxVal - i); @@ -76,15 +77,16 @@ Mat triangleWeights() return w; } -Mat RobertsonWeights() +Mat RobertsonWeights(int length) { - Mat weight(LDR_SIZE, 1, CV_32FC3); - float q = (LDR_SIZE - 1) / 4.0f; + CV_Assert(length >= 1); + Mat weight(length, 1, CV_32FC3); + float q = (length - 1) / 4.0f; float e4 = exp(4.f); float scale = e4/(e4 - 1.f); float shift = 1 / (1.f - e4); - for(int i = 0; i < LDR_SIZE; i++) { + for(int i = 0; i < length; i++) { float value = i / q - 2.0f; value = scale*exp(-value * value) + shift; weight.at(i) = Vec3f::all(value); @@ -104,11 +106,28 @@ void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation) merge(channels, dst); } -Mat linearResponse(int channels) +Mat linearResponse(int channels, int length) { - Mat response = Mat(LDR_SIZE, 1, CV_MAKETYPE(CV_32F, channels)); - for(int i = 0; i < LDR_SIZE; i++) { - response.at(i) = Vec3f::all(static_cast(i)); + CV_Assert(length >= 1); + Mat response = Mat(length, 1, CV_MAKETYPE(CV_32F, channels)); + + if (channels == 1) + { + for (int i = 0; i < length; i++) + { + response.at(i) = static_cast(i); + } + } + else if (channels == 3) + { + for (int i = 0; i < length; i++) + { + response.at(i) = Vec3f::all(static_cast(i)); + } + } + else + { + CV_Error(Error::StsBadArg, "Unsupported number of channels in linearResponse"); } return response; } diff --git a/modules/photo/src/hdr_common.hpp b/modules/photo/src/hdr_common.hpp index b9846fd7e9..cb96068bd9 100644 --- a/modules/photo/src/hdr_common.hpp +++ b/modules/photo/src/hdr_common.hpp @@ -50,13 +50,13 @@ namespace cv void checkImageDimensions(const std::vector& images); -Mat triangleWeights(); +Mat triangleWeights(int length = LDR_SIZE); void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation); -Mat RobertsonWeights(); +Mat RobertsonWeights(int length = LDR_SIZE); -Mat linearResponse(int channels); +Mat linearResponse(int channels, int length = LDR_SIZE); } #endif diff --git a/modules/photo/src/merge.cpp b/modules/photo/src/merge.cpp index 0707d27e9f..2f7868b4bd 100644 --- a/modules/photo/src/merge.cpp +++ b/modules/photo/src/merge.cpp @@ -66,25 +66,46 @@ public: CV_Assert(images.size() == times.total()); checkImageDimensions(images); - CV_Assert(images[0].depth() == CV_8U); + int depth = images[0].depth(); + CV_Assert(depth == CV_8U || depth == CV_16U || depth == CV_32F); int channels = images[0].channels(); Size size = images[0].size(); int CV_32FCC = CV_MAKETYPE(CV_32F, channels); + const bool use16bitLUT = (depth == CV_16U || depth == CV_32F); + const int lutLength = use16bitLUT ? 65536 : LDR_SIZE; + + std::vector lutImages(images.size()); + if (depth == CV_8U || depth == CV_16U) + { + lutImages = images; + } + else + { + const double scale = static_cast(lutLength - 1); + for (size_t i = 0; i < images.size(); ++i) + { + Mat clipped; + cv::max(images[i], 0.0, clipped); + cv::min(clipped, 1.0, clipped); + clipped.convertTo(lutImages[i], CV_16U, scale); + } + } + dst.create(images[0].size(), CV_32FCC); Mat result = dst.getMat(); Mat response = input_response.getMat(); if(response.empty()) { - response = linearResponse(channels); + response = linearResponse(channels, lutLength); response.at(0) = response.at(1); } Mat log_response; log(response, log_response); - CV_Assert(log_response.rows == LDR_SIZE && log_response.cols == 1 && + CV_Assert(log_response.rows == lutLength && log_response.cols == 1 && log_response.channels() == channels); Mat exp_values(times.clone()); @@ -95,19 +116,23 @@ public: split(result, result_split); Mat weight_sum = Mat::zeros(size, CV_32F); + Mat weights_lut = use16bitLUT + ? triangleWeights(lutLength) + : weights; + for(size_t i = 0; i < images.size(); i++) { std::vector splitted; - split(images[i], splitted); + split(lutImages[i], splitted); Mat w = Mat::zeros(size, CV_32F); for(int c = 0; c < channels; c++) { - LUT(splitted[c], weights, splitted[c]); + LUT(splitted[c], weights_lut, splitted[c]); w += splitted[c]; } w /= channels; Mat response_img; - LUT(images[i], log_response, response_img); + LUT(lutImages[i], log_response, response_img); split(response_img, splitted); for(int c = 0; c < channels; c++) { result_split[c] += w.mul(splitted[c] - exp_values.at((int)i)); @@ -328,28 +353,52 @@ public: CV_Assert(images.size() == times.total()); checkImageDimensions(images); - CV_Assert(images[0].depth() == CV_8U); + int depth = images[0].depth(); + CV_Assert(depth == CV_8U || depth == CV_16U || depth == CV_32F); int channels = images[0].channels(); int CV_32FCC = CV_MAKETYPE(CV_32F, channels); + const bool use16bitLUT = (depth == CV_16U || depth == CV_32F); + const int lutLength = use16bitLUT ? 65536 : LDR_SIZE; + + // Build LUT index images (see MergeDebevecImpl for details). + std::vector lutImages(images.size()); + if (depth == CV_8U || depth == CV_16U) + { + lutImages = images; + } + else // CV_32F + { + const double scale = static_cast(lutLength - 1); + for (size_t i = 0; i < images.size(); ++i) + { + images[i].convertTo(lutImages[i], CV_16U, scale); + } + } + dst.create(images[0].size(), CV_32FCC); Mat result = dst.getMat(); Mat response = input_response.getMat(); if(response.empty()) { - float middle = static_cast(LDR_SIZE) / 2.0f; - response = linearResponse(channels) / middle; + float middle = static_cast(lutLength) / 2.0f; + response = linearResponse(channels, lutLength) / middle; } - CV_Assert(response.rows == LDR_SIZE && response.cols == 1 && + CV_Assert(response.rows == lutLength && response.cols == 1 && response.channels() == channels); result = Mat::zeros(images[0].size(), CV_32FCC); Mat wsum = Mat::zeros(images[0].size(), CV_32FCC); + + Mat weight_lut = use16bitLUT + ? RobertsonWeights(lutLength) + : weight; + for(size_t i = 0; i < images.size(); i++) { Mat im, w; - LUT(images[i], weight, w); - LUT(images[i], response, im); + LUT(lutImages[i], weight_lut, w); + LUT(lutImages[i], response, im); result += times.at((int)i) * w.mul(im); wsum += times.at((int)i) * times.at((int)i) * w; diff --git a/modules/photo/test/test_hdr.cpp b/modules/photo/test/test_hdr.cpp index 264a7d7257..9922a01fff 100644 --- a/modules/photo/test/test_hdr.cpp +++ b/modules/photo/test/test_hdr.cpp @@ -209,6 +209,68 @@ TEST(Photo_MergeRobertson, regression) checkEqual(expected, result, eps, "MergeRobertson"); } +TEST(Photo_MergeDebevec, regression_depth_consistency) +{ + string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/"; + + vector images8; + vector times; + loadExposureSeq(test_path + "exposures/", images8, times); + + vector images16(images8.size()), images32(images8.size()); + for (size_t i = 0; i < images8.size(); ++i) + { + images8[i].convertTo(images16[i], CV_16UC3, 257.0); + images8[i].convertTo(images32[i], CV_32FC3, 1.0 / 255.0); + } + + Ptr merge = createMergeDebevec(); + Ptr map = createTonemap(); + + Mat hdr8, hdr16, hdr32; + merge->process(images8, hdr8, times); + merge->process(images16, hdr16, times); + merge->process(images32, hdr32, times); + + map->process(hdr8, hdr8); + map->process(hdr16, hdr16); + map->process(hdr32, hdr32); + + checkEqual(hdr8, hdr16, 2e-2f, "Debevec realdata 16U vs 8U"); + checkEqual(hdr8, hdr32, 2e-2f, "Debevec realdata 32F vs 8U"); +} + +TEST(Photo_MergeRobertson, regression_depth_consistency) +{ + string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/"; + + vector images8; + vector times; + loadExposureSeq(test_path + "exposures/", images8, times); + + vector images16(images8.size()), images32(images8.size()); + for (size_t i = 0; i < images8.size(); ++i) + { + images8[i].convertTo(images16[i], CV_16UC3, 257.0); + images8[i].convertTo(images32[i], CV_32FC3, 1.0 / 255.0); + } + + Ptr merge = createMergeRobertson(); + Ptr map = createTonemap(); + + Mat hdr8, hdr16, hdr32; + merge->process(images8, hdr8, times); + merge->process(images16, hdr16, times); + merge->process(images32, hdr32, times); + + map->process(hdr8, hdr8); + map->process(hdr16, hdr16); + map->process(hdr32, hdr32); + + checkEqual(hdr8, hdr16, 3e-2f, "Robertson realdata 16U vs 8U"); + checkEqual(hdr8, hdr32, 3e-2f, "Robertson realdata 32F vs 8U"); +} + TEST(Photo_CalibrateDebevec, regression) { string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";