/*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(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(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::RegisterTransforms2D(templateWindowSize); HaarTransform::forwardTransform2D(src, dst, templateWindowSize, templateWindowSize); HaarTransform::inverseTransform2D(dst, templateWindowSize); // Use generic transforms HaarTransform2D::ForwardTransformXxX(src, dstSec, templateWindowSize, templateWindowSize); HaarTransform2D::InverseTransformXxX(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(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(src[i]), dst[i]); } template static void Test1dTransform( T *thrMap, int groupSize, int templateWindowSizeSq, BlockMatch *bm, BlockMatch *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::calcThresholdMap3D(thrMapTransform, 0, templateWindowSize, maxGroupSize); HaarTransform::calcThresholdMap3D(thrMapShrinkage, h, templateWindowSize, maxGroupSize); // Generate some data BlockMatch *bm = new BlockMatch[maxGroupSize]; BlockMatch *bmOrig = new BlockMatch[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(thrMapTransform, 2, templateWindowSizeSq, bm, bmOrig); Test1dTransform(thrMapTransform, 4, templateWindowSizeSq, bm, bmOrig); Test1dTransform(thrMapTransform, 8, templateWindowSizeSq, bm, bmOrig); Test1dTransform(thrMapTransform, 16, templateWindowSizeSq, bm, bmOrig); Test1dTransform(thrMapTransform, 32, templateWindowSizeSq, bm, bmOrig); Test1dTransform(thrMapTransform, 64, templateWindowSizeSq, bm, bmOrig); // Verify shrinkage Test1dTransform(thrMapShrinkage, 2, templateWindowSizeSq, bm, bmOrig, 6); Test1dTransform(thrMapShrinkage, 4, templateWindowSizeSq, bm, bmOrig, 6); Test1dTransform(thrMapShrinkage, 8, templateWindowSizeSq, bm, bmOrig, 6); Test1dTransform(thrMapShrinkage, 16, templateWindowSizeSq, bm, bmOrig, 6); Test1dTransform(thrMapShrinkage, 32, templateWindowSizeSq, bm, bmOrig, 6); Test1dTransform(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::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::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::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