Commit 74b83cfc authored by Oliver Schreer's avatar Oliver Schreer Committed by Maksim Shabunin

Modified and improved the method for chessboard detection. It is now faster and…

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
parent 3590c3c4
......@@ -46,3 +46,12 @@
#endif
#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);
......@@ -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 "opencv2/imgproc/imgproc_c.h"
#include "opencv2/calib3d/calib3d_c.h"
#include "circlesgrid.hpp"
#include <stdarg.h>
#include <vector>
//#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 );
/***************************************************************************************************/
//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
static void
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;
CvCBQuad *quads = 0, **quad_group = 0;
CvCBCorner *corners = 0, **corner_group = 0;
IplImage* cImgSeg = 0;
try
{
......@@ -247,6 +455,11 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
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 +468,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 );
......@@ -300,209 +512,303 @@ 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 );
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.
// 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 );
//USE BINARY IMAGE COMPUTED USING icvBinarizationHistogramBased METHOD
cvDilate( thresh_img_new, thresh_img_new, 0, 1 );
// empiric threshold level
double mean = cvAvg( img ).val[0];
int thresh_level = cvRound( mean - 10 );
thresh_level = MAX( thresh_level, 10 );
// 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);
cvThreshold( img, thresh_img, thresh_level, 255, CV_THRESH_BINARY );
cvDilate( thresh_img, thresh_img, 0, dilations );
}
if( quad_count <= 0 )
{
continue;
}
#ifdef DEBUG_CHESSBOARD
cvCvtColor(thresh_img,dbg_img,CV_GRAY2BGR);
#endif
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
// 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);
// 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 );
quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags, &max_quad_buf_size);
for( group_idx = 0; ; group_idx++ )
{
int count = 0;
count = icvFindConnectedQuads( quads, quad_count, quad_group, group_idx, storage );
PRINTF("Quad count: %d/%d\n", quad_count, expected_corners_num);
}
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;
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 );
}
#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);
}
}
cvCvtColor(thresh_img,dbg_img,CV_GRAY2BGR);
#endif
// 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);
cvShowImage("all_quads", (IplImage*)dbg1_img);
cvWaitKey();
quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags, &max_quad_buf_size);
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;
if( quad_count <= 0 )
{
continue;
}
// Find quad's neighbors
icvFindQuadNeighbors( quads, quad_count );
// 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 );
// 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 );
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;
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);
// 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();
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 z=0; z<4; z++)
{
CvPoint2D32f pt1, pt2;
CvScalar color = CV_RGB(30,255,30);
if (quads[i].ordered)
color = CV_RGB(255,30,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( 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 == 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);
// 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);
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;
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));
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 > 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( out_corner_count )
*out_corner_count = n;
if( count == pattern_size.width*pattern_size.height &&
icvCheckBoardMonotony( out_corners, pattern_size ))
{
found = 1;
break;
}
}
}
if( count == pattern_size.width*pattern_size.height && icvCheckBoardMonotony( out_corners, pattern_size ))
{
found = 1;
break;
}
}
}
}//dilations
}//
}// for k = 0 -> 6
}
if( found )
found = icvCheckBoardMonotony( out_corners, pattern_size );
......@@ -559,6 +865,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
cvFree(&corners);
cvFree(&quad_group);
cvFree(&corner_group);
cvFree(&cImgSeg);
throw;
}
......@@ -566,6 +873,7 @@ int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
cvFree(&corners);
cvFree(&quad_group);
cvFree(&corner_group);
cvFree(&cImgSeg);
return found;
}
......
......@@ -59,8 +59,8 @@
static void icvGetQuadrangleHypotheses(CvSeq* contours, std::vector<std::pair<float, int> >& quads, int class_id)
{
const float min_aspect_ratio = 0.3f;
const float max_aspect_ratio = 3.0f;
const float min_aspect_ratio = 0.2f;
const float max_aspect_ratio = 5.0f;
const float min_box_size = 10.0f;
for(CvSeq* seq = contours; seq != NULL; seq = seq->h_next)
......@@ -205,3 +205,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;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment