Path: blob/master/modules/calib3d/test/test_chesscorners.cpp
16337 views
/*M///////////////////////////////////////////////////////////////////////////////////////1//2// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.3//4// By downloading, copying, installing or using the software you agree to this license.5// If you do not agree to this license, do not download, install,6// copy or use the software.7//8//9// Intel License Agreement10// For Open Source Computer Vision Library11//12// Copyright (C) 2000, Intel Corporation, all rights reserved.13// Third party copyrights are property of their respective owners.14//15// Redistribution and use in source and binary forms, with or without modification,16// are permitted provided that the following conditions are met:17//18// * Redistribution's of source code must retain the above copyright notice,19// this list of conditions and the following disclaimer.20//21// * Redistribution's in binary form must reproduce the above copyright notice,22// this list of conditions and the following disclaimer in the documentation23// and/or other materials provided with the distribution.24//25// * The name of Intel Corporation may not be used to endorse or promote products26// derived from this software without specific prior written permission.27//28// This software is provided by the copyright holders and contributors "as is" and29// any express or implied warranties, including, but not limited to, the implied30// warranties of merchantability and fitness for a particular purpose are disclaimed.31// In no event shall the Intel Corporation or contributors be liable for any direct,32// indirect, incidental, special, exemplary, or consequential damages33// (including, but not limited to, procurement of substitute goods or services;34// loss of use, data, or profits; or business interruption) however caused35// and on any theory of liability, whether in contract, strict liability,36// or tort (including negligence or otherwise) arising in any way out of37// the use of this software, even if advised of the possibility of such damage.38//39//M*/4041#include "test_precomp.hpp"42#include "test_chessboardgenerator.hpp"4344#include <functional>4546namespace opencv_test { namespace {4748#define _L2_ERR4950//#define DEBUG_CHESSBOARD5152#ifdef DEBUG_CHESSBOARD53void show_points( const Mat& gray, const Mat& expected, const vector<Point2f>& actual, bool was_found )54{55Mat rgb( gray.size(), CV_8U);56merge(vector<Mat>(3, gray), rgb);5758for(size_t i = 0; i < actual.size(); i++ )59circle( rgb, actual[i], 5, Scalar(0, 0, 200), 1, LINE_AA);6061if( !expected.empty() )62{63const Point2f* u_data = expected.ptr<Point2f>();64size_t count = expected.cols * expected.rows;65for(size_t i = 0; i < count; i++ )66circle(rgb, u_data[i], 4, Scalar(0, 240, 0), 1, LINE_AA);67}68putText(rgb, was_found ? "FOUND !!!" : "NOT FOUND", Point(5, 20), FONT_HERSHEY_PLAIN, 1, Scalar(0, 240, 0));69imshow( "test", rgb ); while ((uchar)waitKey(0) != 'q') {};70}71#else72#define show_points(...)73#endif7475enum Pattern { CHESSBOARD,CHESSBOARD_SB,CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID};7677class CV_ChessboardDetectorTest : public cvtest::BaseTest78{79public:80CV_ChessboardDetectorTest( Pattern pattern, int algorithmFlags = 0 );81protected:82void run(int);83void run_batch(const string& filename);84bool checkByGenerator();85bool checkByGeneratorHighAccuracy();8687// wraps calls based on the given pattern88bool findChessboardCornersWrapper(InputArray image, Size patternSize, OutputArray corners,int flags);8990Pattern pattern;91int algorithmFlags;92};9394CV_ChessboardDetectorTest::CV_ChessboardDetectorTest( Pattern _pattern, int _algorithmFlags )95{96pattern = _pattern;97algorithmFlags = _algorithmFlags;98}99100double calcError(const vector<Point2f>& v, const Mat& u)101{102int count_exp = u.cols * u.rows;103const Point2f* u_data = u.ptr<Point2f>();104105double err = std::numeric_limits<double>::max();106for( int k = 0; k < 2; ++k )107{108double err1 = 0;109for( int j = 0; j < count_exp; ++j )110{111int j1 = k == 0 ? j : count_exp - j - 1;112double dx = fabs( v[j].x - u_data[j1].x );113double dy = fabs( v[j].y - u_data[j1].y );114115#if defined(_L2_ERR)116err1 += dx*dx + dy*dy;117#else118dx = MAX( dx, dy );119if( dx > err1 )120err1 = dx;121#endif //_L2_ERR122//printf("dx = %f\n", dx);123}124//printf("\n");125err = min(err, err1);126}127128#if defined(_L2_ERR)129err = sqrt(err/count_exp);130#endif //_L2_ERR131132return err;133}134135const double rough_success_error_level = 2.5;136const double precise_success_error_level = 2;137138139/* ///////////////////// chess_corner_test ///////////////////////// */140void CV_ChessboardDetectorTest::run( int /*start_from */)141{142ts->set_failed_test_info( cvtest::TS::OK );143144/*if (!checkByGenerator())145return;*/146switch( pattern )147{148case CHESSBOARD_SB:149checkByGeneratorHighAccuracy(); // not supported by CHESSBOARD150/* fallthrough */151case CHESSBOARD:152checkByGenerator();153if (ts->get_err_code() != cvtest::TS::OK)154{155break;156}157158run_batch("negative_list.dat");159if (ts->get_err_code() != cvtest::TS::OK)160{161break;162}163164run_batch("chessboard_list.dat");165if (ts->get_err_code() != cvtest::TS::OK)166{167break;168}169170run_batch("chessboard_list_subpixel.dat");171break;172case CIRCLES_GRID:173run_batch("circles_list.dat");174break;175case ASYMMETRIC_CIRCLES_GRID:176run_batch("acircles_list.dat");177break;178}179}180181void CV_ChessboardDetectorTest::run_batch( const string& filename )182{183ts->printf(cvtest::TS::LOG, "\nRunning batch %s\n", filename.c_str());184//#define WRITE_POINTS 1185#ifndef WRITE_POINTS186double max_rough_error = 0, max_precise_error = 0;187#endif188string folder;189switch( pattern )190{191case CHESSBOARD:192case CHESSBOARD_SB:193folder = string(ts->get_data_path()) + "cv/cameracalibration/";194break;195case CIRCLES_GRID:196folder = string(ts->get_data_path()) + "cv/cameracalibration/circles/";197break;198case ASYMMETRIC_CIRCLES_GRID:199folder = string(ts->get_data_path()) + "cv/cameracalibration/asymmetric_circles/";200break;201}202203FileStorage fs( folder + filename, FileStorage::READ );204FileNode board_list = fs["boards"];205206if( !fs.isOpened() || board_list.empty() || !board_list.isSeq() || board_list.size() % 2 != 0 )207{208ts->printf( cvtest::TS::LOG, "%s can not be read or is not valid\n", (folder + filename).c_str() );209ts->printf( cvtest::TS::LOG, "fs.isOpened=%d, board_list.empty=%d, board_list.isSeq=%d,board_list.size()%2=%d\n",210fs.isOpened(), (int)board_list.empty(), board_list.isSeq(), board_list.size()%2);211ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA );212return;213}214215int progress = 0;216int max_idx = (int)board_list.size()/2;217double sum_error = 0.0;218int count = 0;219220for(int idx = 0; idx < max_idx; ++idx )221{222ts->update_context( this, idx, true );223224/* read the image */225String img_file = board_list[idx * 2];226Mat gray = imread( folder + img_file, 0);227228if( gray.empty() )229{230ts->printf( cvtest::TS::LOG, "one of chessboard images can't be read: %s\n", img_file.c_str() );231ts->set_failed_test_info( cvtest::TS::FAIL_MISSING_TEST_DATA );232return;233}234235String _filename = folder + (String)board_list[idx * 2 + 1];236bool doesContatinChessboard;237Mat expected;238{239FileStorage fs1(_filename, FileStorage::READ);240fs1["corners"] >> expected;241fs1["isFound"] >> doesContatinChessboard;242fs1.release();243}244size_t count_exp = static_cast<size_t>(expected.cols * expected.rows);245Size pattern_size = expected.size();246247vector<Point2f> v;248int flags = 0;249switch( pattern )250{251case CHESSBOARD:252flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;253break;254case CIRCLES_GRID:255case CHESSBOARD_SB:256case ASYMMETRIC_CIRCLES_GRID:257default:258flags = 0;259}260bool result = findChessboardCornersWrapper(gray, pattern_size,v,flags);261if(result ^ doesContatinChessboard || (doesContatinChessboard && v.size() != count_exp))262{263ts->printf( cvtest::TS::LOG, "chessboard is detected incorrectly in %s\n", img_file.c_str() );264ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT );265show_points( gray, expected, v, result );266return;267}268269if( result )270{271272#ifndef WRITE_POINTS273double err = calcError(v, expected);274max_rough_error = MAX( max_rough_error, err );275#endif276if( pattern == CHESSBOARD )277cornerSubPix( gray, v, Size(5, 5), Size(-1,-1), TermCriteria(TermCriteria::EPS|TermCriteria::MAX_ITER, 30, 0.1));278//find4QuadCornerSubpix(gray, v, Size(5, 5));279show_points( gray, expected, v, result );280#ifndef WRITE_POINTS281// printf("called find4QuadCornerSubpix\n");282err = calcError(v, expected);283sum_error += err;284count++;285if( err > precise_success_error_level )286{287ts->printf( cvtest::TS::LOG, "Image %s: bad accuracy of adjusted corners %f\n", img_file.c_str(), err );288ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );289return;290}291ts->printf(cvtest::TS::LOG, "Error on %s is %f\n", img_file.c_str(), err);292max_precise_error = MAX( max_precise_error, err );293#endif294}295else296{297show_points( gray, Mat(), v, result );298}299300#ifdef WRITE_POINTS301Mat mat_v(pattern_size, CV_32FC2, (void*)&v[0]);302FileStorage fs(_filename, FileStorage::WRITE);303fs << "isFound" << result;304fs << "corners" << mat_v;305fs.release();306#endif307progress = update_progress( progress, idx, max_idx, 0 );308}309310if (count != 0)311sum_error /= count;312ts->printf(cvtest::TS::LOG, "Average error is %f (%d patterns have been found)\n", sum_error, count);313}314315double calcErrorMinError(const Size& cornSz, const vector<Point2f>& corners_found, const vector<Point2f>& corners_generated)316{317Mat m1(cornSz, CV_32FC2, (Point2f*)&corners_generated[0]);318Mat m2; flip(m1, m2, 0);319320Mat m3; flip(m1, m3, 1); m3 = m3.t(); flip(m3, m3, 1);321322Mat m4 = m1.t(); flip(m4, m4, 1);323324double min1 = min(calcError(corners_found, m1), calcError(corners_found, m2));325double min2 = min(calcError(corners_found, m3), calcError(corners_found, m4));326return min(min1, min2);327}328329bool validateData(const ChessBoardGenerator& cbg, const Size& imgSz,330const vector<Point2f>& corners_generated)331{332Size cornersSize = cbg.cornersSize();333Mat_<Point2f> mat(cornersSize.height, cornersSize.width, (Point2f*)&corners_generated[0]);334335double minNeibDist = std::numeric_limits<double>::max();336double tmp = 0;337for(int i = 1; i < mat.rows - 2; ++i)338for(int j = 1; j < mat.cols - 2; ++j)339{340const Point2f& cur = mat(i, j);341342tmp = cv::norm(cur - mat(i + 1, j + 1)); // TODO cvtest343if (tmp < minNeibDist)344minNeibDist = tmp;345346tmp = cv::norm(cur - mat(i - 1, j + 1)); // TODO cvtest347if (tmp < minNeibDist)348minNeibDist = tmp;349350tmp = cv::norm(cur - mat(i + 1, j - 1)); // TODO cvtest351if (tmp < minNeibDist)352minNeibDist = tmp;353354tmp = cv::norm(cur - mat(i - 1, j - 1)); // TODO cvtest355if (tmp < minNeibDist)356minNeibDist = tmp;357}358359const double threshold = 0.25;360double cbsize = (max(cornersSize.width, cornersSize.height) + 1) * minNeibDist;361int imgsize = min(imgSz.height, imgSz.width);362return imgsize * threshold < cbsize;363}364365bool CV_ChessboardDetectorTest::findChessboardCornersWrapper(InputArray image, Size patternSize, OutputArray corners,int flags)366{367switch(pattern)368{369case CHESSBOARD:370return findChessboardCorners(image,patternSize,corners,flags);371case CHESSBOARD_SB:372// check default settings until flags have been specified373return findChessboardCornersSB(image,patternSize,corners,0);374case ASYMMETRIC_CIRCLES_GRID:375flags |= CALIB_CB_ASYMMETRIC_GRID | algorithmFlags;376return findCirclesGrid(image, patternSize,corners,flags);377case CIRCLES_GRID:378flags |= CALIB_CB_SYMMETRIC_GRID;379return findCirclesGrid(image, patternSize,corners,flags);380default:381ts->printf( cvtest::TS::LOG, "Internal Error: unsupported chessboard pattern" );382ts->set_failed_test_info( cvtest::TS::FAIL_GENERIC);383}384return false;385}386387bool CV_ChessboardDetectorTest::checkByGenerator()388{389bool res = true;390391//theRNG() = 0x58e6e895b9913160;392//cv::DefaultRngAuto dra;393//theRNG() = *ts->get_rng();394395Mat bg(Size(800, 600), CV_8UC3, Scalar::all(255));396randu(bg, Scalar::all(0), Scalar::all(255));397GaussianBlur(bg, bg, Size(7,7), 3.0);398399Mat_<float> camMat(3, 3);400camMat << 300.f, 0.f, bg.cols/2.f, 0, 300.f, bg.rows/2.f, 0.f, 0.f, 1.f;401402Mat_<float> distCoeffs(1, 5);403distCoeffs << 1.2f, 0.2f, 0.f, 0.f, 0.f;404405const Size sizes[] = { Size(6, 6), Size(8, 6), Size(11, 12), Size(5, 4) };406const size_t sizes_num = sizeof(sizes)/sizeof(sizes[0]);407const int test_num = 16;408int progress = 0;409for(int i = 0; i < test_num; ++i)410{411SCOPED_TRACE(cv::format("test_num=%d", test_num));412413progress = update_progress( progress, i, test_num, 0 );414ChessBoardGenerator cbg(sizes[i % sizes_num]);415416vector<Point2f> corners_generated;417418Mat cb = cbg(bg, camMat, distCoeffs, corners_generated);419420if(!validateData(cbg, cb.size(), corners_generated))421{422ts->printf( cvtest::TS::LOG, "Chess board skipped - too small" );423continue;424}425426/*cb = cb * 0.8 + Scalar::all(30);427GaussianBlur(cb, cb, Size(3, 3), 0.8); */428//cv::addWeighted(cb, 0.8, bg, 0.2, 20, cb);429//cv::namedWindow("CB"); cv::imshow("CB", cb); cv::waitKey();430431vector<Point2f> corners_found;432int flags = i % 8; // need to check branches for all flags433bool found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found, flags);434if (!found)435{436ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" );437ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );438res = false;439return res;440}441442double err = calcErrorMinError(cbg.cornersSize(), corners_found, corners_generated);443EXPECT_LE(err, rough_success_error_level) << "bad accuracy of corner guesses";444#if 0445if (err >= rough_success_error_level)446{447imshow("cb", cb);448Mat cb_corners = cb.clone();449cv::drawChessboardCorners(cb_corners, cbg.cornersSize(), Mat(corners_found), found);450imshow("corners", cb_corners);451waitKey(0);452}453#endif454}455456/* ***** negative ***** */457{458vector<Point2f> corners_found;459bool found = findChessboardCornersWrapper(bg, Size(8, 7), corners_found,0);460if (found)461res = false;462463ChessBoardGenerator cbg(Size(8, 7));464465vector<Point2f> cg;466Mat cb = cbg(bg, camMat, distCoeffs, cg);467468found = findChessboardCornersWrapper(cb, Size(3, 4), corners_found,0);469if (found)470res = false;471472Point2f c = std::accumulate(cg.begin(), cg.end(), Point2f(), std::plus<Point2f>()) * (1.f/cg.size());473474Mat_<double> aff(2, 3);475aff << 1.0, 0.0, -(double)c.x, 0.0, 1.0, 0.0;476Mat sh;477warpAffine(cb, sh, aff, cb.size());478479found = findChessboardCornersWrapper(sh, cbg.cornersSize(), corners_found,0);480if (found)481res = false;482483vector< vector<Point> > cnts(1);484vector<Point>& cnt = cnts[0];485cnt.push_back(cg[ 0]); cnt.push_back(cg[0+2]);486cnt.push_back(cg[7+0]); cnt.push_back(cg[7+2]);487cv::drawContours(cb, cnts, -1, Scalar::all(128), FILLED);488489found = findChessboardCornersWrapper(cb, cbg.cornersSize(), corners_found,0);490if (found)491res = false;492493cv::drawChessboardCorners(cb, cbg.cornersSize(), Mat(corners_found), found);494}495496return res;497}498499// generates artificial checkerboards using warpPerspective which supports500// subpixel rendering. The transformation is found by transferring corners to501// the camera image using a virtual plane.502bool CV_ChessboardDetectorTest::checkByGeneratorHighAccuracy()503{504// draw 2D pattern505cv::Size pattern_size(6,5);506int cell_size = 80;507bool bwhite = true;508cv::Mat image = cv::Mat::ones((pattern_size.height+3)*cell_size,(pattern_size.width+3)*cell_size,CV_8UC1)*255;509cv::Mat pimage = image(Rect(cell_size,cell_size,(pattern_size.width+1)*cell_size,(pattern_size.height+1)*cell_size));510pimage = 0;511for(int row=0;row<=pattern_size.height;++row)512{513int y = int(cell_size*row+0.5F);514bool bwhite2 = bwhite;515for(int col=0;col<=pattern_size.width;++col)516{517if(bwhite2)518{519int x = int(cell_size*col+0.5F);520pimage(cv::Rect(x,y,cell_size,cell_size)) = 255;521}522bwhite2 = !bwhite2;523524}525bwhite = !bwhite;526}527528// generate 2d points529std::vector<Point2f> pts1,pts2,pts1_all,pts2_all;530std::vector<Point3f> pts3d;531for(int row=0;row<pattern_size.height;++row)532{533int y = int(cell_size*(row+2));534for(int col=0;col<pattern_size.width;++col)535{536int x = int(cell_size*(col+2));537pts1_all.push_back(cv::Point2f(x-0.5F,y-0.5F));538}539}540541// back project chessboard corners to a virtual plane542double fx = 500;543double fy = 500;544cv::Point2f center(250,250);545double fxi = 1.0/fx;546double fyi = 1.0/fy;547for(auto &&pt : pts1_all)548{549// calc camera ray550cv::Vec3f ray(float((pt.x-center.x)*fxi),float((pt.y-center.y)*fyi),1.0F);551ray /= cv::norm(ray);552553// intersect ray with virtual plane554cv::Scalar plane(0,0,1,-1);555cv::Vec3f n(float(plane(0)),float(plane(1)),float(plane(2)));556cv::Point3f p0(0,0,0);557558cv::Point3f l0(0,0,0); // camera center in world coordinates559p0.z = float(-plane(3)/plane(2));560double val1 = ray.dot(n);561if(val1 == 0)562{563ts->printf( cvtest::TS::LOG, "Internal Error: ray and plane are parallel" );564ts->set_failed_test_info( cvtest::TS::FAIL_GENERIC);565return false;566}567pts3d.push_back(Point3f(ray/val1*cv::Vec3f((p0-l0)).dot(n))+l0);568}569570// generate multiple rotations571for(int i=15;i<90;i=i+15)572{573// project 3d points to new camera574Vec3f rvec(0.0F,0.05F,float(float(i)/180.0*CV_PI));575Vec3f tvec(0,0,0);576cv::Mat k = (cv::Mat_<double>(3,3) << fx/2,0,center.x*2, 0,fy/2,center.y, 0,0,1);577cv::projectPoints(pts3d,rvec,tvec,k,cv::Mat(),pts2_all);578579// get perspective transform using four correspondences and wrap original image580pts1.clear();581pts2.clear();582pts1.push_back(pts1_all[0]);583pts1.push_back(pts1_all[pattern_size.width-1]);584pts1.push_back(pts1_all[pattern_size.width*pattern_size.height-1]);585pts1.push_back(pts1_all[pattern_size.width*(pattern_size.height-1)]);586pts2.push_back(pts2_all[0]);587pts2.push_back(pts2_all[pattern_size.width-1]);588pts2.push_back(pts2_all[pattern_size.width*pattern_size.height-1]);589pts2.push_back(pts2_all[pattern_size.width*(pattern_size.height-1)]);590Mat m2 = getPerspectiveTransform(pts1,pts2);591Mat out(image.size(),image.type());592warpPerspective(image,out,m2,out.size());593594// find checkerboard595vector<Point2f> corners_found;596bool found = findChessboardCornersWrapper(out,pattern_size,corners_found,0);597if (!found)598{599ts->printf( cvtest::TS::LOG, "Chess board corners not found\n" );600ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );601return false;602}603double err = calcErrorMinError(pattern_size,corners_found,pts2_all);604if(err > 0.08)605{606ts->printf( cvtest::TS::LOG, "bad accuracy of corner guesses" );607ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );608return false;609}610//cv::cvtColor(out,out,cv::COLOR_GRAY2BGR);611//cv::drawChessboardCorners(out,pattern_size,corners_found,true);612//cv::imshow("img",out);613//cv::waitKey(-1);614}615return true;616}617618TEST(Calib3d_ChessboardDetector, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD ); test.safe_run(); }619TEST(Calib3d_ChessboardDetector2, accuracy) { CV_ChessboardDetectorTest test( CHESSBOARD_SB ); test.safe_run(); }620TEST(Calib3d_CirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( CIRCLES_GRID ); test.safe_run(); }621TEST(Calib3d_AsymmetricCirclesPatternDetector, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID ); test.safe_run(); }622#ifdef HAVE_OPENCV_FLANN623TEST(Calib3d_AsymmetricCirclesPatternDetectorWithClustering, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID, CALIB_CB_CLUSTERING ); test.safe_run(); }624#endif625626}} // namespace627/* End of file. */628629630