Merge pull request #25842 from mshabunin:cpp-imgproc-test-4.x

imgproc: remove C-API usage from tests #25842

Final cleanup will be done in 5.x after regular merge.

Some tests have been reworked, some required only slight modifications.
This commit is contained in:
Maksim Shabunin 2024-07-04 16:29:08 +03:00 committed by GitHub
parent efbc9f0b66
commit 94b7a2d320
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 831 additions and 2435 deletions

View File

@ -43,130 +43,7 @@
namespace opencv_test { namespace {
class CV_CannyTest : public cvtest::ArrayTest
{
public:
CV_CannyTest(bool custom_deriv = false);
protected:
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 );
int prepare_test_case( int test_case_idx );
void run_func();
void prepare_to_validation( int );
int validate_test_results( int /*test_case_idx*/ );
int aperture_size;
bool use_true_gradient;
double threshold1, threshold2;
bool test_cpp;
bool test_custom_deriv;
Mat img;
};
CV_CannyTest::CV_CannyTest(bool custom_deriv)
{
test_array[INPUT].push_back(NULL);
test_array[OUTPUT].push_back(NULL);
test_array[REF_OUTPUT].push_back(NULL);
element_wise_relative_error = true;
aperture_size = 0;
use_true_gradient = false;
threshold1 = threshold2 = 0;
test_custom_deriv = custom_deriv;
const char imgPath[] = "shared/fruits.png";
img = cv::imread(cvtest::TS::ptr()->get_data_path() + imgPath, IMREAD_GRAYSCALE);
}
void CV_CannyTest::get_test_array_types_and_sizes( int test_case_idx,
vector<vector<Size> >& sizes,
vector<vector<int> >& types )
{
RNG& rng = ts->get_rng();
double thresh_range;
cvtest::ArrayTest::get_test_array_types_and_sizes( test_case_idx, sizes, types );
types[INPUT][0] = types[OUTPUT][0] = types[REF_OUTPUT][0] = CV_8U;
aperture_size = cvtest::randInt(rng) % 2 ? 5 : 3;
thresh_range = aperture_size == 3 ? 300 : 1000;
threshold1 = cvtest::randReal(rng)*thresh_range;
threshold2 = cvtest::randReal(rng)*thresh_range*0.3;
if( cvtest::randInt(rng) % 2 )
CV_SWAP( threshold1, threshold2, thresh_range );
use_true_gradient = cvtest::randInt(rng) % 2 != 0;
test_cpp = (cvtest::randInt(rng) & 256) == 0;
ts->printf(cvtest::TS::LOG, "Canny(size = %d x %d, aperture_size = %d, threshold1 = %g, threshold2 = %g, L2 = %s) test_cpp = %s (test case #%d)\n",
sizes[0][0].width, sizes[0][0].height, aperture_size, threshold1, threshold2, use_true_gradient ? "TRUE" : "FALSE", test_cpp ? "TRUE" : "FALSE", test_case_idx);
}
int CV_CannyTest::prepare_test_case( int test_case_idx )
{
int code = cvtest::ArrayTest::prepare_test_case( test_case_idx );
if( code > 0 )
{
RNG& rng = ts->get_rng();
Mat& src = test_mat[INPUT][0];
//GaussianBlur(src, src, Size(11, 11), 5, 5);
if(src.cols > img.cols || src.rows > img.rows)
resize(img, src, src.size(), 0, 0, INTER_LINEAR_EXACT);
else
img(
Rect(
cvtest::randInt(rng) % (img.cols-src.cols),
cvtest::randInt(rng) % (img.rows-src.rows),
src.cols,
src.rows
)
).copyTo(src);
GaussianBlur(src, src, Size(5, 5), 0);
}
return code;
}
double CV_CannyTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ )
{
return 0;
}
void CV_CannyTest::run_func()
{
if (test_custom_deriv)
{
cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]);
cv::Mat src = cv::cvarrToMat(test_array[INPUT][0]);
cv::Mat dx, dy;
int m = aperture_size;
Point anchor(m/2, m/2);
Mat dxkernel = cvtest::calcSobelKernel2D( 1, 0, m, 0 );
Mat dykernel = cvtest::calcSobelKernel2D( 0, 1, m, 0 );
cvtest::filter2D(src, dx, CV_16S, dxkernel, anchor, 0, BORDER_REPLICATE);
cvtest::filter2D(src, dy, CV_16S, dykernel, anchor, 0, BORDER_REPLICATE);
cv::Canny(dx, dy, _out, threshold1, threshold2, use_true_gradient);
}
else
{
cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]);
cv::Canny(cv::cvarrToMat(test_array[INPUT][0]), _out, threshold1, threshold2,
aperture_size, use_true_gradient);
}
}
static void
cannyFollow( int x, int y, float lowThreshold, const Mat& mag, Mat& dst )
static void Canny_reference_follow( int x, int y, float lowThreshold, const Mat& mag, Mat& dst )
{
static const int ofs[][2] = {{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1},{0,1},{1,1}};
int i;
@ -181,16 +58,15 @@ cannyFollow( int x, int y, float lowThreshold, const Mat& mag, Mat& dst )
(unsigned)y1 < (unsigned)mag.rows &&
mag.at<float>(y1, x1) > lowThreshold &&
!dst.at<uchar>(y1, x1) )
cannyFollow( x1, y1, lowThreshold, mag, dst );
Canny_reference_follow( x1, y1, lowThreshold, mag, dst );
}
}
static void
test_Canny( const Mat& src, Mat& dst,
static void Canny_reference( const Mat& src, Mat& dst,
double threshold1, double threshold2,
int aperture_size, bool use_true_gradient )
{
dst.create(src.size(), src.type());
int m = aperture_size;
Point anchor(m/2, m/2);
const double tan_pi_8 = tan(CV_PI/8.);
@ -273,47 +149,80 @@ test_Canny( const Mat& src, Mat& dst,
{
for( x = 0; x < width; x++ )
if( mag.at<float>(y, x) > highThreshold && !dst.at<uchar>(y, x) )
cannyFollow( x, y, lowThreshold, mag, dst );
Canny_reference_follow( x, y, lowThreshold, mag, dst );
}
}
//==============================================================================
void CV_CannyTest::prepare_to_validation( int )
// aperture, true gradient
typedef testing::TestWithParam<testing::tuple<int, bool>> Canny_Modes;
TEST_P(Canny_Modes, accuracy)
{
Mat src = test_mat[INPUT][0], dst = test_mat[REF_OUTPUT][0];
test_Canny( src, dst, threshold1, threshold2, aperture_size, use_true_gradient );
const int aperture = get<0>(GetParam());
const bool trueGradient = get<1>(GetParam());
const double range = aperture == 3 ? 300. : 1000.;
RNG & rng = TS::ptr()->get_rng();
for (int ITER = 0; ITER < 20; ++ITER)
{
SCOPED_TRACE(cv::format("iteration %d", ITER));
const std::string fname = cvtest::findDataFile("shared/fruits.png");
const Mat original = cv::imread(fname, IMREAD_GRAYSCALE);
const double thresh1 = rng.uniform(0., range);
const double thresh2 = rng.uniform(0., range * 0.3);
const Size sz(rng.uniform(127, 800), rng.uniform(127, 600));
const Size osz = original.size();
// preparation
Mat img;
if (sz.width >= osz.width || sz.height >= osz.height)
{
// larger image -> scale
resize(original, img, sz, 0, 0, INTER_LINEAR_EXACT);
}
else
{
// smaller image -> crop
Point origin(rng.uniform(0, osz.width - sz.width), rng.uniform(0, osz.height - sz.height));
Rect roi(origin, sz);
original(roi).copyTo(img);
}
GaussianBlur(img, img, Size(5, 5), 0);
// regular function
Mat result;
{
cv::Canny(img, result, thresh1, thresh2, aperture, trueGradient);
}
// custom derivatives
Mat customResult;
{
Mat dxkernel = cvtest::calcSobelKernel2D(1, 0, aperture, 0);
Mat dykernel = cvtest::calcSobelKernel2D(0, 1, aperture, 0);
Point anchor(aperture / 2, aperture / 2);
cv::Mat dx, dy;
cvtest::filter2D(img, dx, CV_16S, dxkernel, anchor, 0, BORDER_REPLICATE);
cvtest::filter2D(img, dy, CV_16S, dykernel, anchor, 0, BORDER_REPLICATE);
cv::Canny(dx, dy, customResult, thresh1, thresh2, trueGradient);
}
Mat reference;
Canny_reference(img, reference, thresh1, thresh2, aperture, trueGradient);
EXPECT_MAT_NEAR(result, reference, 0);
EXPECT_MAT_NEAR(customResult, reference, 0);
}
}
int CV_CannyTest::validate_test_results( int test_case_idx )
{
int code = cvtest::TS::OK, nz0;
prepare_to_validation(test_case_idx);
double err = cvtest::norm(test_mat[OUTPUT][0], test_mat[REF_OUTPUT][0], CV_L1);
if( err == 0 )
return code;
if( err != cvRound(err) || cvRound(err)%255 != 0 )
{
ts->printf( cvtest::TS::LOG, "Some of the pixels, produced by Canny, are not 0's or 255's; the difference is %g\n", err );
ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );
return code;
}
nz0 = cvRound(cvtest::norm(test_mat[REF_OUTPUT][0], CV_L1)/255);
err = (err/255/MAX(nz0,100))*100;
if( err > 1 )
{
ts->printf( cvtest::TS::LOG, "Too high percentage of non-matching edge pixels = %g%%\n", err);
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
}
return code;
}
TEST(Imgproc_Canny, accuracy) { CV_CannyTest test; test.safe_run(); }
TEST(Imgproc_Canny, accuracy_deriv) { CV_CannyTest test(true); test.safe_run(); }
INSTANTIATE_TEST_CASE_P(/**/, Canny_Modes,
testing::Combine(
testing::Values(3, 5),
testing::Values(true, false)));
/*

View File

@ -76,7 +76,6 @@ protected:
bool inplace;
bool custom_inv_transform;
int fwd_code, inv_code;
bool test_cpp;
int hue_range;
bool srgb;
};
@ -97,7 +96,6 @@ CV_ColorCvtBaseTest::CV_ColorCvtBaseTest( bool _custom_inv_transform, bool _allo
fwd_code_str = inv_code_str = 0;
test_cpp = false;
hue_range = 0;
blue_idx = 0;
srgb = false;
@ -147,7 +145,6 @@ void CV_ColorCvtBaseTest::get_test_array_types_and_sizes( int test_case_idx,
types[OUTPUT][1] = types[REF_OUTPUT][1] = CV_MAKETYPE(depth, cn);
inplace = cn == 3 && cvtest::randInt(rng) % 2 != 0;
test_cpp = (cvtest::randInt(rng) & 256) == 0;
}
@ -161,23 +158,17 @@ int CV_ColorCvtBaseTest::prepare_test_case( int test_case_idx )
void CV_ColorCvtBaseTest::run_func()
{
CvArr* out0 = test_array[OUTPUT][0];
cv::Mat _out0 = cv::cvarrToMat(out0), _out1 = cv::cvarrToMat(test_array[OUTPUT][1]);
cv::Mat out0 = test_mat[OUTPUT][0];
cv::Mat _out0 = out0, _out1 = test_mat[OUTPUT][1];
if(!test_cpp)
cvCvtColor( inplace ? out0 : test_array[INPUT][0], out0, fwd_code );
else
cv::cvtColor( cv::cvarrToMat(inplace ? out0 : test_array[INPUT][0]), _out0, fwd_code, _out0.channels());
cv::cvtColor( inplace ? out0 : test_mat[INPUT][0], _out0, fwd_code, _out0.channels());
if( inplace )
{
cvCopy( out0, test_array[OUTPUT][1] );
out0 = test_array[OUTPUT][1];
out0.copyTo(test_mat[OUTPUT][1]);
out0 = test_mat[OUTPUT][1];
}
if(!test_cpp)
cvCvtColor( out0, test_array[OUTPUT][1], inv_code );
else
cv::cvtColor(cv::cvarrToMat(out0), _out1, inv_code, _out1.channels());
cv::cvtColor(out0, _out1, inv_code, _out1.channels());
}
@ -1730,13 +1721,8 @@ double CV_ColorBayerTest::get_success_error_level( int /*test_case_idx*/, int /*
void CV_ColorBayerTest::run_func()
{
if(!test_cpp)
cvCvtColor( test_array[INPUT][0], test_array[OUTPUT][0], fwd_code );
else
{
cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]);
cv::cvtColor(cv::cvarrToMat(test_array[INPUT][0]), _out, fwd_code, _out.channels());
}
cv::Mat _out = test_mat[OUTPUT][0];
cv::cvtColor(test_mat[INPUT][0], _out, fwd_code, _out.channels());
}

File diff suppressed because it is too large Load Diff

View File

@ -470,7 +470,7 @@ void CV_DerivBaseTest::get_test_array_types_and_sizes( int test_case_idx,
int sameDepth = cvtest::randInt(rng) % 2;
types[OUTPUT][0] = types[REF_OUTPUT][0] = sameDepth ? depth : CV_MAKETYPE(depth==CV_8U?CV_16S:CV_32F,1);
_aperture_size = (cvtest::randInt(rng)%5)*2 - 1;
sizes[INPUT][1] = aperture_size = cvSize(_aperture_size, _aperture_size);
sizes[INPUT][1] = aperture_size = Size(_aperture_size, _aperture_size);
}
@ -519,21 +519,21 @@ void CV_SobelTest::get_test_array_types_and_sizes( int test_case_idx,
}
if( _aperture_size < 0 )
aperture_size = cvSize(3, 3);
aperture_size = Size(3, 3);
else if( _aperture_size == 1 )
{
if( dx == 0 )
aperture_size = cvSize(1, 3);
aperture_size = Size(1, 3);
else if( dy == 0 )
aperture_size = cvSize(3, 1);
aperture_size = Size(3, 1);
else
{
_aperture_size = 3;
aperture_size = cvSize(3, 3);
aperture_size = Size(3, 3);
}
}
else
aperture_size = cvSize(_aperture_size, _aperture_size);
aperture_size = Size(_aperture_size, _aperture_size);
sizes[INPUT][1] = aperture_size;
anchor.x = aperture_size.width / 2;
@ -647,10 +647,10 @@ void CV_LaplaceTest::get_test_array_types_and_sizes( int test_case_idx,
{
if( _aperture_size < 0 )
_aperture_size = 1;
aperture_size = cvSize(3, 3);
aperture_size = Size(3, 3);
}
else
aperture_size = cvSize(_aperture_size, _aperture_size);
aperture_size = Size(_aperture_size, _aperture_size);
sizes[INPUT][1] = aperture_size;
anchor.x = aperture_size.width / 2;
@ -1575,7 +1575,7 @@ CV_PreCornerDetectTest::CV_PreCornerDetectTest() : CV_FeatureSelBaseTest( 1 )
void CV_PreCornerDetectTest::run_func()
{
cvPreCornerDetect( test_array[INPUT][0], test_array[OUTPUT][0], aperture_size );
cv::preCornerDetect( test_mat[INPUT][0], test_mat[OUTPUT][0], aperture_size, BORDER_REPLICATE );
}

View File

@ -39,6 +39,8 @@
//
//M*/
#include "opencv2/ts/ocl_test.hpp"
#include "opencv2/ts/ts_gtest.h"
#include "test_precomp.hpp"
namespace opencv_test { namespace {
@ -768,8 +770,8 @@ void CV_RemapTest::fill_array( int test_case_idx, int i, int j, Mat& arr )
void CV_RemapTest::run_func()
{
cvRemap( test_array[INPUT][0], test_array[INPUT_OUTPUT][0],
test_array[INPUT][1], test_array[INPUT][2], interpolation );
cv::remap(test_mat[INPUT][0], test_mat[INPUT_OUTPUT][0],
test_mat[INPUT][1], test_mat[INPUT][2], interpolation );
}
@ -873,7 +875,7 @@ protected:
double get_success_error_level( int test_case_idx, int i, int j );
void fill_array( int test_case_idx, int i, int j, Mat& arr );
CvPoint2D32f center;
Point2f center;
bool test_cpp;
};
@ -925,13 +927,8 @@ void CV_GetRectSubPixTest::fill_array( int test_case_idx, int i, int j, Mat& arr
void CV_GetRectSubPixTest::run_func()
{
if(!test_cpp)
cvGetRectSubPix( test_array[INPUT][0], test_array[INPUT_OUTPUT][0], center );
else
{
cv::Mat _out = cv::cvarrToMat(test_array[INPUT_OUTPUT][0]);
cv::getRectSubPix( cv::cvarrToMat(test_array[INPUT][0]), _out.size(), center, _out, _out.type());
}
cv::Mat _out = test_mat[INPUT_OUTPUT][0];
cv::getRectSubPix(test_mat[INPUT][0], _out.size(), center, _out, _out.type());
}

View File

@ -42,6 +42,8 @@
#include "test_precomp.hpp"
#define CV_DXT_MUL_CONJ 8
namespace opencv_test { namespace {
/// phase correlation
@ -181,7 +183,7 @@ void CV_DivSpectrumsTest::get_test_array_types_and_sizes( int test_case_idx, vec
// Get the flag of the input.
const int rand_int_flags = cvtest::randInt(rng);
flags = rand_int_flags & (CV_DXT_MUL_CONJ | CV_DXT_ROWS);
flags = rand_int_flags & (CV_DXT_MUL_CONJ | DFT_ROWS);
// Get input type.
const int rand_int_type = cvtest::randInt(rng);

View File

@ -43,299 +43,6 @@
namespace opencv_test { namespace {
class CV_TemplMatchTest : public cvtest::ArrayTest
{
public:
CV_TemplMatchTest();
protected:
int read_params( const cv::FileStorage& fs );
void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
void get_minmax_bounds( int i, int j, int type, Scalar& low, Scalar& high );
double get_success_error_level( int test_case_idx, int i, int j );
void run_func();
void prepare_to_validation( int );
int max_template_size;
int method;
bool test_cpp;
};
CV_TemplMatchTest::CV_TemplMatchTest()
{
test_array[INPUT].push_back(NULL);
test_array[INPUT].push_back(NULL);
test_array[OUTPUT].push_back(NULL);
test_array[REF_OUTPUT].push_back(NULL);
element_wise_relative_error = false;
max_template_size = 100;
method = 0;
test_cpp = false;
}
int CV_TemplMatchTest::read_params( const cv::FileStorage& fs )
{
int code = cvtest::ArrayTest::read_params( fs );
if( code < 0 )
return code;
read( find_param( fs, "max_template_size" ), max_template_size, max_template_size );
max_template_size = cvtest::clipInt( max_template_size, 1, 100 );
return code;
}
void CV_TemplMatchTest::get_minmax_bounds( int i, int j, int type, Scalar& low, Scalar& high )
{
cvtest::ArrayTest::get_minmax_bounds( i, j, type, low, high );
int depth = CV_MAT_DEPTH(type);
if( depth == CV_32F )
{
low = Scalar::all(-10.);
high = Scalar::all(10.);
}
}
void CV_TemplMatchTest::get_test_array_types_and_sizes( int test_case_idx,
vector<vector<Size> >& sizes, vector<vector<int> >& types )
{
RNG& rng = ts->get_rng();
int depth = cvtest::randInt(rng) % 2, cn = cvtest::randInt(rng) & 1 ? 3 : 1;
cvtest::ArrayTest::get_test_array_types_and_sizes( test_case_idx, sizes, types );
depth = depth == 0 ? CV_8U : CV_32F;
types[INPUT][0] = types[INPUT][1] = CV_MAKETYPE(depth,cn);
types[OUTPUT][0] = types[REF_OUTPUT][0] = CV_32FC1;
sizes[INPUT][1].width = cvtest::randInt(rng)%MIN(sizes[INPUT][1].width,max_template_size) + 1;
sizes[INPUT][1].height = cvtest::randInt(rng)%MIN(sizes[INPUT][1].height,max_template_size) + 1;
sizes[OUTPUT][0].width = sizes[INPUT][0].width - sizes[INPUT][1].width + 1;
sizes[OUTPUT][0].height = sizes[INPUT][0].height - sizes[INPUT][1].height + 1;
sizes[REF_OUTPUT][0] = sizes[OUTPUT][0];
method = cvtest::randInt(rng)%6;
test_cpp = (cvtest::randInt(rng) & 256) == 0;
}
double CV_TemplMatchTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ )
{
if( test_mat[INPUT][1].depth() == CV_8U ||
(method >= cv::TM_CCOEFF && test_mat[INPUT][1].cols*test_mat[INPUT][1].rows <= 2) )
return 1e-2;
else
return 1e-3;
}
void CV_TemplMatchTest::run_func()
{
if(!test_cpp)
cvMatchTemplate( test_array[INPUT][0], test_array[INPUT][1], test_array[OUTPUT][0], method );
else
{
cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]);
cv::matchTemplate(cv::cvarrToMat(test_array[INPUT][0]), cv::cvarrToMat(test_array[INPUT][1]), _out, method);
}
}
static void cvTsMatchTemplate( const CvMat* img, const CvMat* templ, CvMat* result, int method )
{
int i, j, k, l;
int depth = CV_MAT_DEPTH(img->type), cn = CV_MAT_CN(img->type);
int width_n = templ->cols*cn, height = templ->rows;
int a_step = img->step / CV_ELEM_SIZE(img->type & CV_MAT_DEPTH_MASK);
int b_step = templ->step / CV_ELEM_SIZE(templ->type & CV_MAT_DEPTH_MASK);
CvScalar b_mean = CV_STRUCT_INITIALIZER, b_sdv = CV_STRUCT_INITIALIZER;
double b_denom = 1., b_sum2 = 0;
int area = templ->rows*templ->cols;
cvAvgSdv(templ, &b_mean, &b_sdv);
for( i = 0; i < cn; i++ )
b_sum2 += (b_sdv.val[i]*b_sdv.val[i] + b_mean.val[i]*b_mean.val[i])*area;
if( b_sdv.val[0]*b_sdv.val[0] + b_sdv.val[1]*b_sdv.val[1] +
b_sdv.val[2]*b_sdv.val[2] + b_sdv.val[3]*b_sdv.val[3] < DBL_EPSILON &&
method == cv::TM_CCOEFF_NORMED )
{
cvSet( result, cvScalarAll(1.) );
return;
}
if( method & 1 )
{
b_denom = 0;
if( method != cv::TM_CCOEFF_NORMED )
{
b_denom = b_sum2;
}
else
{
for( i = 0; i < cn; i++ )
b_denom += b_sdv.val[i]*b_sdv.val[i]*area;
}
b_denom = sqrt(b_denom);
if( b_denom == 0 )
b_denom = 1.;
}
CV_Assert( cv::TM_SQDIFF <= method && method <= cv::TM_CCOEFF_NORMED );
for( i = 0; i < result->rows; i++ )
{
for( j = 0; j < result->cols; j++ )
{
Scalar a_sum(0), a_sum2(0);
Scalar ccorr(0);
double value = 0.;
if( depth == CV_8U )
{
const uchar* a = img->data.ptr + i*img->step + j*cn;
const uchar* b = templ->data.ptr;
if( cn == 1 || method < cv::TM_CCOEFF )
{
for( k = 0; k < height; k++, a += a_step, b += b_step )
for( l = 0; l < width_n; l++ )
{
ccorr.val[0] += a[l]*b[l];
a_sum.val[0] += a[l];
a_sum2.val[0] += a[l]*a[l];
}
}
else
{
for( k = 0; k < height; k++, a += a_step, b += b_step )
for( l = 0; l < width_n; l += 3 )
{
ccorr.val[0] += a[l]*b[l];
ccorr.val[1] += a[l+1]*b[l+1];
ccorr.val[2] += a[l+2]*b[l+2];
a_sum.val[0] += a[l];
a_sum.val[1] += a[l+1];
a_sum.val[2] += a[l+2];
a_sum2.val[0] += a[l]*a[l];
a_sum2.val[1] += a[l+1]*a[l+1];
a_sum2.val[2] += a[l+2]*a[l+2];
}
}
}
else
{
const float* a = (const float*)(img->data.ptr + i*img->step) + j*cn;
const float* b = (const float*)templ->data.ptr;
if( cn == 1 || method < cv::TM_CCOEFF )
{
for( k = 0; k < height; k++, a += a_step, b += b_step )
for( l = 0; l < width_n; l++ )
{
ccorr.val[0] += a[l]*b[l];
a_sum.val[0] += a[l];
a_sum2.val[0] += a[l]*a[l];
}
}
else
{
for( k = 0; k < height; k++, a += a_step, b += b_step )
for( l = 0; l < width_n; l += 3 )
{
ccorr.val[0] += a[l]*b[l];
ccorr.val[1] += a[l+1]*b[l+1];
ccorr.val[2] += a[l+2]*b[l+2];
a_sum.val[0] += a[l];
a_sum.val[1] += a[l+1];
a_sum.val[2] += a[l+2];
a_sum2.val[0] += a[l]*a[l];
a_sum2.val[1] += a[l+1]*a[l+1];
a_sum2.val[2] += a[l+2]*a[l+2];
}
}
}
switch( method )
{
case cv::TM_CCORR:
case cv::TM_CCORR_NORMED:
value = ccorr.val[0];
break;
case cv::TM_SQDIFF:
case cv::TM_SQDIFF_NORMED:
value = (a_sum2.val[0] + b_sum2 - 2*ccorr.val[0]);
break;
default:
value = (ccorr.val[0] - a_sum.val[0]*b_mean.val[0]+
ccorr.val[1] - a_sum.val[1]*b_mean.val[1]+
ccorr.val[2] - a_sum.val[2]*b_mean.val[2]);
}
if( method & 1 )
{
double denom;
// calc denominator
if( method != cv::TM_CCOEFF_NORMED )
{
denom = a_sum2.val[0] + a_sum2.val[1] + a_sum2.val[2];
}
else
{
denom = a_sum2.val[0] - (a_sum.val[0]*a_sum.val[0])/area;
denom += a_sum2.val[1] - (a_sum.val[1]*a_sum.val[1])/area;
denom += a_sum2.val[2] - (a_sum.val[2]*a_sum.val[2])/area;
}
denom = sqrt(MAX(denom,0))*b_denom;
if( fabs(value) < denom )
value /= denom;
else if( fabs(value) < denom*1.125 )
value = value > 0 ? 1 : -1;
else
value = method != cv::TM_SQDIFF_NORMED ? 0 : 1;
}
((float*)(result->data.ptr + result->step*i))[j] = (float)value;
}
}
}
void CV_TemplMatchTest::prepare_to_validation( int /*test_case_idx*/ )
{
CvMat _input = cvMat(test_mat[INPUT][0]), _templ = cvMat(test_mat[INPUT][1]);
CvMat _output = cvMat(test_mat[REF_OUTPUT][0]);
cvTsMatchTemplate( &_input, &_templ, &_output, method );
//if( ts->get_current_test_info()->test_case_idx == 0 )
/*{
CvFileStorage* fs = cvOpenFileStorage( "_match_template.yml", 0, CV_STORAGE_WRITE );
cvWrite( fs, "image", &test_mat[INPUT][0] );
cvWrite( fs, "template", &test_mat[INPUT][1] );
cvWrite( fs, "ref", &test_mat[REF_OUTPUT][0] );
cvWrite( fs, "opencv", &test_mat[OUTPUT][0] );
cvWriteInt( fs, "method", method );
cvReleaseFileStorage( &fs );
}*/
if( method >= cv::TM_CCOEFF )
{
// avoid numerical stability problems in singular cases (when the results are near to 0)
const double delta = 10.;
test_mat[REF_OUTPUT][0] += Scalar::all(delta);
test_mat[OUTPUT][0] += Scalar::all(delta);
}
}
TEST(Imgproc_MatchTemplate, accuracy) { CV_TemplMatchTest test; test.safe_run(); }
}
TEST(Imgproc_MatchTemplate, bug_9597) {
const uint8_t img[] = {
245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245,
@ -425,4 +132,214 @@ TEST(Imgproc_MatchTemplate, bug_9597) {
cv::minMaxLoc(result, &minValue, NULL, NULL, NULL);
ASSERT_GE(minValue, 0);
}
} // namespace
//==============================================================================
static void matchTemplate_reference(Mat & img, Mat & templ, Mat & result, const int method)
{
CV_Assert(cv::TM_SQDIFF <= method && method <= cv::TM_CCOEFF_NORMED);
const Size res_sz(img.cols - templ.cols + 1, img.rows - templ.rows + 1);
result.create(res_sz, CV_32FC1);
const int depth = img.depth();
const int cn = img.channels();
const int area = templ.size().area();
const int width_n = templ.cols * cn;
const int height = templ.rows;
int a_step = (int)(img.step / img.elemSize1());
int b_step = (int)(templ.step / templ.elemSize1());
Scalar b_mean = Scalar::all(0);
Scalar b_sdv = Scalar::all(0);
cv::meanStdDev(templ, b_mean, b_sdv);
double b_sum2 = 0.;
for (int i = 0; i < cn; i++ )
b_sum2 += (b_sdv.val[i] * b_sdv.val[i] + b_mean.val[i] * b_mean.val[i]) * area;
if (b_sdv.val[0] * b_sdv.val[0] + b_sdv.val[1] * b_sdv.val[1] +
b_sdv.val[2] * b_sdv.val[2] + b_sdv.val[3] * b_sdv.val[3] < DBL_EPSILON &&
method == cv::TM_CCOEFF_NORMED)
{
result = Scalar::all(1.);
return;
}
double b_denom = 1.;
if (method & 1) // _NORMED
{
b_denom = 0;
if (method != cv::TM_CCOEFF_NORMED)
{
b_denom = b_sum2;
}
else
{
for (int i = 0; i < cn; i++)
b_denom += b_sdv.val[i] * b_sdv.val[i] * area;
}
b_denom = sqrt(b_denom);
if (b_denom == 0)
b_denom = 1.;
}
for (int i = 0; i < result.rows; i++)
{
for (int j = 0; j < result.cols; j++)
{
Scalar a_sum(0), a_sum2(0);
Scalar ccorr(0);
double value = 0.;
if (depth == CV_8U)
{
const uchar* a = img.ptr<uchar>(i, j); // ??? ->data.ptr + i*img->step + j*cn;
const uchar* b = templ.ptr<uchar>();
if( cn == 1 || method < cv::TM_CCOEFF )
{
for (int k = 0; k < height; k++, a += a_step, b += b_step)
for (int l = 0; l < width_n; l++)
{
ccorr.val[0] += a[l]*b[l];
a_sum.val[0] += a[l];
a_sum2.val[0] += a[l]*a[l];
}
}
else
{
for (int k = 0; k < height; k++, a += a_step, b += b_step)
for (int l = 0; l < width_n; l += 3)
{
ccorr.val[0] += a[l]*b[l];
ccorr.val[1] += a[l+1]*b[l+1];
ccorr.val[2] += a[l+2]*b[l+2];
a_sum.val[0] += a[l];
a_sum.val[1] += a[l+1];
a_sum.val[2] += a[l+2];
a_sum2.val[0] += a[l]*a[l];
a_sum2.val[1] += a[l+1]*a[l+1];
a_sum2.val[2] += a[l+2]*a[l+2];
}
}
}
else // CV_32F
{
const float* a = img.ptr<float>(i, j); // ???? (const float*)(img->data.ptr + i*img->step) + j*cn;
const float* b = templ.ptr<float>();
if( cn == 1 || method < cv::TM_CCOEFF )
{
for (int k = 0; k < height; k++, a += a_step, b += b_step)
for (int l = 0; l < width_n; l++)
{
ccorr.val[0] += a[l]*b[l];
a_sum.val[0] += a[l];
a_sum2.val[0] += a[l]*a[l];
}
}
else
{
for (int k = 0; k < height; k++, a += a_step, b += b_step)
for (int l = 0; l < width_n; l += 3)
{
ccorr.val[0] += a[l]*b[l];
ccorr.val[1] += a[l+1]*b[l+1];
ccorr.val[2] += a[l+2]*b[l+2];
a_sum.val[0] += a[l];
a_sum.val[1] += a[l+1];
a_sum.val[2] += a[l+2];
a_sum2.val[0] += a[l]*a[l];
a_sum2.val[1] += a[l+1]*a[l+1];
a_sum2.val[2] += a[l+2]*a[l+2];
}
}
}
switch( method )
{
case cv::TM_CCORR:
case cv::TM_CCORR_NORMED:
value = ccorr.val[0];
break;
case cv::TM_SQDIFF:
case cv::TM_SQDIFF_NORMED:
value = (a_sum2.val[0] + b_sum2 - 2*ccorr.val[0]);
break;
default:
value = (ccorr.val[0] - a_sum.val[0]*b_mean.val[0]+
ccorr.val[1] - a_sum.val[1]*b_mean.val[1]+
ccorr.val[2] - a_sum.val[2]*b_mean.val[2]);
}
if( method & 1 )
{
double denom;
// calc denominator
if( method != cv::TM_CCOEFF_NORMED )
{
denom = a_sum2.val[0] + a_sum2.val[1] + a_sum2.val[2];
}
else
{
denom = a_sum2.val[0] - (a_sum.val[0]*a_sum.val[0])/area;
denom += a_sum2.val[1] - (a_sum.val[1]*a_sum.val[1])/area;
denom += a_sum2.val[2] - (a_sum.val[2]*a_sum.val[2])/area;
}
denom = sqrt(MAX(denom,0))*b_denom;
if( fabs(value) < denom )
value /= denom;
else if( fabs(value) < denom*1.125 )
value = value > 0 ? 1 : -1;
else
value = method != cv::TM_SQDIFF_NORMED ? 0 : 1;
}
result.at<float>(i, j) = (float)value;
}
}
}
//==============================================================================
CV_ENUM(MatchModes, TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED);
typedef testing::TestWithParam<testing::tuple<perf::MatDepth, int, MatchModes>> matchTemplate_Modes;
TEST_P(matchTemplate_Modes, accuracy)
{
const int data_type = CV_MAKE_TYPE(get<0>(GetParam()), get<1>(GetParam()));
const int method = get<2>(GetParam());
RNG & rng = TS::ptr()->get_rng();
for (int ITER = 0; ITER < 20; ++ITER)
{
SCOPED_TRACE(cv::format("iteration %d", ITER));
const Size imgSize(rng.uniform(128, 320), rng.uniform(128, 240));
const Size templSize(rng.uniform(1, 100), rng.uniform(1, 100));
Mat img(imgSize, data_type, Scalar::all(0));
Mat templ(templSize, data_type, Scalar::all(0));
cvtest::randUni(rng, img, Scalar::all(0), Scalar::all(255));
cvtest::randUni(rng, templ, Scalar::all(0), Scalar::all(255));
Mat result;
cv::matchTemplate(img, templ, result, method);
Mat reference;
matchTemplate_reference(img, templ, reference, method);
EXPECT_MAT_NEAR_RELATIVE(result, reference, 1e-3);
}
}
INSTANTIATE_TEST_CASE_P(/**/,
matchTemplate_Modes,
testing::Combine(
testing::Values(CV_8U, CV_32F),
testing::Values(1, 3),
testing::Values(TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED)));
}} // namespace