Merge pull request #15214 from jumostedu:matchtemplmask

* imgproc: templmatch: Add support for mask for all methods

Add support for masked template matching. Fix/scrub old implementation
for masked matching, as it did partly not even really do a meaningful
masking, and only supported limited template matching methods.

Add documentation including formulas for masked matching.

* imgproc: test: Add tests for masked template matching

Test accuracy by comparing to naive implementation for one point.
Test compatibility/correctness by comparing results without mask and
with all ones mask.
All tests are done for all methods, all supported depths, and for 1 and
3 channels.

* imgproc: test: templmatch: Add test for crossCorr

Add a test for the crossCorr function in templmatch.cpp. crossCorr() had
to be added to exported functions to be testable.

This test can maybe help to identify the problem with template matching
on MacOSX.

* fix: Fixed wrong evaluations of the MatExpr on Clang

* fix: removed crossCorr from public interface.

If it should be exported, it should be done as separate PR.

Co-authored-by: Vadim Levin <vadim.levin@xperience.ai>
pull/17317/head
jumostedu 5 years ago committed by GitHub
parent 486de65067
commit 5b095dfcb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      modules/imgproc/include/opencv2/imgproc.hpp
  2. 1
      modules/imgproc/src/filterengine.hpp
  3. 175
      modules/imgproc/src/templmatch.cpp
  4. 278
      modules/imgproc/test/test_templmatchmask.cpp

@ -3658,14 +3658,43 @@ CV_EXPORTS_W void HuMoments( const Moments& m, OutputArray hu );
//! type of the template matching operation
enum TemplateMatchModes {
TM_SQDIFF = 0, //!< \f[R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2\f]
TM_SQDIFF_NORMED = 1, //!< \f[R(x,y)= \frac{\sum_{x',y'} (T(x',y')-I(x+x',y+y'))^2}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}\f]
TM_CCORR = 2, //!< \f[R(x,y)= \sum _{x',y'} (T(x',y') \cdot I(x+x',y+y'))\f]
TM_CCORR_NORMED = 3, //!< \f[R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y'))}{\sqrt{\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}\f]
TM_CCOEFF = 4, //!< \f[R(x,y)= \sum _{x',y'} (T'(x',y') \cdot I'(x+x',y+y'))\f]
//!< where
//!< \f[\begin{array}{l} T'(x',y')=T(x',y') - 1/(w \cdot h) \cdot \sum _{x'',y''} T(x'',y'') \\ I'(x+x',y+y')=I(x+x',y+y') - 1/(w \cdot h) \cdot \sum _{x'',y''} I(x+x'',y+y'') \end{array}\f]
TM_CCOEFF_NORMED = 5 //!< \f[R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x',y+y')) }{ \sqrt{\sum_{x',y'}T'(x',y')^2 \cdot \sum_{x',y'} I'(x+x',y+y')^2} }\f]
TM_SQDIFF = 0, /*!< \f[R(x,y)= \sum _{x',y'} (T(x',y')-I(x+x',y+y'))^2\f]
with mask:
\f[R(x,y)= \sum _{x',y'} \left( (T(x',y')-I(x+x',y+y')) \cdot
M(x',y') \right)^2\f] */
TM_SQDIFF_NORMED = 1, /*!< \f[R(x,y)= \frac{\sum_{x',y'} (T(x',y')-I(x+x',y+y'))^2}{\sqrt{\sum_{
x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}\f]
with mask:
\f[R(x,y)= \frac{\sum _{x',y'} \left( (T(x',y')-I(x+x',y+y')) \cdot
M(x',y') \right)^2}{\sqrt{\sum_{x',y'} \left( T(x',y') \cdot
M(x',y') \right)^2 \cdot \sum_{x',y'} \left( I(x+x',y+y') \cdot
M(x',y') \right)^2}}\f] */
TM_CCORR = 2, /*!< \f[R(x,y)= \sum _{x',y'} (T(x',y') \cdot I(x+x',y+y'))\f]
with mask:
\f[R(x,y)= \sum _{x',y'} (T(x',y') \cdot I(x+x',y+y') \cdot M(x',y')
^2)\f] */
TM_CCORR_NORMED = 3, /*!< \f[R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y'))}{\sqrt{
\sum_{x',y'}T(x',y')^2 \cdot \sum_{x',y'} I(x+x',y+y')^2}}\f]
with mask:
\f[R(x,y)= \frac{\sum_{x',y'} (T(x',y') \cdot I(x+x',y+y') \cdot
M(x',y')^2)}{\sqrt{\sum_{x',y'} \left( T(x',y') \cdot M(x',y')
\right)^2 \cdot \sum_{x',y'} \left( I(x+x',y+y') \cdot M(x',y')
\right)^2}}\f] */
TM_CCOEFF = 4, /*!< \f[R(x,y)= \sum _{x',y'} (T'(x',y') \cdot I'(x+x',y+y'))\f]
where
\f[\begin{array}{l} T'(x',y')=T(x',y') - 1/(w \cdot h) \cdot \sum _{
x'',y''} T(x'',y'') \\ I'(x+x',y+y')=I(x+x',y+y') - 1/(w \cdot h)
\cdot \sum _{x'',y''} I(x+x'',y+y'') \end{array}\f]
with mask:
\f[\begin{array}{l} T'(x',y')=M(x',y') \cdot \left( T(x',y') -
\frac{1}{\sum _{x'',y''} M(x'',y'')} \cdot \sum _{x'',y''}
(T(x'',y'') \cdot M(x'',y'')) \right) \\ I'(x+x',y+y')=M(x',y')
\cdot \left( I(x+x',y+y') - \frac{1}{\sum _{x'',y''} M(x'',y'')}
\cdot \sum _{x'',y''} (I(x+x'',y+y'') \cdot M(x'',y'')) \right)
\end{array} \f] */
TM_CCOEFF_NORMED = 5 /*!< \f[R(x,y)= \frac{ \sum_{x',y'} (T'(x',y') \cdot I'(x+x',y+y')) }{
\sqrt{\sum_{x',y'}T'(x',y')^2 \cdot \sum_{x',y'} I'(x+x',y+y')^2}
}\f] */
};
/** @example samples/cpp/tutorial_code/Histograms_Matching/MatchTemplate_Demo.cpp
@ -3675,9 +3704,10 @@ An example using Template Matching algorithm
/** @brief Compares a template against overlapped image regions.
The function slides through image , compares the overlapped patches of size \f$w \times h\f$ against
templ using the specified method and stores the comparison results in result . Here are the formulae
for the available comparison methods ( \f$I\f$ denotes image, \f$T\f$ template, \f$R\f$ result ). The summation
is done over template and/or the image patch: \f$x' = 0...w-1, y' = 0...h-1\f$
templ using the specified method and stores the comparison results in result . #TemplateMatchModes
describes the formulae for the available comparison methods ( \f$I\f$ denotes image, \f$T\f$
template, \f$R\f$ result, \f$M\f$ the optional mask ). The summation is done over template and/or
the image patch: \f$x' = 0...w-1, y' = 0...h-1\f$
After the function finishes the comparison, the best matches can be found as global minimums (when
#TM_SQDIFF was used) or maximums (when #TM_CCORR or #TM_CCOEFF was used) using the
@ -3692,8 +3722,12 @@ data type.
@param result Map of comparison results. It must be single-channel 32-bit floating-point. If image
is \f$W \times H\f$ and templ is \f$w \times h\f$ , then result is \f$(W-w+1) \times (H-h+1)\f$ .
@param method Parameter specifying the comparison method, see #TemplateMatchModes
@param mask Mask of searched template. It must have the same datatype and size with templ. It is
not set by default. Currently, only the #TM_SQDIFF and #TM_CCORR_NORMED methods are supported.
@param mask Optional mask. It must have the same size as templ. It must either have the same number
of channels as template or only one channel, which is then used for all template and
image channels. If the data type is #CV_8U, the mask is interpreted as a binary mask,
meaning only elements where mask is nonzero are used and are kept unchanged independent
of the actual mask value (weight equals 1). For data tpye #CV_32F, the mask values are
used as weights. The exact formulas are documented in #TemplateMatchModes.
*/
CV_EXPORTS_W void matchTemplate( InputArray image, InputArray templ,
OutputArray result, int method, InputArray mask = noArray() );

@ -369,7 +369,6 @@ void crossCorr( const Mat& src, const Mat& templ, Mat& dst,
Point anchor=Point(0,0), double delta=0,
int borderType=BORDER_REFLECT_101 );
}
#ifdef HAVE_IPP_IW

@ -761,81 +761,146 @@ void crossCorr( const Mat& img, const Mat& _templ, Mat& corr,
static void matchTemplateMask( InputArray _img, InputArray _templ, OutputArray _result, int method, InputArray _mask )
{
int type = _img.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
CV_Assert( CV_TM_SQDIFF <= method && method <= CV_TM_CCOEFF_NORMED );
CV_Assert( (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 );
CV_Assert(_mask.depth() == CV_8U || _mask.depth() == CV_32F);
CV_Assert(_mask.channels() == _templ.channels() || _mask.channels() == 1);
CV_Assert(_templ.size() == _mask.size());
CV_Assert(_img.size().height >= _templ.size().height &&
_img.size().width >= _templ.size().width);
Mat img = _img.getMat(), templ = _templ.getMat(), mask = _mask.getMat();
int ttype = templ.type(), tdepth = CV_MAT_DEPTH(ttype), tcn = CV_MAT_CN(ttype);
int mtype = img.type(), mdepth = CV_MAT_DEPTH(type), mcn = CV_MAT_CN(mtype);
if (depth == CV_8U)
if (img.depth() == CV_8U)
{
depth = CV_32F;
type = CV_MAKETYPE(CV_32F, cn);
img.convertTo(img, type, 1.0 / 255);
img.convertTo(img, CV_32F);
}
if (tdepth == CV_8U)
if (templ.depth() == CV_8U)
{
tdepth = CV_32F;
ttype = CV_MAKETYPE(CV_32F, tcn);
templ.convertTo(templ, ttype, 1.0 / 255);
templ.convertTo(templ, CV_32F);
}
if (mdepth == CV_8U)
if (mask.depth() == CV_8U)
{
mdepth = CV_32F;
mtype = CV_MAKETYPE(CV_32F, mcn);
compare(mask, Scalar::all(0), mask, CMP_NE);
mask.convertTo(mask, mtype, 1.0 / 255);
// To keep compatibility to other masks in OpenCV: CV_8U masks are binary masks
threshold(mask, mask, 0/*threshold*/, 1.0/*maxVal*/, THRESH_BINARY);
mask.convertTo(mask, CV_32F);
}
Size corrSize(img.cols - templ.cols + 1, img.rows - templ.rows + 1);
_result.create(corrSize, CV_32F);
Mat result = _result.getMat();
// If mask has only one channel, we repeat it for every image/template channel
if (templ.type() != mask.type())
{
// Assertions above ensured, that depth is the same and only number of channel differ
std::vector<Mat> maskChannels(templ.channels(), mask);
merge(maskChannels.data(), templ.channels(), mask);
}
if (method == CV_TM_SQDIFF || method == CV_TM_SQDIFF_NORMED)
{
Mat temp_result(corrSize, CV_32F);
Mat img2 = img.mul(img);
Mat mask2 = mask.mul(mask);
Mat mask_templ = templ.mul(mask);
Scalar templMean, templSdv;
double templSum2 = 0;
meanStdDev( mask_templ, templMean, templSdv );
templSum2 = templSdv[0]*templSdv[0] + templSdv[1]*templSdv[1] + templSdv[2]*templSdv[2] + templSdv[3]*templSdv[3];
templSum2 += templMean[0]*templMean[0] + templMean[1]*templMean[1] + templMean[2]*templMean[2] + templMean[3]*templMean[3];
templSum2 *= ((double)templ.rows * templ.cols);
if (method == CV_TM_SQDIFF)
// If the mul() is ever unnested, declare MatExpr, *not* Mat, to be more efficient.
// NORM_L2SQR calculates sum of squares
double templ2_mask2_sum = norm(templ.mul(mask), NORM_L2SQR);
crossCorr(img2, mask2, temp_result, Point(0,0), 0, 0);
crossCorr(img, templ.mul(mask2), result, Point(0,0), 0, 0);
// result and temp_result should not be switched, because temp_result is potentially needed
// for normalization.
result = -2 * result + temp_result + templ2_mask2_sum;
if (method == CV_TM_SQDIFF_NORMED)
{
Mat mask2_templ = templ.mul(mask2);
Mat corr(corrSize, CV_32F);
crossCorr( img, mask2_templ, corr, Point(0,0), 0, 0 );
crossCorr( img2, mask, result, Point(0,0), 0, 0 );
sqrt(templ2_mask2_sum * temp_result, temp_result);
result /= temp_result;
}
}
else if (method == CV_TM_CCORR || method == CV_TM_CCORR_NORMED)
{
// If the mul() is ever unnested, declare MatExpr, *not* Mat, to be more efficient.
Mat templ_mask2 = templ.mul(mask.mul(mask));
crossCorr(img, templ_mask2, result, Point(0,0), 0, 0);
result -= corr * 2;
result += templSum2;
if (method == CV_TM_CCORR_NORMED)
{
Mat temp_result(corrSize, CV_32F);
Mat img2 = img.mul(img);
Mat mask2 = mask.mul(mask);
// NORM_L2SQR calculates sum of squares
double templ2_mask2_sum = norm(templ.mul(mask), NORM_L2SQR);
crossCorr( img2, mask2, temp_result, Point(0,0), 0, 0 );
sqrt(templ2_mask2_sum * temp_result, temp_result);
result /= temp_result;
}
else if (method == CV_TM_CCORR_NORMED)
}
else if (method == CV_TM_CCOEFF || method == CV_TM_CCOEFF_NORMED)
{
if (templSum2 < DBL_EPSILON)
// Do mul() inline or declare MatExpr where possible, *not* Mat, to be more efficient.
Scalar mask_sum = sum(mask);
// T' * M where T' = M * (T - 1/sum(M)*sum(M*T))
Mat templx_mask = mask.mul(mask.mul(templ - sum(mask.mul(templ)).div(mask_sum)));
Mat img_mask_corr(corrSize, img.type()); // Needs separate channels
// CCorr(I, T'*M)
crossCorr(img, templx_mask, result, Point(0, 0), 0, 0);
// CCorr(I, M)
crossCorr(img, mask, img_mask_corr, Point(0, 0), 0, 0);
// CCorr(I', T') = CCorr(I, T'*M) - sum(T'*M)/sum(M)*CCorr(I, M)
// It does not matter what to use Mat/MatExpr, it should be evaluated to perform assign subtraction
Mat temp_res = img_mask_corr.mul(sum(templx_mask).div(mask_sum));
if (img.channels() == 1)
{
result = Scalar::all(1);
return;
result -= temp_res;
}
Mat corr(corrSize, CV_32F);
crossCorr( img2, mask2, corr, Point(0,0), 0, 0 );
crossCorr( img, mask_templ, result, Point(0,0), 0, 0 );
sqrt(corr, corr);
result = result.mul(1/corr);
result /= std::sqrt(templSum2);
else
{
// Sum channels of expression
temp_res = temp_res.reshape(1, result.rows * result.cols);
// channels are now columns
reduce(temp_res, temp_res, 1, REDUCE_SUM);
// transform back, but now with only one channel
result -= temp_res.reshape(1, result.rows);
}
if (method == CV_TM_CCOEFF_NORMED)
{
// norm(T')
double norm_templx = norm(mask.mul(templ - sum(mask.mul(templ)).div(mask_sum)),
NORM_L2);
// norm(I') = sqrt{ CCorr(I^2, M^2) - 2*CCorr(I, M^2)/sum(M)*CCorr(I, M)
// + sum(M^2)*CCorr(I, M)^2/sum(M)^2 }
// = sqrt{ CCorr(I^2, M^2)
// + CCorr(I, M)/sum(M)*{ sum(M^2) / sum(M) * CCorr(I,M)
// - 2 * CCorr(I, M^2) } }
Mat norm_imgx(corrSize, CV_32F);
Mat img2 = img.mul(img);
Mat mask2 = mask.mul(mask);
Scalar mask2_sum = sum(mask2);
Mat img_mask2_corr(corrSize, img.type());
crossCorr(img2, mask2, norm_imgx, Point(0,0), 0, 0);
crossCorr(img, mask2, img_mask2_corr, Point(0,0), 0, 0);
temp_res = img_mask_corr.mul(Scalar(1.0, 1.0, 1.0, 1.0).div(mask_sum))
.mul(img_mask_corr.mul(mask2_sum.div(mask_sum)) - 2 * img_mask2_corr);
if (img.channels() == 1)
{
norm_imgx += temp_res;
}
else
CV_Error(Error::StsNotImplemented, "");
{
// Sum channels of expression
temp_res = temp_res.reshape(1, result.rows*result.cols);
// channels are now columns
// reduce sums columns (= channels)
reduce(temp_res, temp_res, 1, REDUCE_SUM);
// transform back, but now with only one channel
norm_imgx += temp_res.reshape(1, result.rows);
}
sqrt(norm_imgx, norm_imgx);
result /= norm_imgx * norm_templx;
}
}
}
static void common_matchTemplate( Mat& img, Mat& templ, Mat& result, int method, int cn )
@ -1093,16 +1158,16 @@ void cv::matchTemplate( InputArray _img, InputArray _templ, OutputArray _result,
{
CV_INSTRUMENT_REGION();
int type = _img.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
CV_Assert( CV_TM_SQDIFF <= method && method <= CV_TM_CCOEFF_NORMED );
CV_Assert( (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 );
if (!_mask.empty())
{
cv::matchTemplateMask(_img, _templ, _result, method, _mask);
return;
}
int type = _img.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
CV_Assert( CV_TM_SQDIFF <= method && method <= CV_TM_CCOEFF_NORMED );
CV_Assert( (depth == CV_8U || depth == CV_32F) && type == _templ.type() && _img.dims() <= 2 );
bool needswap = _img.size().height < _templ.size().height || _img.size().width < _templ.size().width;
if (needswap)
{

@ -0,0 +1,278 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
namespace opencv_test { namespace {
CV_ENUM(MatchTemplType, CV_TM_CCORR, CV_TM_CCORR_NORMED,
CV_TM_SQDIFF, CV_TM_SQDIFF_NORMED,
CV_TM_CCOEFF, CV_TM_CCOEFF_NORMED)
class Imgproc_MatchTemplateWithMask : public TestWithParam<std::tuple<MatType,MatType>>
{
protected:
// Member functions inherited from ::testing::Test
void SetUp() override;
// Matrices for test calculations (always CV_32)
Mat img_;
Mat templ_;
Mat mask_;
Mat templ_masked_;
Mat img_roi_masked_;
// Matrices for call to matchTemplate (have test type)
Mat img_testtype_;
Mat templ_testtype_;
Mat mask_testtype_;
Mat result_;
// Constants
static const Size IMG_SIZE;
static const Size TEMPL_SIZE;
static const Point TEST_POINT;
};
// Arbitraryly chosen test constants
const Size Imgproc_MatchTemplateWithMask::IMG_SIZE(160, 100);
const Size Imgproc_MatchTemplateWithMask::TEMPL_SIZE(21, 13);
const Point Imgproc_MatchTemplateWithMask::TEST_POINT(8, 9);
void Imgproc_MatchTemplateWithMask::SetUp()
{
int type = std::get<0>(GetParam());
int type_mask = std::get<1>(GetParam());
// Matrices are created with the depth to test (for the call to matchTemplate()), but are also
// converted to CV_32 for the test calculations, because matchTemplate() also only operates on
// and returns CV_32.
img_testtype_.create(IMG_SIZE, type);
templ_testtype_.create(TEMPL_SIZE, type);
mask_testtype_.create(TEMPL_SIZE, type_mask);
randu(img_testtype_, 0, 10);
randu(templ_testtype_, 0, 10);
randu(mask_testtype_, 0, 5);
img_testtype_.convertTo(img_, CV_32F);
templ_testtype_.convertTo(templ_, CV_32F);
mask_testtype_.convertTo(mask_, CV_32F);
if (CV_MAT_DEPTH(type_mask) == CV_8U)
{
// CV_8U masks are interpreted as binary masks
mask_.setTo(Scalar::all(1), mask_ != 0);
}
if (mask_.channels() != templ_.channels())
{
std::vector<Mat> mask_channels(templ_.channels(), mask_);
merge(mask_channels.data(), templ_.channels(), mask_);
}
Rect roi(TEST_POINT, TEMPL_SIZE);
img_roi_masked_ = img_(roi).mul(mask_);
templ_masked_ = templ_.mul(mask_);
}
TEST_P(Imgproc_MatchTemplateWithMask, CompareNaiveImplSQDIFF)
{
matchTemplate(img_testtype_, templ_testtype_, result_, CV_TM_SQDIFF, mask_testtype_);
// Naive implementation for one point
Mat temp = img_roi_masked_ - templ_masked_;
Scalar temp_s = sum(temp.mul(temp));
double val = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
EXPECT_NEAR(val, result_.at<float>(TEST_POINT), TEMPL_SIZE.area()*abs(val)*FLT_EPSILON);
}
TEST_P(Imgproc_MatchTemplateWithMask, CompareNaiveImplSQDIFF_NORMED)
{
matchTemplate(img_testtype_, templ_testtype_, result_, CV_TM_SQDIFF_NORMED, mask_testtype_);
// Naive implementation for one point
Mat temp = img_roi_masked_ - templ_masked_;
Scalar temp_s = sum(temp.mul(temp));
double val = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
// Normalization
temp_s = sum(templ_masked_.mul(templ_masked_));
double norm = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
temp_s = sum(img_roi_masked_.mul(img_roi_masked_));
norm *= temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
norm = sqrt(norm);
val /= norm;
EXPECT_NEAR(val, result_.at<float>(TEST_POINT), TEMPL_SIZE.area()*abs(val)*FLT_EPSILON);
}
TEST_P(Imgproc_MatchTemplateWithMask, CompareNaiveImplCCORR)
{
matchTemplate(img_testtype_, templ_testtype_, result_, CV_TM_CCORR, mask_testtype_);
// Naive implementation for one point
Scalar temp_s = sum(templ_masked_.mul(img_roi_masked_));
double val = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
EXPECT_NEAR(val, result_.at<float>(TEST_POINT), TEMPL_SIZE.area()*abs(val)*FLT_EPSILON);
}
TEST_P(Imgproc_MatchTemplateWithMask, CompareNaiveImplCCORR_NORMED)
{
matchTemplate(img_testtype_, templ_testtype_, result_, CV_TM_CCORR_NORMED, mask_testtype_);
// Naive implementation for one point
Scalar temp_s = sum(templ_masked_.mul(img_roi_masked_));
double val = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
// Normalization
temp_s = sum(templ_masked_.mul(templ_masked_));
double norm = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
temp_s = sum(img_roi_masked_.mul(img_roi_masked_));
norm *= temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
norm = sqrt(norm);
val /= norm;
EXPECT_NEAR(val, result_.at<float>(TEST_POINT), TEMPL_SIZE.area()*abs(val)*FLT_EPSILON);
}
TEST_P(Imgproc_MatchTemplateWithMask, CompareNaiveImplCCOEFF)
{
matchTemplate(img_testtype_, templ_testtype_, result_, CV_TM_CCOEFF, mask_testtype_);
// Naive implementation for one point
Scalar temp_s = sum(mask_);
for (int i = 0; i < 4; i++)
{
if (temp_s[i] != 0.0)
temp_s[i] = 1.0 / temp_s[i];
else
temp_s[i] = 1.0;
}
Mat temp = mask_.clone(); temp = temp_s; // Workaround to multiply Mat by Scalar
Mat temp2 = mask_.clone(); temp2 = sum(templ_masked_); // Workaround to multiply Mat by Scalar
Mat templx = templ_masked_ - mask_.mul(temp).mul(temp2);
temp2 = sum(img_roi_masked_); // Workaround to multiply Mat by Scalar
Mat imgx = img_roi_masked_ - mask_.mul(temp).mul(temp2);
temp_s = sum(templx.mul(imgx));
double val = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
EXPECT_NEAR(val, result_.at<float>(TEST_POINT), TEMPL_SIZE.area()*abs(val)*FLT_EPSILON);
}
TEST_P(Imgproc_MatchTemplateWithMask, CompareNaiveImplCCOEFF_NORMED)
{
matchTemplate(img_testtype_, templ_testtype_, result_, CV_TM_CCOEFF_NORMED, mask_testtype_);
// Naive implementation for one point
Scalar temp_s = sum(mask_);
for (int i = 0; i < 4; i++)
{
if (temp_s[i] != 0.0)
temp_s[i] = 1.0 / temp_s[i];
else
temp_s[i] = 1.0;
}
Mat temp = mask_.clone(); temp = temp_s; // Workaround to multiply Mat by Scalar
Mat temp2 = mask_.clone(); temp2 = sum(templ_masked_); // Workaround to multiply Mat by Scalar
Mat templx = templ_masked_ - mask_.mul(temp).mul(temp2);
temp2 = sum(img_roi_masked_); // Workaround to multiply Mat by Scalar
Mat imgx = img_roi_masked_ - mask_.mul(temp).mul(temp2);
temp_s = sum(templx.mul(imgx));
double val = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
// Normalization
temp_s = sum(templx.mul(templx));
double norm = temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
temp_s = sum(imgx.mul(imgx));
norm *= temp_s[0] + temp_s[1] + temp_s[2] + temp_s[3];
norm = sqrt(norm);
val /= norm;
EXPECT_NEAR(val, result_.at<float>(TEST_POINT), TEMPL_SIZE.area()*abs(val)*FLT_EPSILON);
}
INSTANTIATE_TEST_CASE_P(SingleChannelMask, Imgproc_MatchTemplateWithMask,
Combine(
Values(CV_32FC1, CV_32FC3, CV_8UC1, CV_8UC3),
Values(CV_32FC1, CV_8UC1)));
INSTANTIATE_TEST_CASE_P(MultiChannelMask, Imgproc_MatchTemplateWithMask,
Combine(
Values(CV_32FC3, CV_8UC3),
Values(CV_32FC3, CV_8UC3)));
class Imgproc_MatchTemplateWithMask2 : public TestWithParam<std::tuple<MatType,MatType,
MatchTemplType>>
{
protected:
// Member functions inherited from ::testing::Test
void SetUp() override;
// Data members
Mat img_;
Mat templ_;
Mat mask_;
Mat result_withoutmask_;
Mat result_withmask_;
// Constants
static const Size IMG_SIZE;
static const Size TEMPL_SIZE;
};
// Arbitraryly chosen test constants
const Size Imgproc_MatchTemplateWithMask2::IMG_SIZE(160, 100);
const Size Imgproc_MatchTemplateWithMask2::TEMPL_SIZE(21, 13);
void Imgproc_MatchTemplateWithMask2::SetUp()
{
int type = std::get<0>(GetParam());
int type_mask = std::get<1>(GetParam());
img_.create(IMG_SIZE, type);
templ_.create(TEMPL_SIZE, type);
mask_.create(TEMPL_SIZE, type_mask);
randu(img_, 0, 100);
randu(templ_, 0, 100);
if (CV_MAT_DEPTH(type_mask) == CV_8U)
{
// CV_8U implies binary mask, so all nonzero values should work
randu(mask_, 1, 255);
}
else
{
mask_ = Scalar(1, 1, 1, 1);
}
}
TEST_P(Imgproc_MatchTemplateWithMask2, CompareWithAndWithoutMask)
{
int method = std::get<2>(GetParam());
matchTemplate(img_, templ_, result_withmask_, method, mask_);
matchTemplate(img_, templ_, result_withoutmask_, method);
// Get maximum result for relative error calculation
double min_val, max_val;
minMaxLoc(abs(result_withmask_), &min_val, &max_val);
// Get maximum of absolute diff for comparison
double mindiff, maxdiff;
minMaxLoc(abs(result_withmask_ - result_withoutmask_), &mindiff, &maxdiff);
EXPECT_LT(maxdiff, max_val*TEMPL_SIZE.area()*FLT_EPSILON);
}
INSTANTIATE_TEST_CASE_P(SingleChannelMask, Imgproc_MatchTemplateWithMask2,
Combine(
Values(CV_32FC1, CV_32FC3, CV_8UC1, CV_8UC3),
Values(CV_32FC1, CV_8UC1),
Values(CV_TM_SQDIFF, CV_TM_SQDIFF_NORMED, CV_TM_CCORR, CV_TM_CCORR_NORMED,
CV_TM_CCOEFF, CV_TM_CCOEFF_NORMED)));
INSTANTIATE_TEST_CASE_P(MultiChannelMask, Imgproc_MatchTemplateWithMask2,
Combine(
Values(CV_32FC3, CV_8UC3),
Values(CV_32FC3, CV_8UC3),
Values(CV_TM_SQDIFF, CV_TM_SQDIFF_NORMED, CV_TM_CCORR, CV_TM_CCORR_NORMED,
CV_TM_CCOEFF, CV_TM_CCOEFF_NORMED)));
}} // namespace
Loading…
Cancel
Save