mirror of
https://github.com/opencv/opencv.git
synced 2025-06-08 01:53:19 +08:00
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>
This commit is contained in:
parent
486de65067
commit
5b095dfcb6
@ -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;
|
||||
// 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;
|
||||
|
||||
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 (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 );
|
||||
|
||||
result -= corr * 2;
|
||||
result += templSum2;
|
||||
sqrt(templ2_mask2_sum * temp_result, temp_result);
|
||||
result /= temp_result;
|
||||
}
|
||||
else if (method == CV_TM_CCORR_NORMED)
|
||||
{
|
||||
if (templSum2 < DBL_EPSILON)
|
||||
{
|
||||
result = Scalar::all(1);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
|
||||
Mat corr(corrSize, CV_32F);
|
||||
crossCorr( img2, mask2, corr, Point(0,0), 0, 0 );
|
||||
crossCorr( img, mask_templ, 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.
|
||||
|
||||
sqrt(corr, corr);
|
||||
result = result.mul(1/corr);
|
||||
result /= std::sqrt(templSum2);
|
||||
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
|
||||
CV_Error(Error::StsNotImplemented, "");
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
278
modules/imgproc/test/test_templmatchmask.cpp
Normal file
278
modules/imgproc/test/test_templmatchmask.cpp
Normal file
@ -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…
Reference in New Issue
Block a user