From 17609b90c742b49f6e2592eeedf61eb364ed85f8 Mon Sep 17 00:00:00 2001 From: Fedor Morozov Date: Mon, 5 Aug 2013 19:22:42 +0400 Subject: [PATCH] Mantiuk's tonemapping --- modules/highgui/src/grfmt_hdr.cpp | 5 +- modules/photo/include/opencv2/photo.hpp | 117 +++--- modules/photo/src/align.cpp | 326 ++++++++--------- modules/photo/src/calibrate.cpp | 118 +++---- modules/photo/src/hdr_common.cpp | 42 ++- modules/photo/src/hdr_common.hpp | 2 + modules/photo/src/merge.cpp | 322 ++++++++--------- modules/photo/src/tonemap.cpp | 606 ++++++++++++++++++++------------ modules/photo/test/test_hdr.cpp | 11 +- 9 files changed, 877 insertions(+), 672 deletions(-) diff --git a/modules/highgui/src/grfmt_hdr.cpp b/modules/highgui/src/grfmt_hdr.cpp index 13886b8..8e6b86e 100644 --- a/modules/highgui/src/grfmt_hdr.cpp +++ b/modules/highgui/src/grfmt_hdr.cpp @@ -123,10 +123,9 @@ 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); - } else { + 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); diff --git a/modules/photo/include/opencv2/photo.hpp b/modules/photo/include/opencv2/photo.hpp index 35787c0..4202eeb 100644 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@ -87,8 +87,8 @@ class CV_EXPORTS_W Tonemap : public Algorithm 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 @@ -102,71 +102,92 @@ CV_EXPORTS_W Ptr createTonemapLinear(float gamma = 1.0f); class CV_EXPORTS_W TonemapDrago : public Tonemap { public: - CV_WRAP virtual float getBias() const = 0; - CV_WRAP virtual void setBias(float bias) = 0; + + CV_WRAP virtual float getSaturation() const = 0; + CV_WRAP virtual void setSaturation(float saturation) = 0; + + CV_WRAP virtual float getBias() const = 0; + CV_WRAP virtual void setBias(float bias) = 0; }; -CV_EXPORTS_W Ptr createTonemapDrago(float gamma = 1.0f, float bias = 0.85f); +CV_EXPORTS_W Ptr createTonemapDrago(float gamma = 1.0f, float saturation = 1.0f, float bias = 0.85f); // "Fast Bilateral Filtering for the Display of High-Dynamic-Range Images", Durand, Dorsey, 2002 class CV_EXPORTS_W TonemapDurand : public Tonemap { public: - CV_WRAP virtual float getContrast() const = 0; - CV_WRAP virtual void setContrast(float contrast) = 0; - CV_WRAP virtual float getSigmaSpace() const = 0; - CV_WRAP virtual void setSigmaSpace(float sigma_space) = 0; + CV_WRAP virtual float getSaturation() const = 0; + CV_WRAP virtual void setSaturation(float saturation) = 0; + + CV_WRAP virtual float getContrast() const = 0; + CV_WRAP virtual void setContrast(float contrast) = 0; - CV_WRAP virtual float getSigmaColor() const = 0; - CV_WRAP virtual void setSigmaColor(float sigma_color) = 0; + CV_WRAP virtual float getSigmaSpace() const = 0; + CV_WRAP virtual void setSigmaSpace(float sigma_space) = 0; + + CV_WRAP virtual float getSigmaColor() const = 0; + CV_WRAP virtual void setSigmaColor(float sigma_color) = 0; }; CV_EXPORTS_W Ptr -createTonemapDurand(float gamma = 1.0f, float contrast = 4.0f, float sigma_space = 2.0f, float sigma_color = 2.0f); +createTonemapDurand(float gamma = 1.0f, float saturation = 1.0f, float contrast = 4.0f, float sigma_space = 2.0f, float sigma_color = 2.0f); // "Dynamic Range Reduction Inspired by Photoreceptor Physiology", Reinhard, Devlin, 2005 class CV_EXPORTS_W TonemapReinhardDevlin : public Tonemap { public: - CV_WRAP virtual float getIntensity() const = 0; - CV_WRAP virtual void setIntensity(float intensity) = 0; + CV_WRAP virtual float getIntensity() const = 0; + CV_WRAP virtual void setIntensity(float intensity) = 0; - CV_WRAP virtual float getLightAdaptation() const = 0; - CV_WRAP virtual void setLightAdaptation(float light_adapt) = 0; + CV_WRAP virtual float getLightAdaptation() const = 0; + CV_WRAP virtual void setLightAdaptation(float light_adapt) = 0; - CV_WRAP virtual float getColorAdaptation() const = 0; - CV_WRAP virtual void setColorAdaptation(float color_adapt) = 0; + CV_WRAP virtual float getColorAdaptation() const = 0; + CV_WRAP virtual void setColorAdaptation(float color_adapt) = 0; }; CV_EXPORTS_W Ptr createTonemapReinhardDevlin(float gamma = 1.0f, float intensity = 0.0f, float light_adapt = 1.0f, float color_adapt = 0.0f); +class CV_EXPORTS_W TonemapMantiuk : public Tonemap +{ +public: + CV_WRAP virtual float getScale() const = 0; + CV_WRAP virtual void setScale(float scale) = 0; + + CV_WRAP virtual float getSaturation() const = 0; + CV_WRAP virtual void setSaturation(float saturation) = 0; +}; + +CV_EXPORTS_W Ptr +createTonemapMantiuk(float gamma = 1.0f, float scale = 0.7f, float saturation = 1.0f); + class CV_EXPORTS_W ExposureAlign : public Algorithm { public: - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst, - const std::vector& times, InputArray response) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst, + const std::vector& times, InputArray response) = 0; }; class CV_EXPORTS_W AlignMTB : public ExposureAlign { public: - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst, - const std::vector& times, InputArray response) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst, + const std::vector& times, InputArray response) = 0; - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst) = 0; - CV_WRAP virtual void calculateShift(InputArray img0, InputArray img1, Point& shift) = 0; - CV_WRAP virtual void shiftMat(InputArray src, OutputArray dst, const Point shift) = 0; + CV_WRAP virtual void calculateShift(InputArray img0, InputArray img1, Point& shift) = 0; + CV_WRAP virtual void shiftMat(InputArray src, OutputArray dst, const Point shift) = 0; - CV_WRAP virtual int getMaxBits() const = 0; - CV_WRAP virtual void setMaxBits(int max_bits) = 0; + CV_WRAP virtual int getMaxBits() const = 0; + CV_WRAP virtual void setMaxBits(int max_bits) = 0; - CV_WRAP virtual int getExcludeRange() const = 0; - CV_WRAP virtual void setExcludeRange(int exclude_range) = 0; + CV_WRAP virtual int getExcludeRange() const = 0; + CV_WRAP virtual void setExcludeRange(int exclude_range) = 0; }; // "Fast, Robust Image Registration for Compositing High Dynamic Range Photographs from Handheld Exposures", Ward, 2003 @@ -176,7 +197,7 @@ CV_EXPORTS_W Ptr createAlignMTB(int max_bits = 6, int exclude_range = class CV_EXPORTS_W ExposureCalibrate : public Algorithm { public: - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, std::vector& times) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, std::vector& times) = 0; }; // "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997 @@ -184,11 +205,11 @@ public: class CV_EXPORTS_W CalibrateDebevec : public ExposureCalibrate { public: - CV_WRAP virtual float getLambda() const = 0; + CV_WRAP virtual float getLambda() const = 0; CV_WRAP virtual void setLambda(float lambda) = 0; - - CV_WRAP virtual int getSamples() const = 0; - CV_WRAP virtual void setSamples(int samples) = 0; + + CV_WRAP virtual int getSamples() const = 0; + CV_WRAP virtual void setSamples(int samples) = 0; }; CV_EXPORTS_W Ptr createCalibrateDebevec(int samples = 50, float lambda = 10.0f); @@ -196,8 +217,8 @@ CV_EXPORTS_W Ptr createCalibrateDebevec(int samples = 50, floa class CV_EXPORTS_W ExposureMerge : public Algorithm { public: - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, - const std::vector& times, InputArray response) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, + const std::vector& times, InputArray response) = 0; }; // "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997 @@ -205,9 +226,9 @@ public: class CV_EXPORTS_W MergeDebevec : public ExposureMerge { public: - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, - const std::vector& times, InputArray response) = 0; - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, const std::vector& times) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, + const std::vector& times, InputArray response) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, const std::vector& times) = 0; }; CV_EXPORTS_W Ptr createMergeDebevec(); @@ -217,18 +238,18 @@ CV_EXPORTS_W Ptr createMergeDebevec(); class CV_EXPORTS_W MergeMertens : public ExposureMerge { public: - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, - const std::vector& times, InputArray response) = 0; - CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, + const std::vector& times, InputArray response) = 0; + CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst) = 0; - CV_WRAP virtual float getContrastWeight() const = 0; - CV_WRAP virtual void setContrastWeight(float contrast_weiht) = 0; + CV_WRAP virtual float getContrastWeight() const = 0; + CV_WRAP virtual void setContrastWeight(float contrast_weiht) = 0; - CV_WRAP virtual float getSaturationWeight() const = 0; - CV_WRAP virtual void setSaturationWeight(float saturation_weight) = 0; + CV_WRAP virtual float getSaturationWeight() const = 0; + CV_WRAP virtual void setSaturationWeight(float saturation_weight) = 0; - CV_WRAP virtual float getExposureWeight() const = 0; - CV_WRAP virtual void setExposureWeight(float exposure_weight) = 0; + CV_WRAP virtual float getExposureWeight() const = 0; + CV_WRAP virtual void setExposureWeight(float exposure_weight) = 0; }; CV_EXPORTS_W Ptr diff --git a/modules/photo/src/align.cpp b/modules/photo/src/align.cpp index 804aabb..5c7a802 100644 --- a/modules/photo/src/align.cpp +++ b/modules/photo/src/align.cpp @@ -51,115 +51,115 @@ namespace cv class AlignMTBImpl : public AlignMTB { public: - AlignMTBImpl(int max_bits, int exclude_range) : - max_bits(max_bits), - exclude_range(exclude_range), - name("AlignMTB") - { - } - - void process(InputArrayOfArrays src, OutputArrayOfArrays dst, - const std::vector& times, InputArray response) - { - process(src, dst); - } - - void process(InputArrayOfArrays _src, OutputArray _dst) - { - std::vector src, dst; - _src.getMatVector(src); - _dst.getMatVector(dst); - - checkImageDimensions(src); - dst.resize(src.size()); - - size_t pivot = src.size() / 2; - dst[pivot] = src[pivot]; - Mat gray_base; - cvtColor(src[pivot], gray_base, COLOR_RGB2GRAY); - - for(size_t i = 0; i < src.size(); i++) { - if(i == pivot) { - continue; - } - Mat gray; - cvtColor(src[i], gray, COLOR_RGB2GRAY); - Point shift; - calculateShift(gray_base, gray, shift); - shiftMat(src[i], dst[i], shift); - } - } - - void calculateShift(InputArray _img0, InputArray _img1, Point& shift) - { - Mat img0 = _img0.getMat(); - Mat img1 = _img1.getMat(); - CV_Assert(img0.channels() == 1 && img0.type() == img1.type()); - CV_Assert(img0.size() == img0.size()); - - int maxlevel = static_cast(log((double)max(img0.rows, img0.cols)) / log(2.0)) - 1; - maxlevel = min(maxlevel, max_bits - 1); - - std::vector pyr0; - std::vector pyr1; - buildPyr(img0, pyr0, maxlevel); - buildPyr(img1, pyr1, maxlevel); - - shift = Point(0, 0); - for(int level = maxlevel; level >= 0; level--) { - - shift *= 2; - Mat tb1, tb2, eb1, eb2; - computeBitmaps(pyr0[level], tb1, eb1, exclude_range); - computeBitmaps(pyr1[level], tb2, eb2, exclude_range); - - int min_err = pyr0[level].total(); - Point new_shift(shift); - for(int i = -1; i <= 1; i++) { - for(int j = -1; j <= 1; j++) { - Point test_shift = shift + Point(i, j); - Mat shifted_tb2, shifted_eb2, diff; - shiftMat(tb2, shifted_tb2, test_shift); - shiftMat(eb2, shifted_eb2, test_shift); - bitwise_xor(tb1, shifted_tb2, diff); - bitwise_and(diff, eb1, diff); - bitwise_and(diff, shifted_eb2, diff); - int err = countNonZero(diff); - if(err < min_err) { - new_shift = test_shift; - min_err = err; - } - } - } - shift = new_shift; - } - } - - void shiftMat(InputArray _src, OutputArray _dst, const Point shift) - { - Mat src = _src.getMat(); - _dst.create(src.size(), src.type()); - Mat dst = _dst.getMat(); - - dst = Mat::zeros(src.size(), src.type()); - int width = src.cols - abs(shift.x); - int height = src.rows - abs(shift.y); - Rect dst_rect(max(shift.x, 0), max(shift.y, 0), width, height); - Rect src_rect(max(-shift.x, 0), max(-shift.y, 0), width, height); - src(src_rect).copyTo(dst(dst_rect)); - } - - int getMaxBits() const { return max_bits; } - void setMaxBits(int val) { max_bits = val; } - - int getExcludeRange() const { return exclude_range; } - void setExcludeRange(int val) { exclude_range = val; } - - void write(FileStorage& fs) const + AlignMTBImpl(int max_bits, int exclude_range) : + max_bits(max_bits), + exclude_range(exclude_range), + name("AlignMTB") + { + } + + void process(InputArrayOfArrays src, OutputArrayOfArrays dst, + const std::vector& times, InputArray response) + { + process(src, dst); + } + + void process(InputArrayOfArrays _src, OutputArray _dst) + { + std::vector src, dst; + _src.getMatVector(src); + _dst.getMatVector(dst); + + checkImageDimensions(src); + dst.resize(src.size()); + + size_t pivot = src.size() / 2; + dst[pivot] = src[pivot]; + Mat gray_base; + cvtColor(src[pivot], gray_base, COLOR_RGB2GRAY); + + for(size_t i = 0; i < src.size(); i++) { + if(i == pivot) { + continue; + } + Mat gray; + cvtColor(src[i], gray, COLOR_RGB2GRAY); + Point shift; + calculateShift(gray_base, gray, shift); + shiftMat(src[i], dst[i], shift); + } + } + + void calculateShift(InputArray _img0, InputArray _img1, Point& shift) + { + Mat img0 = _img0.getMat(); + Mat img1 = _img1.getMat(); + CV_Assert(img0.channels() == 1 && img0.type() == img1.type()); + CV_Assert(img0.size() == img0.size()); + + int maxlevel = static_cast(log((double)max(img0.rows, img0.cols)) / log(2.0)) - 1; + maxlevel = min(maxlevel, max_bits - 1); + + std::vector pyr0; + std::vector pyr1; + buildPyr(img0, pyr0, maxlevel); + buildPyr(img1, pyr1, maxlevel); + + shift = Point(0, 0); + for(int level = maxlevel; level >= 0; level--) { + + shift *= 2; + Mat tb1, tb2, eb1, eb2; + computeBitmaps(pyr0[level], tb1, eb1, exclude_range); + computeBitmaps(pyr1[level], tb2, eb2, exclude_range); + + int min_err = pyr0[level].total(); + Point new_shift(shift); + for(int i = -1; i <= 1; i++) { + for(int j = -1; j <= 1; j++) { + Point test_shift = shift + Point(i, j); + Mat shifted_tb2, shifted_eb2, diff; + shiftMat(tb2, shifted_tb2, test_shift); + shiftMat(eb2, shifted_eb2, test_shift); + bitwise_xor(tb1, shifted_tb2, diff); + bitwise_and(diff, eb1, diff); + bitwise_and(diff, shifted_eb2, diff); + int err = countNonZero(diff); + if(err < min_err) { + new_shift = test_shift; + min_err = err; + } + } + } + shift = new_shift; + } + } + + void shiftMat(InputArray _src, OutputArray _dst, const Point shift) + { + Mat src = _src.getMat(); + _dst.create(src.size(), src.type()); + Mat dst = _dst.getMat(); + + dst = Mat::zeros(src.size(), src.type()); + int width = src.cols - abs(shift.x); + int height = src.rows - abs(shift.y); + Rect dst_rect(max(shift.x, 0), max(shift.y, 0), width, height); + Rect src_rect(max(-shift.x, 0), max(-shift.y, 0), width, height); + src(src_rect).copyTo(dst(dst_rect)); + } + + int getMaxBits() const { return max_bits; } + void setMaxBits(int val) { max_bits = val; } + + int getExcludeRange() const { return exclude_range; } + void setExcludeRange(int val) { exclude_range = val; } + + void write(FileStorage& fs) const { fs << "name" << name - << "max_bits" << max_bits - << "exclude_range" << exclude_range; + << "max_bits" << max_bits + << "exclude_range" << exclude_range; } void read(const FileNode& fn) @@ -167,69 +167,69 @@ public: FileNode n = fn["name"]; CV_Assert(n.isString() && String(n) == name); max_bits = fn["max_bits"]; - exclude_range = fn["exclude_range"]; + exclude_range = fn["exclude_range"]; } protected: - String name; - int max_bits, exclude_range; - - void downsample(Mat& src, Mat& dst) - { - dst = Mat(src.rows / 2, src.cols / 2, CV_8UC1); - - int offset = src.cols * 2; - uchar *src_ptr = src.ptr(); - uchar *dst_ptr = dst.ptr(); - for(int y = 0; y < dst.rows; y ++) { - uchar *ptr = src_ptr; - for(int x = 0; x < dst.cols; x++) { - dst_ptr[0] = ptr[0]; - dst_ptr++; - ptr += 2; - } - src_ptr += offset; - } - } - - void buildPyr(Mat& img, std::vector& pyr, int maxlevel) - { - pyr.resize(maxlevel + 1); - pyr[0] = img.clone(); - for(int level = 0; level < maxlevel; level++) { - downsample(pyr[level], pyr[level + 1]); - } - } - - int getMedian(Mat& img) - { - int channels = 0; - Mat hist; - int hist_size = 256; - float range[] = {0, 256} ; - const float* ranges[] = {range}; - calcHist(&img, 1, &channels, Mat(), hist, 1, &hist_size, ranges); - float *ptr = hist.ptr(); - int median = 0, sum = 0; - int thresh = img.total() / 2; - while(sum < thresh && median < 256) { - sum += static_cast(ptr[median]); - median++; - } - return median; - } - - void computeBitmaps(Mat& img, Mat& tb, Mat& eb, int exclude_range) - { - int median = getMedian(img); - compare(img, median, tb, CMP_GT); - compare(abs(img - median), exclude_range, eb, CMP_GT); - } + String name; + int max_bits, exclude_range; + + void downsample(Mat& src, Mat& dst) + { + dst = Mat(src.rows / 2, src.cols / 2, CV_8UC1); + + int offset = src.cols * 2; + uchar *src_ptr = src.ptr(); + uchar *dst_ptr = dst.ptr(); + for(int y = 0; y < dst.rows; y ++) { + uchar *ptr = src_ptr; + for(int x = 0; x < dst.cols; x++) { + dst_ptr[0] = ptr[0]; + dst_ptr++; + ptr += 2; + } + src_ptr += offset; + } + } + + void buildPyr(Mat& img, std::vector& pyr, int maxlevel) + { + pyr.resize(maxlevel + 1); + pyr[0] = img.clone(); + for(int level = 0; level < maxlevel; level++) { + downsample(pyr[level], pyr[level + 1]); + } + } + + int getMedian(Mat& img) + { + int channels = 0; + Mat hist; + int hist_size = 256; + float range[] = {0, 256} ; + const float* ranges[] = {range}; + calcHist(&img, 1, &channels, Mat(), hist, 1, &hist_size, ranges); + float *ptr = hist.ptr(); + int median = 0, sum = 0; + int thresh = img.total() / 2; + while(sum < thresh && median < 256) { + sum += static_cast(ptr[median]); + median++; + } + return median; + } + + void computeBitmaps(Mat& img, Mat& tb, Mat& eb, int exclude_range) + { + int median = getMedian(img); + compare(img, median, tb, CMP_GT); + compare(abs(img - median), exclude_range, eb, CMP_GT); + } }; 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); } } \ No newline at end of file diff --git a/modules/photo/src/calibrate.cpp b/modules/photo/src/calibrate.cpp index 73a9c33..8a51f53 100644 --- a/modules/photo/src/calibrate.cpp +++ b/modules/photo/src/calibrate.cpp @@ -47,73 +47,73 @@ namespace cv { - + class CalibrateDebevecImpl : public CalibrateDebevec { public: - CalibrateDebevecImpl(int samples, float lambda) : - samples(samples), - lambda(lambda), - name("CalibrateDebevec"), - w(tringleWeights()) - { - } - - void process(InputArrayOfArrays src, OutputArray dst, std::vector& times) - { - std::vector images; - src.getMatVector(images); - dst.create(256, images[0].channels(), CV_32F); - Mat response = dst.getMat(); + CalibrateDebevecImpl(int samples, float lambda) : + samples(samples), + lambda(lambda), + name("CalibrateDebevec"), + w(tringleWeights()) + { + } + + void process(InputArrayOfArrays src, OutputArray dst, std::vector& times) + { + 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); - checkImageDimensions(images); + CV_Assert(!images.empty() && images.size() == times.size()); + CV_Assert(images[0].depth() == CV_8U); + checkImageDimensions(images); - for(int channel = 0; channel < images[0].channels(); channel++) { - Mat A = Mat::zeros(samples * images.size() + 257, 256 + samples, CV_32F); - Mat B = Mat::zeros(A.rows, 1, CV_32F); + for(int channel = 0; channel < images[0].channels(); channel++) { + Mat A = Mat::zeros(samples * images.size() + 257, 256 + samples, CV_32F); + Mat B = Mat::zeros(A.rows, 1, CV_32F); - int eq = 0; - for(int i = 0; i < samples; i++) { + int eq = 0; + for(int i = 0; i < samples; i++) { - int pos = 3 * (rand() % images[0].total()) + channel; - for(size_t j = 0; j < images.size(); j++) { + int pos = 3 * (rand() % images[0].total()) + channel; + for(size_t j = 0; j < images.size(); j++) { - int val = (images[j].ptr() + pos)[0]; - A.at(eq, val) = w.at(val); - A.at(eq, 256 + i) = -w.at(val); - B.at(eq, 0) = w.at(val) * log(times[j]); - eq++; - } - } - A.at(eq, 128) = 1; - eq++; + int val = (images[j].ptr() + pos)[0]; + A.at(eq, val) = w.at(val); + A.at(eq, 256 + i) = -w.at(val); + B.at(eq, 0) = w.at(val) * log(times[j]); + eq++; + } + } + A.at(eq, 128) = 1; + eq++; - for(int i = 0; i < 254; i++) { - A.at(eq, i) = lambda * w.at(i + 1); - A.at(eq, i + 1) = -2 * lambda * w.at(i + 1); - A.at(eq, i + 2) = lambda * w.at(i + 1); - eq++; - } - Mat solution; - solve(A, B, solution, DECOMP_SVD); - solution.rowRange(0, 256).copyTo(response.col(channel)); - } - exp(response, response); - } + for(int i = 0; i < 254; i++) { + A.at(eq, i) = lambda * w.at(i + 1); + A.at(eq, i + 1) = -2 * lambda * w.at(i + 1); + A.at(eq, i + 2) = lambda * w.at(i + 1); + eq++; + } + Mat solution; + solve(A, B, solution, DECOMP_SVD); + solution.rowRange(0, 256).copyTo(response.col(channel)); + } + exp(response, response); + } - int getSamples() const { return samples; } - void setSamples(int val) { samples = val; } + int getSamples() const { return samples; } + void setSamples(int val) { samples = val; } - float getLambda() const { return lambda; } - void setLambda(float val) { lambda = val; } + float getLambda() const { return lambda; } + void setLambda(float val) { lambda = val; } - void write(FileStorage& fs) const + void write(FileStorage& fs) const { fs << "name" << name - << "samples" << samples - << "lambda" << lambda; + << "samples" << samples + << "lambda" << lambda; } void read(const FileNode& fn) @@ -121,19 +121,19 @@ public: FileNode n = fn["name"]; CV_Assert(n.isString() && String(n) == name); samples = fn["samples"]; - lambda = fn["lambda"]; + lambda = fn["lambda"]; } protected: - String name; - int samples; - float lambda; - Mat w; + String name; + int samples; + float lambda; + Mat w; }; Ptr createCalibrateDebevec(int samples, float lambda) { - return new CalibrateDebevecImpl(samples, lambda); + return new CalibrateDebevecImpl(samples, lambda); } } \ No newline at end of file diff --git a/modules/photo/src/hdr_common.cpp b/modules/photo/src/hdr_common.cpp index 202eb01..66278e5 100644 --- a/modules/photo/src/hdr_common.cpp +++ b/modules/photo/src/hdr_common.cpp @@ -49,26 +49,38 @@ namespace cv void checkImageDimensions(const std::vector& images) { - CV_Assert(!images.empty()); - int width = images[0].cols; - int height = images[0].rows; - int type = images[0].type(); + CV_Assert(!images.empty()); + int width = images[0].cols; + int height = images[0].rows; + int type = images[0].type(); - for(size_t i = 0; i < images.size(); i++) { - CV_Assert(images[i].cols == width && images[i].rows == height); - CV_Assert(images[i].type() == type); - } + for(size_t i = 0; i < images.size(); i++) { + CV_Assert(images[i].cols == width && images[i].rows == height); + CV_Assert(images[i].type() == type); + } } 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); } }; \ No newline at end of file diff --git a/modules/photo/src/hdr_common.hpp b/modules/photo/src/hdr_common.hpp index 63cfe44..9625d20 100644 --- a/modules/photo/src/hdr_common.hpp +++ b/modules/photo/src/hdr_common.hpp @@ -53,6 +53,8 @@ void checkImageDimensions(const std::vector& images); Mat tringleWeights(); +void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation); + }; #endif \ No newline at end of file diff --git a/modules/photo/src/merge.cpp b/modules/photo/src/merge.cpp index 011b583..cf603ac 100644 --- a/modules/photo/src/merge.cpp +++ b/modules/photo/src/merge.cpp @@ -52,193 +52,193 @@ namespace cv class MergeDebevecImpl : public MergeDebevec { public: - MergeDebevecImpl() : - name("MergeDebevec"), - weights(tringleWeights()) - { - } - - void process(InputArrayOfArrays src, OutputArray dst, const std::vector& times, InputArray input_response) - { - std::vector images; - src.getMatVector(images); - dst.create(images[0].size(), CV_MAKETYPE(CV_32F, images[0].channels())); - Mat result = dst.getMat(); + MergeDebevecImpl() : + name("MergeDebevec"), + weights(tringleWeights()) + { + } + + void process(InputArrayOfArrays src, OutputArray dst, const std::vector& times, InputArray input_response) + { + 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.size() == times.size()); + CV_Assert(images[0].depth() == CV_8U); + checkImageDimensions(images); - 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]); - } - - int channels = images[0].channels(); - float *res_ptr = result.ptr(); - for(size_t pos = 0; pos < result.total(); pos++, res_ptr += channels) { + 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]); + } + + int channels = images[0].channels(); + float *res_ptr = result.ptr(); + for(size_t pos = 0; pos < result.total(); pos++, res_ptr += channels) { - std::vector sum(channels, 0); - float weight_sum = 0; - for(size_t im = 0; im < images.size(); im++) { + std::vector sum(channels, 0); + float weight_sum = 0; + for(size_t im = 0; im < images.size(); im++) { - 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]); - } - } - for(int channel = 0; channel < channels; channel++) { - res_ptr[channel] = exp(sum[channel] / weight_sum); - } - } - } + 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]); + } + } + for(int channel = 0; channel < channels; channel++) { + res_ptr[channel] = exp(sum[channel] / weight_sum); + } + } + } - 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) = max(i, 1); - } - } - process(src, dst, times, response); - } + 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); + } protected: - String name; - Mat weights; + String name; + Mat weights; }; Ptr createMergeDebevec() { - return new MergeDebevecImpl; + return new MergeDebevecImpl; } class MergeMertensImpl : public MergeMertens { public: - MergeMertensImpl(float wcon, float wsat, float wexp) : - wcon(wcon), - wsat(wsat), - wexp(wexp), - name("MergeMertens") - { - } - - void process(InputArrayOfArrays src, OutputArrayOfArrays dst, const std::vector& times, InputArray response) - { - process(src, dst); - } + MergeMertensImpl(float wcon, float wsat, float wexp) : + wcon(wcon), + wsat(wsat), + wexp(wexp), + name("MergeMertens") + { + } + + void process(InputArrayOfArrays src, OutputArrayOfArrays dst, const std::vector& times, InputArray response) + { + process(src, dst); + } - void process(InputArrayOfArrays src, OutputArray dst) - { - std::vector images; - src.getMatVector(images); - checkImageDimensions(images); + void process(InputArrayOfArrays src, OutputArray dst) + { + std::vector images; + 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); + 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); - images[im].convertTo(img, CV_32FC3, 1.0/255.0); - cvtColor(img, gray, COLOR_RGB2GRAY); - split(img, channels); + images[im].convertTo(img, CV_32FC3, 1.0/255.0); + cvtColor(img, gray, COLOR_RGB2GRAY); + split(img, channels); - Laplacian(gray, contrast, CV_32F); - contrast = abs(contrast); + 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); - saturation += deviation; - } - sqrt(saturation, saturation); + 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); + 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 = wellexp.mul(exp); - } + 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 = wellexp.mul(exp); + } - pow(contrast, wcon, contrast); - pow(saturation, wsat, saturation); - pow(wellexp, wexp, wellexp); + pow(contrast, wcon, contrast); + 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]; - } - int maxlevel = static_cast(logf(static_cast(max(images[0].rows, images[0].cols))) / logf(2.0)) - 1; - std::vector res_pyr(maxlevel + 1); + weights[im] = contrast; + weights[im] = weights[im].mul(saturation); + weights[im] = weights[im].mul(wellexp); + weight_sum += weights[im]; + } + int maxlevel = static_cast(logf(static_cast(max(images[0].rows, images[0].cols))) / logf(2.0)) - 1; + std::vector res_pyr(maxlevel + 1); - for(size_t im = 0; im < images.size(); im++) { - weights[im] /= weight_sum; - Mat img; - images[im].convertTo(img, CV_32FC3, 1/255.0); - std::vector img_pyr, weight_pyr; - buildPyramid(img, img_pyr, maxlevel); - buildPyramid(weights[im], 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]); - } - merge(channels, img_pyr[lvl]); - if(res_pyr[lvl].empty()) { - res_pyr[lvl] = img_pyr[lvl]; - } else { - res_pyr[lvl] += img_pyr[lvl]; - } - } - } - for(int lvl = maxlevel; lvl > 0; lvl--) { - Mat up; - pyrUp(res_pyr[lvl], up, res_pyr[lvl - 1].size()); - res_pyr[lvl - 1] += up; - } - dst.create(images[0].size(), CV_32FC3); - res_pyr[0].copyTo(dst.getMat()); - } + for(size_t im = 0; im < images.size(); im++) { + weights[im] /= weight_sum; + Mat img; + images[im].convertTo(img, CV_32FC3, 1/255.0); + std::vector img_pyr, weight_pyr; + buildPyramid(img, img_pyr, maxlevel); + buildPyramid(weights[im], 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]); + } + merge(channels, img_pyr[lvl]); + if(res_pyr[lvl].empty()) { + res_pyr[lvl] = img_pyr[lvl]; + } else { + res_pyr[lvl] += img_pyr[lvl]; + } + } + } + for(int lvl = maxlevel; lvl > 0; lvl--) { + Mat up; + pyrUp(res_pyr[lvl], up, res_pyr[lvl - 1].size()); + res_pyr[lvl - 1] += up; + } + dst.create(images[0].size(), CV_32FC3); + res_pyr[0].copyTo(dst.getMat()); + } - float getContrastWeight() const { return wcon; } - void setContrastWeight(float val) { wcon = val; } + float getContrastWeight() const { return wcon; } + void setContrastWeight(float val) { wcon = val; } - float getSaturationWeight() const { return wsat; } - void setSaturationWeight(float val) { wsat = val; } + float getSaturationWeight() const { return wsat; } + void setSaturationWeight(float val) { wsat = val; } - float getExposureWeight() const { return wexp; } - void setExposureWeight(float val) { wexp = val; } + float getExposureWeight() const { return wexp; } + void setExposureWeight(float val) { wexp = val; } - void write(FileStorage& fs) const + void write(FileStorage& fs) const { fs << "name" << name - << "contrast_weight" << wcon - << "saturation_weight" << wsat - << "exposure_weight" << wexp; + << "contrast_weight" << wcon + << "saturation_weight" << wsat + << "exposure_weight" << wexp; } void read(const FileNode& fn) @@ -246,18 +246,18 @@ public: FileNode n = fn["name"]; CV_Assert(n.isString() && String(n) == name); wcon = fn["contrast_weight"]; - wsat = fn["saturation_weight"]; - wexp = fn["exposure_weight"]; + wsat = fn["saturation_weight"]; + wexp = fn["exposure_weight"]; } protected: - String name; - float wcon, wsat, wexp; + String name; + float wcon, wsat, wexp; }; Ptr createMergeMertens(float wcon, float wsat, float wexp) { - return new MergeMertensImpl(wcon, wsat, wexp); + return new MergeMertensImpl(wcon, wsat, wexp); } } \ No newline at end of file diff --git a/modules/photo/src/tonemap.cpp b/modules/photo/src/tonemap.cpp index 62725cb..0c85b5f 100644 --- a/modules/photo/src/tonemap.cpp +++ b/modules/photo/src/tonemap.cpp @@ -43,6 +43,7 @@ #include "precomp.hpp" #include "opencv2/photo.hpp" #include "opencv2/imgproc.hpp" +#include "hdr_common.hpp" namespace cv { @@ -50,32 +51,32 @@ namespace cv class TonemapLinearImpl : public TonemapLinear { public: - 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 + 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 { fs << "name" << name << "gamma" << gamma; @@ -89,79 +90,76 @@ public: } protected: - String name; - float gamma; + String name; + float gamma; }; Ptr createTonemapLinear(float gamma) { - return new TonemapLinearImpl(gamma); + return new TonemapLinearImpl(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); + 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 - << "bias" << bias; + << "bias" << bias + << "saturation" << saturation; } void read(const FileNode& fn) @@ -169,82 +167,82 @@ public: FileNode n = fn["name"]; CV_Assert(n.isString() && String(n) == name); gamma = fn["gamma"]; - bias = fn["bias"]; + bias = fn["bias"]; + saturation = fn["saturation"]; } protected: - String name; - float gamma, bias; + String name; + float gamma, saturation, bias; }; -Ptr createTonemapDrago(float gamma, float bias) +Ptr createTonemapDrago(float gamma, float saturation, float bias) { - return new TonemapDragoImpl(gamma, bias); + return new TonemapDragoImpl(gamma, saturation, bias); } 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); + 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 - << "contrast" << contrast - << "sigma_color" << sigma_color - << "sigma_space" << sigma_space; + << "contrast" << contrast + << "sigma_color" << sigma_color + << "sigma_space" << sigma_space + << "saturation" << saturation; } void read(const FileNode& fn) @@ -252,95 +250,95 @@ public: FileNode n = fn["name"]; CV_Assert(n.isString() && String(n) == name); gamma = fn["gamma"]; - contrast = fn["contrast"]; - sigma_color = fn["sigma_color"]; - sigma_space = fn["sigma_space"]; + contrast = fn["contrast"]; + sigma_color = fn["sigma_color"]; + sigma_space = fn["sigma_space"]; + saturation = fn["saturation"]; } protected: - String name; - float gamma, contrast, sigma_color, sigma_space; + String name; + float gamma, saturation, contrast, sigma_color, sigma_space; }; -Ptr createTonemapDurand(float gamma, float contrast, float sigma_color, float sigma_space) +Ptr createTonemapDurand(float gamma, float saturation, float contrast, float sigma_color, float sigma_space) { - return new TonemapDurandImpl(gamma, contrast, sigma_color, sigma_space); + return new TonemapDurandImpl(gamma, saturation, contrast, sigma_color, sigma_space); } 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); + 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 - << "intensity" << intensity - << "light_adapt" << light_adapt - << "color_adapt" << color_adapt; + << "intensity" << intensity + << "light_adapt" << light_adapt + << "color_adapt" << color_adapt; } void read(const FileNode& fn) @@ -348,19 +346,187 @@ public: FileNode n = fn["name"]; CV_Assert(n.isString() && String(n) == name); gamma = fn["gamma"]; - intensity = fn["intensity"]; - light_adapt = fn["light_adapt"]; - color_adapt = fn["color_adapt"]; + intensity = fn["intensity"]; + light_adapt = fn["light_adapt"]; + color_adapt = fn["color_adapt"]; } protected: - String name; - float gamma, intensity, light_adapt, color_adapt; + String name; + float gamma, intensity, light_adapt, color_adapt; }; 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); + 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->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); } } \ No newline at end of file diff --git a/modules/photo/test/test_hdr.cpp b/modules/photo/test/test_hdr.cpp index dd44b16..92738c2 100644 --- a/modules/photo/test/test_hdr.cpp +++ b/modules/photo/test/test_hdr.cpp @@ -91,12 +91,11 @@ void loadResponseCSV(String path, Mat& response) 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); linear->process(img, result); @@ -121,6 +120,12 @@ TEST(Photo_Tonemap, regression) loadImage(test_path + "reinharddevlin.png", expected); result.convertTo(result, CV_8UC3, 255); checkEqual(result, expected, 0); + + Ptr mantiuk = createTonemapMantiuk(gamma); + mantiuk->process(img, result); + loadImage(test_path + "mantiuk.png", expected); + result.convertTo(result, CV_8UC3, 255); + checkEqual(result, expected, 0); } TEST(Photo_AlignMTB, regression) -- 2.7.4