mirror of
https://github.com/opencv/opencv.git
synced 2025-01-18 06:03:15 +08:00
Merge pull request #6895 from mshabunin:check-6851
This commit is contained in:
commit
5d3860703d
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
cvSolve( xy, uv, affine_trans, CV_SVD );
|
||||
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;
|
||||
}
|
||||
#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++ )
|
||||
{
|
||||
if (found)
|
||||
break; // already found it
|
||||
|
||||
cvFree(&quads);
|
||||
cvFree(&corners);
|
||||
|
||||
int max_quad_buf_size = 0;
|
||||
|
||||
//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, max_quad_buf_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
|
||||
|
||||
PRINTF("Chessboard detection result 0: %d\n", found);
|
||||
|
||||
// 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
|
||||
if (found)
|
||||
break; // already found it
|
||||
|
||||
cvFree(&quads);
|
||||
cvFree(&corners);
|
||||
cvFree(&quads);
|
||||
cvFree(&corners);
|
||||
|
||||
/*if( k == 1 )
|
||||
// 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 );
|
||||
|
||||
// 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);
|
||||
|
||||
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++ )
|
||||
{
|
||||
//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);
|
||||
int ni = 0;
|
||||
float avgi = corner_group[i]->meanDist(&ni);
|
||||
sum_dist += avgi*ni;
|
||||
total += ni;
|
||||
}
|
||||
else*/
|
||||
prev_sqr_size = cvRound(sum_dist/MAX(total, 1));
|
||||
|
||||
if( count > 0 || (out_corner_count && -count > *out_corner_count) )
|
||||
{
|
||||
// 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;
|
||||
// copy corners to output array
|
||||
for(int i = 0; i < n; i++ )
|
||||
out_corners[i] = corner_group[i]->pt;
|
||||
|
||||
// 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 );
|
||||
if( out_corner_count )
|
||||
*out_corner_count = n;
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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…
Reference in New Issue
Block a user