/*M/////////////////////////////////////////////////////////////////////////////////////// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // // By downloading, copying, installing or using the software you agree to this license. // If you do not agree to this license, do not download, install, // copy or use the software. // // // License Agreement // For Open Source Computer Vision Library // // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. // Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistribution's of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // * The name of the copyright holders may not be used to endorse or promote products // derived from this software without specific prior written permission. // // This software is provided by the copyright holders and contributors "as is" and // any express or implied warranties, including, but not limited to, the implied // warranties of merchantability and fitness for a particular purpose are disclaimed. // In no event shall the Intel Corporation or contributors be liable for any direct, // indirect, incidental, special, exemplary, or consequential damages // (including, but not limited to, procurement of substitute goods or services; // loss of use, data, or profits; or business interruption) however caused // and on any theory of liability, whether in contract, strict liability, // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // //M*/ #include "test_precomp.hpp" namespace opencv_test { namespace { class CV_ECC_BaseTest : public cvtest::BaseTest { public: CV_ECC_BaseTest(); virtual ~CV_ECC_BaseTest(); protected: double computeRMS(const Mat& mat1, const Mat& mat2); bool isMapCorrect(const Mat& mat); virtual bool test(const Mat) { return true; }; // single test bool testAllTypes(const Mat img); // run test for all supported data types (U8, U16, F32, F64) bool testAllChNum(const Mat img); // run test for all supported channels count (gray, RGB) void run(int); bool checkMap(const Mat& map, const Mat& ground); double MAX_RMS_ECC; // upper bound for RMS error int ntests; // number of tests per motion type int ECC_iterations; // number of iterations for ECC double ECC_epsilon; // we choose a negative value, so that // ECC_iterations are always executed TermCriteria criteria; }; CV_ECC_BaseTest::CV_ECC_BaseTest() { MAX_RMS_ECC = 0.1; ntests = 3; ECC_iterations = 50; ECC_epsilon = -1; //-> negative value means that ECC_Iterations will be executed criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, ECC_iterations, ECC_epsilon); } CV_ECC_BaseTest::~CV_ECC_BaseTest() {} bool CV_ECC_BaseTest::isMapCorrect(const Mat& map) { bool tr = true; float mapVal; for (int i = 0; i < map.rows; i++) for (int j = 0; j < map.cols; j++) { mapVal = map.at(i, j); tr = tr & (!cvIsNaN(mapVal) && (fabs(mapVal) < 1e9)); } return tr; } double CV_ECC_BaseTest::computeRMS(const Mat& mat1, const Mat& mat2) { CV_Assert(mat1.rows == mat2.rows); CV_Assert(mat1.cols == mat2.cols); Mat errorMat; subtract(mat1, mat2, errorMat); return sqrt(errorMat.dot(errorMat) / (mat1.rows * mat1.cols * mat1.channels())); } bool CV_ECC_BaseTest::checkMap(const Mat& map, const Mat& ground) { if (!isMapCorrect(map)) { ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); return false; } if (computeRMS(map, ground) > MAX_RMS_ECC) { ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); ts->printf(ts->LOG, "RMS = %f", computeRMS(map, ground)); return false; } return true; } bool CV_ECC_BaseTest::testAllTypes(const Mat img) { auto types = {CV_8U, CV_16U, CV_32F, CV_64F}; for (auto type : types) { Mat timg; img.convertTo(timg, type); if (!test(timg)) return false; } return true; } bool CV_ECC_BaseTest::testAllChNum(const Mat img) { if (!testAllTypes(img)) return false; Mat gray; cvtColor(img, gray, COLOR_RGB2GRAY); if (!testAllTypes(gray)) return false; return true; } void CV_ECC_BaseTest::run(int) { Mat img = imread(string(ts->get_data_path()) + "shared/fruits.png"); if (img.empty()) { ts->printf(ts->LOG, "test image can not be read"); ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); return; } Mat testImg; resize(img, testImg, Size(216, 216), 0, 0, INTER_LINEAR_EXACT); testAllChNum(testImg); ts->set_failed_test_info(cvtest::TS::OK); } class CV_ECC_Test_Translation : public CV_ECC_BaseTest { public: CV_ECC_Test_Translation(); protected: bool test(const Mat); }; CV_ECC_Test_Translation::CV_ECC_Test_Translation() {} bool CV_ECC_Test_Translation::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; for (int k = 0; k < ntests; k++) { ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); Mat translationGround = (Mat_(2, 3) << 1, 0, (rng.uniform(10.f, 20.f)), 0, 1, (rng.uniform(10.f, 20.f))); Mat warpedImage; warpAffine(testImg, warpedImage, translationGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); Mat mapTranslation = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria); if (!checkMap(mapTranslation, translationGround)) return false; } return true; } class CV_ECC_Test_Euclidean : public CV_ECC_BaseTest { public: CV_ECC_Test_Euclidean(); protected: bool test(const Mat); }; CV_ECC_Test_Euclidean::CV_ECC_Test_Euclidean() {} bool CV_ECC_Test_Euclidean::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; for (int k = 0; k < ntests; k++) { ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); double angle = CV_PI / 30 + CV_PI * rng.uniform((double)-2.f, (double)2.f) / 180; Mat euclideanGround = (Mat_(2, 3) << cos(angle), -sin(angle), (rng.uniform(10.f, 20.f)), sin(angle), cos(angle), (rng.uniform(10.f, 20.f))); Mat warpedImage; warpAffine(testImg, warpedImage, euclideanGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); Mat mapEuclidean = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); findTransformECC(warpedImage, testImg, mapEuclidean, 1, criteria); if (!checkMap(mapEuclidean, euclideanGround)) return false; } return true; } class CV_ECC_Test_Affine : public CV_ECC_BaseTest { public: CV_ECC_Test_Affine(); protected: bool test(const Mat img); }; CV_ECC_Test_Affine::CV_ECC_Test_Affine() {} bool CV_ECC_Test_Affine::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; for (int k = 0; k < ntests; k++) { ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); Mat affineGround = (Mat_(2, 3) << (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)), (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(10.f, 20.f))); Mat warpedImage; warpAffine(testImg, warpedImage, affineGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); Mat mapAffine = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); findTransformECC(warpedImage, testImg, mapAffine, 2, criteria); if (!checkMap(mapAffine, affineGround)) return false; } return true; } class CV_ECC_Test_Homography : public CV_ECC_BaseTest { public: CV_ECC_Test_Homography(); protected: bool test(const Mat testImg); }; CV_ECC_Test_Homography::CV_ECC_Test_Homography() {} bool CV_ECC_Test_Homography::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; for (int k = 0; k < ntests; k++) { ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); Mat homoGround = (Mat_(3, 3) << (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)), (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(10.f, 20.f)), (rng.uniform(0.0001f, 0.0003f)), (rng.uniform(0.0001f, 0.0003f)), 1.f); Mat warpedImage; warpPerspective(testImg, warpedImage, homoGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); Mat mapHomography = Mat::eye(3, 3, CV_32F); findTransformECC(warpedImage, testImg, mapHomography, 3, criteria); if (!checkMap(mapHomography, homoGround)) return false; } return true; } class CV_ECC_Test_Mask : public CV_ECC_BaseTest { public: CV_ECC_Test_Mask(); protected: bool test(const Mat); }; CV_ECC_Test_Mask::CV_ECC_Test_Mask() {} bool CV_ECC_Test_Mask::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; for (int k = 0; k < ntests; k++) { ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); Mat translationGround = (Mat_(2, 3) << 1, 0, (rng.uniform(10.f, 20.f)), 0, 1, (rng.uniform(10.f, 20.f))); Mat warpedImage; warpAffine(testImg, warpedImage, translationGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); Mat mapTranslation = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); Mat_ mask = Mat_::ones(testImg.rows, testImg.cols); Rect region(testImg.cols * 2 / 3, testImg.rows * 2 / 3, testImg.cols / 3, testImg.rows / 3); rectangle(testImg, region, Scalar::all(0), FILLED); rectangle(mask, region, Scalar(0), FILLED); findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria, mask); if (!checkMap(mapTranslation, translationGround)) return false; // Test with non-default gaussian blur. findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria, mask, 1); if (!checkMap(mapTranslation, translationGround)) return false; // Test with template mask. Mat_ warpedMask = Mat_::ones(warpedImage.rows, warpedImage.cols); for (int i=warpedImage.rows*1/3; i({O, Z, Z}), R); cv::merge(std::vector({Z, O, Z}), G); cv::merge(std::vector({Z, Z, O}), B); cv::merge(std::vector({x, x, x}), X); cv::merge(std::vector({y, y, y}), Y); // 1. The channels are orthogonal and independent EXPECT_NEAR(computeECC(X.mul(R), X.mul(G)), 0, eps); EXPECT_NEAR(computeECC(X.mul(R), X.mul(B)), 0, eps); EXPECT_NEAR(computeECC(X.mul(B), X.mul(G)), 0, eps); EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(B), X.mul(B) + Y.mul(R)), 0, eps); EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(G) + (X + Y).mul(B), Y.mul(R) + X.mul(G) + (X - Y).mul(B)), 0, eps); // 2. Each channel contribute equally EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(G + B), X), 1.0 / 3, eps); EXPECT_NEAR(computeECC(X.mul(G) + Y.mul(R + B), X), 1.0 / 3, eps); EXPECT_NEAR(computeECC(X.mul(B) + Y.mul(G + R), X), 1.0 / 3, eps); // 3. The coefficient is invariant with respect to the offset of channels EXPECT_NEAR(computeECC(X - R + 2 * G + B, X), 1.0, eps); if (x.type() != CV_8U && x.type() != CV_8U) { EXPECT_NEAR(computeECC(X + R - 2 * G + B, Y), 0.0, eps); } // The channels are independent. Check orthogonal combinations // full squares norm = sum of squared norms EXPECT_NEAR(computeECC(X, Y + X), 1.0 / sqrt(2.0), eps); EXPECT_NEAR(computeECC(X, 2 * Y + X), 1.0 / sqrt(5.0), eps); } TEST(Video_ECC_Test_Compute, properies) { Mat xline(1, 100, CV_32F), x; for (int i = 0; i < xline.cols; ++i) xline.at(0, i) = (float)i; repeat(xline, xline.cols, 1, x); Mat x_f64, x_u8, x_u16; x.convertTo(x_f64, CV_64F); x.convertTo(x_u8, CV_8U); x.convertTo(x_u16, CV_16U); testECCProperties(x, 1e-5f); testECCProperties(x_f64, 1e-5f); testECCProperties(x_u8, 1); testECCProperties(x_u16, 1); } TEST(Video_ECC_Test_Compute, accuracy) { Mat testImg = (Mat_(3, 3) << 1, 0, 0, 1, 0, 0, 1, 0, 0); Mat warpedImage = (Mat_(3, 3) << 0, 1, 0, 0, 1, 0, 0, 1, 0); Mat_ mask = Mat_::ones(testImg.rows, testImg.cols); double ecc = computeECC(warpedImage, testImg, mask); 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_(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(); } TEST(Video_ECC_Homography, accuracy) { CV_ECC_Test_Homography test; test.safe_run(); } TEST(Video_ECC_Mask, accuracy) { CV_ECC_Test_Mask test; test.safe_run(); } } // namespace } // namespace opencv_test