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:
parent
3590c3c48e
commit
74b83cfce5
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user