OpenCV_4.2.0/opencv_contrib-4.2.0/modules/xphoto/test/test_denoise_bm3d.cpp

465 lines
19 KiB
C++
Raw Normal View History

2024-07-25 16:47:56 +08:00
/*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 "test_precomp.hpp"
//#define DUMP_RESULTS
//#define TEST_TRANSFORMS
#ifdef TEST_TRANSFORMS
#include "..\..\xphoto\src\bm3d_denoising_invoker_commons.hpp"
#include "..\..\xphoto\src\bm3d_denoising_transforms.hpp"
#include "..\..\xphoto\src\kaiser_window.hpp"
using namespace cv::xphoto;
#endif
#ifdef DUMP_RESULTS
# define DUMP(image, path) imwrite(path, image)
#else
# define DUMP(image, path)
#endif
#ifdef OPENCV_ENABLE_NONFREE
namespace opencv_test { namespace {
TEST(xphoto_DenoisingBm3dGrayscale, regression_L2)
{
std::string folder = std::string(cvtest::TS::ptr()->get_data_path()) + "cv/xphoto/bm3d_image_denoising/";
std::string original_path = folder + "lena_noised_gaussian_sigma=10.png";
std::string expected_path = folder + "lena_noised_denoised_bm3d_wiener_grayscale_l2_tw=4_sw=16_h=10_bm=400.png";
cv::Mat original = cv::imread(original_path, cv::IMREAD_GRAYSCALE);
cv::Mat expected = cv::imread(expected_path, cv::IMREAD_GRAYSCALE);
ASSERT_FALSE(original.empty()) << "Could not load input image " << original_path;
ASSERT_FALSE(expected.empty()) << "Could not load reference image " << expected_path;
// BM3D: two different calls doing exactly the same thing
cv::Mat result, resultSec;
cv::xphoto::bm3dDenoising(original, noArray(), resultSec, 10, 4, 16, 2500, 400, 8, 1, 0.0f, cv::NORM_L2, cv::xphoto::BM3D_STEPALL);
cv::xphoto::bm3dDenoising(original, result, 10, 4, 16, 2500, 400, 8, 1, 0.0f, cv::NORM_L2, cv::xphoto::BM3D_STEPALL);
DUMP(result, expected_path + ".res.png");
ASSERT_EQ(cvtest::norm(result, resultSec, cv::NORM_L2), 0);
ASSERT_LT(cvtest::norm(result, expected, cv::NORM_L2), 200);
}
TEST(xphoto_DenoisingBm3dGrayscale, regression_L2_separate)
{
std::string folder = std::string(cvtest::TS::ptr()->get_data_path()) + "cv/xphoto/bm3d_image_denoising/";
std::string original_path = folder + "lena_noised_gaussian_sigma=10.png";
std::string expected_basic_path = folder + "lena_noised_denoised_bm3d_grayscale_l2_tw=4_sw=16_h=10_bm=2500.png";
std::string expected_path = folder + "lena_noised_denoised_bm3d_wiener_grayscale_l2_tw=4_sw=16_h=10_bm=400.png";
cv::Mat original = cv::imread(original_path, cv::IMREAD_GRAYSCALE);
cv::Mat expected_basic = cv::imread(expected_basic_path, cv::IMREAD_GRAYSCALE);
cv::Mat expected = cv::imread(expected_path, cv::IMREAD_GRAYSCALE);
ASSERT_FALSE(original.empty()) << "Could not load input image " << original_path;
ASSERT_FALSE(expected_basic.empty()) << "Could not load reference image " << expected_basic_path;
ASSERT_FALSE(expected.empty()) << "Could not load input image " << expected_path;
cv::Mat basic, result;
// BM3D step 1
cv::xphoto::bm3dDenoising(original, basic, 10, 4, 16, 2500, -1, 8, 1, 0.0f, cv::NORM_L2, cv::xphoto::BM3D_STEP1);
ASSERT_LT(cvtest::norm(basic, expected_basic, cv::NORM_L2), 200);
DUMP(basic, expected_basic_path + ".res.basic.png");
// BM3D step 2
cv::xphoto::bm3dDenoising(original, basic, result, 10, 4, 16, 2500, 400, 8, 1, 0.0f, cv::NORM_L2, cv::xphoto::BM3D_STEP2);
ASSERT_LT(cvtest::norm(basic, expected_basic, cv::NORM_L2), 200);
DUMP(basic, expected_basic_path + ".res.basic2.png");
DUMP(result, expected_path + ".res.png");
ASSERT_LT(cvtest::norm(result, expected, cv::NORM_L2), 200);
}
TEST(xphoto_DenoisingBm3dGrayscale, regression_L1)
{
std::string folder = std::string(cvtest::TS::ptr()->get_data_path()) + "cv/xphoto/bm3d_image_denoising/";
std::string original_path = folder + "lena_noised_gaussian_sigma=10.png";
std::string expected_path = folder + "lena_noised_denoised_bm3d_grayscale_l1_tw=4_sw=16_h=10_bm=2500.png";
cv::Mat original = cv::imread(original_path, cv::IMREAD_GRAYSCALE);
cv::Mat expected = cv::imread(expected_path, cv::IMREAD_GRAYSCALE);
ASSERT_FALSE(original.empty()) << "Could not load input image " << original_path;
ASSERT_FALSE(expected.empty()) << "Could not load reference image " << expected_path;
cv::Mat result;
cv::xphoto::bm3dDenoising(original, result, 10, 4, 16, 2500, -1, 8, 1, 0.0f, cv::NORM_L1, cv::xphoto::BM3D_STEP1);
DUMP(result, expected_path + ".res.png");
ASSERT_LT(cvtest::norm(result, expected, cv::NORM_L2), 200);
}
TEST(xphoto_DenoisingBm3dGrayscale, regression_L2_8x8)
{
std::string folder = std::string(cvtest::TS::ptr()->get_data_path()) + "cv/xphoto/bm3d_image_denoising/";
std::string original_path = folder + "lena_noised_gaussian_sigma=10.png";
std::string expected_path = folder + "lena_noised_denoised_bm3d_grayscale_l2_tw=8_sw=16_h=10_bm=2500.png";
cv::Mat original = cv::imread(original_path, cv::IMREAD_GRAYSCALE);
cv::Mat expected = cv::imread(expected_path, cv::IMREAD_GRAYSCALE);
ASSERT_FALSE(original.empty()) << "Could not load input image " << original_path;
ASSERT_FALSE(expected.empty()) << "Could not load reference image " << expected_path;
cv::Mat result;
cv::xphoto::bm3dDenoising(original, result, 10, 8, 16, 2500, -1, 8, 1, 0.0f, cv::NORM_L2, cv::xphoto::BM3D_STEP1);
DUMP(result, expected_path + ".res.png");
ASSERT_LT(cvtest::norm(result, expected, cv::NORM_L2), 200);
}
#ifdef TEST_TRANSFORMS
TEST(xphoto_DenoisingBm3dKaiserWindow, regression_4)
{
float beta = 2.0f;
int N = 4;
cv::Mat kaiserWindow;
calcKaiserWindow1D(kaiserWindow, N, beta);
float kaiser4[] = {
0.43869004f,
0.92432547f,
0.92432547f,
0.43869004f
};
for (int i = 0; i < N; ++i)
ASSERT_FLOAT_EQ(kaiser4[i], kaiserWindow.at<float>(i));
}
TEST(xphoto_DenoisingBm3dKaiserWindow, regression_8)
{
float beta = 2.0f;
int N = 8;
cv::Mat kaiserWindow;
calcKaiserWindow1D(kaiserWindow, N, beta);
float kaiser8[] = {
0.43869004f,
0.68134475f,
0.87685609f,
0.98582518f,
0.98582518f,
0.87685609f,
0.68134463f,
0.43869004f
};
for (int i = 0; i < N; ++i)
ASSERT_FLOAT_EQ(kaiser8[i], kaiserWindow.at<float>(i));
}
TEST(xphoto_DenoisingBm3dTransforms, regression_2D_generic)
{
const int templateWindowSize = 8;
const int templateWindowSizeSq = templateWindowSize * templateWindowSize;
uchar src[templateWindowSizeSq];
short dst[templateWindowSizeSq];
short dstSec[templateWindowSizeSq];
// Initialize array
for (uchar i = 0; i < templateWindowSizeSq; ++i)
src[i] = (i % 10) * 10;
// Use tailored transforms
HaarTransform<uchar, short>::RegisterTransforms2D(templateWindowSize);
HaarTransform<uchar, short>::forwardTransform2D(src, dst, templateWindowSize, templateWindowSize);
HaarTransform<uchar, short>::inverseTransform2D(dst, templateWindowSize);
// Use generic transforms
HaarTransform2D::ForwardTransformXxX<uchar, short, templateWindowSize>(src, dstSec, templateWindowSize, templateWindowSize);
HaarTransform2D::InverseTransformXxX<short, templateWindowSize>(dstSec, templateWindowSize);
for (unsigned i = 0; i < templateWindowSizeSq; ++i)
ASSERT_EQ(dst[i], dstSec[i]);
}
TEST(xphoto_DenoisingBm3dTransforms, regression_2D_4x4)
{
const int templateWindowSize = 4;
const int templateWindowSizeSq = templateWindowSize * templateWindowSize;
uchar src[templateWindowSizeSq];
short dst[templateWindowSizeSq];
// Initialize array
for (uchar i = 0; i < templateWindowSizeSq; ++i)
{
src[i] = i;
}
HaarTransform2D::ForwardTransform4x4(src, dst, templateWindowSize, templateWindowSize);
HaarTransform2D::InverseTransform4x4(dst, templateWindowSize);
for (uchar i = 0; i < templateWindowSizeSq; ++i)
ASSERT_EQ(static_cast<short>(src[i]), dst[i]);
}
TEST(xphoto_DenoisingBm3dTransforms, regression_2D_8x8)
{
const int templateWindowSize = 8;
const int templateWindowSizeSq = templateWindowSize * templateWindowSize;
uchar src[templateWindowSizeSq];
short dst[templateWindowSizeSq];
// Initialize array
for (uchar i = 0; i < templateWindowSizeSq; ++i)
{
src[i] = i;
}
HaarTransform2D::ForwardTransform8x8(src, dst, templateWindowSize, templateWindowSize);
HaarTransform2D::InverseTransform8x8(dst, templateWindowSize);
for (uchar i = 0; i < templateWindowSizeSq; ++i)
ASSERT_EQ(static_cast<short>(src[i]), dst[i]);
}
template <typename T, typename DT, typename CT>
static void Test1dTransform(
T *thrMap,
int groupSize,
int templateWindowSizeSq,
BlockMatch<T, DT, CT> *bm,
BlockMatch<T, DT, CT> *bmOrig,
int expectedNonZeroCount = -1)
{
if (expectedNonZeroCount < 0)
expectedNonZeroCount = groupSize * templateWindowSizeSq;
// Test group size
short sumNonZero = 0;
T *thrMapPtr1D = thrMap + (groupSize - 1) * templateWindowSizeSq;
for (int n = 0; n < templateWindowSizeSq; n++)
{
switch (groupSize)
{
case 16:
HaarTransform1D::ForwardTransform16(bm, n);
sumNonZero += HardThreshold<16>(bm, n, thrMapPtr1D);
HaarTransform1D::InverseTransform16(bm, n);
break;
case 8:
HaarTransform1D::ForwardTransform8(bm, n);
sumNonZero += HardThreshold<8>(bm, n, thrMapPtr1D);
HaarTransform1D::InverseTransform8(bm, n);
break;
case 4:
HaarTransform1D::ForwardTransform4(bm, n);
sumNonZero += HardThreshold<4>(bm, n, thrMapPtr1D);
HaarTransform1D::InverseTransform4(bm, n);
break;
case 2:
HaarTransform1D::ForwardTransform2(bm, n);
sumNonZero += HardThreshold<2>(bm, n, thrMapPtr1D);
HaarTransform1D::InverseTransform2(bm, n);
break;
default:
HaarTransform1D::ForwardTransformN(bm, n, groupSize);
sumNonZero += HardThreshold(bm, n, thrMapPtr1D, groupSize);
HaarTransform1D::InverseTransformN(bm, n, groupSize);
}
}
// Assert transform
if (expectedNonZeroCount == groupSize * templateWindowSizeSq)
{
for (int i = 0; i < groupSize; ++i)
for (int j = 0; j < templateWindowSizeSq; ++j)
ASSERT_EQ(bm[i][j], bmOrig[i][j]);
}
// Assert shrinkage
ASSERT_EQ(sumNonZero, expectedNonZeroCount);
}
TEST(xphoto_DenoisingBm3dTransforms, regression_1D_transform)
{
const int templateWindowSize = 4;
const int templateWindowSizeSq = templateWindowSize * templateWindowSize;
const int searchWindowSize = 16;
const int searchWindowSizeSq = searchWindowSize * searchWindowSize;
const float h = 10;
int maxGroupSize = 64;
// Precompute separate maps for transform and shrinkage verification
short *thrMapTransform = NULL;
short *thrMapShrinkage = NULL;
HaarTransform<short, short>::calcThresholdMap3D(thrMapTransform, 0, templateWindowSize, maxGroupSize);
HaarTransform<short, short>::calcThresholdMap3D(thrMapShrinkage, h, templateWindowSize, maxGroupSize);
// Generate some data
BlockMatch<short, int, short> *bm = new BlockMatch<short, int, short>[maxGroupSize];
BlockMatch<short, int, short> *bmOrig = new BlockMatch<short, int, short>[maxGroupSize];
for (int i = 0; i < maxGroupSize; ++i)
{
bm[i].init(templateWindowSizeSq);
bmOrig[i].init(templateWindowSizeSq);
}
for (short i = 0; i < maxGroupSize; ++i)
{
for (short j = 0; j < templateWindowSizeSq; ++j)
{
bm[i][j] = (j + 1);
bmOrig[i][j] = bm[i][j];
}
}
// Verify transforms
Test1dTransform<short, int, short>(thrMapTransform, 2, templateWindowSizeSq, bm, bmOrig);
Test1dTransform<short, int, short>(thrMapTransform, 4, templateWindowSizeSq, bm, bmOrig);
Test1dTransform<short, int, short>(thrMapTransform, 8, templateWindowSizeSq, bm, bmOrig);
Test1dTransform<short, int, short>(thrMapTransform, 16, templateWindowSizeSq, bm, bmOrig);
Test1dTransform<short, int, short>(thrMapTransform, 32, templateWindowSizeSq, bm, bmOrig);
Test1dTransform<short, int, short>(thrMapTransform, 64, templateWindowSizeSq, bm, bmOrig);
// Verify shrinkage
Test1dTransform<short, int, short>(thrMapShrinkage, 2, templateWindowSizeSq, bm, bmOrig, 6);
Test1dTransform<short, int, short>(thrMapShrinkage, 4, templateWindowSizeSq, bm, bmOrig, 6);
Test1dTransform<short, int, short>(thrMapShrinkage, 8, templateWindowSizeSq, bm, bmOrig, 6);
Test1dTransform<short, int, short>(thrMapShrinkage, 16, templateWindowSizeSq, bm, bmOrig, 6);
Test1dTransform<short, int, short>(thrMapShrinkage, 32, templateWindowSizeSq, bm, bmOrig, 6);
Test1dTransform<short, int, short>(thrMapShrinkage, 64, templateWindowSizeSq, bm, bmOrig, 14);
}
const float sqrt2 = std::sqrt(2.0f);
TEST(xphoto_DenoisingBm3dTransforms, regression_1D_generate)
{
const int numberOfElements = 8;
const int arrSize = (numberOfElements << 1) - 1;
float *thrMap1D = NULL;
HaarTransform<short, short>::calcThresholdMap1D(thrMap1D, numberOfElements);
// Expected array
const float kThrMap1D[arrSize] = {
1.0f, // 1 element
sqrt2 / 2.0f, sqrt2, // 2 elements
0.5f, 1.0f, sqrt2, sqrt2, // 4 elements
sqrt2 / 4.0f, sqrt2 / 2.0f, 1.0f, 1.0f, sqrt2, sqrt2, sqrt2, sqrt2 // 8 elements
};
for (int j = 0; j < arrSize; ++j)
ASSERT_EQ(thrMap1D[j], kThrMap1D[j]);
delete[] thrMap1D;
}
TEST(xphoto_DenoisingBm3dTransforms, regression_2D_generate_4x4)
{
const int templateWindowSize = 4;
float *thrMap2D = NULL;
HaarTransform<short, short>::calcThresholdMap2D(thrMap2D, templateWindowSize);
// Expected array
const float kThrMap4x4[templateWindowSize * templateWindowSize] = {
0.25f, 0.5f, sqrt2 / 2.0f, sqrt2 / 2.0f,
0.5f, 1.0f, sqrt2, sqrt2,
sqrt2 / 2.0f, sqrt2, 2.0f, 2.0f,
sqrt2 / 2.0f, sqrt2, 2.0f, 2.0f
};
for (int j = 0; j < templateWindowSize * templateWindowSize; ++j)
ASSERT_EQ(thrMap2D[j], kThrMap4x4[j]);
delete[] thrMap2D;
}
TEST(xphoto_DenoisingBm3dTransforms, regression_2D_generate_8x8)
{
const int templateWindowSize = 8;
float *thrMap2D = NULL;
HaarTransform<short, short>::calcThresholdMap2D(thrMap2D, templateWindowSize);
// Expected array
const float kThrMap8x8[templateWindowSize * templateWindowSize] = {
0.125f, 0.25f, sqrt2 / 4.0f, sqrt2 / 4.0f, 0.5f, 0.5f, 0.5f, 0.5f,
0.25f, 0.5f, sqrt2 / 2.0f, sqrt2 / 2.0f, 1.0f, 1.0f, 1.0f, 1.0f,
sqrt2 / 4.0f, sqrt2 / 2.0f, 1.0f, 1.0f, sqrt2, sqrt2, sqrt2, sqrt2,
sqrt2 / 4.0f, sqrt2 / 2.0f, 1.0f, 1.0f, sqrt2, sqrt2, sqrt2, sqrt2,
0.5f, 1.0f, sqrt2, sqrt2, 2.0f, 2.0f, 2.0f, 2.0f,
0.5f, 1.0f, sqrt2, sqrt2, 2.0f, 2.0f, 2.0f, 2.0f,
0.5f, 1.0f, sqrt2, sqrt2, 2.0f, 2.0f, 2.0f, 2.0f,
0.5f, 1.0f, sqrt2, sqrt2, 2.0f, 2.0f, 2.0f, 2.0f
};
for (int j = 0; j < templateWindowSize * templateWindowSize; ++j)
ASSERT_EQ(thrMap2D[j], kThrMap8x8[j]);
delete[] thrMap2D;
}
TEST(xphoto_Bm3dDenoising, powerOf2)
{
ASSERT_EQ(8, getLargestPowerOf2SmallerThan(9));
ASSERT_EQ(16, getLargestPowerOf2SmallerThan(21));
ASSERT_EQ(4, getLargestPowerOf2SmallerThan(7));
ASSERT_EQ(8, getLargestPowerOf2SmallerThan(8));
ASSERT_EQ(4, getLargestPowerOf2SmallerThan(5));
ASSERT_EQ(4, getLargestPowerOf2SmallerThan(4));
ASSERT_EQ(2, getLargestPowerOf2SmallerThan(3));
ASSERT_EQ(1, getLargestPowerOf2SmallerThan(1));
ASSERT_EQ(0, getLargestPowerOf2SmallerThan(0));
}
#endif // TEST_TRANSFORMS
}} // namespace
#endif // OPENCV_ENABLE_NONFREE