Modified and improved the method for chessboard detection. It is now faster and detects chessboards under difficult lighting condition as well as when the chessboard has strong out of plane rotations

This commit is contained in:
Oliver Schreer 2015-05-22 10:39:01 +02:00 committed by Maksim Shabunin
parent 3590c3c48e
commit 74b83cfce5
3 changed files with 585 additions and 174 deletions

View File

@ -46,3 +46,12 @@
#endif #endif
#include "opencv2/calib3d.hpp" #include "opencv2/calib3d.hpp"
// Performs 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.
// This method works using a binary image as input
// - 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
CVAPI(int) cvCheckChessboardBinary(IplImage* src, CvSize size);

View File

@ -59,11 +59,22 @@
\************************************************************************************/ \************************************************************************************/
/************************************************************************************\
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 "precomp.hpp"
#include "opencv2/imgproc/imgproc_c.h" #include "opencv2/imgproc/imgproc_c.h"
#include "opencv2/calib3d/calib3d_c.h" #include "opencv2/calib3d/calib3d_c.h"
#include "circlesgrid.hpp" #include "circlesgrid.hpp"
#include <stdarg.h> #include <stdarg.h>
#include <vector>
//#define ENABLE_TRIM_COL_ROW //#define ENABLE_TRIM_COL_ROW
@ -191,6 +202,202 @@ static void icvRemoveQuadFromGroup(CvCBQuad **quads, int count, CvCBQuad *q0);
static int icvCheckBoardMonotony( CvPoint2D32f* corners, CvSize pattern_size ); static int icvCheckBoardMonotony( CvPoint2D32f* corners, CvSize pattern_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 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++ )
{
iVal = (int)pucImage[j*iSizeCols+i];
piHist[iVal]++;
}
}
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++)
{
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;
}
}
}
return true;
}
#if 0 #if 0
static void static void
icvCalcAffineTranf2D32f(CvPoint2D32f* pts1, CvPoint2D32f* pts2, int count, CvMat* affine_trans) icvCalcAffineTranf2D32f(CvPoint2D32f* pts1, CvPoint2D32f* pts2, int count, CvMat* affine_trans)
@ -232,6 +439,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
int found = 0; int found = 0;
CvCBQuad *quads = 0, **quad_group = 0; CvCBQuad *quads = 0, **quad_group = 0;
CvCBCorner *corners = 0, **corner_group = 0; CvCBCorner *corners = 0, **corner_group = 0;
IplImage* cImgSeg = 0;
try try
{ {
@ -247,6 +455,11 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
cv::Ptr<CvMemStorage> storage; cv::Ptr<CvMemStorage> storage;
CvMat stub, *img = (CvMat*)arr; 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); int expected_corners_num = (pattern_size.width/2+1)*(pattern_size.height/2+1);
@ -255,7 +468,6 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
if( out_corner_count ) if( out_corner_count )
*out_corner_count = 0; *out_corner_count = 0;
IplImage _img;
int quad_count = 0, group_idx = 0, dilations = 0; int quad_count = 0, group_idx = 0, dilations = 0;
img = cvGetMat( img, &stub ); img = cvGetMat( img, &stub );
@ -300,209 +512,303 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
if( flags & CV_CALIB_CB_FAST_CHECK) if( flags & CV_CALIB_CB_FAST_CHECK)
{ {
cvGetImage(img, &_img); //perform new method for checking chessboard using a binary image.
int check_chessboard_result = cvCheckChessboard(&_img, pattern_size); //image is binarised using a threshold dependent on the image histogram
if(check_chessboard_result <= 0) icvBinarizationHistogramBased( (unsigned char*) cImgSeg->imageData, cImgSeg->width, cImgSeg->height );
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;
}
} }
} }
// 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 );
}
// Try our standard "1" dilation, but if the pattern is not found, iterate the whole procedure with higher dilations. // Try our standard "1" dilation, but if the pattern is not found, iterate the whole procedure with higher dilations.
// This is necessary because some squares simply do not separate properly with a single dilation. However, // 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, // 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. // making it difficult to detect smaller squares.
for( k = 0; k < 6; k++ ) for( dilations = min_dilations; dilations <= max_dilations; dilations++ )
{ {
if (found)
break; // already found it
cvFree(&quads);
cvFree(&corners);
//USE BINARY IMAGE COMPUTED USING icvBinarizationHistogramBased METHOD
cvDilate( thresh_img_new, thresh_img_new, 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_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);
if( quad_count <= 0 )
{
continue;
}
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
// 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( 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;
// 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, storage );
PRINTF("Orig count: %d After ordering: %d\n", icount, count);
if (count == 0)
continue; // haven't found inner quads
// 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);
count = icvCheckQuadGroup( quad_group, count, corner_group, pattern_size );
PRINTF("Connected group: %d count: %d cleaned: %d\n", group_idx, 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;
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
// revert to old, slower, method if detection failed
if (!found)
{
for( k = 0; k < 6; k++ )
{
int max_quad_buf_size = 0; int max_quad_buf_size = 0;
for( dilations = min_dilations; dilations <= max_dilations; dilations++ ) for( dilations = min_dilations; dilations <= max_dilations; dilations++ )
{ {
if (found) if (found)
break; // already found it break; // already found it
cvFree(&quads); cvFree(&quads);
cvFree(&corners); cvFree(&corners);
/*if( k == 1 ) // convert the input grayscale image to binary (black-n-white)
{ if( flags & CV_CALIB_CB_ADAPTIVE_THRESH )
//Pattern was not found using binarization {
// Run multi-level quads extraction int block_size = cvRound(prev_sqr_size == 0 ?
// In case one-level binarization did not give enough number of quads MIN(img->cols,img->rows)*(k%2 == 0 ? 0.2 : 0.1): prev_sqr_size*2)|1;
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 // convert to binary
cvAdaptiveThreshold( img, thresh_img, 255, cvAdaptiveThreshold( img, thresh_img, 255,
CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, block_size, (k/2)*5 ); CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, block_size, (k/2)*5 );
if (dilations > 0) if (dilations > 0)
cvDilate( thresh_img, thresh_img, 0, dilations-1 ); cvDilate( thresh_img, thresh_img, 0, dilations-1 );
} }
else //if flag CV_CALIB_CB_ADAPTIVE_THRESH is not set it doesn't make sense
{ //to iterate over k
// Make dilation before the thresholding. else
// It splits chessboard corners {
//cvDilate( img, thresh_img, 0, 1 ); k = 6;
cvDilate( thresh_img, thresh_img, 0, 1 );
// empiric threshold level }
double mean = cvAvg( img ).val[0];
int thresh_level = cvRound( mean - 10 );
thresh_level = MAX( thresh_level, 10 );
cvThreshold( img, thresh_img, thresh_level, 255, CV_THRESH_BINARY );
cvDilate( thresh_img, thresh_img, 0, dilations );
}
#ifdef DEBUG_CHESSBOARD #ifdef DEBUG_CHESSBOARD
cvCvtColor(thresh_img,dbg_img,CV_GRAY2BGR); cvCvtColor(thresh_img,dbg_img,CV_GRAY2BGR);
#endif #endif
// So we can find rectangles that go to the edge, we draw a white line around the image edge. // 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. // 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()... // 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, cvRectangle( thresh_img, cvPoint(0,0), cvPoint(thresh_img->cols-1,
thresh_img->rows-1), CV_RGB(255,255,255), 3, 8); thresh_img->rows-1), CV_RGB(255,255,255), 3, 8);
quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags, &max_quad_buf_size); quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags, &max_quad_buf_size);
PRINTF("Quad count: %d/%d\n", quad_count, expected_corners_num);
PRINTF("Quad count: %d/%d\n", quad_count, expected_corners_num); #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 z=0; z<4; z++)
{
CvPoint2D32f pt1, pt2;
CvScalar color = CV_RGB(30,255,30);
pt1 = quads[i].corners[z]->pt;
pt2 = quads[i].corners[(z+1)%4]->pt;
pt2.x = (pt1.x + pt2.x)/2;
pt2.y = (pt1.y + pt2.y)/2;
if (z>0)
color = CV_RGB(200,200,0);
cvLine( dbg1_img, cvPointFrom32f(pt1), cvPointFrom32f(pt2), color, 3, 8);
}
}
cvShowImage("all_quads", (IplImage*)dbg1_img);
cvWaitKey();
#endif
if( quad_count <= 0 )
{
continue;
}
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
// 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( 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;
// 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);
#ifdef DEBUG_CHESSBOARD #ifdef DEBUG_CHESSBOARD
cvCopy(dbg_img, dbg1_img); cvCopy(dbg_img,dbg2_img);
cvNamedWindow("all_quads", 1); cvNamedWindow("connected_group", 1);
// copy corners to temp array // copy corners to temp array
for(int i = 0; i < quad_count; i++ ) for(int i = 0; i < quad_count; i++ )
{ {
for (int k=0; k<4; k++) if (quads[i].group_idx == group_idx)
{ for (int z=0; z<4; z++)
CvPoint2D32f pt1, pt2; {
CvScalar color = CV_RGB(30,255,30); CvPoint2D32f pt1, pt2;
pt1 = quads[i].corners[k]->pt; CvScalar color = CV_RGB(30,255,30);
pt2 = quads[i].corners[(k+1)%4]->pt; if (quads[i].ordered)
pt2.x = (pt1.x + pt2.x)/2; color = CV_RGB(255,30,30);
pt2.y = (pt1.y + pt2.y)/2; pt1 = quads[i].corners[z]->pt;
if (k>0) pt2 = quads[i].corners[(z+1)%4]->pt;
color = CV_RGB(200,200,0); pt2.x = (pt1.x + pt2.x)/2;
cvLine( dbg1_img, cvPointFrom32f(pt1), cvPointFrom32f(pt2), color, 3, 8); pt2.y = (pt1.y + pt2.y)/2;
} if (z>0)
} color = CV_RGB(200,200,0);
cvLine( dbg2_img, cvPointFrom32f(pt1), cvPointFrom32f(pt2), color, 3, 8);
}
cvShowImage("all_quads", (IplImage*)dbg1_img); }
cvWaitKey(); cvShowImage("connected_group", (IplImage*)dbg2_img);
cvWaitKey();
#endif #endif
if( quad_count <= 0 ) if (count == 0)
continue; continue; // haven't found inner quads
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
// allocate extra for adding in icvOrderFoundQuads // If count is more than it should be, this will remove those quads
cvFree(&quad_group); // which cause maximum deviation from a nice square pattern.
cvFree(&corner_group); count = icvCleanFoundConnectedQuads( count, quad_group, pattern_size );
quad_group = (CvCBQuad**)cvAlloc( sizeof(quad_group[0]) * max_quad_buf_size); PRINTF("Connected group: %d orig count: %d cleaned: %d\n", group_idx, icount, count);
corner_group = (CvCBCorner**)cvAlloc( sizeof(corner_group[0]) * max_quad_buf_size * 4 );
for( group_idx = 0; ; group_idx++ ) count = icvCheckQuadGroup( quad_group, count, corner_group, pattern_size );
PRINTF("Connected group: %d count: %d cleaned: %d\n", group_idx, 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;
for(int i = 0; i < n; i++ )
{ {
int count = 0; int ni = 0;
count = icvFindConnectedQuads( quads, quad_count, quad_group, group_idx, storage ); float avgi = corner_group[i]->meanDist(&ni);
sum_dist += avgi*ni;
int icount = count; total += ni;
if( count == 0 )
break;
// 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);
#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
if (count == 0)
continue; // haven't found inner quads
// 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);
count = icvCheckQuadGroup( quad_group, count, corner_group, pattern_size );
PRINTF("Connected group: %d count: %d cleaned: %d\n", group_idx, 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;
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;
}
}
}
} }
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 }//dilations
}// }// for k = 0 -> 6
}
if( found ) if( found )
found = icvCheckBoardMonotony( out_corners, pattern_size ); found = icvCheckBoardMonotony( out_corners, pattern_size );
@ -559,6 +865,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
cvFree(&corners); cvFree(&corners);
cvFree(&quad_group); cvFree(&quad_group);
cvFree(&corner_group); cvFree(&corner_group);
cvFree(&cImgSeg);
throw; throw;
} }
@ -566,6 +873,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
cvFree(&corners); cvFree(&corners);
cvFree(&quad_group); cvFree(&quad_group);
cvFree(&corner_group); cvFree(&corner_group);
cvFree(&cImgSeg);
return found; return found;
} }

View File

@ -59,8 +59,8 @@
static void icvGetQuadrangleHypotheses(CvSeq* contours, std::vector<std::pair<float, int> >& quads, int class_id) static void icvGetQuadrangleHypotheses(CvSeq* contours, std::vector<std::pair<float, int> >& quads, int class_id)
{ {
const float min_aspect_ratio = 0.3f; const float min_aspect_ratio = 0.2f;
const float max_aspect_ratio = 3.0f; const float max_aspect_ratio = 5.0f;
const float min_box_size = 10.0f; const float min_box_size = 10.0f;
for(CvSeq* seq = contours; seq != NULL; seq = seq->h_next) for(CvSeq* seq = contours; seq != NULL; seq = seq->h_next)
@ -205,3 +205,97 @@ int cvCheckChessboard(IplImage* src, CvSize size)
return result; 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;
}