From 690f3d25c2367ea085531ce062c9c2beffb2f276 Mon Sep 17 00:00:00 2001 From: Taiwei Zhang Date: Tue, 18 Aug 2026 13:42:28 +0800 Subject: [PATCH] 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. --- modules/imgproc/src/templmatch.cpp | 5 +++ modules/imgproc/test/test_templmatchmask.cpp | 41 ++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/modules/imgproc/src/templmatch.cpp b/modules/imgproc/src/templmatch.cpp index 6e25607b37..2dacec5b7c 100644 --- a/modules/imgproc/src/templmatch.cpp +++ b/modules/imgproc/src/templmatch.cpp @@ -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) diff --git a/modules/imgproc/test/test_templmatchmask.cpp b/modules/imgproc/test/test_templmatchmask.cpp index c9664cc406..26af81f953 100644 --- a/modules/imgproc/test/test_templmatchmask.cpp +++ b/modules/imgproc/test/test_templmatchmask.cpp @@ -287,4 +287,45 @@ TEST(Imgproc_MatchTemplateWithMask, bug_26389) { } } +typedef testing::tuple ConstantTemplateParams; + +typedef testing::TestWithParam 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(y, x); + EXPECT_FALSE(cvIsNaN(value)); + EXPECT_FALSE(cvIsInf(value)); + EXPECT_EQ(1.0f, value); + } + } +} + +std::vector 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