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 // Intel License Agreement 11 // For Open Source Computer Vision Library 12 // 13 // Copyright (C) 2000, Intel Corporation, all rights reserved. 14 // Third party copyrights are property of their respective owners. 15 // 16 // Redistribution and use in source and binary forms, with or without modification, 17 // are permitted provided that the following conditions are met: 18 // 19 // * Redistribution's of source code must retain the above copyright notice, 20 // this list of conditions and the following disclaimer. 21 // 22 // * Redistribution's in binary form must reproduce the above copyright notice, 23 // this list of conditions and the following disclaimer in the documentation 24 // and/or other materials provided with the distribution. 25 // 26 // * The name of Intel Corporation may not be used to endorse or promote products 27 // derived from this software without specific prior written permission. 28 // 29 // This software is provided by the copyright holders and contributors "as is" and 30 // any express or implied warranties, including, but not limited to, the implied 31 // warranties of merchantability and fitness for a particular purpose are disclaimed. 32 // In no event shall the Intel Corporation or contributors be liable for any direct, 33 // indirect, incidental, special, exemplary, or consequential damages 34 // (including, but not limited to, procurement of substitute goods or services; 35 // loss of use, data, or profits; or business interruption) however caused 36 // and on any theory of liability, whether in contract, strict liability, 37 // or tort (including negligence or otherwise) arising in any way out of 38 // the use of this software, even if advised of the possibility of such damage. 39 // 40 //M*/ 41 42 #include "test_precomp.hpp" 43 #include "test_chessboardgenerator.hpp" 44 45 #include <functional> 46 #include <limits> 47 #include <numeric> 48 49 using namespace std; 50 using namespace cv; 51 52 #define _L2_ERR 53 54 void show_points( const Mat& gray, const Mat& u, const vector<Point2f>& v, Size pattern_size, bool was_found ) 55 { 56 Mat rgb( gray.size(), CV_8U); 57 merge(vector<Mat>(3, gray), rgb); 58 59 for(size_t i = 0; i < v.size(); i++ ) 60 circle( rgb, v[i], 3, Scalar(255, 0, 0), FILLED); 61 62 if( !u.empty() ) 63 { 64 const Point2f* u_data = u.ptr<Point2f>(); 65 size_t count = u.cols * u.rows; 66 for(size_t i = 0; i < count; i++ ) 67 circle( rgb, u_data[i], 3, Scalar(0, 255, 0), FILLED); 68 } 69 if (!v.empty()) 70 { 71 Mat corners((int)v.size(), 1, CV_32FC2, (void*)&v[0]); 72 drawChessboardCorners( rgb, pattern_size, corners, was_found ); 73 } 74 //namedWindow( "test", 0 ); imshow( "test", rgb ); waitKey(0); 75 } 76 77 78 enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID }; 79 80 class CV_ChessboardDetectorTest : public cvtest::BaseTest 81 { 82 public: 83 CV_ChessboardDetectorTest( Pattern pattern, int algorithmFlags = 0 ); 84 protected: 85 void run(int); 86 void run_batch(const string& filename); 87 bool checkByGenerator(); 88 89 Pattern pattern; 90 int algorithmFlags; 91 }; 92 93 CV_ChessboardDetectorTest::CV_ChessboardDetectorTest( Pattern _pattern, int _algorithmFlags ) 94 { 95 pattern = _pattern; 96 algorithmFlags = _algorithmFlags; 97 } 98 99 double calcError(const vector<Point2f>& v, const Mat& u) 100 { 101 int count_exp = u.cols * u.rows; 102 const Point2f* u_data = u.ptr<Point2f>(); 103 104 double err = numeric_limits<double>::max(); 105 for( int k = 0; k < 2; ++k ) 106 { 107 double err1 = 0; 108 for( int j = 0; j < count_exp; ++j ) 109 { 110 int j1 = k == 0 ? j : count_exp - j - 1; 111 double dx = fabs( v[j].x - u_data[j1].x ); 112 double dy = fabs( v[j].y - u_data[j1].y ); 113 114 #if defined(_L2_ERR) 115 err1 += dx*dx + dy*dy; 116 #else 117 dx = MAX( dx, dy ); 118 if( dx > err1 ) 119 err1 = dx; 120 #endif //_L2_ERR 121 //printf("dx = %f\n", dx); 122 } 123 //printf("\n"); 124 err = min(err, err1); 125 } 126 127 #if defined(_L2_ERR) 128 err = sqrt(err/count_exp); 129 #endif //_L2_ERR 130 131 return err; 132 } 133 134 const double rough_success_error_level = 2.5; 135 const double precise_success_error_level = 2; 136 137 138 /* ///////////////////// chess_corner_test ///////////////////////// */ 139 void CV_ChessboardDetectorTest::run( int /*start_from */) 140 { 141 ts->set_failed_test_info( cvtest::TS::OK ); 142 143 /*if (!checkByGenerator()) 144 return;*/ 145 switch( pattern ) 146 { 147 case CHESSBOARD: 148 checkByGenerator(); 149 if (ts->get_err_code() != cvtest::TS::OK) 150 { 151 break; 152 } 153 154 run_batch("negative_list.dat"); 155 if (ts->get_err_code() != cvtest::TS::OK) 156 { 157 break; 158 } 159 160 run_batch("chessboard_list.dat"); 161 if (ts->get_err_code() != cvtest::TS::OK) 162 { 163 break; 164 } 165 166 run_batch("chessboard_list_subpixel.dat"); 167 break; 168 case CIRCLES_GRID: 169 run_batch("circles_list.dat"); 170 break; 171 case ASYMMETRIC_CIRCLES_GRID: 172 run_batch("acircles_list.dat"); 173 break; 174 } 175 } 176 177 void CV_ChessboardDetectorTest::run_batch( const string& filename ) 178 { 179 ts->printf(cvtest::TS::LOG, "\nRunning batch %s\n", filename.c_str()); 180 //#define WRITE_POINTS 1 181 #ifndef WRITE_POINTS 182 double max_rough_error = 0, max_precise_error = 0; 183 #endif 184 string folder; 185 switch( pattern ) 186 { 187 case CHESSBOARD: 188 folder = string(ts->get_data_path()) + "cv/cameracalibration/"; 189 break; 190 case CIRCLES_GRID: 191 folder = string(ts->get_data_path()) + "cv/cameracalibration/circles/"; 192 break; 193 case ASYMMETRIC_CIRCLES_GRID: 194 folder = string(ts->get_data_path()) + "cv/cameracalibration/asymmetric_circles/"; 195 break; 196 } 197 198 FileStorage fs( folder + filename, FileStorage::READ ); 199 FileNode board_list = fs["boards"]; 200 201 if( !fs.isOpened() || board_list.empty() || !board_list.isSeq() || board_list.size() % 2 != 0 ) 202 { 203 ts->printf( cvtest::TS::LOG, "%s can not be readed or is not valid\n", (folder + filename).c_str() ); 204 ts->printf( cvtest::TS::LOG, "fs.isOpened=%d, board_list.empty=%d, board_list.isSeq=%d,board_list.size()%2=%d\n", 205 fs.isOpened(), (int)board_list.empty(), board_list.isSeq(), board_list.size()%2); 206 ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA ); 207 return; 208 } 209 210 int progress = 0; 211 int max_idx = (int)board_list.size()/2; 212 double sum_error = 0.0; 213 int count = 0; 214 215 for(int idx = 0; idx < max_idx; ++idx ) 216 { 217 ts->update_context( this, idx, true ); 218 219 /* read the image */ 220 String img_file = board_list[idx * 2]; 221 Mat gray = imread( folder + img_file, 0); 222 223 if( gray.empty() ) 224 { 225 ts->printf( cvtest::TS::LOG, "one of chessboard images can't be read: %s\n", img_file.c_str() ); 226 ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA ); 227 return; 228 } 229 230 String _filename = folder + (String)board_list[idx * 2 + 1]; 231 bool doesContatinChessboard; 232 Mat expected; 233 { 234 FileStorage fs1(_filename, FileStorage::READ); 235 fs1["corners"] >> expected; 236 fs1["isFound"] >> doesContatinChessboard; 237 fs1.release(); 238 } 239 size_t count_exp = static_cast<size_t>(expected.cols * expected.rows); 240 Size pattern_size = expected.size(); 241 242 vector<Point2f> v; 243 bool result = false; 244 switch( pattern ) 245 { 246 case CHESSBOARD: 247 result = findChessboardCorners(gray, pattern_size, v, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE); 248 break; 249 case CIRCLES_GRID: 250 result = findCirclesGrid(gray, pattern_size, v); 251 break; 252 case ASYMMETRIC_CIRCLES_GRID: 253 result = findCirclesGrid(gray, pattern_size, v, CALIB_CB_ASYMMETRIC_GRID | algorithmFlags); 254 break; 255 } 256 show_points( gray, Mat(), v, pattern_size, result ); 257 258 if( result ^ doesContatinChessboard || v.size() != count_exp ) 259 { 260 ts->printf( cvtest::TS::LOG, "chessboard is detected incorrectly in %s\n", img_file.c_str() ); 261 ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); 262 return; 263 } 264 265 if( result ) 266 { 267 268 #ifndef WRITE_POINTS 269 double err = calcError(v, expected); 270 #if 0 271 if( err > rough_success_error_level ) 272 { 273 ts.printf( cvtest::TS::LOG, "bad accuracy of corner guesses\n" ); 274 ts.set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); 275 continue; 276 } 277 #endif 278 max_rough_error = MAX( max_rough_error, err ); 279 #endif 280 if( pattern == CHESSBOARD ) 281 cornerSubPix( gray, v, Size(5, 5), Size(-1,-1), TermCriteria(TermCriteria::EPS|TermCriteria::MAX_ITER, 30, 0.1)); 282 //find4QuadCornerSubpix(gray, v, Size(5, 5)); 283 show_points( gray, expected, v, pattern_size, result ); 284 #ifndef WRITE_POINTS 285 // printf("called find4QuadCornerSubpix\n"); 286 err = calcError(v, expected); 287 sum_error += err; 288 count++; 289 #if 1 290 if( err > precise_success_error_level ) 291 { 292 ts->printf( cvtest::TS::LOG, "Image %s: bad accuracy of adjusted corners %f\n", img_file.c_str(), err ); 293 ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); 294 return; 295 } 296 #endif 297 ts->printf(cvtest::TS::LOG, "Error on %s is %f\n", img_file.c_str(), err); 298 max_precise_error = MAX( max_precise_error, err ); 299 #endif 300 } 301 302 #ifdef WRITE_POINTS 303 Mat mat_v(pattern_size, CV_32FC2, (void*)&v[0]); 304 FileStorage fs(_filename, FileStorage::WRITE); 305 fs << "isFound" << result; 306 fs << "corners" << mat_v; 307 fs.release(); 308 #endif 309 progress = update_progress( progress, idx, max_idx, 0 ); 310 } 311 312 if (count != 0) 313 sum_error /= count; 314 ts->printf(cvtest::TS::LOG, "Average error is %f (%d patterns have been found)\n", sum_error, count); 315 } 316 317 double calcErrorMinError(const Size& cornSz, const vector<Point2f>& corners_found, const vector<Point2f>& corners_generated) 318 { 319 Mat m1(cornSz, CV_32FC2, (Point2f*)&corners_generated[0]); 320 Mat m2; flip(m1, m2, 0); 321 322 Mat m3; flip(m1, m3, 1); m3 = m3.t(); flip(m3, m3, 1); 323 324 Mat m4 = m1.t(); flip(m4, m4, 1); 325 326 double min1 = min(calcError(corners_found, m1), calcError(corners_found, m2)); 327 double min2 = min(calcError(corners_found, m3), calcError(corners_found, m4)); 328 return min(min1, min2); 329 } 330 331 bool validateData(const ChessBoardGenerator& cbg, const Size& imgSz, 332 const vector<Point2f>& corners_generated) 333 { 334 Size cornersSize = cbg.cornersSize(); 335 Mat_<Point2f> mat(cornersSize.height, cornersSize.width, (Point2f*)&corners_generated[0]); 336 337 double minNeibDist = std::numeric_limits<double>::max(); 338 double tmp = 0; 339 for(int i = 1; i < mat.rows - 2; ++i) 340 for(int j = 1; j < mat.cols - 2; ++j) 341 { 342 const Point2f& cur = mat(i, j); 343 344 tmp = norm( cur - mat(i + 1, j + 1) ); 345 if (tmp < minNeibDist) 346 tmp = minNeibDist; 347 348 tmp = norm( cur - mat(i - 1, j + 1 ) ); 349 if (tmp < minNeibDist) 350 tmp = minNeibDist; 351 352 tmp = norm( cur - mat(i + 1, j - 1) ); 353 if (tmp < minNeibDist) 354 tmp = minNeibDist; 355 356 tmp = norm( cur - mat(i - 1, j - 1) ); 357 if (tmp < minNeibDist) 358 tmp = minNeibDist; 359 } 360 361 const double threshold = 0.25; 362 double cbsize = (max(cornersSize.width, cornersSize.height) + 1) * minNeibDist; 363 int imgsize = min(imgSz.height, imgSz.width); 364 return imgsize * threshold < cbsize; 365 } 366 367 bool CV_ChessboardDetectorTest::checkByGenerator() 368 { 369 bool res = true; 370 371 // for some reason, this test sometimes fails on Ubuntu 372 #if (defined __APPLE__ && defined __x86_64__) || defined _MSC_VER 373 //theRNG() = 0x58e6e895b9913160; 374 //cv::DefaultRngAuto dra; 375 //theRNG() = *ts->get_rng(); 376 377 Mat bg(Size(800, 600), CV_8UC3, Scalar::all(255)); 378 randu(bg, Scalar::all(0), Scalar::all(255)); 379 GaussianBlur(bg, bg, Size(7,7), 3.0); 380 381 Mat_<float> camMat(3, 3); 382 camMat << 300.f, 0.f, bg.cols/2.f, 0, 300.f, bg.rows/2.f, 0.f, 0.f, 1.f; 383 384 Mat_<float> distCoeffs(1, 5); 385 distCoeffs << 1.2f, 0.2f, 0.f, 0.f, 0.f; 386 387 const Size sizes[] = { Size(6, 6), Size(8, 6), Size(11, 12), Size(5, 4) }; 388 const size_t sizes_num = sizeof(sizes)/sizeof(sizes[0]); 389 const int test_num = 16; 390 int progress = 0; 391 for(int i = 0; i < test_num; ++i) 392 { 393 progress = update_progress( progress, i, test_num, 0 ); 394 ChessBoardGenerator cbg(sizes[i % sizes_num]); 395 396 vector<Point2f> corners_generated; 397 398 Mat cb = cbg(bg, camMat, distCoeffs, corners_generated); 399 400 if(!validateData(cbg, cb.size(), corners_generated)) 401 { 402 ts->printf( cvtest::TS::LOG, "Chess board skipped - too small" ); 403 continue; 404 } 405 406 /*cb = cb * 0.8 + Scalar::all(30); 407 GaussianBlur(cb, cb, Size(3, 3), 0.8); */ 408 //cv::addWeighted(cb, 0.8, bg, 0.2, 20, cb); 409 //cv::namedWindow("CB"); cv::imshow("CB", cb); cv::waitKey(); 410 411 vector<Point2f> corners_found; 412 int flags = i % 8; // need to check branches for all flags 413 bool found = findChessboardCorners(cb, cbg.cornersSize(), corners_found, flags); 414 if (!found) 415 { 416 ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" ); 417 ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); 418 res = false; 419 return res; 420 } 421 422 double err = calcErrorMinError(cbg.cornersSize(), corners_found, corners_generated); 423 if( err > rough_success_error_level ) 424 { 425 ts->printf( cvtest::TS::LOG, "bad accuracy of corner guesses" ); 426 ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); 427 res = false; 428 return res; 429 } 430 } 431 432 /* ***** negative ***** */ 433 { 434 vector<Point2f> corners_found; 435 bool found = findChessboardCorners(bg, Size(8, 7), corners_found); 436 if (found) 437 res = false; 438 439 ChessBoardGenerator cbg(Size(8, 7)); 440 441 vector<Point2f> cg; 442 Mat cb = cbg(bg, camMat, distCoeffs, cg); 443 444 found = findChessboardCorners(cb, Size(3, 4), corners_found); 445 if (found) 446 res = false; 447 448 Point2f c = std::accumulate(cg.begin(), cg.end(), Point2f(), plus<Point2f>()) * (1.f/cg.size()); 449 450 Mat_<double> aff(2, 3); 451 aff << 1.0, 0.0, -(double)c.x, 0.0, 1.0, 0.0; 452 Mat sh; 453 warpAffine(cb, sh, aff, cb.size()); 454 455 found = findChessboardCorners(sh, cbg.cornersSize(), corners_found); 456 if (found) 457 res = false; 458 459 vector< vector<Point> > cnts(1); 460 vector<Point>& cnt = cnts[0]; 461 cnt.push_back(cg[ 0]); cnt.push_back(cg[0+2]); 462 cnt.push_back(cg[7+0]); cnt.push_back(cg[7+2]); 463 cv::drawContours(cb, cnts, -1, Scalar::all(128), FILLED); 464 465 found = findChessboardCorners(cb, cbg.cornersSize(), corners_found); 466 if (found) 467 res = false; 468 469 cv::drawChessboardCorners(cb, cbg.cornersSize(), Mat(corners_found), found); 470 } 471 #endif 472 473 return res; 474 } 475 476 TEST(Calib3d_ChessboardDetector, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD ); test.safe_run(); } 477 TEST(Calib3d_CirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( CIRCLES_GRID ); test.safe_run(); } 478 TEST(Calib3d_AsymmetricCirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID ); test.safe_run(); } 479 TEST(Calib3d_AsymmetricCirclesPatternDetectorWithClustering, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID, CALIB_CB_CLUSTERING ); test.safe_run(); } 480 481 /* End of file. */ 482