Home | History | Annotate | Download | only in test
      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