diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 4a43de2ac1..bff48cc4b6 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -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() ); diff --git a/modules/imgproc/src/filterengine.hpp b/modules/imgproc/src/filterengine.hpp index 9ec0b6e8b1..7f656f2707 100644 --- a/modules/imgproc/src/filterengine.hpp +++ b/modules/imgproc/src/filterengine.hpp @@ -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 diff --git a/modules/imgproc/src/templmatch.cpp b/modules/imgproc/src/templmatch.cpp index e84d9c9611..7e34832fe5 100644 --- a/modules/imgproc/src/templmatch.cpp +++ b/modules/imgproc/src/templmatch.cpp @@ -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(); - 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 mask has only one channel, we repeat it for every image/template channel + if (templ.type() != mask.type()) { - 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 ); - - result -= corr * 2; - result += templSum2; + // Assertions above ensured, that depth is the same and only number of channel differ + std::vector maskChannels(templ.channels(), mask); + merge(maskChannels.data(), templ.channels(), mask); } - else if (method == CV_TM_CCORR_NORMED) + + if (method == CV_TM_SQDIFF || method == CV_TM_SQDIFF_NORMED) { - if (templSum2 < DBL_EPSILON) + Mat temp_result(corrSize, CV_32F); + Mat img2 = img.mul(img); + Mat mask2 = mask.mul(mask); + // 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) { - result = Scalar::all(1); - return; + 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); + + 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_CCOEFF || method == CV_TM_CCOEFF_NORMED) + { + // 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 -= temp_res; + } + 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 + { + // 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; } - - 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 - CV_Error(Error::StsNotImplemented, ""); } 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) { diff --git a/modules/imgproc/test/test_templmatchmask.cpp b/modules/imgproc/test/test_templmatchmask.cpp new file mode 100644 index 0000000000..cca110ae22 --- /dev/null +++ b/modules/imgproc/test/test_templmatchmask.cpp @@ -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> +{ +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 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(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(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(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(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(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(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> +{ +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