Merge pull request #19993 from danielenricocahall:fix-compute-ecc-issue
authorDanny <33044223+danielenricocahall@users.noreply.github.com>
Fri, 30 Apr 2021 17:20:52 +0000 (13:20 -0400)
committerGitHub <noreply@github.com>
Fri, 30 Apr 2021 17:20:52 +0000 (17:20 +0000)
Fix unsigned int bug in computeECC

* address issue with unsigned ints in computeEcc

* remove additional logic checking firstOctave

* use swap instead of same src/dst

* simplify the unsigned check logic

modules/video/src/ecc.cpp
modules/video/test/test_ecc.cpp

index b7148c22efbe50fb712969596d173888755af1de..eede0e7071c2ffe76650a76b8fbb64a33640a73b 100644 (file)
@@ -323,17 +323,34 @@ double cv::computeECC(InputArray templateImage, InputArray inputImage, InputArra
     Scalar meanTemplate, sdTemplate;
 
     int active_pixels = inputMask.empty() ? templateImage.size().area() : countNonZero(inputMask);
-
+    int type = templateImage.type();
     meanStdDev(templateImage, meanTemplate, sdTemplate, inputMask);
     Mat templateImage_zeromean = Mat::zeros(templateImage.size(), templateImage.type());
-    subtract(templateImage, meanTemplate, templateImage_zeromean, inputMask);
+    Mat templateMat = templateImage.getMat();
+    Mat inputMat = inputImage.getMat();
+
+    /*
+     * For unsigned ints, when the mean is computed and subtracted, any values less than the mean
+     * will be set to 0 (since there are no negatives values). This impacts the norm and dot product, which
+     * ultimately results in an incorrect ECC. To circumvent this problem, if unsigned ints are provided,
+     * we convert them to a signed ints with larger resolution for the subtraction step.
+     */
+    if(type == CV_8U || type == CV_16U) {
+        int newType = type == CV_8U ? CV_16S : CV_32S;
+        Mat templateMatConverted, inputMatConverted;
+        templateMat.convertTo(templateMatConverted, newType);
+        cv::swap(templateMat, templateMatConverted);
+        inputMat.convertTo(inputMatConverted, newType);
+        cv::swap(inputMat, inputMatConverted);
+    }
+    subtract(templateMat, meanTemplate, templateImage_zeromean, inputMask);
     double templateImagenorm = std::sqrt(active_pixels*sdTemplate.val[0]*sdTemplate.val[0]);
 
     Scalar meanInput, sdInput;
 
     Mat inputImage_zeromean = Mat::zeros(inputImage.size(), inputImage.type());
     meanStdDev(inputImage, meanInput, sdInput, inputMask);
-    subtract(inputImage, meanInput, inputImage_zeromean, inputMask);
+    subtract(inputMat, meanInput, inputImage_zeromean, inputMask);
     double inputImagenorm = std::sqrt(active_pixels*sdInput.val[0]*sdInput.val[0]);
 
     return templateImage_zeromean.dot(inputImage_zeromean)/(templateImagenorm*inputImagenorm);
index 84c5b851f5fca7027e1d3e265c60eeb3deadfc8c..21a5ae391535c1586a7a6aeee7d3401d6805bdd3 100644 (file)
@@ -501,6 +501,18 @@ TEST(Video_ECC_Test_Compute, accuracy)
     EXPECT_NEAR(ecc, -0.5f, 1e-5f);
 }
 
+TEST(Video_ECC_Test_Compute, bug_14657)
+{
+    /*
+     * Simple test case - a 2 x 2 matrix with 10, 10, 10, 6. When the mean (36 / 4 = 9) is subtracted,
+     * it results in 1, 1, 1, 0 for the unsigned int case - compare to  1, 1, 1, -3 in the signed case.
+     * For this reason, when the same matrix was provided as the input and the template, we didn't get 1 as expected.
+     */
+    Mat img = (Mat_<uint8_t>(2, 2) << 10, 10, 10, 6);
+    EXPECT_NEAR(computeECC(img, img), 1.0f, 1e-5f);
+}
+
+
 TEST(Video_ECC_Translation, accuracy) { CV_ECC_Test_Translation test; test.safe_run();}
 TEST(Video_ECC_Euclidean, accuracy) { CV_ECC_Test_Euclidean test; test.safe_run(); }
 TEST(Video_ECC_Affine, accuracy) { CV_ECC_Test_Affine test; test.safe_run(); }