Merge pull request #29071 from zitonwei:fix-masked-ccoeff-normed-constant-template

imgproc: avoid NaN in masked TM_CCOEFF_NORMED for constant templates - #29071
    
### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake

### Summary

Fixes #23257.

This patch handles a degenerate masked `TM_CCOEFF_NORMED` case in `matchTemplate()`. When the template is constant over the effective mask area, the template norm can become zero or NaN, which may propagate NaN/Inf values into the result. The masked path now returns all ones for this case, matching the existing behavior of the unmasked `TM_CCOEFF_NORMED` implementation for constant templates.

### Tests

- `cmake --build build_project4 --target opencv_test_imgproc -j4`
- `./build_project4/bin/opencv_test_imgproc '--gtest_filter=Imgproc_MatchTemplateWithMask.regression_23257_constant_template'`
- `./build_project4/bin/opencv_test_imgproc '--gtest_filter=*MatchTemplate*'`

The MatchTemplate-related test filter ran 147 tests successfully.
This commit is contained in:
Taiwei Zhang
2026-08-18 13:42:28 +08:00
committed by GitHub
parent cc293eaff6
commit 690f3d25c2
2 changed files with 46 additions and 0 deletions

View File

@@ -869,6 +869,11 @@ static void matchTemplateMask( InputArray _img, InputArray _templ, OutputArray _
// norm(T')
double norm_templx = norm(mask.mul(templ - sum(mask.mul(templ)).div(mask_sum)),
NORM_L2);
if (norm_templx < DBL_EPSILON || cvIsNaN(norm_templx))
{
result = Scalar::all(1);
return;
}
// 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)

View File

@@ -287,4 +287,45 @@ TEST(Imgproc_MatchTemplateWithMask, bug_26389) {
}
}
typedef testing::tuple<MatType, MatType, Scalar> ConstantTemplateParams;
typedef testing::TestWithParam<ConstantTemplateParams> ConstantTemplateWithMask;
TEST_P(ConstantTemplateWithMask, regression_23257)
{
int type = std::get<0>(GetParam());
int maskType = std::get<1>(GetParam());
Scalar templValue = std::get<2>(GetParam());
Mat image(Size(16, 16), type);
randu(image, 0, 255);
const Mat templ(Size(3, 3), type, templValue);
Mat mask = Mat::ones(templ.size(), maskType);
mask(Rect(1, 0, 1, 3)) = Scalar::all(0);
Mat result;
matchTemplate(image, templ, result, TM_CCOEFF_NORMED, mask);
for (int y = 0; y < result.rows; ++y)
{
for (int x = 0; x < result.cols; ++x)
{
const float value = result.at<float>(y, x);
EXPECT_FALSE(cvIsNaN(value));
EXPECT_FALSE(cvIsInf(value));
EXPECT_EQ(1.0f, value);
}
}
}
std::vector<ConstantTemplateParams> ctparams = {
{CV_8UC1, CV_8UC1, Scalar::all(1)},
{CV_32FC1, CV_32FC1, Scalar::all(3.5)},
{CV_8UC3, CV_8UC1, Scalar(1, 2, 3)},
{CV_32FC3, CV_32FC3, Scalar(1.5, 2.5, 3.5)}
};
INSTANTIATE_TEST_CASE_P(/**/, ConstantTemplateWithMask, testing::ValuesIn(ctparams));
}} // namespace