Commit 17609b90 authored by Fedor Morozov's avatar Fedor Morozov

Mantiuk's tonemapping

parent c51b50b4
...@@ -123,10 +123,9 @@ HdrEncoder::~HdrEncoder() ...@@ -123,10 +123,9 @@ HdrEncoder::~HdrEncoder()
bool HdrEncoder::write( const Mat& _img, const std::vector<int>& params ) bool HdrEncoder::write( const Mat& _img, const std::vector<int>& params )
{ {
CV_Assert(_img.channels() == 3);
Mat img; Mat img;
if(_img.depth() == CV_32F) { if(_img.depth() != CV_32F) {
_img.convertTo(img, CV_32FC3);
} else {
_img.convertTo(img, CV_32FC3, 1/255.0f); _img.convertTo(img, CV_32FC3, 1/255.0f);
} }
CV_Assert(params.empty() || params[0] == HDR_NONE || params[0] == HDR_RLE); CV_Assert(params.empty() || params[0] == HDR_NONE || params[0] == HDR_RLE);
......
...@@ -87,8 +87,8 @@ class CV_EXPORTS_W Tonemap : public Algorithm ...@@ -87,8 +87,8 @@ class CV_EXPORTS_W Tonemap : public Algorithm
public: public:
CV_WRAP virtual void process(InputArray src, OutputArray dst) = 0; CV_WRAP virtual void process(InputArray src, OutputArray dst) = 0;
CV_WRAP virtual float getGamma() const = 0; CV_WRAP virtual float getGamma() const = 0;
CV_WRAP virtual void setGamma(float gamma) = 0; CV_WRAP virtual void setGamma(float gamma) = 0;
}; };
class CV_EXPORTS_W TonemapLinear : public Tonemap class CV_EXPORTS_W TonemapLinear : public Tonemap
...@@ -102,71 +102,92 @@ CV_EXPORTS_W Ptr<TonemapLinear> createTonemapLinear(float gamma = 1.0f); ...@@ -102,71 +102,92 @@ CV_EXPORTS_W Ptr<TonemapLinear> createTonemapLinear(float gamma = 1.0f);
class CV_EXPORTS_W TonemapDrago : public Tonemap class CV_EXPORTS_W TonemapDrago : public Tonemap
{ {
public: 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<TonemapDrago> createTonemapDrago(float gamma = 1.0f, float bias = 0.85f); CV_EXPORTS_W Ptr<TonemapDrago> 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 // "Fast Bilateral Filtering for the Display of High-Dynamic-Range Images", Durand, Dorsey, 2002
class CV_EXPORTS_W TonemapDurand : public Tonemap class CV_EXPORTS_W TonemapDurand : public Tonemap
{ {
public: 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 float getSaturation() const = 0;
CV_WRAP virtual void setSigmaSpace(float sigma_space) = 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 float getSigmaSpace() const = 0;
CV_WRAP virtual void setSigmaColor(float sigma_color) = 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<TonemapDurand> CV_EXPORTS_W Ptr<TonemapDurand>
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 // "Dynamic Range Reduction Inspired by Photoreceptor Physiology", Reinhard, Devlin, 2005
class CV_EXPORTS_W TonemapReinhardDevlin : public Tonemap class CV_EXPORTS_W TonemapReinhardDevlin : public Tonemap
{ {
public: public:
CV_WRAP virtual float getIntensity() const = 0; CV_WRAP virtual float getIntensity() const = 0;
CV_WRAP virtual void setIntensity(float intensity) = 0; CV_WRAP virtual void setIntensity(float intensity) = 0;
CV_WRAP virtual float getLightAdaptation() const = 0; CV_WRAP virtual float getLightAdaptation() const = 0;
CV_WRAP virtual void setLightAdaptation(float light_adapt) = 0; CV_WRAP virtual void setLightAdaptation(float light_adapt) = 0;
CV_WRAP virtual float getColorAdaptation() const = 0; CV_WRAP virtual float getColorAdaptation() const = 0;
CV_WRAP virtual void setColorAdaptation(float color_adapt) = 0; CV_WRAP virtual void setColorAdaptation(float color_adapt) = 0;
}; };
CV_EXPORTS_W Ptr<TonemapReinhardDevlin> CV_EXPORTS_W Ptr<TonemapReinhardDevlin>
createTonemapReinhardDevlin(float gamma = 1.0f, float intensity = 0.0f, float light_adapt = 1.0f, float color_adapt = 0.0f); 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<TonemapMantiuk>
createTonemapMantiuk(float gamma = 1.0f, float scale = 0.7f, float saturation = 1.0f);
class CV_EXPORTS_W ExposureAlign : public Algorithm class CV_EXPORTS_W ExposureAlign : public Algorithm
{ {
public: public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst, CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst,
const std::vector<float>& times, InputArray response) = 0; const std::vector<float>& times, InputArray response) = 0;
}; };
class CV_EXPORTS_W AlignMTB : public ExposureAlign class CV_EXPORTS_W AlignMTB : public ExposureAlign
{ {
public: public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst, CV_WRAP virtual void process(InputArrayOfArrays src, OutputArrayOfArrays dst,
const std::vector<float>& times, InputArray response) = 0; const std::vector<float>& 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 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 shiftMat(InputArray src, OutputArray dst, const Point shift) = 0;
CV_WRAP virtual int getMaxBits() const = 0; CV_WRAP virtual int getMaxBits() const = 0;
CV_WRAP virtual void setMaxBits(int max_bits) = 0; CV_WRAP virtual void setMaxBits(int max_bits) = 0;
CV_WRAP virtual int getExcludeRange() const = 0; CV_WRAP virtual int getExcludeRange() const = 0;
CV_WRAP virtual void setExcludeRange(int exclude_range) = 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 // "Fast, Robust Image Registration for Compositing High Dynamic Range Photographs from Handheld Exposures", Ward, 2003
...@@ -176,7 +197,7 @@ CV_EXPORTS_W Ptr<AlignMTB> createAlignMTB(int max_bits = 6, int exclude_range = ...@@ -176,7 +197,7 @@ CV_EXPORTS_W Ptr<AlignMTB> createAlignMTB(int max_bits = 6, int exclude_range =
class CV_EXPORTS_W ExposureCalibrate : public Algorithm class CV_EXPORTS_W ExposureCalibrate : public Algorithm
{ {
public: public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times) = 0; CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times) = 0;
}; };
// "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997 // "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997
...@@ -184,11 +205,11 @@ public: ...@@ -184,11 +205,11 @@ public:
class CV_EXPORTS_W CalibrateDebevec : public ExposureCalibrate class CV_EXPORTS_W CalibrateDebevec : public ExposureCalibrate
{ {
public: 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 void setLambda(float lambda) = 0;
CV_WRAP virtual int getSamples() const = 0; CV_WRAP virtual int getSamples() const = 0;
CV_WRAP virtual void setSamples(int samples) = 0; CV_WRAP virtual void setSamples(int samples) = 0;
}; };
CV_EXPORTS_W Ptr<CalibrateDebevec> createCalibrateDebevec(int samples = 50, float lambda = 10.0f); CV_EXPORTS_W Ptr<CalibrateDebevec> createCalibrateDebevec(int samples = 50, float lambda = 10.0f);
...@@ -196,8 +217,8 @@ CV_EXPORTS_W Ptr<CalibrateDebevec> createCalibrateDebevec(int samples = 50, floa ...@@ -196,8 +217,8 @@ CV_EXPORTS_W Ptr<CalibrateDebevec> createCalibrateDebevec(int samples = 50, floa
class CV_EXPORTS_W ExposureMerge : public Algorithm class CV_EXPORTS_W ExposureMerge : public Algorithm
{ {
public: public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
const std::vector<float>& times, InputArray response) = 0; const std::vector<float>& times, InputArray response) = 0;
}; };
// "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997 // "Recovering High Dynamic Range Radiance Maps from Photographs", Debevec, Malik, 1997
...@@ -205,9 +226,9 @@ public: ...@@ -205,9 +226,9 @@ public:
class CV_EXPORTS_W MergeDebevec : public ExposureMerge class CV_EXPORTS_W MergeDebevec : public ExposureMerge
{ {
public: public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
const std::vector<float>& times, InputArray response) = 0; const std::vector<float>& times, InputArray response) = 0;
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times) = 0; CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, const std::vector<float>& times) = 0;
}; };
CV_EXPORTS_W Ptr<MergeDebevec> createMergeDebevec(); CV_EXPORTS_W Ptr<MergeDebevec> createMergeDebevec();
...@@ -217,18 +238,18 @@ CV_EXPORTS_W Ptr<MergeDebevec> createMergeDebevec(); ...@@ -217,18 +238,18 @@ CV_EXPORTS_W Ptr<MergeDebevec> createMergeDebevec();
class CV_EXPORTS_W MergeMertens : public ExposureMerge class CV_EXPORTS_W MergeMertens : public ExposureMerge
{ {
public: public:
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst, CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst,
const std::vector<float>& times, InputArray response) = 0; const std::vector<float>& times, InputArray response) = 0;
CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst) = 0; CV_WRAP virtual void process(InputArrayOfArrays src, OutputArray dst) = 0;
CV_WRAP virtual float getContrastWeight() const = 0; CV_WRAP virtual float getContrastWeight() const = 0;
CV_WRAP virtual void setContrastWeight(float contrast_weiht) = 0; CV_WRAP virtual void setContrastWeight(float contrast_weiht) = 0;
CV_WRAP virtual float getSaturationWeight() const = 0; CV_WRAP virtual float getSaturationWeight() const = 0;
CV_WRAP virtual void setSaturationWeight(float saturation_weight) = 0; CV_WRAP virtual void setSaturationWeight(float saturation_weight) = 0;
CV_WRAP virtual float getExposureWeight() const = 0; CV_WRAP virtual float getExposureWeight() const = 0;
CV_WRAP virtual void setExposureWeight(float exposure_weight) = 0; CV_WRAP virtual void setExposureWeight(float exposure_weight) = 0;
}; };
CV_EXPORTS_W Ptr<MergeMertens> CV_EXPORTS_W Ptr<MergeMertens>
......
This diff is collapsed.
...@@ -47,73 +47,73 @@ ...@@ -47,73 +47,73 @@
namespace cv namespace cv
{ {
class CalibrateDebevecImpl : public CalibrateDebevec class CalibrateDebevecImpl : public CalibrateDebevec
{ {
public: public:
CalibrateDebevecImpl(int samples, float lambda) : CalibrateDebevecImpl(int samples, float lambda) :
samples(samples), samples(samples),
lambda(lambda), lambda(lambda),
name("CalibrateDebevec"), name("CalibrateDebevec"),
w(tringleWeights()) w(tringleWeights())
{ {
} }
void process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times) void process(InputArrayOfArrays src, OutputArray dst, std::vector<float>& times)
{ {
std::vector<Mat> images; std::vector<Mat> images;
src.getMatVector(images); src.getMatVector(images);
dst.create(256, images[0].channels(), CV_32F); dst.create(256, images[0].channels(), CV_32F);
Mat response = dst.getMat(); Mat response = dst.getMat();
CV_Assert(!images.empty() && images.size() == times.size()); CV_Assert(!images.empty() && images.size() == times.size());
CV_Assert(images[0].depth() == CV_8U); CV_Assert(images[0].depth() == CV_8U);
checkImageDimensions(images); checkImageDimensions(images);
for(int channel = 0; channel < images[0].channels(); channel++) { for(int channel = 0; channel < images[0].channels(); channel++) {
Mat A = Mat::zeros(samples * images.size() + 257, 256 + samples, CV_32F); Mat A = Mat::zeros(samples * images.size() + 257, 256 + samples, CV_32F);
Mat B = Mat::zeros(A.rows, 1, CV_32F); Mat B = Mat::zeros(A.rows, 1, CV_32F);
int eq = 0; int eq = 0;
for(int i = 0; i < samples; i++) { for(int i = 0; i < samples; i++) {
int pos = 3 * (rand() % images[0].total()) + channel; int pos = 3 * (rand() % images[0].total()) + channel;
for(size_t j = 0; j < images.size(); j++) { for(size_t j = 0; j < images.size(); j++) {
int val = (images[j].ptr() + pos)[0]; int val = (images[j].ptr() + pos)[0];
A.at<float>(eq, val) = w.at<float>(val); A.at<float>(eq, val) = w.at<float>(val);
A.at<float>(eq, 256 + i) = -w.at<float>(val); A.at<float>(eq, 256 + i) = -w.at<float>(val);
B.at<float>(eq, 0) = w.at<float>(val) * log(times[j]); B.at<float>(eq, 0) = w.at<float>(val) * log(times[j]);
eq++; eq++;
} }
} }
A.at<float>(eq, 128) = 1; A.at<float>(eq, 128) = 1;
eq++; eq++;
for(int i = 0; i < 254; i++) { for(int i = 0; i < 254; i++) {
A.at<float>(eq, i) = lambda * w.at<float>(i + 1); A.at<float>(eq, i) = lambda * w.at<float>(i + 1);
A.at<float>(eq, i + 1) = -2 * lambda * w.at<float>(i + 1); A.at<float>(eq, i + 1) = -2 * lambda * w.at<float>(i + 1);
A.at<float>(eq, i + 2) = lambda * w.at<float>(i + 1); A.at<float>(eq, i + 2) = lambda * w.at<float>(i + 1);
eq++; eq++;
} }
Mat solution; Mat solution;
solve(A, B, solution, DECOMP_SVD); solve(A, B, solution, DECOMP_SVD);
solution.rowRange(0, 256).copyTo(response.col(channel)); solution.rowRange(0, 256).copyTo(response.col(channel));
} }
exp(response, response); exp(response, response);
} }
int getSamples() const { return samples; } int getSamples() const { return samples; }
void setSamples(int val) { samples = val; } void setSamples(int val) { samples = val; }
float getLambda() const { return lambda; } float getLambda() const { return lambda; }
void setLambda(float val) { lambda = val; } void setLambda(float val) { lambda = val; }
void write(FileStorage& fs) const void write(FileStorage& fs) const
{ {
fs << "name" << name fs << "name" << name
<< "samples" << samples << "samples" << samples
<< "lambda" << lambda; << "lambda" << lambda;
} }
void read(const FileNode& fn) void read(const FileNode& fn)
...@@ -121,19 +121,19 @@ public: ...@@ -121,19 +121,19 @@ public:
FileNode n = fn["name"]; FileNode n = fn["name"];
CV_Assert(n.isString() && String(n) == name); CV_Assert(n.isString() && String(n) == name);
samples = fn["samples"]; samples = fn["samples"];
lambda = fn["lambda"]; lambda = fn["lambda"];
} }
protected: protected:
String name; String name;
int samples; int samples;
float lambda; float lambda;
Mat w; Mat w;
}; };
Ptr<CalibrateDebevec> createCalibrateDebevec(int samples, float lambda) Ptr<CalibrateDebevec> createCalibrateDebevec(int samples, float lambda)
{ {
return new CalibrateDebevecImpl(samples, lambda); return new CalibrateDebevecImpl(samples, lambda);
} }
} }
\ No newline at end of file
...@@ -49,26 +49,38 @@ namespace cv ...@@ -49,26 +49,38 @@ namespace cv
void checkImageDimensions(const std::vector<Mat>& images) void checkImageDimensions(const std::vector<Mat>& images)
{ {
CV_Assert(!images.empty()); CV_Assert(!images.empty());
int width = images[0].cols; int width = images[0].cols;
int height = images[0].rows; int height = images[0].rows;
int type = images[0].type(); int type = images[0].type();
for(size_t i = 0; i < images.size(); i++) { for(size_t i = 0; i < images.size(); i++) {
CV_Assert(images[i].cols == width && images[i].rows == height); CV_Assert(images[i].cols == width && images[i].rows == height);
CV_Assert(images[i].type() == type); CV_Assert(images[i].type() == type);
} }
} }
Mat tringleWeights() Mat tringleWeights()
{ {
Mat w(256, 3, CV_32F); Mat w(256, 3, CV_32F);
for(int i = 0; i < 256; i++) { for(int i = 0; i < 256; i++) {
for(int j = 0; j < 3; j++) { for(int j = 0; j < 3; j++) {
w.at<float>(i, j) = i < 128 ? i + 1.0f : 256.0f - i; w.at<float>(i, j) = i < 128 ? i + 1.0f : 256.0f - i;
} }
} }
return w; return w;
}
void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation)
{
std::vector<Mat> 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
...@@ -53,6 +53,8 @@ void checkImageDimensions(const std::vector<Mat>& images); ...@@ -53,6 +53,8 @@ void checkImageDimensions(const std::vector<Mat>& images);
Mat tringleWeights(); Mat tringleWeights();
void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation);
}; };
#endif #endif
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
...@@ -91,12 +91,11 @@ void loadResponseCSV(String path, Mat& response) ...@@ -91,12 +91,11 @@ void loadResponseCSV(String path, Mat& response)
TEST(Photo_Tonemap, regression) 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; Mat img, expected, result;
loadImage(test_path + "rle.hdr", img); loadImage(test_path + "image.hdr", img);
float gamma = 2.2f; float gamma = 2.2f;
test_path += "tonemap/";
Ptr<TonemapLinear> linear = createTonemapLinear(gamma); Ptr<TonemapLinear> linear = createTonemapLinear(gamma);
linear->process(img, result); linear->process(img, result);
...@@ -121,6 +120,12 @@ TEST(Photo_Tonemap, regression) ...@@ -121,6 +120,12 @@ TEST(Photo_Tonemap, regression)
loadImage(test_path + "reinharddevlin.png", expected); loadImage(test_path + "reinharddevlin.png", expected);
result.convertTo(result, CV_8UC3, 255); result.convertTo(result, CV_8UC3, 255);
checkEqual(result, expected, 0); checkEqual(result, expected, 0);
Ptr<TonemapMantiuk> 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) TEST(Photo_AlignMTB, regression)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment