From 4d2ea847fa40bab8b3a556d1b14bc13fb71238b0 Mon Sep 17 00:00:00 2001 From: Fedor Morozov Date: Wed, 31 Jul 2013 16:05:31 +0400 Subject: [PATCH] Tonemap as 3.0 algorithm --- modules/photo/include/opencv2/photo.hpp | 79 ++--- modules/photo/src/align.cpp | 161 --------- modules/photo/src/hdr_fusion.cpp | 294 ---------------- modules/photo/src/tonemap.cpp | 445 ++++++++++++++++-------- modules/photo/test/test_hdr.cpp | 239 ++++++++----- 5 files changed, 474 insertions(+), 744 deletions(-) delete mode 100644 modules/photo/src/hdr_fusion.cpp diff --git a/modules/photo/include/opencv2/photo.hpp b/modules/photo/include/opencv2/photo.hpp index dc7684ac0..aa3044048 100644 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@ -59,6 +59,8 @@ enum INPAINT_TELEA = 1 // A. Telea algorithm }; +CV_EXPORTS_W bool initModule_photo(); + //! restores the damaged image areas using one of the available intpainting algorithms CV_EXPORTS_W void inpaint( InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags ); @@ -80,77 +82,62 @@ CV_EXPORTS_W void fastNlMeansDenoisingColoredMulti( InputArrayOfArrays srcImgs, float h = 3, float hColor = 3, int templateWindowSize = 7, int searchWindowSize = 21); -CV_EXPORTS_W void makeHDR(InputArrayOfArrays srcImgs, const std::vector& exp_times, OutputArray dst, Mat response = Mat()); - -CV_EXPORTS_W void exposureFusion(InputArrayOfArrays srcImgs, OutputArray dst, float wc = 1.0f, float ws = 1.0f, float we = 0.0f); - -CV_EXPORTS_W void shiftMat(InputArray src, Point shift, OutputArray dst); - -CV_EXPORTS_W Point getExpShift(InputArray img0, InputArray img1, int max_bits = 6, int exclude_range = 4); - -CV_EXPORTS_W void estimateResponse(InputArrayOfArrays srcImgs, const std::vector& exp_times, OutputArray dst, int samples = 50, float lambda = 10); - -CV_EXPORTS_W void alignImages(InputArrayOfArrays src, std::vector& dst); - class CV_EXPORTS_W Tonemap : public Algorithm { public: - Tonemap(float gamma); - virtual ~Tonemap(); - void process(InputArray src, OutputArray dst); - static Ptr create(const String& name); -protected: - float gamma; - Mat img; - void linearMap(); - void gammaCorrection(); + CV_WRAP virtual void process(InputArray src, OutputArray dst) = 0; - virtual void tonemap() = 0; + CV_WRAP virtual float getGamma() const = 0; + CV_WRAP virtual void setGamma(float gamma) = 0; }; class CV_EXPORTS_W TonemapLinear : public Tonemap { -public: - TonemapLinear(float gamma = 2.2f); - AlgorithmInfo* info() const; -protected: - void tonemap(); }; +CV_EXPORTS_W Ptr createTonemapLinear(float gamma = 1.0f); + class CV_EXPORTS_W TonemapDrago : public Tonemap { public: - TonemapDrago(float gamma = 2.2f, float bias = 0.85f); - AlgorithmInfo* info() const; -protected: - float bias; - void tonemap(); + 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); + class CV_EXPORTS_W TonemapDurand : public Tonemap { public: - TonemapDurand(float gamma = 2.2f, float contrast = 4.0f, float sigma_color = 2.0f, float sigma_space = 2.0f); - AlgorithmInfo* info() const; -protected: - float contrast; - float sigma_color; - float sigma_space; - void tonemap(); + 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 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); + class CV_EXPORTS_W TonemapReinhardDevlin : public Tonemap { public: - TonemapReinhardDevlin(float gamma = 2.2f, float intensity = 0.0f, float color_adapt = 0.0f, float light_adapt = 1.0f); - AlgorithmInfo* info() const; -protected: - float intensity; - float color_adapt; - float light_adapt; - void tonemap(); + 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 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); + } // cv #endif diff --git a/modules/photo/src/align.cpp b/modules/photo/src/align.cpp index 7261c85a4..e69de29bb 100644 --- a/modules/photo/src/align.cpp +++ b/modules/photo/src/align.cpp @@ -1,161 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#include "precomp.hpp" -#include "opencv2/photo.hpp" -#include "opencv2/imgproc.hpp" - -namespace cv -{ - -static 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; - } -} - -static 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]); - } -} - -static 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 += (int)ptr[median]; - median++; - } - return median; -} - -static 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); -} - -void shiftMat(InputArray _src, Point shift, OutputArray _dst) -{ - 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)); -} - -Point getExpShift(InputArray _img0, InputArray _img1, int max_bits, int exclude_range) -{ - Mat img0 = _img0.getMat(); - Mat img1 = _img1.getMat(); - CV_Assert(img0.type() == CV_8UC1 && img1.type() == CV_8UC1); - CV_Assert(img0.size() == img0.size()); - int maxlevel = (int)(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); - - Point shift(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, test_shift, shifted_tb2); - shiftMat(eb2, test_shift, shifted_eb2); - 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; - } - return shift; -} - -}; diff --git a/modules/photo/src/hdr_fusion.cpp b/modules/photo/src/hdr_fusion.cpp deleted file mode 100644 index c088933a7..000000000 --- a/modules/photo/src/hdr_fusion.cpp +++ /dev/null @@ -1,294 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#include "opencv2/photo.hpp" -#include "opencv2/imgproc.hpp" - -#include - -namespace cv -{ - -static void triangleWeights(float weights[]) -{ - for(int i = 0; i < 128; i++) { - weights[i] = i + 1.0f; - } - for(int i = 128; i < 256; i++) { - weights[i] = 256.0f - i; - } -} - -static Mat linearResponse() -{ - Mat response(256, 1, CV_32F); - for(int i = 1; i < 256; i++) { - response.at(i) = logf((float)i); - } - response.at(0) = response.at(1); - return response; -} - -static void modifyCheckResponse(Mat &response) -{ - if(response.empty()) { - response = linearResponse(); - } - CV_Assert(response.rows == 256 && (response.cols == 1 || response.cols == 3)); - response.convertTo(response, CV_32F); - if(response.cols == 1) { - Mat result(256, 3, CV_32F); - for(int i = 0; i < 3; i++) { - response.copyTo(result.col(i)); - } - response = result; - } -} - -static void checkImages(const std::vector& images, bool hdr, const std::vector& _exp_times = std::vector()) -{ - CV_Assert(!images.empty()); - CV_Assert(!hdr || images.size() == _exp_times.size()); - int width = images[0].cols; - int height = images[0].rows; - int channels = images[0].channels(); - for(size_t i = 0; i < images.size(); i++) { - - CV_Assert(images[i].cols == width && images[i].rows == height); - CV_Assert(images[i].channels() == channels && images[i].depth() == CV_8U); - } -} - -void alignImages(InputArrayOfArrays _src, std::vector& dst) -{ - std::vector src; - _src.getMatVector(src); - checkImages(src, false); - 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 = getExpShift(gray_base, gray); - shiftMat(src[i], shift, dst[i]); - } -} - -void makeHDR(InputArrayOfArrays _images, const std::vector& _exp_times, OutputArray _dst, Mat response) -{ - std::vector images; - _images.getMatVector(images); - checkImages(images, true, _exp_times); - modifyCheckResponse(response); - _dst.create(images[0].size(), CV_MAKETYPE(CV_32F, images[0].channels())); - Mat result = _dst.getMat(); - - std::vector exp_times(_exp_times.size()); - for(size_t i = 0; i < exp_times.size(); i++) { - exp_times[i] = logf(_exp_times[i]); - } - - float weights[256]; - triangleWeights(weights); - - 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++) { - - uchar *img_ptr = images[im].ptr() + channels * pos; - float w = 0; - for(int channel = 0; channel < channels; channel++) { - w += weights[img_ptr[channel]]; - } - w /= channels; - weight_sum += w; - for(int channel = 0; channel < channels; channel++) { - sum[channel] += w * (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 exposureFusion(InputArrayOfArrays _images, OutputArray _dst, float wc, float ws, float we) -{ - std::vector images; - _images.getMatVector(images); - checkImages(images, false); - - 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); - - 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); - - 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, wc, contrast); - pow(saturation, ws, saturation); - pow(wellexp, we, 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); - - 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); - Mat result = _dst.getMat(); - res_pyr[0].copyTo(result); -} - -void estimateResponse(InputArrayOfArrays _images, const std::vector& exp_times, OutputArray _dst, int samples, float lambda) -{ - std::vector images; - _images.getMatVector(images); - checkImages(images, true, exp_times); - _dst.create(256, images[0].channels(), CV_32F); - Mat response = _dst.getMat(); - - float w[256]; - triangleWeights(w); - - 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 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[val]; - A.at(eq, 256 + i) = -w[val]; - B.at(eq, 0) = w[val] * log(exp_times[j]); - eq++; - } - } - A.at(eq, 128) = 1; - eq++; - - for(int i = 0; i < 254; i++) { - A.at(eq, i) = lambda * w[i + 1]; - A.at(eq, i + 1) = -2 * lambda * w[i + 1]; - A.at(eq, i + 2) = lambda * w[i + 1]; - eq++; - } - Mat solution; - solve(A, B, solution, DECOMP_SVD); - solution.rowRange(0, 256).copyTo(response.col(channel)); - } -} - -}; - diff --git a/modules/photo/src/tonemap.cpp b/modules/photo/src/tonemap.cpp index d515cefda..f503be90e 100644 --- a/modules/photo/src/tonemap.cpp +++ b/modules/photo/src/tonemap.cpp @@ -47,183 +47,320 @@ namespace cv { -Tonemap::Tonemap(float gamma) : gamma(gamma) +class TonemapLinearImpl : public TonemapLinear { -} +public: + TonemapLinearImpl(float gamma) : gamma(gamma), name("TonemapLinear") + { + } -Tonemap::~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); + } -void Tonemap::process(InputArray src, OutputArray dst) -{ - Mat srcMat = src.getMat(); - CV_Assert(!srcMat.empty()); - dst.create(srcMat.size(), CV_32FC3); - img = dst.getMat(); - srcMat.copyTo(img); - linearMap(); - tonemap(); - gammaCorrection(); -} + pow(dst, 1.0f / gamma, dst); + } -void Tonemap::linearMap() -{ - double min, max; - minMaxLoc(img, &min, &max); - if(max - min > DBL_EPSILON) { - img = (img - min) / (max - min); + float getGamma() const { return gamma; } + void setGamma(float val) { gamma = val; } + + void write(FileStorage& fs) const + { + fs << "name" << name + << "gamma" << gamma; } -} -void Tonemap::gammaCorrection() -{ - pow(img, 1.0f / gamma, img); -} - -void TonemapLinear::tonemap() -{ -} - -TonemapLinear::TonemapLinear(float gamma) : Tonemap(gamma) -{ -} - -TonemapDrago::TonemapDrago(float gamma, float bias) : - Tonemap(gamma), - bias(bias) -{ -} - -void TonemapDrago::tonemap() -{ - 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 / (float)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); + void read(const FileNode& fn) + { + FileNode n = fn["name"]; + CV_Assert(n.isString() && String(n) == name); + gamma = fn["gamma"]; } - map.release(); - merge(channels, img); - linearMap(); + +protected: + String name; + float gamma; +}; + +Ptr createTonemapLinear(float gamma) +{ + return new TonemapLinearImpl(gamma); } -TonemapDurand::TonemapDurand(float gamma, float contrast, float sigma_color, float sigma_space) : - Tonemap(gamma), - contrast(contrast), - sigma_color(sigma_color), - sigma_space(sigma_space) +class TonemapDragoImpl : public TonemapDrago { -} +public: + TonemapDragoImpl(float gamma, float bias) : + gamma(gamma), + bias(bias), + name("TonemapLinear") + { + } -void TonemapDurand::tonemap() + 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 / (float)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 + { + fs << "name" << name + << "gamma" << gamma + << "bias" << bias; + } + + void read(const FileNode& fn) + { + FileNode n = fn["name"]; + CV_Assert(n.isString() && String(n) == name); + gamma = fn["gamma"]; + bias = fn["bias"]; + } + +protected: + String name; + float gamma, bias; +}; + +Ptr createTonemapDrago(float gamma, float bias) { - 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); + return new TonemapDragoImpl(gamma, bias); +} + +class TonemapDurandImpl : public TonemapDurand +{ +public: + TonemapDurandImpl(float gamma, float contrast, float sigma_color, float sigma_space) : + gamma(gamma), + 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 / (float)(max - min); + double min, max; + minMaxLoc(map_img, &min, &max); + float scale = contrast / (float)(max - min); - 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(); + 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(); - std::vector channels(3); - split(img, channels); - for(int i = 0; i < 3; i++) { - channels[i] = channels[i].mul(map_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 getContrast() const { return contrast; } + void setContrast(float val) { contrast = 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; } + + void write(FileStorage& fs) const + { + fs << "name" << name + << "gamma" << gamma + << "contrast" << contrast + << "sigma_color" << sigma_color + << "sigma_space" << sigma_space; } - merge(channels, img); -} -TonemapReinhardDevlin::TonemapReinhardDevlin(float gamma, float intensity, float color_adapt, float light_adapt) : - Tonemap(gamma), - intensity(intensity), - color_adapt(color_adapt), - light_adapt(light_adapt) -{ -} - -void TonemapReinhardDevlin::tonemap() -{ - Mat gray_img; - cvtColor(img, gray_img, COLOR_RGB2GRAY); - Mat log_img; - log(gray_img, log_img); - - float log_mean = (float)sum(log_img)[0] / log_img.total(); - double log_min, log_max; - minMaxLoc(log_img, &log_min, &log_max); - log_img.release(); - - double key = (float)((log_max - log_mean) / (log_max - log_min)); - float map_key = 0.3f + 0.7f * pow((float)key, 1.4f); - intensity = exp(-intensity); - Scalar chan_mean = mean(img); - float gray_mean = (float)mean(gray_img)[0]; - - std::vector channels(3); - split(img, channels); - - for(int i = 0; i < 3; i++) { - float global = color_adapt * (float)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])); + void read(const FileNode& fn) + { + 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"]; } - gray_img.release(); - merge(channels, img); - linearMap(); -} -Ptr Tonemap::create(const String& TonemapType) +protected: + String name; + float gamma, contrast, sigma_color, sigma_space; +}; + +Ptr createTonemapDurand(float gamma, float contrast, float sigma_color, float sigma_space) { - return Algorithm::create("Tonemap." + TonemapType); + return new TonemapDurandImpl(gamma, contrast, sigma_color, sigma_space); } -CV_INIT_ALGORITHM(TonemapLinear, "Tonemap.Linear", - obj.info()->addParam(obj, "gamma", obj.gamma)); +class TonemapReinhardDevlinImpl : public TonemapReinhardDevlin +{ +public: + 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") + { + } -CV_INIT_ALGORITHM(TonemapDrago, "Tonemap.Drago", - obj.info()->addParam(obj, "gamma", obj.gamma); - obj.info()->addParam(obj, "bias", obj.bias)); + 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); -CV_INIT_ALGORITHM(TonemapDurand, "Tonemap.Durand", - obj.info()->addParam(obj, "gamma", obj.gamma); - obj.info()->addParam(obj, "contrast", obj.contrast); - obj.info()->addParam(obj, "sigma_color", obj.sigma_color); - obj.info()->addParam(obj, "sigma_space", obj.sigma_space)); + float log_mean = (float)sum(log_img)[0] / log_img.total(); + double log_min, log_max; + minMaxLoc(log_img, &log_min, &log_max); + log_img.release(); -CV_INIT_ALGORITHM(TonemapReinhardDevlin, "Tonemap.ReinhardDevlin", - obj.info()->addParam(obj, "gamma", obj.gamma); - obj.info()->addParam(obj, "intensity", obj.intensity); - obj.info()->addParam(obj, "color_adapt", obj.color_adapt); - obj.info()->addParam(obj, "light_adapt", obj.light_adapt)); + double key = (float)((log_max - log_mean) / (log_max - log_min)); + float map_key = 0.3f + 0.7f * pow((float)key, 1.4f); + intensity = exp(-intensity); + Scalar chan_mean = mean(img); + float gray_mean = (float)mean(gray_img)[0]; + + std::vector channels(3); + split(img, channels); + + for(int i = 0; i < 3; i++) { + float global = color_adapt * (float)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; + } + + void read(const FileNode& fn) + { + 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"]; + } + +protected: + 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); } + +} \ No newline at end of file diff --git a/modules/photo/test/test_hdr.cpp b/modules/photo/test/test_hdr.cpp index 9420e971e..61b0cdab4 100644 --- a/modules/photo/test/test_hdr.cpp +++ b/modules/photo/test/test_hdr.cpp @@ -61,98 +61,159 @@ void checkEqual(Mat img0, Mat img1, double threshold) ASSERT_FALSE(max > threshold); } -TEST(Photo_HdrFusion, regression) -{ - string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/"; - string fuse_path = test_path + "fusion/"; - - vector times; - vector images; - - ifstream list_file(fuse_path + "list.txt"); - ASSERT_TRUE(list_file.is_open()); - string name; - float val; - while(list_file >> name >> val) { - Mat img = imread(fuse_path + name); - ASSERT_FALSE(img.empty()) << "Could not load input image " << fuse_path + name; - images.push_back(img); - times.push_back(1 / val); - } - list_file.close(); - - Mat response, expected(256, 3, CV_32F); - ifstream resp_file(test_path + "response.csv"); - for(int i = 0; i < 256; i++) { - for(int channel = 0; channel < 3; channel++) { - resp_file >> expected.at(i, channel); - resp_file.ignore(1); - } - } - resp_file.close(); - - estimateResponse(images, times, response); - checkEqual(expected, response, 0.001); - - Mat result; - loadImage(test_path + "no_calibration.hdr", expected); - makeHDR(images, times, result); - checkEqual(expected, result, 0.01); - - loadImage(test_path + "rle.hdr", expected); - makeHDR(images, times, result, response); - checkEqual(expected, result, 0.01); - - loadImage(test_path + "exp_fusion.png", expected); - exposureFusion(images, result); - result.convertTo(result, CV_8UC3, 255); - checkEqual(expected, result, 0); -} - TEST(Photo_Tonemap, regression) { - string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/tonemap/"; - - Mat img; - loadImage(test_path + "../rle.hdr", img); - ifstream list_file(test_path + "list.txt"); - ASSERT_TRUE(list_file.is_open()); - - string name; - while(list_file >> name) { - Mat expected = imread(test_path + name + ".png"); - ASSERT_FALSE(img.empty()) << "Could not load input image " << test_path + name + ".png"; - Ptr mapper = Tonemap::create(name); - ASSERT_FALSE(mapper.empty()) << "Could not find mapper " << name; - Mat result; - mapper->process(img, result); - result.convertTo(result, CV_8UC3, 255); - checkEqual(expected, result, 0); - } - list_file.close(); -} - -TEST(Photo_Align, regression) -{ - const int TESTS_COUNT = 100; - string folder = string(cvtest::TS::ptr()->get_data_path()) + "shared/"; + string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/"; - string file_name = folder + "lena.png"; - Mat img = imread(file_name); - ASSERT_FALSE(img.empty()) << "Could not load input image " << file_name; - cvtColor(img, img, COLOR_RGB2GRAY); + Mat img, expected, result; + loadImage(test_path + "rle.hdr", img); + float gamma = 2.2f; + test_path += "tonemap/"; + + Ptr linear = createTonemapLinear(gamma); + linear->process(img, result); + loadImage(test_path + "linear.png", expected); + result.convertTo(result, CV_8UC3, 255); + checkEqual(result, expected, 0); - int max_bits = 5; - int max_shift = 32; - srand(static_cast(time(0))); - int errors = 0; + Ptr drago = createTonemapDrago(gamma); + drago->process(img, result); + loadImage(test_path + "drago.png", expected); + result.convertTo(result, CV_8UC3, 255); + checkEqual(result, expected, 0); - for(int i = 0; i < TESTS_COUNT; i++) { - Point shift(rand() % max_shift, rand() % max_shift); - Mat res; - shiftMat(img, shift, res); - Point calc = getExpShift(img, res, max_bits); - errors += (calc != -shift); - } - ASSERT_TRUE(errors < 5); + Ptr durand = createTonemapDurand(gamma); + durand->process(img, result); + loadImage(test_path + "durand.png", expected); + result.convertTo(result, CV_8UC3, 255); + checkEqual(result, expected, 0); + + Ptr reinhard_devlin = createTonemapReinhardDevlin(gamma); + reinhard_devlin->process(img, result); + loadImage(test_path + "reinhard_devlin.png", expected); + result.convertTo(result, CV_8UC3, 255); + checkEqual(result, expected, 0); } + + + +//void loadExposureSeq(String fuse_path, vector& images, vector& times = vector()) +//{ +// ifstream list_file(fuse_path + "list.txt"); +// ASSERT_TRUE(list_file.is_open()); +// string name; +// float val; +// while(list_file >> name >> val) { +// Mat img = imread(fuse_path + name); +// ASSERT_FALSE(img.empty()) << "Could not load input image " << fuse_path + name; +// images.push_back(img); +// times.push_back(1 / val); +// } +// list_file.close(); +//} +//// +////TEST(Photo_MergeMertens, regression) +////{ +//// string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/"; +//// string fuse_path = test_path + "fusion/"; +//// +//// vector images; +//// loadExposureSeq(fuse_path, images); +//// +//// MergeMertens merge; +//// +//// Mat result, expected; +//// loadImage(test_path + "exp_fusion.png", expected); +//// merge.process(images, result); +//// result.convertTo(result, CV_8UC3, 255); +//// checkEqual(expected, result, 0); +////} +// +//TEST(Photo_Debevec, regression) +//{ +// string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/"; +// string fuse_path = test_path + "fusion/"; +// +// vector times; +// vector images; +// +// loadExposureSeq(fuse_path, images, times); +// +// Mat response, expected(256, 3, CV_32F); +// ifstream resp_file(test_path + "response.csv"); +// for(int i = 0; i < 256; i++) { +// for(int channel = 0; channel < 3; channel++) { +// resp_file >> expected.at(i, channel); +// resp_file.ignore(1); +// } +// } +// resp_file.close(); +// +// CalibrateDebevec calib; +// MergeDebevec merge; +// +// //calib.process(images, response, times); +// //checkEqual(expected, response, 0.001); +// // +// Mat result; +// loadImage(test_path + "no_calibration.hdr", expected); +// merge.process(images, result, times); +// checkEqual(expected, result, 0.01); +// +// //loadImage(test_path + "rle.hdr", expected); +// //merge.process(images, result, times, response); +// //checkEqual(expected, result, 0.01); +//} +// +//TEST(Photo_Tonemap, regression) +//{ +// initModule_photo(); +// string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/tonemap/"; +// Mat img; +// loadImage(test_path + "../rle.hdr", img); +// +// vector algorithms; +// Algorithm::getList(algorithms); +// for(size_t i = 0; i < algorithms.size(); i++) { +// String str = algorithms[i]; +// size_t dot = str.find('.'); +// if(dot != String::npos && str.substr(0, dot).compare("Tonemap") == 0) { +// String algo_name = str.substr(dot + 1, str.size()); +// Mat expected; +// loadImage(test_path + algo_name.toLowerCase() + ".png", expected); +// Ptr mapper = Tonemap::create(algo_name); +// ASSERT_FALSE(mapper.empty()) << algo_name; +// Mat result; +// mapper->process(img, result); +// result.convertTo(result, CV_8UC3, 255); +// checkEqual(expected, result, 0); +// } +// } +////} +//// +////TEST(Photo_AlignMTB, regression) +////{ +//// const int TESTS_COUNT = 100; +//// string folder = string(cvtest::TS::ptr()->get_data_path()) + "shared/"; +//// +//// string file_name = folder + "lena.png"; +//// Mat img = imread(file_name); +//// ASSERT_FALSE(img.empty()) << "Could not load input image " << file_name; +//// cvtColor(img, img, COLOR_RGB2GRAY); +//// +//// int max_bits = 5; +//// int max_shift = 32; +//// srand(static_cast(time(0))); +//// int errors = 0; +//// +//// AlignMTB align(max_bits); +//// +//// for(int i = 0; i < TESTS_COUNT; i++) { +//// Point shift(rand() % max_shift, rand() % max_shift); +//// Mat res; +//// align.shiftMat(img, shift, res); +//// Point calc = align.getExpShift(img, res); +//// errors += (calc != -shift); +//// } +//// ASSERT_TRUE(errors < 5); +////}