From bef8d8192ed323bd12535216f27f39d3d2feddc2 Mon Sep 17 00:00:00 2001 From: Fedor Morozov Date: Tue, 6 Aug 2013 15:24:01 +0400 Subject: [PATCH] Minor fixes, new MergeDebevec --- modules/highgui/src/grfmt_hdr.cpp | 13 +- modules/photo/doc/hdr_imaging.rst | 4 +- modules/photo/include/opencv2/photo.hpp | 3 + modules/photo/src/calibrate.cpp | 30 ++-- modules/photo/src/hdr_common.cpp | 6 +- modules/photo/src/merge.cpp | 180 +++++++++++++++--------- modules/photo/src/tonemap.cpp | 6 +- modules/photo/test/test_hdr.cpp | 10 +- 8 files changed, 159 insertions(+), 93 deletions(-) diff --git a/modules/highgui/src/grfmt_hdr.cpp b/modules/highgui/src/grfmt_hdr.cpp index 8e6b86e0d6..8d25fe9eee 100644 --- a/modules/highgui/src/grfmt_hdr.cpp +++ b/modules/highgui/src/grfmt_hdr.cpp @@ -90,6 +90,7 @@ bool HdrDecoder::readData(Mat& _img) } RGBE_ReadPixels_RLE(file, const_cast(img.ptr()), img.cols, img.rows); fclose(file); file = NULL; + if(_img.depth() == img.depth()) { img.convertTo(_img, _img.type()); } else { @@ -123,10 +124,16 @@ HdrEncoder::~HdrEncoder() bool HdrEncoder::write( const Mat& _img, const std::vector& params ) { - CV_Assert(_img.channels() == 3); Mat img; - if(_img.depth() != CV_32F) { - _img.convertTo(img, CV_32FC3, 1/255.0f); + CV_Assert(img.channels() == 3 || img.channels() == 1); + if(img.channels() == 1) { + std::vector splitted(3, _img); + merge(splitted, img); + } else { + _img.copyTo(img); + } + if(img.depth() != CV_32F) { + img.convertTo(img, CV_32FC3, 1/255.0f); } CV_Assert(params.empty() || params[0] == HDR_NONE || params[0] == HDR_RLE); FILE *fout = fopen(m_filename.c_str(), "wb"); diff --git a/modules/photo/doc/hdr_imaging.rst b/modules/photo/doc/hdr_imaging.rst index 1e150ee259..c5f989ae48 100644 --- a/modules/photo/doc/hdr_imaging.rst +++ b/modules/photo/doc/hdr_imaging.rst @@ -193,7 +193,7 @@ Recovers camera response. :param src: vector of input images - :param dst: matrix with calculated camera response, one column per channel + :param dst: matrix with calculated camera response :param times: vector of exposure time values for each image @@ -231,7 +231,7 @@ Merges images. :param times: vector of exposure time values for each image - :param response: matrix with camera response, one column per channel + :param response: one-column matrix with camera response MergeDebevec -------- diff --git a/modules/photo/include/opencv2/photo.hpp b/modules/photo/include/opencv2/photo.hpp index 8c709b3799..c3c91b8c23 100644 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@ -206,6 +206,9 @@ public: CV_WRAP virtual int getSamples() const = 0; CV_WRAP virtual void setSamples(int samples) = 0; + + CV_WRAP virtual bool getTest() const = 0; + CV_WRAP virtual void setTest(bool val) = 0; }; CV_EXPORTS_W Ptr createCalibrateDebevec(int samples = 50, float lambda = 10.0f); diff --git a/modules/photo/src/calibrate.cpp b/modules/photo/src/calibrate.cpp index 8a51f537ae..c8c51cb5f7 100644 --- a/modules/photo/src/calibrate.cpp +++ b/modules/photo/src/calibrate.cpp @@ -55,7 +55,8 @@ public: samples(samples), lambda(lambda), name("CalibrateDebevec"), - w(tringleWeights()) + w(tringleWeights()), + test(false) { } @@ -63,14 +64,19 @@ public: { std::vector images; src.getMatVector(images); - dst.create(256, images[0].channels(), CV_32F); - Mat response = dst.getMat(); - CV_Assert(!images.empty() && images.size() == times.size()); - CV_Assert(images[0].depth() == CV_8U); + CV_Assert(images.size() == times.size()); checkImageDimensions(images); + CV_Assert(images[0].depth() == CV_8U); - for(int channel = 0; channel < images[0].channels(); channel++) { + int channels = images[0].channels(); + int CV_32FCC = CV_MAKETYPE(CV_32F, channels); + + dst.create(256, 1, CV_32FCC); + Mat result = dst.getMat(); + + std::vector result_split(channels); + for(int channel = 0; channel < channels; channel++) { Mat A = Mat::zeros(samples * images.size() + 257, 256 + samples, CV_32F); Mat B = Mat::zeros(A.rows, 1, CV_32F); @@ -78,6 +84,9 @@ public: for(int i = 0; i < samples; i++) { int pos = 3 * (rand() % images[0].total()) + channel; + if(test) { + pos = 3 * i + channel; + } for(size_t j = 0; j < images.size(); j++) { int val = (images[j].ptr() + pos)[0]; @@ -98,11 +107,15 @@ public: } Mat solution; solve(A, B, solution, DECOMP_SVD); - solution.rowRange(0, 256).copyTo(response.col(channel)); + solution.rowRange(0, 256).copyTo(result_split[channel]); } - exp(response, response); + merge(result_split, result); + exp(result, result); } + bool getTest() const { return test; } + void setTest(bool val) { test = val; } + int getSamples() const { return samples; } void setSamples(int val) { samples = val; } @@ -128,6 +141,7 @@ protected: String name; int samples; float lambda; + bool test; Mat w; }; diff --git a/modules/photo/src/hdr_common.cpp b/modules/photo/src/hdr_common.cpp index 4aab5c0451..6e58a239e7 100644 --- a/modules/photo/src/hdr_common.cpp +++ b/modules/photo/src/hdr_common.cpp @@ -61,11 +61,9 @@ void checkImageDimensions(const std::vector& images) Mat tringleWeights() { - Mat w(256, 3, CV_32F); + Mat w(256, 1, CV_32F); for(int i = 0; i < 256; i++) { - for(int j = 0; j < 3; j++) { - w.at(i, j) = i < 128 ? i + 1.0f : 256.0f - i; - } + w.at(i) = i < 128 ? i + 1.0f : 256.0f - i; } return w; } diff --git a/modules/photo/src/merge.cpp b/modules/photo/src/merge.cpp index b7fcac6f83..7f45059c18 100644 --- a/modules/photo/src/merge.cpp +++ b/modules/photo/src/merge.cpp @@ -61,62 +61,87 @@ public: { std::vector images; src.getMatVector(images); - dst.create(images[0].size(), CV_MAKETYPE(CV_32F, images[0].channels())); - Mat result = dst.getMat(); CV_Assert(images.size() == times.size()); - CV_Assert(images[0].depth() == CV_8U); checkImageDimensions(images); + CV_Assert(images[0].depth() == CV_8U); + + int channels = images[0].channels(); + Size size = images[0].size(); + int CV_32FCC = CV_MAKETYPE(CV_32F, channels); + + dst.create(images[0].size(), CV_32FCC); + Mat result = dst.getMat(); Mat response = input_response.getMat(); - CV_Assert(response.rows == 256 && response.cols >= images[0].channels()); - Mat log_response; - log(response, log_response); - - std::vector exp_times(times.size()); - for(size_t i = 0; i < exp_times.size(); i++) { - exp_times[i] = logf(times[i]); + + if(response.empty()) { + response = linearResponse(channels); } + log(response, response); + CV_Assert(response.rows == 256 && response.cols == 1 && + response.channels() == channels); + + Mat exp_values(times); + log(exp_values, exp_values); - int channels = images[0].channels(); - float *res_ptr = result.ptr(); - for(size_t pos = 0; pos < result.total(); pos++, res_ptr += channels) { + result = Mat::zeros(size, CV_32FCC); + std::vector result_split; + split(result, result_split); + Mat weight_sum = Mat::zeros(size, CV_32F); - std::vector sum(channels, 0); - float weight_sum = 0; - for(size_t im = 0; im < images.size(); im++) { + for(size_t i = 0; i < images.size(); i++) { + std::vector splitted; + split(images[i], splitted); - uchar *img_ptr = images[im].ptr() + channels * pos; - float w = 0; - for(int channel = 0; channel < channels; channel++) { - w += weights.at(img_ptr[channel]); - } - w /= channels; - weight_sum += w; - for(int channel = 0; channel < channels; channel++) { - sum[channel] += w * (log_response.at(img_ptr[channel], channel) - exp_times[im]); - } + Mat w = Mat::zeros(size, CV_32F); + for(int c = 0; c < channels; c++) { + LUT(splitted[c], weights, splitted[c]); + w += splitted[c]; } - for(int channel = 0; channel < channels; channel++) { - res_ptr[channel] = exp(sum[channel] / weight_sum); + w /= channels; + + Mat response_img; + LUT(images[i], response, response_img); + split(response_img, splitted); + for(int c = 0; c < channels; c++) { + result_split[c] += w.mul(splitted[c] - exp_values.at(i)); } + weight_sum += w; } + weight_sum = 1.0f / weight_sum; + for(int c = 0; c < channels; c++) { + result_split[c] = result_split[c].mul(weight_sum); + } + merge(result_split, result); + exp(result, result); } void process(InputArrayOfArrays src, OutputArray dst, const std::vector& times) { - Mat response(256, 3, CV_32F); - for(int i = 0; i < 256; i++) { - for(int j = 0; j < 3; j++) { - response.at(i, j) = static_cast(max(i, 1)); - } - } - process(src, dst, times, response); + process(src, dst, times, Mat()); } protected: String name; Mat weights; + + Mat linearResponse(int channels) + { + Mat single_response = Mat(256, 1, CV_32F); + for(int i = 1; i < 256; i++) { + single_response.at(i) = static_cast(i); + } + single_response.at(0) = static_cast(1); + + std::vector splitted(channels); + for(int c = 0; c < channels; c++) { + splitted[c] = single_response; + } + Mat result; + merge(splitted, result); + return result; + } }; Ptr createMergeDebevec() @@ -146,33 +171,48 @@ public: src.getMatVector(images); checkImageDimensions(images); - std::vector weights(images.size()); - Mat weight_sum = Mat::zeros(images[0].size(), CV_32FC1); - for(size_t im = 0; im < images.size(); im++) { - Mat img, gray, contrast, saturation, wellexp; - std::vector channels(3); + int channels = images[0].channels(); + CV_Assert(channels == 1 || channels == 3); + Size size = images[0].size(); + int CV_32FCC = CV_MAKETYPE(CV_32F, channels); - images[im].convertTo(img, CV_32FC3, 1.0/255.0); - cvtColor(img, gray, COLOR_RGB2GRAY); - split(img, channels); + std::vector weights(images.size()); + Mat weight_sum = Mat::zeros(size, CV_32F); + + for(size_t i = 0; i < images.size(); i++) { + Mat img, gray, contrast, saturation, wellexp; + std::vector splitted(channels); + + images[i].convertTo(img, CV_32F, 1.0f/255.0f); + if(channels == 3) { + cvtColor(img, gray, COLOR_RGB2GRAY); + } else { + img.copyTo(gray); + } + split(img, splitted); Laplacian(gray, contrast, CV_32F); contrast = abs(contrast); - Mat mean = (channels[0] + channels[1] + channels[2]) / 3.0f; - saturation = Mat::zeros(channels[0].size(), CV_32FC1); - for(int i = 0; i < 3; i++) { - Mat deviation = channels[i] - mean; - pow(deviation, 2.0, deviation); + Mat mean = Mat::zeros(size, CV_32F); + for(int c = 0; c < channels; c++) { + mean += splitted[c]; + } + mean /= channels; + + saturation = Mat::zeros(size, CV_32F); + for(int c = 0; c < channels; c++) { + Mat deviation = splitted[c] - mean; + pow(deviation, 2.0f, deviation); saturation += deviation; } sqrt(saturation, saturation); - wellexp = Mat::ones(gray.size(), CV_32FC1); - for(int i = 0; i < 3; i++) { - Mat exp = channels[i] - 0.5f; - pow(exp, 2, exp); - exp = -exp / 0.08; + wellexp = Mat::ones(size, CV_32F); + for(int c = 0; c < channels; c++) { + Mat exp = splitted[c] - 0.5f; + pow(exp, 2.0f, exp); + exp = -exp / 0.08f; wellexp = wellexp.mul(exp); } @@ -180,33 +220,37 @@ public: pow(saturation, wsat, saturation); pow(wellexp, wexp, wellexp); - weights[im] = contrast; - weights[im] = weights[im].mul(saturation); - weights[im] = weights[im].mul(wellexp); - weight_sum += weights[im]; + weights[i] = contrast; + if(channels == 3) { + weights[i] = weights[i].mul(saturation); + } + weights[i] = weights[i].mul(wellexp); + weight_sum += weights[i]; } - int maxlevel = static_cast(logf(static_cast(max(images[0].rows, images[0].cols))) / logf(2.0)) - 1; + int maxlevel = static_cast(logf(static_cast(min(size.width, size.height))) / logf(2.0f)); std::vector res_pyr(maxlevel + 1); - for(size_t im = 0; im < images.size(); im++) { - weights[im] /= weight_sum; + for(size_t i = 0; i < images.size(); i++) { + weights[i] /= weight_sum; Mat img; - images[im].convertTo(img, CV_32FC3, 1/255.0); + images[i].convertTo(img, CV_32F, 1.0f/255.0f); + std::vector img_pyr, weight_pyr; buildPyramid(img, img_pyr, maxlevel); - buildPyramid(weights[im], weight_pyr, maxlevel); + buildPyramid(weights[i], weight_pyr, maxlevel); + for(int lvl = 0; lvl < maxlevel; lvl++) { Mat up; pyrUp(img_pyr[lvl + 1], up, img_pyr[lvl].size()); img_pyr[lvl] -= up; } for(int lvl = 0; lvl <= maxlevel; lvl++) { - std::vector channels(3); - split(img_pyr[lvl], channels); - for(int i = 0; i < 3; i++) { - channels[i] = channels[i].mul(weight_pyr[lvl]); + std::vector splitted(channels); + split(img_pyr[lvl], splitted); + for(int c = 0; c < channels; c++) { + splitted[c] = splitted[c].mul(weight_pyr[lvl]); } - merge(channels, img_pyr[lvl]); + merge(splitted, img_pyr[lvl]); if(res_pyr[lvl].empty()) { res_pyr[lvl] = img_pyr[lvl]; } else { @@ -219,7 +263,7 @@ public: pyrUp(res_pyr[lvl], up, res_pyr[lvl - 1].size()); res_pyr[lvl - 1] += up; } - dst.create(images[0].size(), CV_32FC3); + dst.create(size, CV_32FCC); res_pyr[0].copyTo(dst.getMat()); } diff --git a/modules/photo/src/tonemap.cpp b/modules/photo/src/tonemap.cpp index 9115b09f8f..1fd08954de 100644 --- a/modules/photo/src/tonemap.cpp +++ b/modules/photo/src/tonemap.cpp @@ -185,7 +185,7 @@ class TonemapDurandImpl : public TonemapDurand public: TonemapDurandImpl(float gamma, float contrast, float saturation, float sigma_color, float sigma_space) : gamma(gamma), - contrast(contrast), + contrast(contrast), saturation(saturation), sigma_color(sigma_color), sigma_space(sigma_space), @@ -462,8 +462,8 @@ protected: void signedPow(Mat src, float power, Mat& dst) { Mat sign = (src > 0); - sign.convertTo(sign, CV_32F, 1/255.0f); - sign = sign * 2 - 1; + sign.convertTo(sign, CV_32F, 1.0f/255.0f); + sign = sign * 2.0f - 1.0f; pow(abs(src), power, dst); dst = dst.mul(sign); } diff --git a/modules/photo/test/test_hdr.cpp b/modules/photo/test/test_hdr.cpp index 693eac79be..b0a7e49383 100644 --- a/modules/photo/test/test_hdr.cpp +++ b/modules/photo/test/test_hdr.cpp @@ -78,11 +78,11 @@ void loadExposureSeq(String path, vector& images, vector& times = DE void loadResponseCSV(String path, Mat& response) { - response = Mat(256, 3, CV_32F); + response = Mat(256, 1, CV_32FC3); ifstream resp_file(path.c_str()); for(int i = 0; i < 256; i++) { - for(int channel = 0; channel < 3; channel++) { - resp_file >> response.at(i, channel); + for(int c = 0; c < 3; c++) { + resp_file >> response.at(i)[c]; resp_file.ignore(1); } } @@ -187,6 +187,7 @@ TEST(Photo_MergeDebevec, regression) Mat result, expected; loadImage(test_path + "merge/debevec.exr", expected); merge->process(images, result, times, response); + imwrite("test.exr", result); checkEqual(expected, result, 1e-3f); } @@ -199,9 +200,8 @@ TEST(Photo_CalibrateDebevec, regression) Mat expected, response; loadExposureSeq(test_path + "exposures/", images, times); loadResponseCSV(test_path + "calibrate/debevec.csv", expected); - Ptr calibrate = createCalibrateDebevec(); - srand(1); + calibrate->setTest(true); calibrate->process(images, response, times); checkEqual(expected, response, 1e-3f); }