From: Fedor Morozov Date: Mon, 5 Aug 2013 15:31:10 +0000 (+0400) Subject: Mantiuk's tonemapping X-Git-Tag: submit/tizen_ivi/20141117.190038~2^2~900^2~16 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=cb999a231ed4aa0e35b5a815959ed0246dca9eb7;p=profile%2Fivi%2Fopencv.git Mantiuk's tonemapping --- cb999a231ed4aa0e35b5a815959ed0246dca9eb7 diff --cc modules/photo/include/opencv2/photo.hpp index 4202eeb,6c9eab1..258c988 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@@ -87,15 -85,11 +85,11 @@@ class CV_EXPORTS_W Tonemap : public Alg public: CV_WRAP virtual void process(InputArray src, OutputArray dst) = 0; - CV_WRAP virtual float getGamma() const = 0; - CV_WRAP virtual void setGamma(float gamma) = 0; + CV_WRAP virtual float getGamma() const = 0; + CV_WRAP virtual void setGamma(float gamma) = 0; }; - class CV_EXPORTS_W TonemapLinear : public Tonemap - { - }; - - CV_EXPORTS_W Ptr createTonemapLinear(float gamma = 1.0f); -CV_EXPORTS_W Ptr createTonemapLinear(float gamma = 1.0f); ++CV_EXPORTS_W Ptr createTonemap(float gamma = 1.0f); // "Adaptive Logarithmic Mapping For Displaying HighContrast Scenes", Drago et al., 2003 diff --cc modules/photo/src/align.cpp index 5c7a802,9f8b7eb..505ff3e --- a/modules/photo/src/align.cpp +++ b/modules/photo/src/align.cpp @@@ -229,7 -228,7 +228,7 @@@ protected CV_EXPORTS_W Ptr createAlignMTB(int max_bits, int exclude_range) { - return new AlignMTBImpl(max_bits, exclude_range); + return new AlignMTBImpl(max_bits, exclude_range); } - } + } diff --cc modules/photo/src/hdr_common.cpp index 66278e5,de8b16c..4aab5c0 --- a/modules/photo/src/hdr_common.cpp +++ b/modules/photo/src/hdr_common.cpp @@@ -62,25 -61,13 +61,25 @@@ void checkImageDimensions(const std::ve Mat tringleWeights() { - Mat w(256, 3, 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; - } - } - return w; + Mat w(256, 3, 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; + } + } + return w; +} + +void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation) +{ + std::vector channels(3); + split(src, channels); + for(int i = 0; i < 3; i++) { + channels[i] = channels[i].mul(1.0f / lum); + pow(channels[i], saturation, channels[i]); + channels[i] = channels[i].mul(new_lum); + } + merge(channels, dst); } - }; + }; diff --cc modules/photo/src/hdr_common.hpp index 9625d20,9101cba..5aef1f9 --- a/modules/photo/src/hdr_common.hpp +++ b/modules/photo/src/hdr_common.hpp @@@ -53,8 -52,6 +52,8 @@@ void checkImageDimensions(const std::ve Mat tringleWeights(); +void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation); + }; - #endif + #endif diff --cc modules/photo/src/merge.cpp index cf603ac,11a591b..b7fcac6 --- a/modules/photo/src/merge.cpp +++ b/modules/photo/src/merge.cpp @@@ -257,7 -256,7 +256,7 @@@ protected Ptr createMergeMertens(float wcon, float wsat, float wexp) { - return new MergeMertensImpl(wcon, wsat, wexp); + return new MergeMertensImpl(wcon, wsat, wexp); } - } + } diff --cc modules/photo/src/tonemap.cpp index 0c85b5f,8571b63..6088b85 --- a/modules/photo/src/tonemap.cpp +++ b/modules/photo/src/tonemap.cpp @@@ -48,35 -46,35 +47,35 @@@ namespace cv { - class TonemapLinearImpl : public TonemapLinear -class TonemapLinearImpl : public Tonemap ++class TonemapImpl : public Tonemap { public: - TonemapLinearImpl(float gamma) : gamma(gamma), name("TonemapLinear") - TonemapLinearImpl(float gamma) : gamma(gamma), name("TonemapLinear") - { - } - - void process(InputArray _src, OutputArray _dst) - { - Mat src = _src.getMat(); - CV_Assert(!src.empty()); - _dst.create(src.size(), CV_32FC3); - Mat dst = _dst.getMat(); - - double min, max; - minMaxLoc(src, &min, &max); - if(max - min > DBL_EPSILON) { - dst = (src - min) / (max - min); - } else { - src.copyTo(dst); - } - - pow(dst, 1.0f / gamma, dst); - } - - float getGamma() const { return gamma; } - void setGamma(float val) { gamma = val; } - - void write(FileStorage& fs) const ++ TonemapImpl(float gamma) : gamma(gamma), name("Tonemap") + { + } + + void process(InputArray _src, OutputArray _dst) + { + Mat src = _src.getMat(); + CV_Assert(!src.empty()); + _dst.create(src.size(), CV_32FC3); + Mat dst = _dst.getMat(); + + double min, max; + minMaxLoc(src, &min, &max); + if(max - min > DBL_EPSILON) { + dst = (src - min) / (max - min); + } else { + src.copyTo(dst); + } + + pow(dst, 1.0f / gamma, dst); + } + + float getGamma() const { return gamma; } + void setGamma(float val) { gamma = val; } + + void write(FileStorage& fs) const { fs << "name" << name << "gamma" << gamma; @@@ -90,71 -88,75 +89,71 @@@ } protected: - String name; - float gamma; + String name; + float gamma; }; - Ptr createTonemapLinear(float gamma) -Ptr createTonemapLinear(float gamma) ++Ptr createTonemap(float gamma) { - return new TonemapLinearImpl(gamma); - return new TonemapLinearImpl(gamma); ++ return new TonemapImpl(gamma); } class TonemapDragoImpl : public TonemapDrago { public: - TonemapDragoImpl(float gamma, float bias) : - gamma(gamma), + TonemapDragoImpl(float gamma, float saturation, float bias) : + gamma(gamma), + saturation(saturation), bias(bias), - name("TonemapLinear") - { - } - - void process(InputArray _src, OutputArray _dst) - { - Mat src = _src.getMat(); - CV_Assert(!src.empty()); - _dst.create(src.size(), CV_32FC3); - Mat img = _dst.getMat(); - - Ptr linear = createTonemapLinear(1.0f); - linear->process(src, img); - - Mat gray_img; - cvtColor(img, gray_img, COLOR_RGB2GRAY); - Mat log_img; - log(gray_img, log_img); - float mean = expf(static_cast(sum(log_img)[0]) / log_img.total()); - gray_img /= mean; - log_img.release(); - - double max; - minMaxLoc(gray_img, NULL, &max); - - Mat map; - log(gray_img + 1.0f, map); - Mat div; - pow(gray_img / static_cast(max), logf(bias) / logf(0.5f), div); - log(2.0f + 8.0f * div, div); - map = map.mul(1.0f / div); - map = map.mul(1.0f / gray_img); - div.release(); - gray_img.release(); - - std::vector channels(3); - split(img, channels); - for(int i = 0; i < 3; i++) { - channels[i] = channels[i].mul(map); - } - map.release(); - merge(channels, img); - - linear->setGamma(gamma); - linear->process(img, img); - } - - float getGamma() const { return gamma; } - void setGamma(float val) { gamma = val; } - - float getBias() const { return bias; } - void setBias(float val) { bias = val; } - - void write(FileStorage& fs) const + name("TonemapDrago") + { + } + + void process(InputArray _src, OutputArray _dst) + { + Mat src = _src.getMat(); + CV_Assert(!src.empty()); + _dst.create(src.size(), CV_32FC3); + Mat img = _dst.getMat(); + - Ptr linear = createTonemapLinear(1.0f); ++ Ptr linear = createTonemap(1.0f); + linear->process(src, img); + + Mat gray_img; + cvtColor(img, gray_img, COLOR_RGB2GRAY); + Mat log_img; + log(gray_img, log_img); + float mean = expf(static_cast(sum(log_img)[0]) / log_img.total()); + gray_img /= mean; + log_img.release(); + + double max; + minMaxLoc(gray_img, NULL, &max); + + Mat map; + log(gray_img + 1.0f, map); + Mat div; + pow(gray_img / static_cast(max), logf(bias) / logf(0.5f), div); + log(2.0f + 8.0f * div, div); + map = map.mul(1.0f / div); + div.release(); + + mapLuminance(img, img, gray_img, map, saturation); + + linear->setGamma(gamma); + linear->process(img, img); + } + + float getGamma() const { return gamma; } + void setGamma(float val) { gamma = val; } + + float getSaturation() const { return saturation; } + void setSaturation(float val) { saturation = val; } + + float getBias() const { return bias; } + void setBias(float val) { bias = val; } + + void write(FileStorage& fs) const { fs << "name" << name << "gamma" << gamma @@@ -184,58 -184,60 +183,58 @@@ Ptr createTonemapDrago(fl class TonemapDurandImpl : public TonemapDurand { public: - TonemapDurandImpl(float gamma, float contrast, float sigma_color, float sigma_space) : - gamma(gamma), + TonemapDurandImpl(float gamma, float saturation, float contrast, float sigma_color, float sigma_space) : + gamma(gamma), + saturation(saturation), contrast(contrast), - sigma_color(sigma_color), - sigma_space(sigma_space), - name("TonemapDurand") - { - } - - void process(InputArray _src, OutputArray _dst) - { - Mat src = _src.getMat(); - CV_Assert(!src.empty()); - _dst.create(src.size(), CV_32FC3); - Mat dst = _dst.getMat(); - - Mat gray_img; - cvtColor(src, gray_img, COLOR_RGB2GRAY); - Mat log_img; - log(gray_img, log_img); - Mat map_img; - bilateralFilter(log_img, map_img, -1, sigma_color, sigma_space); - - double min, max; - minMaxLoc(map_img, &min, &max); - float scale = contrast / static_cast(max - min); + sigma_color(sigma_color), + sigma_space(sigma_space), + name("TonemapDurand") + { + } - exp(map_img * (scale - 1.0f) + log_img, map_img); - log_img.release(); - map_img = map_img.mul(1.0f / gray_img); - gray_img.release(); + void process(InputArray _src, OutputArray _dst) + { + Mat src = _src.getMat(); + CV_Assert(!src.empty()); + _dst.create(src.size(), CV_32FC3); + Mat img = _dst.getMat(); - Ptr linear = createTonemapLinear(1.0f); ++ Ptr linear = createTonemap(1.0f); + linear->process(src, img); + + Mat gray_img; + cvtColor(img, gray_img, COLOR_RGB2GRAY); + Mat log_img; + log(gray_img, log_img); + Mat map_img; + bilateralFilter(log_img, map_img, -1, sigma_color, sigma_space); + + double min, max; + minMaxLoc(map_img, &min, &max); + float scale = contrast / static_cast(max - min); + exp(map_img * (scale - 1.0f) + log_img, map_img); + log_img.release(); + + mapLuminance(img, img, gray_img, map_img, saturation); + pow(img, 1.0f / gamma, img); + } - std::vector channels(3); - split(src, channels); - for(int i = 0; i < 3; i++) { - channels[i] = channels[i].mul(map_img); - } - merge(channels, dst); - pow(dst, 1.0f / gamma, dst); - } + float getGamma() const { return gamma; } + void setGamma(float val) { gamma = val; } - float getGamma() const { return gamma; } - void setGamma(float val) { gamma = val; } + float getSaturation() const { return saturation; } + void setSaturation(float val) { saturation = val; } - float getContrast() const { return contrast; } - void setContrast(float val) { contrast = val; } + float getContrast() const { return contrast; } + void setContrast(float val) { contrast = val; } - float getSigmaColor() const { return sigma_color; } - void setSigmaColor(float val) { sigma_color = val; } + float getSigmaColor() const { return sigma_color; } + void setSigmaColor(float val) { sigma_color = val; } - float getSigmaSpace() const { return sigma_space; } - void setSigmaSpace(float val) { sigma_space = val; } + float getSigmaSpace() const { return sigma_space; } + void setSigmaSpace(float val) { sigma_space = val; } - void write(FileStorage& fs) const + void write(FileStorage& fs) const { fs << "name" << name << "gamma" << gamma @@@ -269,70 -269,71 +268,70 @@@ Ptr createTonemapDurand( class TonemapReinhardDevlinImpl : public TonemapReinhardDevlin { public: - TonemapReinhardDevlinImpl(float gamma, float intensity, float light_adapt, float color_adapt) : - gamma(gamma), + TonemapReinhardDevlinImpl(float gamma, float intensity, float light_adapt, float color_adapt) : + gamma(gamma), intensity(intensity), - light_adapt(light_adapt), - color_adapt(color_adapt), - name("TonemapReinhardDevlin") - { - } - - void process(InputArray _src, OutputArray _dst) - { - Mat src = _src.getMat(); - CV_Assert(!src.empty()); - _dst.create(src.size(), CV_32FC3); - Mat img = _dst.getMat(); - - Ptr linear = createTonemapLinear(1.0f); - linear->process(src, img); - - Mat gray_img; - cvtColor(img, gray_img, COLOR_RGB2GRAY); - Mat log_img; - log(gray_img, log_img); - - float log_mean = static_cast(sum(log_img)[0] / log_img.total()); - double log_min, log_max; - minMaxLoc(log_img, &log_min, &log_max); - log_img.release(); - - double key = static_cast((log_max - log_mean) / (log_max - log_min)); - float map_key = 0.3f + 0.7f * pow(static_cast(key), 1.4f); - intensity = exp(-intensity); - Scalar chan_mean = mean(img); - float gray_mean = static_cast(mean(gray_img)[0]); - - std::vector channels(3); - split(img, channels); - - for(int i = 0; i < 3; i++) { - float global = color_adapt * static_cast(chan_mean[i]) + (1.0f - color_adapt) * gray_mean; - Mat adapt = color_adapt * channels[i] + (1.0f - color_adapt) * gray_img; - adapt = light_adapt * adapt + (1.0f - light_adapt) * global; - pow(intensity * adapt, map_key, adapt); - channels[i] = channels[i].mul(1.0f / (adapt + channels[i])); - } - gray_img.release(); - merge(channels, img); - - linear->setGamma(gamma); - linear->process(img, img); - } - - float getGamma() const { return gamma; } - void setGamma(float val) { gamma = val; } - - float getIntensity() const { return intensity; } - void setIntensity(float val) { intensity = val; } - - float getLightAdaptation() const { return light_adapt; } - void setLightAdaptation(float val) { light_adapt = val; } - - float getColorAdaptation() const { return color_adapt; } - void setColorAdaptation(float val) { color_adapt = val; } - - void write(FileStorage& fs) const + light_adapt(light_adapt), + color_adapt(color_adapt), + name("TonemapReinhardDevlin") + { + } + + void process(InputArray _src, OutputArray _dst) + { + Mat src = _src.getMat(); + CV_Assert(!src.empty()); + _dst.create(src.size(), CV_32FC3); + Mat img = _dst.getMat(); - Ptr linear = createTonemapLinear(1.0f); ++ Ptr linear = createTonemap(1.0f); + linear->process(src, img); + + Mat gray_img; + cvtColor(img, gray_img, COLOR_RGB2GRAY); + Mat log_img; + log(gray_img, log_img); + + float log_mean = static_cast(sum(log_img)[0] / log_img.total()); + double log_min, log_max; + minMaxLoc(log_img, &log_min, &log_max); + log_img.release(); + + double key = static_cast((log_max - log_mean) / (log_max - log_min)); + float map_key = 0.3f + 0.7f * pow(static_cast(key), 1.4f); + intensity = exp(-intensity); + Scalar chan_mean = mean(img); + float gray_mean = static_cast(mean(gray_img)[0]); + + std::vector channels(3); + split(img, channels); + + for(int i = 0; i < 3; i++) { + float global = color_adapt * static_cast(chan_mean[i]) + (1.0f - color_adapt) * gray_mean; + Mat adapt = color_adapt * channels[i] + (1.0f - color_adapt) * gray_img; + adapt = light_adapt * adapt + (1.0f - light_adapt) * global; + pow(intensity * adapt, map_key, adapt); + channels[i] = channels[i].mul(1.0f / (adapt + channels[i])); + } + gray_img.release(); + merge(channels, img); + + linear->setGamma(gamma); + linear->process(img, img); + } + + float getGamma() const { return gamma; } + void setGamma(float val) { gamma = val; } + + float getIntensity() const { return intensity; } + void setIntensity(float val) { intensity = val; } + + float getLightAdaptation() const { return light_adapt; } + void setLightAdaptation(float val) { light_adapt = val; } + + float getColorAdaptation() const { return color_adapt; } + void setColorAdaptation(float val) { color_adapt = val; } + + void write(FileStorage& fs) const { fs << "name" << name << "gamma" << gamma @@@ -358,175 -359,7 +357,175 @@@ protected Ptr createTonemapReinhardDevlin(float gamma, float contrast, float sigma_color, float sigma_space) { - return new TonemapReinhardDevlinImpl(gamma, contrast, sigma_color, sigma_space); + return new TonemapReinhardDevlinImpl(gamma, contrast, sigma_color, sigma_space); +} + +class TonemapMantiukImpl : public TonemapMantiuk +{ +public: + TonemapMantiukImpl(float gamma, float scale, float saturation) : + gamma(gamma), + scale(scale), + saturation(saturation), + name("TonemapMantiuk") + { + } + + void process(InputArray _src, OutputArray _dst) + { + Mat src = _src.getMat(); + CV_Assert(!src.empty()); + _dst.create(src.size(), CV_32FC3); + Mat img = _dst.getMat(); - Ptr linear = createTonemapLinear(1.0f); ++ Ptr linear = createTonemap(1.0f); + linear->process(src, img); + + Mat gray_img; + cvtColor(img, gray_img, COLOR_RGB2GRAY); + Mat log_img; + log(gray_img, log_img); + + std::vector x_contrast, y_contrast; + getContrast(log_img, x_contrast, y_contrast); + + for(size_t i = 0; i < x_contrast.size(); i++) { + mapContrast(x_contrast[i], scale); + mapContrast(y_contrast[i], scale); + } + + Mat right(src.size(), CV_32F); + calculateSum(x_contrast, y_contrast, right); + + Mat p, r, product, x = log_img; + calculateProduct(x, r); + r = right - r; + r.copyTo(p); + + const float target_error = 1e-3f; + float target_norm = static_cast(right.dot(right)) * powf(target_error, 2.0f); + int max_iterations = 100; + float rr = static_cast(r.dot(r)); + + for(int i = 0; i < max_iterations; i++) + { + calculateProduct(p, product); + float alpha = rr / static_cast(p.dot(product)); + + r -= alpha * product; + x += alpha * p; + + float new_rr = static_cast(r.dot(r)); + p = r + (new_rr / rr) * p; + rr = new_rr; + + if(rr < target_norm) { + break; + } + } + exp(x, x); + mapLuminance(img, img, gray_img, x, saturation); + - linear = createTonemapLinear(gamma); ++ linear = createTonemap(gamma); + linear->process(img, img); + } + + float getGamma() const { return gamma; } + void setGamma(float val) { gamma = val; } + + float getScale() const { return scale; } + void setScale(float val) { scale = val; } + + float getSaturation() const { return saturation; } + void setSaturation(float val) { saturation = val; } + + void write(FileStorage& fs) const + { + fs << "name" << name + << "gamma" << gamma + << "scale" << scale + << "saturation" << saturation; + } + + void read(const FileNode& fn) + { + FileNode n = fn["name"]; + CV_Assert(n.isString() && String(n) == name); + gamma = fn["gamma"]; + scale = fn["scale"]; + saturation = fn["saturation"]; + } + +protected: + String name; + float gamma, scale, saturation; + + void signedPow(Mat src, float power, Mat& dst) + { + Mat sign = (src > 0); + sign.convertTo(sign, CV_32F, 1/255.0f); + sign = sign * 2 - 1; + pow(abs(src), power, dst); + dst = dst.mul(sign); + } + + void mapContrast(Mat& contrast, float scale) + { + const float response_power = 0.4185f; + signedPow(contrast, response_power, contrast); + contrast *= scale; + signedPow(contrast, 1.0f / response_power, contrast); + } + + void getGradient(Mat src, Mat& dst, int pos) + { + dst = Mat::zeros(src.size(), CV_32F); + Mat a, b; + Mat grad = src.colRange(1, src.cols) - src.colRange(0, src.cols - 1); + grad.copyTo(dst.colRange(pos, src.cols + pos - 1)); + if(pos == 1) { + src.col(0).copyTo(dst.col(0)); + } + } + + void getContrast(Mat src, std::vector& x_contrast, std::vector& y_contrast) + { + int levels = static_cast(logf(static_cast(min(src.rows, src.cols))) / logf(2.0f)); + x_contrast.resize(levels); + y_contrast.resize(levels); + + Mat layer; + src.copyTo(layer); + for(int i = 0; i < levels; i++) { + getGradient(layer, x_contrast[i], 0); + getGradient(layer.t(), y_contrast[i], 0); + resize(layer, layer, Size(layer.cols / 2, layer.rows / 2)); + } + } + + void calculateSum(std::vector& x_contrast, std::vector& y_contrast, Mat& sum) + { + sum = Mat::zeros(x_contrast[x_contrast.size() - 1].size(), CV_32F); + for(int i = x_contrast.size() - 1; i >= 0; i--) + { + Mat grad_x, grad_y; + getGradient(x_contrast[i], grad_x, 1); + getGradient(y_contrast[i], grad_y, 1); + resize(sum, sum, x_contrast[i].size()); + sum += grad_x + grad_y.t(); + } + } + + void calculateProduct(Mat src, Mat& dst) + { + std::vector x_contrast, y_contrast; + getContrast(src, x_contrast, y_contrast); + calculateSum(x_contrast, y_contrast, dst); + } +}; + +Ptr createTonemapMantiuk(float gamma, float scale, float saturation) +{ + return new TonemapMantiukImpl(gamma, scale, saturation); } - } + } diff --cc modules/photo/test/test_hdr.cpp index 92738c2,39c0a1e..2f2dfe2 --- a/modules/photo/test/test_hdr.cpp +++ b/modules/photo/test/test_hdr.cpp @@@ -91,13 -91,14 +91,13 @@@ void loadResponseCSV(String path, Mat& TEST(Photo_Tonemap, regression) { - string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/"; + string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/tonemap/"; Mat img, expected, result; - loadImage(test_path + "rle.hdr", img); + loadImage(test_path + "image.hdr", img); float gamma = 2.2f; - test_path += "tonemap/"; - Ptr linear = createTonemapLinear(gamma); + Ptr linear = createTonemapLinear(gamma); linear->process(img, result); loadImage(test_path + "linear.png", expected); result.convertTo(result, CV_8UC3, 255);