mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 15:53:03 +04:00
Merge pull request #28168 from abhishek-gola:mergeDebevec_fix
Added 16U and 32F support in merge functions in photo module #28168 closes: https://github.com/opencv/opencv/issues/27873 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -59,16 +59,17 @@ void checkImageDimensions(const std::vector<Mat>& images)
|
||||
}
|
||||
}
|
||||
|
||||
Mat triangleWeights()
|
||||
Mat triangleWeights(int length)
|
||||
{
|
||||
// hat function
|
||||
Mat w(LDR_SIZE, 1, CV_32F);
|
||||
int half = LDR_SIZE / 2;
|
||||
int maxVal = LDR_SIZE - 1;
|
||||
CV_Assert(length >= 2);
|
||||
Mat w(length, 1, CV_32F);
|
||||
int half = length / 2;
|
||||
int maxVal = length - 1;
|
||||
float epsilon = 1e-6f;
|
||||
w.at<float>(0) = epsilon;
|
||||
w.at<float>(LDR_SIZE-1) = epsilon;
|
||||
for (int i = 1; i < LDR_SIZE-1; i++){
|
||||
w.at<float>(length - 1) = epsilon;
|
||||
for (int i = 1; i < length - 1; i++){
|
||||
w.at<float>(i) = (i < half)
|
||||
? static_cast<float>(i)
|
||||
: static_cast<float>(maxVal - i);
|
||||
@@ -76,15 +77,16 @@ Mat triangleWeights()
|
||||
return w;
|
||||
}
|
||||
|
||||
Mat RobertsonWeights()
|
||||
Mat RobertsonWeights(int length)
|
||||
{
|
||||
Mat weight(LDR_SIZE, 1, CV_32FC3);
|
||||
float q = (LDR_SIZE - 1) / 4.0f;
|
||||
CV_Assert(length >= 1);
|
||||
Mat weight(length, 1, CV_32FC3);
|
||||
float q = (length - 1) / 4.0f;
|
||||
float e4 = exp(4.f);
|
||||
float scale = e4/(e4 - 1.f);
|
||||
float shift = 1 / (1.f - e4);
|
||||
|
||||
for(int i = 0; i < LDR_SIZE; i++) {
|
||||
for(int i = 0; i < length; i++) {
|
||||
float value = i / q - 2.0f;
|
||||
value = scale*exp(-value * value) + shift;
|
||||
weight.at<Vec3f>(i) = Vec3f::all(value);
|
||||
@@ -104,11 +106,28 @@ void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation)
|
||||
merge(channels, dst);
|
||||
}
|
||||
|
||||
Mat linearResponse(int channels)
|
||||
Mat linearResponse(int channels, int length)
|
||||
{
|
||||
Mat response = Mat(LDR_SIZE, 1, CV_MAKETYPE(CV_32F, channels));
|
||||
for(int i = 0; i < LDR_SIZE; i++) {
|
||||
response.at<Vec3f>(i) = Vec3f::all(static_cast<float>(i));
|
||||
CV_Assert(length >= 1);
|
||||
Mat response = Mat(length, 1, CV_MAKETYPE(CV_32F, channels));
|
||||
|
||||
if (channels == 1)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
response.at<float>(i) = static_cast<float>(i);
|
||||
}
|
||||
}
|
||||
else if (channels == 3)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
response.at<Vec3f>(i) = Vec3f::all(static_cast<float>(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "Unsupported number of channels in linearResponse");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ namespace cv
|
||||
|
||||
void checkImageDimensions(const std::vector<Mat>& images);
|
||||
|
||||
Mat triangleWeights();
|
||||
Mat triangleWeights(int length = LDR_SIZE);
|
||||
|
||||
void mapLuminance(Mat src, Mat dst, Mat lum, Mat new_lum, float saturation);
|
||||
|
||||
Mat RobertsonWeights();
|
||||
Mat RobertsonWeights(int length = LDR_SIZE);
|
||||
|
||||
Mat linearResponse(int channels);
|
||||
Mat linearResponse(int channels, int length = LDR_SIZE);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+61
-12
@@ -66,25 +66,46 @@ public:
|
||||
|
||||
CV_Assert(images.size() == times.total());
|
||||
checkImageDimensions(images);
|
||||
CV_Assert(images[0].depth() == CV_8U);
|
||||
int depth = images[0].depth();
|
||||
CV_Assert(depth == CV_8U || depth == CV_16U || depth == CV_32F);
|
||||
|
||||
int channels = images[0].channels();
|
||||
Size size = images[0].size();
|
||||
int CV_32FCC = CV_MAKETYPE(CV_32F, channels);
|
||||
|
||||
const bool use16bitLUT = (depth == CV_16U || depth == CV_32F);
|
||||
const int lutLength = use16bitLUT ? 65536 : LDR_SIZE;
|
||||
|
||||
std::vector<Mat> lutImages(images.size());
|
||||
if (depth == CV_8U || depth == CV_16U)
|
||||
{
|
||||
lutImages = images;
|
||||
}
|
||||
else
|
||||
{
|
||||
const double scale = static_cast<double>(lutLength - 1);
|
||||
for (size_t i = 0; i < images.size(); ++i)
|
||||
{
|
||||
Mat clipped;
|
||||
cv::max(images[i], 0.0, clipped);
|
||||
cv::min(clipped, 1.0, clipped);
|
||||
clipped.convertTo(lutImages[i], CV_16U, scale);
|
||||
}
|
||||
}
|
||||
|
||||
dst.create(images[0].size(), CV_32FCC);
|
||||
Mat result = dst.getMat();
|
||||
|
||||
Mat response = input_response.getMat();
|
||||
|
||||
if(response.empty()) {
|
||||
response = linearResponse(channels);
|
||||
response = linearResponse(channels, lutLength);
|
||||
response.at<Vec3f>(0) = response.at<Vec3f>(1);
|
||||
}
|
||||
|
||||
Mat log_response;
|
||||
log(response, log_response);
|
||||
CV_Assert(log_response.rows == LDR_SIZE && log_response.cols == 1 &&
|
||||
CV_Assert(log_response.rows == lutLength && log_response.cols == 1 &&
|
||||
log_response.channels() == channels);
|
||||
|
||||
Mat exp_values(times.clone());
|
||||
@@ -95,19 +116,23 @@ public:
|
||||
split(result, result_split);
|
||||
Mat weight_sum = Mat::zeros(size, CV_32F);
|
||||
|
||||
Mat weights_lut = use16bitLUT
|
||||
? triangleWeights(lutLength)
|
||||
: weights;
|
||||
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
std::vector<Mat> splitted;
|
||||
split(images[i], splitted);
|
||||
split(lutImages[i], splitted);
|
||||
|
||||
Mat w = Mat::zeros(size, CV_32F);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
LUT(splitted[c], weights, splitted[c]);
|
||||
LUT(splitted[c], weights_lut, splitted[c]);
|
||||
w += splitted[c];
|
||||
}
|
||||
w /= channels;
|
||||
|
||||
Mat response_img;
|
||||
LUT(images[i], log_response, response_img);
|
||||
LUT(lutImages[i], log_response, response_img);
|
||||
split(response_img, splitted);
|
||||
for(int c = 0; c < channels; c++) {
|
||||
result_split[c] += w.mul(splitted[c] - exp_values.at<float>((int)i));
|
||||
@@ -328,28 +353,52 @@ public:
|
||||
|
||||
CV_Assert(images.size() == times.total());
|
||||
checkImageDimensions(images);
|
||||
CV_Assert(images[0].depth() == CV_8U);
|
||||
int depth = images[0].depth();
|
||||
CV_Assert(depth == CV_8U || depth == CV_16U || depth == CV_32F);
|
||||
|
||||
int channels = images[0].channels();
|
||||
int CV_32FCC = CV_MAKETYPE(CV_32F, channels);
|
||||
|
||||
const bool use16bitLUT = (depth == CV_16U || depth == CV_32F);
|
||||
const int lutLength = use16bitLUT ? 65536 : LDR_SIZE;
|
||||
|
||||
// Build LUT index images (see MergeDebevecImpl for details).
|
||||
std::vector<Mat> lutImages(images.size());
|
||||
if (depth == CV_8U || depth == CV_16U)
|
||||
{
|
||||
lutImages = images;
|
||||
}
|
||||
else // CV_32F
|
||||
{
|
||||
const double scale = static_cast<double>(lutLength - 1);
|
||||
for (size_t i = 0; i < images.size(); ++i)
|
||||
{
|
||||
images[i].convertTo(lutImages[i], CV_16U, scale);
|
||||
}
|
||||
}
|
||||
|
||||
dst.create(images[0].size(), CV_32FCC);
|
||||
Mat result = dst.getMat();
|
||||
|
||||
Mat response = input_response.getMat();
|
||||
if(response.empty()) {
|
||||
float middle = static_cast<float>(LDR_SIZE) / 2.0f;
|
||||
response = linearResponse(channels) / middle;
|
||||
float middle = static_cast<float>(lutLength) / 2.0f;
|
||||
response = linearResponse(channels, lutLength) / middle;
|
||||
}
|
||||
CV_Assert(response.rows == LDR_SIZE && response.cols == 1 &&
|
||||
CV_Assert(response.rows == lutLength && response.cols == 1 &&
|
||||
response.channels() == channels);
|
||||
|
||||
result = Mat::zeros(images[0].size(), CV_32FCC);
|
||||
Mat wsum = Mat::zeros(images[0].size(), CV_32FCC);
|
||||
|
||||
Mat weight_lut = use16bitLUT
|
||||
? RobertsonWeights(lutLength)
|
||||
: weight;
|
||||
|
||||
for(size_t i = 0; i < images.size(); i++) {
|
||||
Mat im, w;
|
||||
LUT(images[i], weight, w);
|
||||
LUT(images[i], response, im);
|
||||
LUT(lutImages[i], weight_lut, w);
|
||||
LUT(lutImages[i], response, im);
|
||||
|
||||
result += times.at<float>((int)i) * w.mul(im);
|
||||
wsum += times.at<float>((int)i) * times.at<float>((int)i) * w;
|
||||
|
||||
@@ -209,6 +209,68 @@ TEST(Photo_MergeRobertson, regression)
|
||||
checkEqual(expected, result, eps, "MergeRobertson");
|
||||
}
|
||||
|
||||
TEST(Photo_MergeDebevec, regression_depth_consistency)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
vector<Mat> images8;
|
||||
vector<float> times;
|
||||
loadExposureSeq(test_path + "exposures/", images8, times);
|
||||
|
||||
vector<Mat> images16(images8.size()), images32(images8.size());
|
||||
for (size_t i = 0; i < images8.size(); ++i)
|
||||
{
|
||||
images8[i].convertTo(images16[i], CV_16UC3, 257.0);
|
||||
images8[i].convertTo(images32[i], CV_32FC3, 1.0 / 255.0);
|
||||
}
|
||||
|
||||
Ptr<MergeDebevec> merge = createMergeDebevec();
|
||||
Ptr<Tonemap> map = createTonemap();
|
||||
|
||||
Mat hdr8, hdr16, hdr32;
|
||||
merge->process(images8, hdr8, times);
|
||||
merge->process(images16, hdr16, times);
|
||||
merge->process(images32, hdr32, times);
|
||||
|
||||
map->process(hdr8, hdr8);
|
||||
map->process(hdr16, hdr16);
|
||||
map->process(hdr32, hdr32);
|
||||
|
||||
checkEqual(hdr8, hdr16, 2e-2f, "Debevec realdata 16U vs 8U");
|
||||
checkEqual(hdr8, hdr32, 2e-2f, "Debevec realdata 32F vs 8U");
|
||||
}
|
||||
|
||||
TEST(Photo_MergeRobertson, regression_depth_consistency)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
vector<Mat> images8;
|
||||
vector<float> times;
|
||||
loadExposureSeq(test_path + "exposures/", images8, times);
|
||||
|
||||
vector<Mat> images16(images8.size()), images32(images8.size());
|
||||
for (size_t i = 0; i < images8.size(); ++i)
|
||||
{
|
||||
images8[i].convertTo(images16[i], CV_16UC3, 257.0);
|
||||
images8[i].convertTo(images32[i], CV_32FC3, 1.0 / 255.0);
|
||||
}
|
||||
|
||||
Ptr<MergeRobertson> merge = createMergeRobertson();
|
||||
Ptr<Tonemap> map = createTonemap();
|
||||
|
||||
Mat hdr8, hdr16, hdr32;
|
||||
merge->process(images8, hdr8, times);
|
||||
merge->process(images16, hdr16, times);
|
||||
merge->process(images32, hdr32, times);
|
||||
|
||||
map->process(hdr8, hdr8);
|
||||
map->process(hdr16, hdr16);
|
||||
map->process(hdr32, hdr32);
|
||||
|
||||
checkEqual(hdr8, hdr16, 3e-2f, "Robertson realdata 16U vs 8U");
|
||||
checkEqual(hdr8, hdr32, 3e-2f, "Robertson realdata 32F vs 8U");
|
||||
}
|
||||
|
||||
TEST(Photo_CalibrateDebevec, regression)
|
||||
{
|
||||
string test_path = string(cvtest::TS::ptr()->get_data_path()) + "hdr/";
|
||||
|
||||
Reference in New Issue
Block a user