mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 23:33:05 +04: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:
@@ -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<Mat> 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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user