test_ecc.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /*M///////////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
  4. //
  5. // By downloading, copying, installing or using the software you agree to this license.
  6. // If you do not agree to this license, do not download, install,
  7. // copy or use the software.
  8. //
  9. //
  10. // License Agreement
  11. // For Open Source Computer Vision Library
  12. //
  13. // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
  14. // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
  15. // Third party copyrights are property of their respective owners.
  16. //
  17. // Redistribution and use in source and binary forms, with or without modification,
  18. // are permitted provided that the following conditions are met:
  19. //
  20. // * Redistribution's of source code must retain the above copyright notice,
  21. // this list of conditions and the following disclaimer.
  22. //
  23. // * Redistribution's in binary form must reproduce the above copyright notice,
  24. // this list of conditions and the following disclaimer in the documentation
  25. // and/or other materials provided with the distribution.
  26. //
  27. // * The name of the copyright holders may not be used to endorse or promote products
  28. // derived from this software without specific prior written permission.
  29. //
  30. // This software is provided by the copyright holders and contributors "as is" and
  31. // any express or implied warranties, including, but not limited to, the implied
  32. // warranties of merchantability and fitness for a particular purpose are disclaimed.
  33. // In no event shall the Intel Corporation or contributors be liable for any direct,
  34. // indirect, incidental, special, exemplary, or consequential damages
  35. // (including, but not limited to, procurement of substitute goods or services;
  36. // loss of use, data, or profits; or business interruption) however caused
  37. // and on any theory of liability, whether in contract, strict liability,
  38. // or tort (including negligence or otherwise) arising in any way out of
  39. // the use of this software, even if advised of the possibility of such damage.
  40. //
  41. //M*/
  42. #include "test_precomp.hpp"
  43. namespace opencv_test {
  44. namespace {
  45. class CV_ECC_BaseTest : public cvtest::BaseTest {
  46. public:
  47. CV_ECC_BaseTest();
  48. virtual ~CV_ECC_BaseTest();
  49. protected:
  50. double computeRMS(const Mat& mat1, const Mat& mat2);
  51. bool isMapCorrect(const Mat& mat);
  52. virtual bool test(const Mat) { return true; }; // single test
  53. bool testAllTypes(const Mat img); // run test for all supported data types (U8, U16, F32, F64)
  54. bool testAllChNum(const Mat img); // run test for all supported channels count (gray, RGB)
  55. void run(int);
  56. bool checkMap(const Mat& map, const Mat& ground);
  57. double MAX_RMS_ECC; // upper bound for RMS error
  58. int ntests; // number of tests per motion type
  59. int ECC_iterations; // number of iterations for ECC
  60. double ECC_epsilon; // we choose a negative value, so that
  61. // ECC_iterations are always executed
  62. TermCriteria criteria;
  63. };
  64. CV_ECC_BaseTest::CV_ECC_BaseTest() {
  65. MAX_RMS_ECC = 0.1;
  66. ntests = 3;
  67. ECC_iterations = 50;
  68. ECC_epsilon = -1; //-> negative value means that ECC_Iterations will be executed
  69. criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, ECC_iterations, ECC_epsilon);
  70. }
  71. CV_ECC_BaseTest::~CV_ECC_BaseTest() {}
  72. bool CV_ECC_BaseTest::isMapCorrect(const Mat& map) {
  73. bool tr = true;
  74. float mapVal;
  75. for (int i = 0; i < map.rows; i++)
  76. for (int j = 0; j < map.cols; j++) {
  77. mapVal = map.at<float>(i, j);
  78. tr = tr & (!cvIsNaN(mapVal) && (fabs(mapVal) < 1e9));
  79. }
  80. return tr;
  81. }
  82. double CV_ECC_BaseTest::computeRMS(const Mat& mat1, const Mat& mat2) {
  83. CV_Assert(mat1.rows == mat2.rows);
  84. CV_Assert(mat1.cols == mat2.cols);
  85. Mat errorMat;
  86. subtract(mat1, mat2, errorMat);
  87. return sqrt(errorMat.dot(errorMat) / (mat1.rows * mat1.cols * mat1.channels()));
  88. }
  89. bool CV_ECC_BaseTest::checkMap(const Mat& map, const Mat& ground) {
  90. if (!isMapCorrect(map)) {
  91. ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT);
  92. return false;
  93. }
  94. if (computeRMS(map, ground) > MAX_RMS_ECC) {
  95. ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
  96. ts->printf(ts->LOG, "RMS = %f", computeRMS(map, ground));
  97. return false;
  98. }
  99. return true;
  100. }
  101. bool CV_ECC_BaseTest::testAllTypes(const Mat img) {
  102. auto types = {CV_8U, CV_16U, CV_32F, CV_64F};
  103. for (auto type : types) {
  104. Mat timg;
  105. img.convertTo(timg, type);
  106. if (!test(timg))
  107. return false;
  108. }
  109. return true;
  110. }
  111. bool CV_ECC_BaseTest::testAllChNum(const Mat img) {
  112. if (!testAllTypes(img))
  113. return false;
  114. Mat gray;
  115. cvtColor(img, gray, COLOR_RGB2GRAY);
  116. if (!testAllTypes(gray))
  117. return false;
  118. return true;
  119. }
  120. void CV_ECC_BaseTest::run(int) {
  121. Mat img = imread(string(ts->get_data_path()) + "shared/fruits.png");
  122. if (img.empty()) {
  123. ts->printf(ts->LOG, "test image can not be read");
  124. ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA);
  125. return;
  126. }
  127. Mat testImg;
  128. resize(img, testImg, Size(216, 216), 0, 0, INTER_LINEAR_EXACT);
  129. testAllChNum(testImg);
  130. ts->set_failed_test_info(cvtest::TS::OK);
  131. }
  132. class CV_ECC_Test_Translation : public CV_ECC_BaseTest {
  133. public:
  134. CV_ECC_Test_Translation();
  135. protected:
  136. bool test(const Mat);
  137. };
  138. CV_ECC_Test_Translation::CV_ECC_Test_Translation() {}
  139. bool CV_ECC_Test_Translation::test(const Mat testImg) {
  140. cv::RNG rng = ts->get_rng();
  141. int progress = 0;
  142. for (int k = 0; k < ntests; k++) {
  143. ts->update_context(this, k, true);
  144. progress = update_progress(progress, k, ntests, 0);
  145. Mat translationGround = (Mat_<float>(2, 3) << 1, 0, (rng.uniform(10.f, 20.f)), 0, 1, (rng.uniform(10.f, 20.f)));
  146. Mat warpedImage;
  147. warpAffine(testImg, warpedImage, translationGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
  148. Mat mapTranslation = (Mat_<float>(2, 3) << 1, 0, 0, 0, 1, 0);
  149. findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria);
  150. if (!checkMap(mapTranslation, translationGround))
  151. return false;
  152. }
  153. return true;
  154. }
  155. class CV_ECC_Test_Euclidean : public CV_ECC_BaseTest {
  156. public:
  157. CV_ECC_Test_Euclidean();
  158. protected:
  159. bool test(const Mat);
  160. };
  161. CV_ECC_Test_Euclidean::CV_ECC_Test_Euclidean() {}
  162. bool CV_ECC_Test_Euclidean::test(const Mat testImg) {
  163. cv::RNG rng = ts->get_rng();
  164. int progress = 0;
  165. for (int k = 0; k < ntests; k++) {
  166. ts->update_context(this, k, true);
  167. progress = update_progress(progress, k, ntests, 0);
  168. double angle = CV_PI / 30 + CV_PI * rng.uniform((double)-2.f, (double)2.f) / 180;
  169. Mat euclideanGround = (Mat_<float>(2, 3) << cos(angle), -sin(angle), (rng.uniform(10.f, 20.f)), sin(angle),
  170. cos(angle), (rng.uniform(10.f, 20.f)));
  171. Mat warpedImage;
  172. warpAffine(testImg, warpedImage, euclideanGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
  173. Mat mapEuclidean = (Mat_<float>(2, 3) << 1, 0, 0, 0, 1, 0);
  174. findTransformECC(warpedImage, testImg, mapEuclidean, 1, criteria);
  175. if (!checkMap(mapEuclidean, euclideanGround))
  176. return false;
  177. }
  178. return true;
  179. }
  180. class CV_ECC_Test_Affine : public CV_ECC_BaseTest {
  181. public:
  182. CV_ECC_Test_Affine();
  183. protected:
  184. bool test(const Mat img);
  185. };
  186. CV_ECC_Test_Affine::CV_ECC_Test_Affine() {}
  187. bool CV_ECC_Test_Affine::test(const Mat testImg) {
  188. cv::RNG rng = ts->get_rng();
  189. int progress = 0;
  190. for (int k = 0; k < ntests; k++) {
  191. ts->update_context(this, k, true);
  192. progress = update_progress(progress, k, ntests, 0);
  193. Mat affineGround = (Mat_<float>(2, 3) << (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(-0.03f, 0.03f)),
  194. (rng.uniform(10.f, 20.f)), (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)),
  195. (rng.uniform(10.f, 20.f)));
  196. Mat warpedImage;
  197. warpAffine(testImg, warpedImage, affineGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
  198. Mat mapAffine = (Mat_<float>(2, 3) << 1, 0, 0, 0, 1, 0);
  199. findTransformECC(warpedImage, testImg, mapAffine, 2, criteria);
  200. if (!checkMap(mapAffine, affineGround))
  201. return false;
  202. }
  203. return true;
  204. }
  205. class CV_ECC_Test_Homography : public CV_ECC_BaseTest {
  206. public:
  207. CV_ECC_Test_Homography();
  208. protected:
  209. bool test(const Mat testImg);
  210. };
  211. CV_ECC_Test_Homography::CV_ECC_Test_Homography() {}
  212. bool CV_ECC_Test_Homography::test(const Mat testImg) {
  213. cv::RNG rng = ts->get_rng();
  214. int progress = 0;
  215. for (int k = 0; k < ntests; k++) {
  216. ts->update_context(this, k, true);
  217. progress = update_progress(progress, k, ntests, 0);
  218. Mat homoGround =
  219. (Mat_<float>(3, 3) << (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(-0.03f, 0.03f)),
  220. (rng.uniform(10.f, 20.f)), (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)),
  221. (rng.uniform(10.f, 20.f)), (rng.uniform(0.0001f, 0.0003f)), (rng.uniform(0.0001f, 0.0003f)), 1.f);
  222. Mat warpedImage;
  223. warpPerspective(testImg, warpedImage, homoGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
  224. Mat mapHomography = Mat::eye(3, 3, CV_32F);
  225. findTransformECC(warpedImage, testImg, mapHomography, 3, criteria);
  226. if (!checkMap(mapHomography, homoGround))
  227. return false;
  228. }
  229. return true;
  230. }
  231. class CV_ECC_Test_Mask : public CV_ECC_BaseTest {
  232. public:
  233. CV_ECC_Test_Mask();
  234. protected:
  235. bool test(const Mat);
  236. };
  237. CV_ECC_Test_Mask::CV_ECC_Test_Mask() {}
  238. bool CV_ECC_Test_Mask::test(const Mat testImg) {
  239. cv::RNG rng = ts->get_rng();
  240. int progress = 0;
  241. for (int k = 0; k < ntests; k++) {
  242. ts->update_context(this, k, true);
  243. progress = update_progress(progress, k, ntests, 0);
  244. Mat translationGround = (Mat_<float>(2, 3) << 1, 0, (rng.uniform(10.f, 20.f)), 0, 1, (rng.uniform(10.f, 20.f)));
  245. Mat warpedImage;
  246. warpAffine(testImg, warpedImage, translationGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
  247. Mat mapTranslation = (Mat_<float>(2, 3) << 1, 0, 0, 0, 1, 0);
  248. Mat_<unsigned char> mask = Mat_<unsigned char>::ones(testImg.rows, testImg.cols);
  249. Rect region(testImg.cols * 2 / 3, testImg.rows * 2 / 3, testImg.cols / 3, testImg.rows / 3);
  250. rectangle(testImg, region, Scalar::all(0), FILLED);
  251. rectangle(mask, region, Scalar(0), FILLED);
  252. findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria, mask);
  253. if (!checkMap(mapTranslation, translationGround))
  254. return false;
  255. // Test with non-default gaussian blur.
  256. findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria, mask, 1);
  257. if (!checkMap(mapTranslation, translationGround))
  258. return false;
  259. // Test with template mask.
  260. Mat_<unsigned char> warpedMask = Mat_<unsigned char>::ones(warpedImage.rows, warpedImage.cols);
  261. for (int i=warpedImage.rows*1/3; i<warpedImage.rows*2/3; i++) {
  262. for (int j=warpedImage.cols*1/3; j<warpedImage.cols*2/3; j++) {
  263. warpedMask(i, j) = 0;
  264. }
  265. }
  266. findTransformECCWithMask(warpedImage, testImg, warpedMask, mask, mapTranslation, 0,
  267. TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, ECC_iterations, ECC_epsilon));
  268. if (!checkMap(mapTranslation, translationGround))
  269. return false;
  270. // Test with non-default gaussian blur.
  271. findTransformECCWithMask(warpedImage, testImg, warpedMask, mask, mapTranslation, 0, criteria, 1);
  272. if (!checkMap(mapTranslation, translationGround))
  273. return false;
  274. }
  275. return true;
  276. }
  277. void testECCProperties(Mat x, float eps) {
  278. // The channels are independent
  279. Mat y = x.t();
  280. Mat Z = Mat::zeros(x.size(), y.type());
  281. Mat O = Mat::ones(x.size(), y.type());
  282. EXPECT_NEAR(computeECC(x, y), 0.0, eps);
  283. if (x.type() != CV_8U && x.type() != CV_8U) {
  284. EXPECT_NEAR(computeECC(x + y, x - y), 0.0, eps);
  285. }
  286. EXPECT_NEAR(computeECC(x, x), 1.0, eps);
  287. Mat R, G, B, X, Y;
  288. cv::merge(std::vector<cv::Mat>({O, Z, Z}), R);
  289. cv::merge(std::vector<cv::Mat>({Z, O, Z}), G);
  290. cv::merge(std::vector<cv::Mat>({Z, Z, O}), B);
  291. cv::merge(std::vector<cv::Mat>({x, x, x}), X);
  292. cv::merge(std::vector<cv::Mat>({y, y, y}), Y);
  293. // 1. The channels are orthogonal and independent
  294. EXPECT_NEAR(computeECC(X.mul(R), X.mul(G)), 0, eps);
  295. EXPECT_NEAR(computeECC(X.mul(R), X.mul(B)), 0, eps);
  296. EXPECT_NEAR(computeECC(X.mul(B), X.mul(G)), 0, eps);
  297. EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(B), X.mul(B) + Y.mul(R)), 0, eps);
  298. 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);
  299. // 2. Each channel contribute equally
  300. EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(G + B), X), 1.0 / 3, eps);
  301. EXPECT_NEAR(computeECC(X.mul(G) + Y.mul(R + B), X), 1.0 / 3, eps);
  302. EXPECT_NEAR(computeECC(X.mul(B) + Y.mul(G + R), X), 1.0 / 3, eps);
  303. // 3. The coefficient is invariant with respect to the offset of channels
  304. EXPECT_NEAR(computeECC(X - R + 2 * G + B, X), 1.0, eps);
  305. if (x.type() != CV_8U && x.type() != CV_8U) {
  306. EXPECT_NEAR(computeECC(X + R - 2 * G + B, Y), 0.0, eps);
  307. }
  308. // The channels are independent. Check orthogonal combinations
  309. // full squares norm = sum of squared norms
  310. EXPECT_NEAR(computeECC(X, Y + X), 1.0 / sqrt(2.0), eps);
  311. EXPECT_NEAR(computeECC(X, 2 * Y + X), 1.0 / sqrt(5.0), eps);
  312. }
  313. TEST(Video_ECC_Test_Compute, properies) {
  314. Mat xline(1, 100, CV_32F), x;
  315. for (int i = 0; i < xline.cols; ++i) xline.at<float>(0, i) = (float)i;
  316. repeat(xline, xline.cols, 1, x);
  317. Mat x_f64, x_u8, x_u16;
  318. x.convertTo(x_f64, CV_64F);
  319. x.convertTo(x_u8, CV_8U);
  320. x.convertTo(x_u16, CV_16U);
  321. testECCProperties(x, 1e-5f);
  322. testECCProperties(x_f64, 1e-5f);
  323. testECCProperties(x_u8, 1);
  324. testECCProperties(x_u16, 1);
  325. }
  326. TEST(Video_ECC_Test_Compute, accuracy) {
  327. Mat testImg = (Mat_<float>(3, 3) << 1, 0, 0, 1, 0, 0, 1, 0, 0);
  328. Mat warpedImage = (Mat_<float>(3, 3) << 0, 1, 0, 0, 1, 0, 0, 1, 0);
  329. Mat_<unsigned char> mask = Mat_<unsigned char>::ones(testImg.rows, testImg.cols);
  330. double ecc = computeECC(warpedImage, testImg, mask);
  331. EXPECT_NEAR(ecc, -0.5f, 1e-5f);
  332. }
  333. TEST(Video_ECC_Test_Compute, bug_14657) {
  334. /*
  335. * Simple test case - a 2 x 2 matrix with 10, 10, 10, 6. When the mean (36 / 4 = 9) is subtracted,
  336. * it results in 1, 1, 1, 0 for the unsigned int case - compare to 1, 1, 1, -3 in the signed case.
  337. * For this reason, when the same matrix was provided as the input and the template, we didn't get 1 as expected.
  338. */
  339. Mat img = (Mat_<uint8_t>(2, 2) << 10, 10, 10, 6);
  340. EXPECT_NEAR(computeECC(img, img), 1.0f, 1e-5f);
  341. }
  342. TEST(Video_ECC_Translation, accuracy) {
  343. CV_ECC_Test_Translation test;
  344. test.safe_run();
  345. }
  346. TEST(Video_ECC_Euclidean, accuracy) {
  347. CV_ECC_Test_Euclidean test;
  348. test.safe_run();
  349. }
  350. TEST(Video_ECC_Affine, accuracy) {
  351. CV_ECC_Test_Affine test;
  352. test.safe_run();
  353. }
  354. TEST(Video_ECC_Homography, accuracy) {
  355. CV_ECC_Test_Homography test;
  356. test.safe_run();
  357. }
  358. TEST(Video_ECC_Mask, accuracy) {
  359. CV_ECC_Test_Mask test;
  360. test.safe_run();
  361. }
  362. } // namespace
  363. } // namespace opencv_test