Unverified Commit f4d55d51 authored by Alexander Alekhin's avatar Alexander Alekhin Committed by GitHub

imgproc: fix bit-exact GaussianBlur() / sepFilter2D() (#15855)

* imgproc: fix bit-exact GaussianBlur() / sepFilter2D()

- avoid kernels with bad approximation
- GaussiabBlur - apply error-diffusion approximation for kernel (8-bit fraction)

* java(test): update features2d ref data

* test: update test_facedetect
parent 955b2023
...@@ -85,11 +85,11 @@ public class BruteForceDescriptorMatcherTest extends OpenCVTestCase { ...@@ -85,11 +85,11 @@ public class BruteForceDescriptorMatcherTest extends OpenCVTestCase {
matSize = 100; matSize = 100;
truth = new DMatch[] { truth = new DMatch[] {
new DMatch(0, 0, 0, 0.6211397f), new DMatch(0, 0, 0, 0.6159003f),
new DMatch(1, 1, 0, 0.9177120f), new DMatch(1, 1, 0, 0.9177120f),
new DMatch(2, 1, 0, 0.3112163f), new DMatch(2, 1, 0, 0.3112163f),
new DMatch(3, 1, 0, 0.2925074f), new DMatch(3, 1, 0, 0.2925074f),
new DMatch(4, 1, 0, 0.9309178f) new DMatch(4, 1, 0, 0.26520672f)
}; };
} }
......
...@@ -85,11 +85,11 @@ public class BruteForceL1DescriptorMatcherTest extends OpenCVTestCase { ...@@ -85,11 +85,11 @@ public class BruteForceL1DescriptorMatcherTest extends OpenCVTestCase {
matSize = 100; matSize = 100;
truth = new DMatch[] { truth = new DMatch[] {
new DMatch(0, 0, 0, 3.0975165f), new DMatch(0, 0, 0, 3.0710702f),
new DMatch(1, 1, 0, 3.5680308f), new DMatch(1, 1, 0, 3.562016f),
new DMatch(2, 1, 0, 1.3722466f), new DMatch(2, 1, 0, 1.3682679f),
new DMatch(3, 1, 0, 1.3041023f), new DMatch(3, 1, 0, 1.3012862f),
new DMatch(4, 1, 0, 3.5970376f) new DMatch(4, 1, 0, 1.1852086f)
}; };
} }
......
...@@ -90,11 +90,11 @@ public class BruteForceSL2DescriptorMatcherTest extends OpenCVTestCase { ...@@ -90,11 +90,11 @@ public class BruteForceSL2DescriptorMatcherTest extends OpenCVTestCase {
matSize = 100; matSize = 100;
truth = new DMatch[] { truth = new DMatch[] {
new DMatch(0, 0, 0, 0.3858146f), new DMatch(0, 0, 0, 0.37933317f),
new DMatch(1, 1, 0, 0.8421953f), new DMatch(1, 1, 0, 0.8421953f),
new DMatch(2, 1, 0, 0.0968556f), new DMatch(2, 1, 0, 0.0968556f),
new DMatch(3, 1, 0, 0.0855606f), new DMatch(3, 1, 0, 0.0855606f),
new DMatch(4, 1, 0, 0.8666080f) new DMatch(4, 1, 0, 0.07033461f)
}; };
} }
......
...@@ -160,11 +160,11 @@ public class FlannBasedDescriptorMatcherTest extends OpenCVTestCase { ...@@ -160,11 +160,11 @@ public class FlannBasedDescriptorMatcherTest extends OpenCVTestCase {
matcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED); matcher = DescriptorMatcher.create(DescriptorMatcher.FLANNBASED);
matSize = 100; matSize = 100;
truth = new DMatch[] { truth = new DMatch[] {
new DMatch(0, 0, 0, 0.6211397f), new DMatch(0, 0, 0, 0.6159003f),
new DMatch(1, 1, 0, 0.9177120f), new DMatch(1, 1, 0, 0.9177120f),
new DMatch(2, 1, 0, 0.3112163f), new DMatch(2, 1, 0, 0.3112163f),
new DMatch(3, 1, 0, 0.2925075f), new DMatch(3, 1, 0, 0.2925075f),
new DMatch(4, 1, 0, 0.9309179f) new DMatch(4, 1, 0, 0.26520672f)
}; };
} }
......
...@@ -53,7 +53,7 @@ public class ORBDescriptorExtractorTest extends OpenCVTestCase { ...@@ -53,7 +53,7 @@ public class ORBDescriptorExtractorTest extends OpenCVTestCase {
Mat truth = new Mat(1, 32, CvType.CV_8UC1) { Mat truth = new Mat(1, 32, CvType.CV_8UC1) {
{ {
put(0, 0, put(0, 0,
6, 74, 6, 129, 2, 130, 56, 0, 36, 132, 66, 165, 172, 6, 3, 72, 102, 61, 163, 214, 0, 144, 65, 232, 4, 32, 138, 129, 4, 21, 37, 88); 6, 74, 6, 129, 2, 130, 56, 0, 44, 132, 66, 165, 172, 6, 3, 72, 102, 61, 171, 214, 0, 144, 65, 232, 4, 32, 138, 131, 4, 21, 37, 217);
} }
}; };
assertDescriptorsClose(truth, descriptors, 1); assertDescriptorsClose(truth, descriptors, 1);
...@@ -92,7 +92,7 @@ public class ORBDescriptorExtractorTest extends OpenCVTestCase { ...@@ -92,7 +92,7 @@ public class ORBDescriptorExtractorTest extends OpenCVTestCase {
Mat truth = new Mat(1, 32, CvType.CV_8UC1) { Mat truth = new Mat(1, 32, CvType.CV_8UC1) {
{ {
put(0, 0, put(0, 0,
6, 10, 22, 5, 2, 130, 56, 0, 44, 164, 66, 165, 140, 6, 1, 72, 38, 61, 163, 210, 0, 208, 1, 104, 4, 32, 10, 131, 0, 37, 37, 67); 6, 10, 22, 5, 2, 130, 56, 0, 44, 164, 66, 165, 140, 6, 1, 72, 38, 61, 163, 210, 0, 208, 1, 104, 4, 32, 74, 131, 0, 37, 37, 67);
} }
}; };
assertDescriptorsClose(truth, descriptors, 1); assertDescriptorsClose(truth, descriptors, 1);
......
...@@ -41,6 +41,12 @@ ...@@ -41,6 +41,12 @@
//M*/ //M*/
#include "precomp.hpp" #include "precomp.hpp"
#include <opencv2/core/utils/logger.defines.hpp>
#undef CV_LOG_STRIP_LEVEL
#define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_DEBUG + 1
#include <opencv2/core/utils/logger.hpp>
#include "opencv2/core/opencl/ocl_defs.hpp" #include "opencv2/core/opencl/ocl_defs.hpp"
#include "opencl_kernels_imgproc.hpp" #include "opencl_kernels_imgproc.hpp"
#include "hal_replacement.hpp" #include "hal_replacement.hpp"
...@@ -273,6 +279,22 @@ Ptr<BaseColumnFilter> getLinearColumnFilter( ...@@ -273,6 +279,22 @@ Ptr<BaseColumnFilter> getLinearColumnFilter(
CV_CPU_DISPATCH_MODES_ALL); CV_CPU_DISPATCH_MODES_ALL);
} }
static bool createBitExactKernel_32S(const Mat& kernel, Mat& kernel_dst, int bits)
{
kernel.convertTo(kernel_dst, CV_32S, (1 << bits));
Mat_<double> kernel_64f;
kernel.convertTo(kernel_64f, CV_64F, (1 << bits));
int ksize = (int)kernel.total();
const double eps = 10 * FLT_EPSILON * (1 << bits);
for (int i = 0; i < ksize; i++)
{
int bitExactValue = kernel_dst.at<int>(i);
double approxValue = kernel_64f.at<double>(i);
if (fabs(approxValue - bitExactValue) > eps)
return false;
}
return true;
}
Ptr<FilterEngine> createSeparableLinearFilter( Ptr<FilterEngine> createSeparableLinearFilter(
int _srcType, int _dstType, int _srcType, int _dstType,
...@@ -299,6 +321,7 @@ Ptr<FilterEngine> createSeparableLinearFilter( ...@@ -299,6 +321,7 @@ Ptr<FilterEngine> createSeparableLinearFilter(
_columnKernel.rows == 1 ? Point(_anchor.y, 0) : Point(0, _anchor.y)); _columnKernel.rows == 1 ? Point(_anchor.y, 0) : Point(0, _anchor.y));
Mat rowKernel, columnKernel; Mat rowKernel, columnKernel;
bool isBitExactMode = false;
int bdepth = std::max(CV_32F,std::max(sdepth, ddepth)); int bdepth = std::max(CV_32F,std::max(sdepth, ddepth));
int bits = 0; int bits = 0;
...@@ -311,14 +334,27 @@ Ptr<FilterEngine> createSeparableLinearFilter( ...@@ -311,14 +334,27 @@ Ptr<FilterEngine> createSeparableLinearFilter(
(rtype & ctype & KERNEL_INTEGER) && (rtype & ctype & KERNEL_INTEGER) &&
ddepth == CV_16S)) ) ddepth == CV_16S)) )
{ {
bdepth = CV_32S; int bits_ = ddepth == CV_8U ? 8 : 0;
bits = ddepth == CV_8U ? 8 : 0; bool isValidBitExactRowKernel = createBitExactKernel_32S(_rowKernel, rowKernel, bits_);
_rowKernel.convertTo( rowKernel, CV_32S, 1 << bits ); bool isValidBitExactColumnKernel = createBitExactKernel_32S(_columnKernel, columnKernel, bits_);
_columnKernel.convertTo( columnKernel, CV_32S, 1 << bits ); if (!isValidBitExactRowKernel)
bits *= 2; {
_delta *= (1 << bits); CV_LOG_DEBUG(NULL, "createSeparableLinearFilter: bit-exact row-kernel can't be applied: ksize=" << _rowKernel.total());
}
else if (!isValidBitExactColumnKernel)
{
CV_LOG_DEBUG(NULL, "createSeparableLinearFilter: bit-exact column-kernel can't be applied: ksize=" << _columnKernel.total());
}
else
{
bdepth = CV_32S;
bits = bits_;
bits *= 2;
_delta *= (1 << bits);
isBitExactMode = true;
}
} }
else if (!isBitExactMode)
{ {
if( _rowKernel.type() != bdepth ) if( _rowKernel.type() != bdepth )
_rowKernel.convertTo( rowKernel, bdepth ); _rowKernel.convertTo( rowKernel, bdepth );
......
...@@ -355,6 +355,9 @@ public: ...@@ -355,6 +355,9 @@ public:
CV_ALWAYS_INLINE bool isZero() { return val == 0; } CV_ALWAYS_INLINE bool isZero() { return val == 0; }
static CV_ALWAYS_INLINE ufixedpoint16 zero() { return ufixedpoint16(); } static CV_ALWAYS_INLINE ufixedpoint16 zero() { return ufixedpoint16(); }
static CV_ALWAYS_INLINE ufixedpoint16 one() { return ufixedpoint16((uint16_t)(1 << fixedShift)); } static CV_ALWAYS_INLINE ufixedpoint16 one() { return ufixedpoint16((uint16_t)(1 << fixedShift)); }
static CV_ALWAYS_INLINE ufixedpoint16 fromRaw(uint16_t v) { return ufixedpoint16(v); }
CV_ALWAYS_INLINE ufixedpoint16 raw() { return val; }
}; };
} }
......
This diff is collapsed.
...@@ -59,6 +59,13 @@ protected: ...@@ -59,6 +59,13 @@ protected:
bool fp_kernel; bool fp_kernel;
bool inplace; bool inplace;
int border; int border;
void dump_test_case(int test_case_idx, std::ostream* out) CV_OVERRIDE
{
ArrayTest::dump_test_case(test_case_idx, out);
*out << "border=" << border << std::endl;
}
}; };
...@@ -685,6 +692,12 @@ protected: ...@@ -685,6 +692,12 @@ protected:
void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types ); void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
double get_success_error_level( int test_case_idx, int i, int j ); double get_success_error_level( int test_case_idx, int i, int j );
const char* smooth_type; const char* smooth_type;
void dump_test_case(int test_case_idx, std::ostream* out) CV_OVERRIDE
{
CV_FilterBaseTest::dump_test_case(test_case_idx, out);
*out << "smooth_type=" << smooth_type << std::endl;
}
}; };
...@@ -795,6 +808,12 @@ protected: ...@@ -795,6 +808,12 @@ protected:
double get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ ); double get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ );
double sigma; double sigma;
int param1, param2; int param1, param2;
void dump_test_case(int test_case_idx, std::ostream* out) CV_OVERRIDE
{
CV_SmoothBaseTest::dump_test_case(test_case_idx, out);
*out << "kernel=(" << param1 << ", " << param2 << ") sigma=" << sigma << std::endl;
}
}; };
...@@ -838,7 +857,7 @@ void CV_GaussianBlurTest::run_func() ...@@ -838,7 +857,7 @@ void CV_GaussianBlurTest::run_func()
// !!! Copied from cvSmooth, if the code is changed in cvSmooth, // !!! Copied from cvSmooth, if the code is changed in cvSmooth,
// make sure to update this one too. // make sure to update this one too.
#define SMALL_GAUSSIAN_SIZE 7 #define SMALL_GAUSSIAN_SIZE 9
static void static void
calcGaussianKernel( int n, double sigma, vector<float>& kernel ) calcGaussianKernel( int n, double sigma, vector<float>& kernel )
{ {
...@@ -847,14 +866,15 @@ calcGaussianKernel( int n, double sigma, vector<float>& kernel ) ...@@ -847,14 +866,15 @@ calcGaussianKernel( int n, double sigma, vector<float>& kernel )
{1.f}, {1.f},
{0.25f, 0.5f, 0.25f}, {0.25f, 0.5f, 0.25f},
{0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f}, {0.0625f, 0.25f, 0.375f, 0.25f, 0.0625f},
{0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125} {0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125},
{4.0 / 256, 13.0 / 256, 30.0 / 256, 51.0 / 256, 60.0 / 256, 51.0 / 256, 30.0 / 256, 13.0 / 256, 4.0 / 256}
}; };
kernel.resize(n); kernel.resize(n);
if( n <= SMALL_GAUSSIAN_SIZE && sigma <= 0 ) if( n <= SMALL_GAUSSIAN_SIZE && sigma <= 0 )
{ {
assert( n%2 == 1 ); CV_Assert(n%2 == 1);
memcpy( &kernel[0], small_gaussian_tab[n>>1], n*sizeof(kernel[0])); memcpy(&kernel[0], small_gaussian_tab[n / 2], n*sizeof(kernel[0]));
} }
else else
{ {
......
...@@ -14,11 +14,14 @@ namespace opencv_test { namespace { ...@@ -14,11 +14,14 @@ namespace opencv_test { namespace {
{ fixedOne >> 2, fixedOne >> 1, fixedOne >> 2 }, // size 3, sigma 0 { fixedOne >> 2, fixedOne >> 1, fixedOne >> 2 }, // size 3, sigma 0
{ fixedOne >> 4, fixedOne >> 2, 6 * (fixedOne >> 4), fixedOne >> 2, fixedOne >> 4 }, // size 5, sigma 0 { fixedOne >> 4, fixedOne >> 2, 6 * (fixedOne >> 4), fixedOne >> 2, fixedOne >> 4 }, // size 5, sigma 0
{ fixedOne >> 5, 7 * (fixedOne >> 6), 7 * (fixedOne >> 5), 9 * (fixedOne >> 5), 7 * (fixedOne >> 5), 7 * (fixedOne >> 6), fixedOne >> 5 }, // size 7, sigma 0 { fixedOne >> 5, 7 * (fixedOne >> 6), 7 * (fixedOne >> 5), 9 * (fixedOne >> 5), 7 * (fixedOne >> 5), 7 * (fixedOne >> 6), fixedOne >> 5 }, // size 7, sigma 0
{ 4, 13, 30, 51, 61, 51, 30, 13, 4 }, // size 9, sigma 0 { 4, 13, 30, 51, 60, 51, 30, 13, 4 }, // size 9, sigma 0
{ 81, 95, 81 }, // size 3, sigma 1.75 #if 1
{ 65, 125, 65 }, // size 3, sigma 0.875 #define CV_TEST_INACCURATE_GAUSSIAN_BLUR
{ 81, 94, 81 }, // size 3, sigma 1.75
{ 65, 126, 65 }, // size 3, sigma 0.875
{ 0, 7, 242, 7, 0 }, // size 5, sigma 0.375 { 0, 7, 242, 7, 0 }, // size 5, sigma 0.375
{ 4, 56, 136, 56, 4 } // size 5, sigma 0.75 { 4, 56, 136, 56, 4 } // size 5, sigma 0.75
#endif
}; };
template <typename T, int fixedShift> template <typename T, int fixedShift>
...@@ -68,11 +71,13 @@ TEST(GaussianBlur_Bitexact, Linear8U) ...@@ -68,11 +71,13 @@ TEST(GaussianBlur_Bitexact, Linear8U)
{ CV_8UC1, Size( 256, 128), Size(5, 5), 0, 0, vector<int64_t>(v[2], v[2]+5), vector<int64_t>(v[2], v[2]+5) }, { CV_8UC1, Size( 256, 128), Size(5, 5), 0, 0, vector<int64_t>(v[2], v[2]+5), vector<int64_t>(v[2], v[2]+5) },
{ CV_8UC1, Size( 256, 128), Size(7, 7), 0, 0, vector<int64_t>(v[3], v[3]+7), vector<int64_t>(v[3], v[3]+7) }, { CV_8UC1, Size( 256, 128), Size(7, 7), 0, 0, vector<int64_t>(v[3], v[3]+7), vector<int64_t>(v[3], v[3]+7) },
{ CV_8UC1, Size( 256, 128), Size(9, 9), 0, 0, vector<int64_t>(v[4], v[4]+9), vector<int64_t>(v[4], v[4]+9) }, { CV_8UC1, Size( 256, 128), Size(9, 9), 0, 0, vector<int64_t>(v[4], v[4]+9), vector<int64_t>(v[4], v[4]+9) },
#ifdef CV_TEST_INACCURATE_GAUSSIAN_BLUR
{ CV_8UC1, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) }, { CV_8UC1, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) },
{ CV_8UC2, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) }, { CV_8UC2, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) },
{ CV_8UC3, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) }, { CV_8UC3, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) },
{ CV_8UC4, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) }, { CV_8UC4, Size( 256, 128), Size(3, 3), 1.75, 0.875, vector<int64_t>(v[5], v[5]+3), vector<int64_t>(v[6], v[6]+3) },
{ CV_8UC1, Size( 256, 128), Size(5, 5), 0.375, 0.75, vector<int64_t>(v[7], v[7]+5), vector<int64_t>(v[8], v[8]+5) } { CV_8UC1, Size( 256, 128), Size(5, 5), 0.375, 0.75, vector<int64_t>(v[7], v[7]+5), vector<int64_t>(v[8], v[8]+5) }
#endif
}; };
int bordermodes[] = { int bordermodes[] = {
...@@ -162,8 +167,28 @@ TEST(GaussianBlur_Bitexact, regression_15015) ...@@ -162,8 +167,28 @@ TEST(GaussianBlur_Bitexact, regression_15015)
{ {
Mat src(100,100,CV_8UC3,Scalar(255,255,255)); Mat src(100,100,CV_8UC3,Scalar(255,255,255));
Mat dst; Mat dst;
GaussianBlur(src, dst, Size(5, 5), 9); GaussianBlur(src, dst, Size(5, 5), 0);
ASSERT_EQ(0.0, cvtest::norm(dst, src, NORM_INF)); ASSERT_EQ(0.0, cvtest::norm(dst, src, NORM_INF));
} }
static void checkGaussianBlur_8Uvs32F(const Mat& src8u, const Mat& src32f, int N, double sigma)
{
Mat dst8u; GaussianBlur(src8u, dst8u, Size(N, N), sigma); // through bit-exact path
Mat dst8u_32f; dst8u.convertTo(dst8u_32f, CV_32F);
Mat dst32f; GaussianBlur(src32f, dst32f, Size(N, N), sigma); // without bit-exact computations
double normINF_32f = cv::norm(dst8u_32f, dst32f, NORM_INF);
EXPECT_LE(normINF_32f, 1.0);
}
TEST(GaussianBlur_Bitexact, regression_9863)
{
Mat src8u = imread(cvtest::findDataFile("shared/lena.png"));
Mat src32f; src8u.convertTo(src32f, CV_32F);
checkGaussianBlur_8Uvs32F(src8u, src32f, 151, 30);
}
}} // namespace }} // namespace
...@@ -50,7 +50,7 @@ class facedetect_test(NewOpenCVTests): ...@@ -50,7 +50,7 @@ class facedetect_test(NewOpenCVTests):
img = self.get_sample( sample) img = self.get_sample( sample)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray = cv.GaussianBlur(gray, (5, 5), 5.1) gray = cv.GaussianBlur(gray, (5, 5), 0)
rects = detect(gray, cascade) rects = detect(gray, cascade)
faces.append(rects) faces.append(rects)
......
...@@ -428,6 +428,9 @@ protected: ...@@ -428,6 +428,9 @@ protected:
// updates progress bar // updates progress bar
virtual int update_progress( int progress, int test_case_idx, int count, double dt ); virtual int update_progress( int progress, int test_case_idx, int count, double dt );
// dump test case input parameters
virtual void dump_test_case(int test_case_idx, std::ostream* out);
// finds test parameter // finds test parameter
const CvFileNode* find_param( CvFileStorage* fs, const char* param_name ); const CvFileNode* find_param( CvFileStorage* fs, const char* param_name );
......
...@@ -350,7 +350,13 @@ void BaseTest::run( int start_from ) ...@@ -350,7 +350,13 @@ void BaseTest::run( int start_from )
return; return;
if( validate_test_results( test_case_idx ) < 0 || ts->get_err_code() < 0 ) if( validate_test_results( test_case_idx ) < 0 || ts->get_err_code() < 0 )
{
std::stringstream ss;
dump_test_case(test_case_idx, &ss);
std::string s = ss.str();
ts->printf( TS::LOG, "%s", s.c_str());
return; return;
}
} }
} }
...@@ -401,6 +407,12 @@ int BaseTest::update_progress( int progress, int test_case_idx, int count, doubl ...@@ -401,6 +407,12 @@ int BaseTest::update_progress( int progress, int test_case_idx, int count, doubl
} }
void BaseTest::dump_test_case(int test_case_idx, std::ostream* out)
{
*out << "test_case_idx = " << test_case_idx << std::endl;
}
BadArgTest::BadArgTest() BadArgTest::BadArgTest()
{ {
test_case_idx = -1; test_case_idx = -1;
......
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