Merge pull request #6895 from mshabunin:check-6851

pull/6879/head
Vadim Pisarevsky 8 years ago
commit 5d3860703d
  1. 677
      modules/calib3d/src/calibinit.cpp
  2. 96
      modules/calib3d/src/checkchessboard.cpp
  3. 6
      modules/calib3d/test/test_chesscorners_timing.cpp

@ -59,23 +59,27 @@
\************************************************************************************/
/************************************************************************************\
This version adds a new and improved variant of chessboard corner detection
that works better in poor lighting condition. It is based on work from
Oliver Schreer and Stefano Masneri. This method works faster than the previous
one and reverts back to the older method in case no chessboard detection is
possible. Overall performance improves also because now the method avoids
performing the same computation multiple times when not necessary.
\************************************************************************************/
#include "precomp.hpp"
#include "opencv2/imgproc/imgproc_c.h"
#include "opencv2/calib3d/calib3d_c.h"
#include "circlesgrid.hpp"
#include <stdarg.h>
#include <vector>
//#define ENABLE_TRIM_COL_ROW
//#define DEBUG_CHESSBOARD
#ifdef DEBUG_CHESSBOARD
# include "opencv2/opencv_modules.hpp"
# ifdef HAVE_OPENCV_HIGHGUI
# include "opencv2/highgui.hpp"
# else
# undef DEBUG_CHESSBOARD
# endif
#endif
#ifdef DEBUG_CHESSBOARD
static int PRINTF( const char* fmt, ... )
{
@ -191,38 +195,204 @@ static void icvRemoveQuadFromGroup(CvCBQuad **quads, int count, CvCBQuad *q0);
static int icvCheckBoardMonotony( CvPoint2D32f* corners, CvSize pattern_size );
#if 0
static void
icvCalcAffineTranf2D32f(CvPoint2D32f* pts1, CvPoint2D32f* pts2, int count, CvMat* affine_trans)
int cvCheckChessboardBinary(IplImage* src, CvSize size);
/***************************************************************************************************/
//COMPUTE INTENSITY HISTOGRAM OF INPUT IMAGE
static int icvGetIntensityHistogram( unsigned char* pucImage, int iSizeCols, int iSizeRows, std::vector<int>& piHist );
//SMOOTH HISTOGRAM USING WINDOW OF SIZE 2*iWidth+1
static int icvSmoothHistogram( const std::vector<int>& piHist, std::vector<int>& piHistSmooth, int iWidth );
//COMPUTE FAST HISTOGRAM GRADIENT
static int icvGradientOfHistogram( const std::vector<int>& piHist, std::vector<int>& piHistGrad );
//PERFORM SMART IMAGE THRESHOLDING BASED ON ANALYSIS OF INTENSTY HISTOGRAM
static bool icvBinarizationHistogramBased( unsigned char* pucImg, int iCols, int iRows );
/***************************************************************************************************/
int icvGetIntensityHistogram( unsigned char* pucImage, int iSizeCols, int iSizeRows, std::vector<int>& piHist )
{
int i, j;
int real_count = 0;
for( j = 0; j < count; j++ )
int iVal;
// sum up all pixel in row direction and divide by number of columns
for ( int j=0; j<iSizeRows; j++ )
{
for ( int i=0; i<iSizeCols; i++ )
{
if( pts1[j].x >= 0 ) real_count++;
iVal = (int)pucImage[j*iSizeCols+i];
piHist[iVal]++;
}
if(real_count < 3) return;
cv::Ptr<CvMat> xy = cvCreateMat( 2*real_count, 6, CV_32FC1 );
cv::Ptr<CvMat> uv = cvCreateMat( 2*real_count, 1, CV_32FC1 );
//estimate affine transfromation
for( i = 0, j = 0; j < count; j++ )
}
return 0;
}
/***************************************************************************************************/
int icvSmoothHistogram( const std::vector<int>& piHist, std::vector<int>& piHistSmooth, int iWidth )
{
int iIdx;
for ( int i=0; i<256; i++)
{
int iSmooth = 0;
for ( int ii=-iWidth; ii<=iWidth; ii++)
{
if( pts1[j].x >= 0 )
{
CV_MAT_ELEM( *xy, float, i*2+1, 2 ) = CV_MAT_ELEM( *xy, float, i*2, 0 ) = pts2[j].x;
CV_MAT_ELEM( *xy, float, i*2+1, 3 ) = CV_MAT_ELEM( *xy, float, i*2, 1 ) = pts2[j].y;
CV_MAT_ELEM( *xy, float, i*2, 2 ) = CV_MAT_ELEM( *xy, float, i*2, 3 ) = CV_MAT_ELEM( *xy, float, i*2, 5 ) = \
CV_MAT_ELEM( *xy, float, i*2+1, 0 ) = CV_MAT_ELEM( *xy, float, i*2+1, 1 ) = CV_MAT_ELEM( *xy, float, i*2+1, 4 ) = 0;
CV_MAT_ELEM( *xy, float, i*2, 4 ) = CV_MAT_ELEM( *xy, float, i*2+1, 5 ) = 1;
CV_MAT_ELEM( *uv, float, i*2, 0 ) = pts1[j].x;
CV_MAT_ELEM( *uv, float, i*2+1, 0 ) = pts1[j].y;
i++;
}
iIdx = i+ii;
if (iIdx > 0 && iIdx < 256)
{
iSmooth += piHist[iIdx];
}
}
piHistSmooth[i] = iSmooth/(2*iWidth+1);
}
return 0;
}
/***************************************************************************************************/
int icvGradientOfHistogram( const std::vector<int>& piHist, std::vector<int>& piHistGrad )
{
piHistGrad[0] = 0;
for ( int i=1; i<255; i++)
{
piHistGrad[i] = piHist[i-1] - piHist[i+1];
if ( abs(piHistGrad[i]) < 100 )
{
if ( piHistGrad[i-1] == 0)
piHistGrad[i] = -100;
else
piHistGrad[i] = piHistGrad[i-1];
}
}
return 0;
}
/***************************************************************************************************/
bool icvBinarizationHistogramBased( unsigned char* pucImg, int iCols, int iRows )
{
int iMaxPix = iCols*iRows;
int iMaxPix1 = iMaxPix/100;
const int iNumBins = 256;
std::vector<int> piHistIntensity(iNumBins, 0);
std::vector<int> piHistSmooth(iNumBins, 0);
std::vector<int> piHistGrad(iNumBins, 0);
std::vector<int> piAccumSum(iNumBins, 0);
std::vector<int> piMaxPos(20, 0);
int iThresh = 0;
int iIdx;
int iWidth = 1;
icvGetIntensityHistogram( pucImg, iCols, iRows, piHistIntensity );
// get accumulated sum starting from bright
piAccumSum[iNumBins-1] = piHistIntensity[iNumBins-1];
for ( int i=iNumBins-2; i>=0; i-- )
{
piAccumSum[i] = piHistIntensity[i] + piAccumSum[i+1];
}
// first smooth the distribution
icvSmoothHistogram( piHistIntensity, piHistSmooth, iWidth );
// compute gradient
icvGradientOfHistogram( piHistSmooth, piHistGrad );
// check for zeros
int iCntMaxima = 0;
for ( int i=iNumBins-2; (i>2) && (iCntMaxima<20); i--)
{
if ( (piHistGrad[i-1] < 0) && (piHistGrad[i] > 0) )
{
piMaxPos[iCntMaxima] = i;
iCntMaxima++;
}
}
iIdx = 0;
int iSumAroundMax = 0;
for ( int i=0; i<iCntMaxima; i++ )
{
iIdx = piMaxPos[i];
iSumAroundMax = piHistSmooth[iIdx-1] + piHistSmooth[iIdx] + piHistSmooth[iIdx+1];
if ( iSumAroundMax < iMaxPix1 && iIdx < 64 )
{
for ( int j=i; j<iCntMaxima-1; j++ )
{
piMaxPos[j] = piMaxPos[j+1];
}
iCntMaxima--;
i--;
}
}
if ( iCntMaxima == 1)
{
iThresh = piMaxPos[0]/2;
}
else if ( iCntMaxima == 2)
{
iThresh = (piMaxPos[0] + piMaxPos[1])/2;
}
else // iCntMaxima >= 3
{
// CHECKING THRESHOLD FOR WHITE
int iIdxAccSum = 0, iAccum = 0;
for (int i=iNumBins-1; i>0; i--)
{
iAccum += piHistIntensity[i];
// iMaxPix/18 is about 5,5%, minimum required number of pixels required for white part of chessboard
if ( iAccum > (iMaxPix/18) )
{
iIdxAccSum = i;
break;
}
}
int iIdxBGMax = 0;
int iBrightMax = piMaxPos[0];
// printf("iBrightMax = %d\n", iBrightMax);
for ( int n=0; n<iCntMaxima-1; n++)
{
iIdxBGMax = n+1;
if ( piMaxPos[n] < iIdxAccSum )
{
break;
}
iBrightMax = piMaxPos[n];
}
// CHECKING THRESHOLD FOR BLACK
int iMaxVal = piHistIntensity[piMaxPos[iIdxBGMax]];
//IF TOO CLOSE TO 255, jump to next maximum
if ( piMaxPos[iIdxBGMax] >= 250 && iIdxBGMax < iCntMaxima )
{
iIdxBGMax++;
iMaxVal = piHistIntensity[piMaxPos[iIdxBGMax]];
}
for ( int n=iIdxBGMax + 1; n<iCntMaxima; n++)
{
if ( piHistIntensity[piMaxPos[n]] >= iMaxVal )
{
iMaxVal = piHistIntensity[piMaxPos[n]];
iIdxBGMax = n;
}
}
//SETTING THRESHOLD FOR BINARIZATION
int iDist2 = (iBrightMax - piMaxPos[iIdxBGMax])/2;
iThresh = iBrightMax - iDist2;
PRINTF("THRESHOLD SELECTED = %d, BRIGHTMAX = %d, DARKMAX = %d\n", iThresh, iBrightMax, piMaxPos[iIdxBGMax]);
}
if ( iThresh > 0 )
{
for ( int jj=0; jj<iRows; jj++)
{
for ( int ii=0; ii<iCols; ii++)
{
if ( pucImg[jj*iCols+ii]< iThresh )
pucImg[jj*iCols+ii] = 0;
else
pucImg[jj*iCols+ii] = 255;
}
}
}
cvSolve( xy, uv, affine_trans, CV_SVD );
return true;
}
#endif
CV_IMPL
int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
@ -232,6 +402,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
int found = 0;
CvCBQuad *quads = 0, **quad_group = 0;
CvCBCorner *corners = 0, **corner_group = 0;
IplImage* cImgSeg = 0;
try
{
@ -239,14 +410,14 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
const int min_dilations = 0;
const int max_dilations = 7;
cv::Ptr<CvMat> norm_img, thresh_img;
#ifdef DEBUG_CHESSBOARD
cv::Ptr<IplImage> dbg_img;
cv::Ptr<IplImage> dbg1_img;
cv::Ptr<IplImage> dbg2_img;
#endif
cv::Ptr<CvMemStorage> storage;
CvMat stub, *img = (CvMat*)arr;
cImgSeg = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1 );
memcpy( cImgSeg->imageData, cvPtr1D( img, 0), img->rows*img->cols );
CvMat stub2, *thresh_img_new;
thresh_img_new = cvGetMat( cImgSeg, &stub2, 0, 0 );
int expected_corners_num = (pattern_size.width/2+1)*(pattern_size.height/2+1);
@ -255,7 +426,6 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
if( out_corner_count )
*out_corner_count = 0;
IplImage _img;
int quad_count = 0, group_idx = 0, dilations = 0;
img = cvGetMat( img, &stub );
@ -273,12 +443,6 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
storage.reset(cvCreateMemStorage(0));
thresh_img.reset(cvCreateMat( img->rows, img->cols, CV_8UC1 ));
#ifdef DEBUG_CHESSBOARD
dbg_img = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3 );
dbg1_img = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3 );
dbg2_img = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3 );
#endif
if( CV_MAT_CN(img->type) != 1 || (flags & CV_CALIB_CB_NORMALIZE_IMAGE) )
{
// equalize the input image histogram -
@ -300,11 +464,19 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
if( flags & CV_CALIB_CB_FAST_CHECK)
{
cvGetImage(img, &_img);
int check_chessboard_result = cvCheckChessboard(&_img, pattern_size);
if(check_chessboard_result <= 0)
//perform new method for checking chessboard using a binary image.
//image is binarised using a threshold dependent on the image histogram
icvBinarizationHistogramBased( (unsigned char*) cImgSeg->imageData, cImgSeg->width, cImgSeg->height );
int check_chessboard_result = cvCheckChessboardBinary(cImgSeg, pattern_size);
if(check_chessboard_result <= 0) //fall back to the old method
{
return 0;
IplImage _img;
cvGetImage(img, &_img);
check_chessboard_result = cvCheckChessboard(&_img, pattern_size);
if(check_chessboard_result <= 0)
{
return 0;
}
}
}
@ -312,201 +484,238 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
// This is necessary because some squares simply do not separate properly with a single dilation. However,
// we want to use the minimum number of dilations possible since dilations cause the squares to become smaller,
// making it difficult to detect smaller squares.
for( k = 0; k < 6; k++ )
for( dilations = min_dilations; dilations <= max_dilations; dilations++ )
{
int max_quad_buf_size = 0;
for( dilations = min_dilations; dilations <= max_dilations; dilations++ )
{
if (found)
break; // already found it
if (found)
break; // already found it
cvFree(&quads);
cvFree(&corners);
cvFree(&quads);
cvFree(&corners);
/*if( k == 1 )
{
//Pattern was not found using binarization
// Run multi-level quads extraction
// In case one-level binarization did not give enough number of quads
CV_CALL( quad_count = icvGenerateQuadsEx( &quads, &corners, storage, img, thresh_img, dilations, flags ));
PRINTF("EX quad count: %d/%d\n", quad_count, expected_corners_num);
}
else*/
{
// convert the input grayscale image to binary (black-n-white)
if( flags & CV_CALIB_CB_ADAPTIVE_THRESH )
{
int block_size = cvRound(prev_sqr_size == 0 ?
MIN(img->cols,img->rows)*(k%2 == 0 ? 0.2 : 0.1): prev_sqr_size*2)|1;
// convert to binary
cvAdaptiveThreshold( img, thresh_img, 255,
CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, block_size, (k/2)*5 );
if (dilations > 0)
cvDilate( thresh_img, thresh_img, 0, dilations-1 );
}
else
{
// Make dilation before the thresholding.
// It splits chessboard corners
//cvDilate( img, thresh_img, 0, 1 );
int max_quad_buf_size = 0;
// empiric threshold level
double mean = cvAvg( img ).val[0];
int thresh_level = cvRound( mean - 10 );
thresh_level = MAX( thresh_level, 10 );
//USE BINARY IMAGE COMPUTED USING icvBinarizationHistogramBased METHOD
cvDilate( thresh_img_new, thresh_img_new, 0, 1 );
cvThreshold( img, thresh_img, thresh_level, 255, CV_THRESH_BINARY );
cvDilate( thresh_img, thresh_img, 0, dilations );
}
// So we can find rectangles that go to the edge, we draw a white line around the image edge.
// Otherwise FindContours will miss those clipped rectangle contours.
// The border color will be the image mean, because otherwise we risk screwing up filters like cvSmooth()...
cvRectangle( thresh_img_new, cvPoint(0,0), cvPoint(thresh_img_new->cols-1, thresh_img_new->rows-1), CV_RGB(255,255,255), 3, 8);
quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img_new, flags, &max_quad_buf_size );
PRINTF("Quad count: %d/%d\n", quad_count, expected_corners_num);
#ifdef DEBUG_CHESSBOARD
cvCvtColor(thresh_img,dbg_img,CV_GRAY2BGR);
#endif
if( quad_count <= 0 )
{
continue;
}
// So we can find rectangles that go to the edge, we draw a white line around the image edge.
// Otherwise FindContours will miss those clipped rectangle contours.
// The border color will be the image mean, because otherwise we risk screwing up filters like cvSmooth()...
cvRectangle( thresh_img, cvPoint(0,0), cvPoint(thresh_img->cols-1,
thresh_img->rows-1), CV_RGB(255,255,255), 3, 8);
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags, &max_quad_buf_size);
// allocate extra for adding in icvOrderFoundQuads
cvFree(&quad_group);
cvFree(&corner_group);
quad_group = (CvCBQuad**)cvAlloc( sizeof(quad_group[0]) * max_quad_buf_size);
corner_group = (CvCBCorner**)cvAlloc( sizeof(corner_group[0]) * max_quad_buf_size * 4 );
PRINTF("Quad count: %d/%d\n", quad_count, expected_corners_num);
}
for( group_idx = 0; ; group_idx++ )
{
int count = 0;
count = icvFindConnectedQuads( quads, quad_count, quad_group, group_idx, storage );
int icount = count;
if( count == 0 )
break;
#ifdef DEBUG_CHESSBOARD
cvCopy(dbg_img, dbg1_img);
cvNamedWindow("all_quads", 1);
// copy corners to temp array
for(int i = 0; i < quad_count; i++ )
{
for (int k=0; k<4; k++)
{
CvPoint2D32f pt1, pt2;
CvScalar color = CV_RGB(30,255,30);
pt1 = quads[i].corners[k]->pt;
pt2 = quads[i].corners[(k+1)%4]->pt;
pt2.x = (pt1.x + pt2.x)/2;
pt2.y = (pt1.y + pt2.y)/2;
if (k>0)
color = CV_RGB(200,200,0);
cvLine( dbg1_img, cvPointFrom32f(pt1), cvPointFrom32f(pt2), color, 3, 8);
}
}
// order the quad corners globally
// maybe delete or add some
PRINTF("Starting ordering of inner quads\n");
count = icvOrderFoundConnectedQuads(count, quad_group, &quad_count, &quads, &corners, pattern_size, max_quad_buf_size, storage );
PRINTF("Orig count: %d After ordering: %d\n", icount, count);
if (count == 0)
continue; // haven't found inner quads
cvShowImage("all_quads", (IplImage*)dbg1_img);
cvWaitKey();
#endif
// If count is more than it should be, this will remove those quads
// which cause maximum deviation from a nice square pattern.
count = icvCleanFoundConnectedQuads( count, quad_group, pattern_size );
PRINTF("Connected group: %d orig count: %d cleaned: %d\n", group_idx, icount, count);
if( quad_count <= 0 )
continue;
count = icvCheckQuadGroup( quad_group, count, corner_group, pattern_size );
PRINTF("Connected group: %d count: %d cleaned: %d\n", group_idx, icount, count);
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
int n = count > 0 ? pattern_size.width * pattern_size.height : -count;
n = MIN( n, pattern_size.width * pattern_size.height );
float sum_dist = 0;
int total = 0;
// allocate extra for adding in icvOrderFoundQuads
cvFree(&quad_group);
cvFree(&corner_group);
quad_group = (CvCBQuad**)cvAlloc( sizeof(quad_group[0]) * max_quad_buf_size);
corner_group = (CvCBCorner**)cvAlloc( sizeof(corner_group[0]) * max_quad_buf_size * 4 );
for(int i = 0; i < n; i++ )
{
int ni = 0;
float avgi = corner_group[i]->meanDist(&ni);
sum_dist += avgi*ni;
total += ni;
}
prev_sqr_size = cvRound(sum_dist/MAX(total, 1));
for( group_idx = 0; ; group_idx++ )
{
int count = 0;
count = icvFindConnectedQuads( quads, quad_count, quad_group, group_idx, storage );
if( count > 0 || (out_corner_count && -count > *out_corner_count) )
{
// copy corners to output array
for(int i = 0; i < n; i++ )
out_corners[i] = corner_group[i]->pt;
if( out_corner_count )
*out_corner_count = n;
if( count == pattern_size.width*pattern_size.height &&
icvCheckBoardMonotony( out_corners, pattern_size ))
{
found = 1;
break;
}
}
}
}//dilations
int icount = count;
if( count == 0 )
break;
PRINTF("Chessboard detection result 0: %d\n", found);
// order the quad corners globally
// maybe delete or add some
PRINTF("Starting ordering of inner quads\n");
count = icvOrderFoundConnectedQuads(count, quad_group, &quad_count, &quads, &corners,
pattern_size, max_quad_buf_size, storage );
PRINTF("Orig count: %d After ordering: %d\n", icount, count);
// revert to old, slower, method if detection failed
if (!found)
{
PRINTF("Fallback to old algorithm\n");
// empiric threshold level
// thresholding performed here and not inside the cycle to save processing time
int thresh_level;
if ( !(flags & CV_CALIB_CB_ADAPTIVE_THRESH) )
{
double mean = cvAvg( img ).val[0];
thresh_level = cvRound( mean - 10 );
thresh_level = MAX( thresh_level, 10 );
cvThreshold( img, thresh_img, thresh_level, 255, CV_THRESH_BINARY );
}
for( k = 0; k < 6; k++ )
{
int max_quad_buf_size = 0;
for( dilations = min_dilations; dilations <= max_dilations; dilations++ )
{
if (found)
break; // already found it
cvFree(&quads);
cvFree(&corners);
// convert the input grayscale image to binary (black-n-white)
if( flags & CV_CALIB_CB_ADAPTIVE_THRESH )
{
int block_size = cvRound(prev_sqr_size == 0 ?
MIN(img->cols,img->rows)*(k%2 == 0 ? 0.2 : 0.1): prev_sqr_size*2)|1;
// convert to binary
cvAdaptiveThreshold( img, thresh_img, 255,
CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, block_size, (k/2)*5 );
if (dilations > 0)
cvDilate( thresh_img, thresh_img, 0, dilations-1 );
}
//if flag CV_CALIB_CB_ADAPTIVE_THRESH is not set it doesn't make sense
//to iterate over k
else
{
k = 6;
cvDilate( thresh_img, thresh_img, 0, 1 );
}
// So we can find rectangles that go to the edge, we draw a white line around the image edge.
// Otherwise FindContours will miss those clipped rectangle contours.
// The border color will be the image mean, because otherwise we risk screwing up filters like cvSmooth()...
cvRectangle( thresh_img, cvPoint(0,0), cvPoint(thresh_img->cols-1,
thresh_img->rows-1), CV_RGB(255,255,255), 3, 8);
quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags, &max_quad_buf_size);
PRINTF("Quad count: %d/%d\n", quad_count, expected_corners_num);
if( quad_count <= 0 )
{
continue;
}
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
#ifdef DEBUG_CHESSBOARD
cvCopy(dbg_img,dbg2_img);
cvNamedWindow("connected_group", 1);
// copy corners to temp array
for(int i = 0; i < quad_count; i++ )
{
if (quads[i].group_idx == group_idx)
for (int k=0; k<4; k++)
{
CvPoint2D32f pt1, pt2;
CvScalar color = CV_RGB(30,255,30);
if (quads[i].ordered)
color = CV_RGB(255,30,30);
pt1 = quads[i].corners[k]->pt;
pt2 = quads[i].corners[(k+1)%4]->pt;
pt2.x = (pt1.x + pt2.x)/2;
pt2.y = (pt1.y + pt2.y)/2;
if (k>0)
color = CV_RGB(200,200,0);
cvLine( dbg2_img, cvPointFrom32f(pt1), cvPointFrom32f(pt2), color, 3, 8);
}
}
cvShowImage("connected_group", (IplImage*)dbg2_img);
cvWaitKey();
#endif
// allocate extra for adding in icvOrderFoundQuads
cvFree(&quad_group);
cvFree(&corner_group);
quad_group = (CvCBQuad**)cvAlloc( sizeof(quad_group[0]) * max_quad_buf_size);
corner_group = (CvCBCorner**)cvAlloc( sizeof(corner_group[0]) * max_quad_buf_size * 4 );
if (count == 0)
continue; // haven't found inner quads
for( group_idx = 0; ; group_idx++ )
{
int count = 0;
count = icvFindConnectedQuads( quads, quad_count, quad_group, group_idx, storage );
int icount = count;
if( count == 0 )
break;
// If count is more than it should be, this will remove those quads
// which cause maximum deviation from a nice square pattern.
count = icvCleanFoundConnectedQuads( count, quad_group, pattern_size );
PRINTF("Connected group: %d orig count: %d cleaned: %d\n", group_idx, icount, count);
// order the quad corners globally
// maybe delete or add some
PRINTF("Starting ordering of inner quads\n");
count = icvOrderFoundConnectedQuads(count, quad_group, &quad_count, &quads, &corners, pattern_size, max_quad_buf_size, storage );
count = icvCheckQuadGroup( quad_group, count, corner_group, pattern_size );
PRINTF("Connected group: %d count: %d cleaned: %d\n", group_idx, icount, count);
PRINTF("Orig count: %d After ordering: %d\n", icount, count);
{
int n = count > 0 ? pattern_size.width * pattern_size.height : -count;
n = MIN( n, pattern_size.width * pattern_size.height );
float sum_dist = 0;
int total = 0;
if (count == 0)
continue; // haven't found inner quads
for(int i = 0; i < n; i++ )
{
int ni = 0;
float avgi = corner_group[i]->meanDist(&ni);
sum_dist += avgi*ni;
total += ni;
}
prev_sqr_size = cvRound(sum_dist/MAX(total, 1));
if( count > 0 || (out_corner_count && -count > *out_corner_count) )
{
// copy corners to output array
for(int i = 0; i < n; i++ )
out_corners[i] = corner_group[i]->pt;
// If count is more than it should be, this will remove those quads
// which cause maximum deviation from a nice square pattern.
count = icvCleanFoundConnectedQuads( count, quad_group, pattern_size );
PRINTF("Connected group: %d orig count: %d cleaned: %d\n", group_idx, icount, count);
if( out_corner_count )
*out_corner_count = n;
count = icvCheckQuadGroup( quad_group, count, corner_group, pattern_size );
PRINTF("Connected group: %d count: %d cleaned: %d\n", group_idx, icount, count);
if( count == pattern_size.width*pattern_size.height &&
icvCheckBoardMonotony( out_corners, pattern_size ))
{
found = 1;
break;
}
}
}
int n = count > 0 ? pattern_size.width * pattern_size.height : -count;
n = MIN( n, pattern_size.width * pattern_size.height );
float sum_dist = 0;
int total = 0;
for(int i = 0; i < n; i++ )
{
int ni = 0;
float avgi = corner_group[i]->meanDist(&ni);
sum_dist += avgi*ni;
total += ni;
}
prev_sqr_size = cvRound(sum_dist/MAX(total, 1));
if( count > 0 || (out_corner_count && -count > *out_corner_count) )
{
// copy corners to output array
for(int i = 0; i < n; i++ )
out_corners[i] = corner_group[i]->pt;
if( out_corner_count )
*out_corner_count = n;
if( count == pattern_size.width*pattern_size.height && icvCheckBoardMonotony( out_corners, pattern_size ))
{
found = 1;
break;
}
}
}
}//dilations
}//
}// for k = 0 -> 6
}
PRINTF("Chessboard detection result 1: %d\n", found);
if( found )
found = icvCheckBoardMonotony( out_corners, pattern_size );
PRINTF("Chessboard detection result 2: %d\n", found);
// check that none of the found corners is too close to the image boundary
if( found )
{
@ -521,36 +730,38 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
found = k == pattern_size.width*pattern_size.height;
}
if( found && pattern_size.height % 2 == 0 && pattern_size.width % 2 == 0 )
PRINTF("Chessboard detection result 3: %d\n", found);
if( found )
{
if ( pattern_size.height % 2 == 0 && pattern_size.width % 2 == 0 )
{
int last_row = (pattern_size.height-1)*pattern_size.width;
double dy0 = out_corners[last_row].y - out_corners[0].y;
if( dy0 < 0 )
{
int n = pattern_size.width*pattern_size.height;
for(int i = 0; i < n/2; i++ )
{
CvPoint2D32f temp;
CV_SWAP(out_corners[i], out_corners[n-i-1], temp);
}
int n = pattern_size.width*pattern_size.height;
for(int i = 0; i < n/2; i++ )
{
CvPoint2D32f temp;
CV_SWAP(out_corners[i], out_corners[n-i-1], temp);
}
}
}
if( found )
{
cv::Ptr<CvMat> gray;
if( CV_MAT_CN(img->type) != 1 )
{
gray.reset(cvCreateMat(img->rows, img->cols, CV_8UC1));
cvCvtColor(img, gray, CV_BGR2GRAY);
}
else
{
gray.reset(cvCloneMat(img));
}
int wsize = 2;
cvFindCornerSubPix( gray, out_corners, pattern_size.width*pattern_size.height,
cvSize(wsize, wsize), cvSize(-1,-1), cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 15, 0.1));
}
cv::Ptr<CvMat> gray;
if( CV_MAT_CN(img->type) != 1 )
{
gray.reset(cvCreateMat(img->rows, img->cols, CV_8UC1));
cvCvtColor(img, gray, CV_BGR2GRAY);
}
else
{
gray.reset(cvCloneMat(img));
}
int wsize = 2;
cvFindCornerSubPix( gray, out_corners, pattern_size.width*pattern_size.height,
cvSize(wsize, wsize), cvSize(-1,-1),
cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 15, 0.1));
}
}
catch(...)
@ -559,6 +770,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
cvFree(&corners);
cvFree(&quad_group);
cvFree(&corner_group);
cvFree(&cImgSeg);
throw;
}
@ -566,6 +778,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
cvFree(&corners);
cvFree(&quad_group);
cvFree(&corner_group);
cvFree(&cImgSeg);
return found;
}

@ -57,6 +57,8 @@
# endif
#endif
int cvCheckChessboardBinary(IplImage* src, CvSize size);
static void icvGetQuadrangleHypotheses(CvSeq* contours, std::vector<std::pair<float, int> >& quads, int class_id)
{
const float min_aspect_ratio = 0.3f;
@ -205,3 +207,97 @@ int cvCheckChessboard(IplImage* src, CvSize size)
return result;
}
// does a fast check if a chessboard is in the input image. This is a workaround to
// a problem of cvFindChessboardCorners being slow on images with no chessboard
// - src: input binary image
// - size: chessboard size
// Returns 1 if a chessboard can be in this image and findChessboardCorners should be called,
// 0 if there is no chessboard, -1 in case of error
int cvCheckChessboardBinary(IplImage* src, CvSize size)
{
if(src->nChannels > 1)
{
cvError(CV_BadNumChannels, "cvCheckChessboard", "supports single-channel images only",
__FILE__, __LINE__);
}
if(src->depth != 8)
{
cvError(CV_BadDepth, "cvCheckChessboard", "supports depth=8 images only",
__FILE__, __LINE__);
}
CvMemStorage* storage = cvCreateMemStorage();
IplImage* white = cvCloneImage(src);
IplImage* black = cvCloneImage(src);
IplImage* thresh = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
int result = 0;
for ( int erosion_count = 0; erosion_count <= 3; erosion_count++ )
{
if ( 1 == result )
break;
if ( 0 != erosion_count ) // first iteration keeps original images
{
cvErode(white, white, NULL, 1);
cvDilate(black, black, NULL, 1);
}
cvThreshold(white, thresh, 128, 255, CV_THRESH_BINARY);
CvSeq* first = 0;
std::vector<std::pair<float, int> > quads;
cvFindContours(thresh, storage, &first, sizeof(CvContour), CV_RETR_CCOMP);
icvGetQuadrangleHypotheses(first, quads, 1);
cvThreshold(black, thresh, 128, 255, CV_THRESH_BINARY_INV);
cvFindContours(thresh, storage, &first, sizeof(CvContour), CV_RETR_CCOMP);
icvGetQuadrangleHypotheses(first, quads, 0);
const size_t min_quads_count = size.width*size.height/2;
std::sort(quads.begin(), quads.end(), less_pred);
// now check if there are many hypotheses with similar sizes
// do this by floodfill-style algorithm
const float size_rel_dev = 0.4f;
for(size_t i = 0; i < quads.size(); i++)
{
size_t j = i + 1;
for(; j < quads.size(); j++)
{
if(quads[j].first/quads[i].first > 1.0f + size_rel_dev)
{
break;
}
}
if(j + 1 > min_quads_count + i)
{
// check the number of black and white squares
std::vector<int> counts;
countClasses(quads, i, j, counts);
const int black_count = cvRound(ceil(size.width/2.0)*ceil(size.height/2.0));
const int white_count = cvRound(floor(size.width/2.0)*floor(size.height/2.0));
if(counts[0] < black_count*0.75 ||
counts[1] < white_count*0.75)
{
continue;
}
result = 1;
break;
}
}
}
cvReleaseImage(&thresh);
cvReleaseImage(&white);
cvReleaseImage(&black);
cvReleaseMemStorage(&storage);
return result;
}

@ -113,11 +113,7 @@ void CV_ChessboardDetectorTimingTest::run( int start_from )
if( img2.empty() )
{
ts->printf( cvtest::TS::LOG, "one of chessboard images can't be read: %s\n", filename.c_str() );
if( max_idx == 1 )
{
code = cvtest::TS::FAIL_MISSING_TEST_DATA;
goto _exit_;
}
code = cvtest::TS::FAIL_MISSING_TEST_DATA;
continue;
}

Loading…
Cancel
Save