blob: bc43238564f90f0dc0d97067bca4460e840ace76 [file] [log] [blame]
//M*//////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of Intel Corporation may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
/************************************************************************************\
This is improved variant of chessboard corner detection algorithm that
uses a graph of connected quads. It is based on the code contributed
by Vladimir Vezhnevets and Philip Gruebele.
Here is the copyright notice from the original Vladimir's code:
===============================================================
The algorithms developed and implemented by Vezhnevets Vldimir
aka Dead Moroz (vvp@graphics.cs.msu.ru)
See http://graphics.cs.msu.su/en/research/calibration/opencv.html
for detailed information.
Reliability additions and modifications made by Philip Gruebele.
<a href="mailto:pgruebele@cox.net">pgruebele@cox.net</a>
Some further improvements for detection of partially ocluded boards at non-ideal
lighting conditions have been made by Alex Bovyrin and Kurt Kolonige
\************************************************************************************/
#include "_cv.h"
#include <stdarg.h>
//#define DEBUG_CHESSBOARD
#ifdef DEBUG_CHESSBOARD
static int PRINTF( const char* fmt, ... )
{
va_list args;
va_start(args, fmt);
return vprintf(fmt, args);
}
#include "..//..//otherlibs/highgui/highgui.h"
#else
static int PRINTF( const char*, ... )
{
return 0;
}
#endif
//=====================================================================================
// Implementation for the enhanced calibration object detection
//=====================================================================================
#define MAX_CONTOUR_APPROX 7
typedef struct CvContourEx
{
CV_CONTOUR_FIELDS()
int counter;
}
CvContourEx;
//=====================================================================================
/// Corner info structure
/** This structure stores information about the chessboard corner.*/
typedef struct CvCBCorner
{
CvPoint2D32f pt; // Coordinates of the corner
int row; // Board row index
int count; // Number of neighbor corners
struct CvCBCorner* neighbors[4]; // Neighbor corners
}
CvCBCorner;
//=====================================================================================
/// Quadrangle contour info structure
/** This structure stores information about the chessboard quadrange.*/
typedef struct CvCBQuad
{
int count; // Number of quad neighbors
int group_idx; // quad group ID
int row, col; // row and column of this quad
bool ordered; // true if corners/neighbors are ordered counter-clockwise
float edge_len; // quad edge len, in pix^2
// neighbors and corners are synced, i.e., neighbor 0 shares corner 0
CvCBCorner *corners[4]; // Coordinates of quad corners
struct CvCBQuad *neighbors[4]; // Pointers of quad neighbors
}
CvCBQuad;
//=====================================================================================
//static CvMat* debug_img = 0;
static int icvGenerateQuads( CvCBQuad **quads, CvCBCorner **corners,
CvMemStorage *storage, CvMat *image, int flags );
static int
icvGenerateQuadsEx( CvCBQuad **out_quads, CvCBCorner **out_corners,
CvMemStorage *storage, CvMat *image, CvMat *thresh_img, int dilation, int flags );
static void icvFindQuadNeighbors( CvCBQuad *quads, int quad_count );
static int icvFindConnectedQuads( CvCBQuad *quads, int quad_count,
CvCBQuad **quad_group, int group_idx,
CvMemStorage* storage );
static int icvCheckQuadGroup( CvCBQuad **quad_group, int count,
CvCBCorner **out_corners, CvSize pattern_size );
static int icvCleanFoundConnectedQuads( int quad_count,
CvCBQuad **quads, CvSize pattern_size );
static int icvOrderFoundConnectedQuads( int quad_count, CvCBQuad **quads,
int *all_count, CvCBQuad **all_quads, CvCBCorner **corners,
CvSize pattern_size, CvMemStorage* storage );
static void icvOrderQuad(CvCBQuad *quad, CvCBCorner *corner, int common);
static int icvTrimCol(CvCBQuad **quads, int count, int col, int dir);
static int icvTrimRow(CvCBQuad **quads, int count, int row, int dir);
static int icvAddOuterQuad(CvCBQuad *quad, CvCBQuad **quads, int quad_count,
CvCBQuad **all_quads, int all_count, CvCBCorner **corners);
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 i, j;
int real_count = 0;
for( j = 0; j < count; j++ )
{
if( pts1[j].x >= 0 ) real_count++;
}
if(real_count < 3) return;
CvMat* xy = cvCreateMat( 2*real_count, 6, CV_32FC1 );
CvMat* uv = cvCreateMat( 2*real_count, 1, CV_32FC1 );
//estimate affine transfromation
for( i = 0, j = 0; j < count; j++ )
{
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++;
}
}
cvSolve( xy, uv, affine_trans, CV_SVD );
cvReleaseMat(&xy);
cvReleaseMat(&uv);
}
#endif
CV_IMPL
int cvFindChessboardCorners( const void* arr, CvSize pattern_size,
CvPoint2D32f* out_corners, int* out_corner_count,
int flags )
{
int k = 0;
const int min_dilations = 0;
const int max_dilations = 3;
int found = 0;
CvMat* norm_img = 0;
CvMat* thresh_img = 0;
#ifdef DEBUG_CHESSBOARD
IplImage *dbg_img = 0;
IplImage *dbg1_img = 0;
IplImage *dbg2_img = 0;
#endif
CvMemStorage* storage = 0;
CvCBQuad *quads = 0, **quad_group = 0;
CvCBCorner *corners = 0, **corner_group = 0;
int expected_corners_num = (pattern_size.width/2+1)*(pattern_size.height/2+1);
if( out_corner_count )
*out_corner_count = 0;
CV_FUNCNAME( "cvFindChessBoardCornerGuesses2" );
__BEGIN__;
int quad_count = 0, group_idx = 0, i = 0, dilations = 0;
CvMat stub, *img = (CvMat*)arr;
CV_CALL( img = cvGetMat( img, &stub ));
//debug_img = img;
if( CV_MAT_DEPTH( img->type ) != CV_8U || CV_MAT_CN( img->type ) == 2 )
CV_ERROR( CV_StsUnsupportedFormat, "Only 8-bit grayscale or color images are supported" );
if( pattern_size.width <= 2 || pattern_size.height <= 2 )
CV_ERROR( CV_StsOutOfRange, "Both width and height of the pattern should have bigger than 2" );
if( !out_corners )
CV_ERROR( CV_StsNullPtr, "Null pointer to corners" );
CV_CALL( storage = cvCreateMemStorage(0) );
CV_CALL( thresh_img = cvCreateMat( img->rows, img->cols, CV_8UC1 ));
#ifdef DEBUG_CHESSBOARD
CV_CALL( dbg_img = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3 ));
CV_CALL( dbg1_img = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3 ));
CV_CALL( 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 -
// that should make the contrast between "black" and "white" areas big enough
CV_CALL( norm_img = cvCreateMat( img->rows, img->cols, CV_8UC1 ));
if( CV_MAT_CN(img->type) != 1 )
{
CV_CALL( cvCvtColor( img, norm_img, CV_BGR2GRAY ));
img = norm_img;
}
if( flags & CV_CALIB_CB_NORMALIZE_IMAGE )
{
cvEqualizeHist( img, norm_img );
img = norm_img;
}
}
// 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 < 2; k++ )
{
for( dilations = min_dilations; dilations <= max_dilations; dilations++ )
{
if (found) break; // already found it
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(MIN(img->cols,img->rows)*0.2)|1;
// convert to binary
cvAdaptiveThreshold( img, thresh_img, 255,
CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, block_size, 0 );
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 );
// empiric threshold level
double mean = cvMean( img );
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);
CV_CALL( quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags ));
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( 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
CV_CALL( icvFindQuadNeighbors( quads, quad_count ));
// allocate extra for adding in icvOrderFoundQuads
CV_CALL( quad_group = (CvCBQuad**)cvAlloc( sizeof(quad_group[0]) * (quad_count+quad_count / 2)));
CV_CALL( corner_group = (CvCBCorner**)cvAlloc( sizeof(corner_group[0]) * (quad_count+quad_count / 2)*4 ));
for( group_idx = 0; ; group_idx++ )
{
int count = 0;
CV_CALL( 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);
#ifdef DEBUG_CHESSBOARD
cvCopy(dbg_img,dbg2_img);
cvNamedWindow("connected_group", 1);
// copy corners to temp array
for( 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.
CV_CALL( count = icvCleanFoundConnectedQuads( count, quad_group, pattern_size ));
PRINTF("Connected group: %d orig count: %d cleaned: %d\n", group_idx, icount, count);
CV_CALL( count = icvCheckQuadGroup( quad_group, count, corner_group, pattern_size ));
PRINTF("Connected group: %d count: %d cleaned: %d\n", group_idx, icount, count);
if( count > 0 || (out_corner_count && -count > *out_corner_count) )
{
int n = count > 0 ? pattern_size.width * pattern_size.height : -count;
n = MIN( n, pattern_size.width * pattern_size.height );
// copy corners to output array
for( i = 0; i < n; i++ )
out_corners[i] = corner_group[i]->pt;
if( out_corner_count )
*out_corner_count = n;
if( count > 0 )
{
found = 1;
break;
}
}
}
cvFree( &quads );
cvFree( &corners );
cvFree( &quad_group );
cvFree( &corner_group );
}//dilations
}//
__END__;
cvReleaseMemStorage( &storage );
cvReleaseMat( &norm_img );
cvReleaseMat( &thresh_img );
cvFree( &quads );
cvFree( &corners );
if( found )
found = icvCheckBoardMonotony( out_corners, pattern_size );
if( found && 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 i, n = pattern_size.width*pattern_size.height;
for( i = 0; i < n/2; i++ )
{
CvPoint2D32f temp;
CV_SWAP(out_corners[i], out_corners[n-i-1], temp);
}
}
}
return found;
}
//
// Checks that each board row and column is pretty much monotonous curve:
// It analyzes each row and each column of the chessboard as following:
// for each corner c lying between end points in the same row/column it checks that
// the point projection to the line segment (a,b) is lying between projections
// of the neighbor corners in the same row/column.
//
// This function has been created as temporary workaround for the bug in current implementation
// of cvFindChessboardCornes that produces absolutely unordered sets of corners.
//
static int
icvCheckBoardMonotony( CvPoint2D32f* corners, CvSize pattern_size )
{
int i, j, k;
for( k = 0; k < 2; k++ )
{
for( i = 0; i < (k == 0 ? pattern_size.height : pattern_size.width); i++ )
{
CvPoint2D32f a = k == 0 ? corners[i*pattern_size.width] : corners[i];
CvPoint2D32f b = k == 0 ? corners[(i+1)*pattern_size.width-1] :
corners[(pattern_size.height-1)*pattern_size.width + i];
float prevt = 0, dx0 = b.x - a.x, dy0 = b.y - a.y;
if( fabs(dx0) + fabs(dy0) < FLT_EPSILON )
return 0;
for( j = 1; j < (k == 0 ? pattern_size.width : pattern_size.height) - 1; j++ )
{
CvPoint2D32f c = k == 0 ? corners[i*pattern_size.width + j] :
corners[j*pattern_size.width + i];
float t = ((c.x - a.x)*dx0 + (c.y - a.y)*dy0)/(dx0*dx0 + dy0*dy0);
if( t < prevt || t > 1 )
return 0;
prevt = t;
}
}
}
return 1;
}
//
// order a group of connected quads
// order of corners:
// 0 is top left
// clockwise from there
// note: "top left" is nominal, depends on initial ordering of starting quad
// but all other quads are ordered consistently
//
// can change the number of quads in the group
// can add quads, so we need to have quad/corner arrays passed in
//
static int
icvOrderFoundConnectedQuads( int quad_count, CvCBQuad **quads,
int *all_count, CvCBQuad **all_quads, CvCBCorner **corners,
CvSize pattern_size, CvMemStorage* storage )
{
CvMemStorage* temp_storage = cvCreateChildMemStorage( storage );
CvSeq* stack = cvCreateSeq( 0, sizeof(*stack), sizeof(void*), temp_storage );
int i;
// first find an interior quad
CvCBQuad *start = NULL;
for (i=0; i<quad_count; i++)
{
if (quads[i]->count == 4)
{
start = quads[i];
break;
}
}
if (start == NULL)
{
cvReleaseMemStorage( &temp_storage );
return 0; // no 4-connected quad
}
// start with first one, assign rows/cols
int row_min = 0, col_min = 0, row_max=0, col_max = 0;
#define HSIZE 20
int col_hist[HSIZE*2];
int row_hist[HSIZE*2]; // bad programming, should allow variable size
for (i=0; i<HSIZE*2; i++) // init to zero
col_hist[i] = row_hist[i] = 0;
cvSeqPush(stack, &start);
start->row = 0;
start->col = 0;
start->ordered = true;
// Recursively order the quads so that all position numbers (e.g.,
// 0,1,2,3) are in the at the same relative corner (e.g., lower right).
while( stack->total )
{
CvCBQuad* q;
cvSeqPop( stack, &q );
int col = q->col;
int row = q->row;
col_hist[col+HSIZE]++;
row_hist[row+HSIZE]++;
// check min/max
if (row > row_max) row_max = row;
if (row < row_min) row_min = row;
if (col > col_max) col_max = col;
if (col < col_min) col_min = col;
for(int i = 0; i < 4; i++ )
{
CvCBQuad *neighbor = q->neighbors[i];
switch(i) // adjust col, row for this quad
{ // start at top left, go clockwise
case 0:
row--; col--; break;
case 1:
col += 2; break;
case 2:
row += 2; break;
case 3:
col -= 2; break;
}
// just do inside quads
if (neighbor && neighbor->ordered == false && neighbor->count == 4)
{
PRINTF("col: %d row: %d\n", col, row);
icvOrderQuad(neighbor, q->corners[i], (i+2)%4); // set in order
neighbor->ordered = true;
neighbor->row = row;
neighbor->col = col;
cvSeqPush( stack, &neighbor );
}
}
}
cvReleaseMemStorage( &temp_storage );
for (i=col_min; i<=col_max; i++)
PRINTF("HIST[%d] = %d\n", i, col_hist[i+HSIZE]);
// analyze inner quad structure
int w = pattern_size.width - 1;
int h = pattern_size.height - 1;
int drow = row_max - row_min + 1;
int dcol = col_max - col_min + 1;
// normalize pattern and found quad indices
if ((w > h && dcol < drow) ||
(w < h && drow < dcol))
{
h = pattern_size.width - 1;
w = pattern_size.height - 1;
}
PRINTF("Size: %dx%d Pattern: %dx%d\n", dcol, drow, w, h);
// check if there are enough inner quads
if (dcol < w || drow < h) // found enough inner quads?
{
PRINTF("Too few inner quad rows/cols\n");
return 0; // no, return
}
// too many columns, not very common
if (dcol == w+1) // too many, trim
{
PRINTF("Trimming cols\n");
if (col_hist[col_max+HSIZE] > col_hist[col_min+HSIZE])
{
PRINTF("Trimming left col\n");
quad_count = icvTrimCol(quads,quad_count,col_min,-1);
}
else
{
PRINTF("Trimming right col\n");
quad_count = icvTrimCol(quads,quad_count,col_max,+1);
}
}
// too many rows, not very common
if (drow == h+1) // too many, trim
{
PRINTF("Trimming rows\n");
if (row_hist[row_max+HSIZE] > row_hist[row_min+HSIZE])
{
PRINTF("Trimming top row\n");
quad_count = icvTrimRow(quads,quad_count,row_min,-1);
}
else
{
PRINTF("Trimming bottom row\n");
quad_count = icvTrimRow(quads,quad_count,row_max,+1);
}
}
// check edges of inner quads
// if there is an outer quad missing, fill it in
// first order all inner quads
int found = 0;
for (i=0; i<quad_count; i++)
{
if (quads[i]->count == 4)
{ // ok, look at neighbors
int col = quads[i]->col;
int row = quads[i]->row;
for (int j=0; j<4; j++)
{
switch(j) // adjust col, row for this quad
{ // start at top left, go clockwise
case 0:
row--; col--; break;
case 1:
col += 2; break;
case 2:
row += 2; break;
case 3:
col -= 2; break;
}
CvCBQuad *neighbor = quads[i]->neighbors[j];
if (neighbor && !neighbor->ordered && // is it an inner quad?
col <= col_max && col >= col_min &&
row <= row_max && row >= row_min)
{
// if so, set in order
PRINTF("Adding inner: col: %d row: %d\n", col, row);
found++;
icvOrderQuad(neighbor, quads[i]->corners[j], (j+2)%4);
neighbor->ordered = true;
neighbor->row = row;
neighbor->col = col;
}
}
}
}
// if we have found inner quads, add corresponding outer quads,
// which are missing
if (found > 0)
{
PRINTF("Found %d inner quads not connected to outer quads, repairing\n", found);
for (int i=0; i<quad_count; i++)
{
if (quads[i]->count < 4 && quads[i]->ordered)
{
int added = icvAddOuterQuad(quads[i],quads,quad_count,all_quads,*all_count,corners);
*all_count += added;
quad_count += added;
}
}
}
// final trimming of outer quads
if (dcol == w && drow == h) // found correct inner quads
{
PRINTF("Inner bounds ok, check outer quads\n");
int rcount = quad_count;
for (int i=quad_count-1; i>=0; i--) // eliminate any quad not connected to
// an ordered quad
{
if (quads[i]->ordered == false)
{
bool outer = false;
for (int j=0; j<4; j++) // any neighbors that are ordered?
{
if (quads[i]->neighbors[j] && quads[i]->neighbors[j]->ordered)
outer = true;
}
if (!outer) // not an outer quad, eliminate
{
PRINTF("Removing quad %d\n", i);
icvRemoveQuadFromGroup(quads,rcount,quads[i]);
rcount--;
}
}
}
return rcount;
}
return 0;
}
// add an outer quad
// looks for the neighbor of <quad> that isn't present,
// tries to add it in.
// <quad> is ordered
static int
icvAddOuterQuad( CvCBQuad *quad, CvCBQuad **quads, int quad_count,
CvCBQuad **all_quads, int all_count, CvCBCorner **corners )
{
int added = 0;
for (int i=0; i<4; i++) // find no-neighbor corners
{
if (!quad->neighbors[i]) // ok, create and add neighbor
{
int j = (i+2)%4;
PRINTF("Adding quad as neighbor 2\n");
CvCBQuad *q = &(*all_quads)[all_count];
memset( q, 0, sizeof(*q) );
added++;
quads[quad_count] = q;
quad_count++;
// set neighbor and group id
quad->neighbors[i] = q;
quad->count += 1;
q->neighbors[j] = quad;
q->group_idx = quad->group_idx;
q->count = 1; // number of neighbors
q->ordered = false;
q->edge_len = quad->edge_len;
// make corners of new quad
// same as neighbor quad, but offset
CvPoint2D32f pt = quad->corners[i]->pt;
CvCBCorner* corner;
float dx = pt.x - quad->corners[j]->pt.x;
float dy = pt.y - quad->corners[j]->pt.y;
for (int k=0; k<4; k++)
{
corner = &(*corners)[all_count*4+k];
pt = quad->corners[k]->pt;
memset( corner, 0, sizeof(*corner) );
corner->pt = pt;
q->corners[k] = corner;
corner->pt.x += dx;
corner->pt.y += dy;
}
// have to set exact corner
q->corners[j] = quad->corners[i];
// now find other neighbor and add it, if possible
if (quad->neighbors[(i+3)%4] &&
quad->neighbors[(i+3)%4]->ordered &&
quad->neighbors[(i+3)%4]->neighbors[i] &&
quad->neighbors[(i+3)%4]->neighbors[i]->ordered )
{
CvCBQuad *qn = quad->neighbors[(i+3)%4]->neighbors[i];
q->count = 2;
q->neighbors[(j+1)%4] = qn;
qn->neighbors[(i+1)%4] = q;
qn->count += 1;
// have to set exact corner
q->corners[(j+1)%4] = qn->corners[(i+1)%4];
}
all_count++;
}
}
return added;
}
// trimming routines
static int
icvTrimCol(CvCBQuad **quads, int count, int col, int dir)
{
int rcount = count;
// find the right quad(s)
for (int i=0; i<count; i++)
{
#ifdef DEBUG_CHESSBOARD
if (quads[i]->ordered)
PRINTF("index: %d cur: %d\n", col, quads[i]->col);
#endif
if (quads[i]->ordered && quads[i]->col == col)
{
if (dir == 1)
{
if (quads[i]->neighbors[1])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[1]);
rcount--;
}
if (quads[i]->neighbors[2])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[2]);
rcount--;
}
}
else
{
if (quads[i]->neighbors[0])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[0]);
rcount--;
}
if (quads[i]->neighbors[3])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[3]);
rcount--;
}
}
}
}
return rcount;
}
static int
icvTrimRow(CvCBQuad **quads, int count, int row, int dir)
{
int i, rcount = count;
// find the right quad(s)
for (i=0; i<count; i++)
{
#ifdef DEBUG_CHESSBOARD
if (quads[i]->ordered)
PRINTF("index: %d cur: %d\n", row, quads[i]->row);
#endif
if (quads[i]->ordered && quads[i]->row == row)
{
if (dir == 1) // remove from bottom
{
if (quads[i]->neighbors[2])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[2]);
rcount--;
}
if (quads[i]->neighbors[3])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[3]);
rcount--;
}
}
else // remove from top
{
if (quads[i]->neighbors[0])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[0]);
rcount--;
}
if (quads[i]->neighbors[1])
{
icvRemoveQuadFromGroup(quads,rcount,quads[i]->neighbors[1]);
rcount--;
}
}
}
}
return rcount;
}
//
// remove quad from quad group
//
static void
icvRemoveQuadFromGroup(CvCBQuad **quads, int count, CvCBQuad *q0)
{
int i, j;
// remove any references to this quad as a neighbor
for(i = 0; i < count; i++ )
{
CvCBQuad *q = quads[i];
for(j = 0; j < 4; j++ )
{
if( q->neighbors[j] == q0 )
{
q->neighbors[j] = 0;
q->count--;
for(int k = 0; k < 4; k++ )
if( q0->neighbors[k] == q )
{
q0->neighbors[k] = 0;
q0->count--;
break;
}
break;
}
}
}
// remove the quad
for(i = 0; i < count; i++ )
{
CvCBQuad *q = quads[i];
if (q == q0)
{
quads[i] = quads[count-1];
break;
}
}
}
//
// put quad into correct order, where <corner> has value <common>
//
static void
icvOrderQuad(CvCBQuad *quad, CvCBCorner *corner, int common)
{
// find the corner
int tc;
for (tc=0; tc<4; tc++)
if (quad->corners[tc]->pt.x == corner->pt.x &&
quad->corners[tc]->pt.y == corner->pt.y)
break;
// set corner order
// shift
while (tc != common)
{
// shift by one
CvCBCorner *tempc;
CvCBQuad *tempq;
tempc = quad->corners[3];
tempq = quad->neighbors[3];
for (int i=3; i>0; i--)
{
quad->corners[i] = quad->corners[i-1];
quad->neighbors[i] = quad->neighbors[i-1];
}
quad->corners[0] = tempc;
quad->neighbors[0] = tempq;
tc++;
tc = tc%4;
}
}
// if we found too many connect quads, remove those which probably do not belong.
static int
icvCleanFoundConnectedQuads( int quad_count, CvCBQuad **quad_group, CvSize pattern_size )
{
CvMemStorage *temp_storage = 0;
CvPoint2D32f *centers = 0;
CV_FUNCNAME( "icvCleanFoundConnectedQuads" );
__BEGIN__;
CvPoint2D32f center = {0,0};
int i, j, k;
// number of quads this pattern should contain
int count = ((pattern_size.width + 1)*(pattern_size.height + 1) + 1)/2;
// if we have more quadrangles than we should,
// try to eliminate duplicates or ones which don't belong to the pattern rectangle...
if( quad_count <= count )
EXIT;
// create an array of quadrangle centers
CV_CALL( centers = (CvPoint2D32f *)cvAlloc( sizeof(centers[0])*quad_count ));
CV_CALL( temp_storage = cvCreateMemStorage(0));
for( i = 0; i < quad_count; i++ )
{
CvPoint2D32f ci = {0,0};
CvCBQuad* q = quad_group[i];
for( j = 0; j < 4; j++ )
{
CvPoint2D32f pt = q->corners[j]->pt;
ci.x += pt.x;
ci.y += pt.y;
}
ci.x *= 0.25f;
ci.y *= 0.25f;
centers[i] = ci;
center.x += ci.x;
center.y += ci.y;
}
center.x /= quad_count;
center.y /= quad_count;
// If we still have more quadrangles than we should,
// we try to eliminate bad ones based on minimizing the bounding box.
// We iteratively remove the point which reduces the size of
// the bounding box of the blobs the most
// (since we want the rectangle to be as small as possible)
// remove the quadrange that causes the biggest reduction
// in pattern size until we have the correct number
for( ; quad_count > count; quad_count-- )
{
double min_box_area = DBL_MAX;
int skip, min_box_area_index = -1;
CvCBQuad *q0, *q;
// For each point, calculate box area without that point
for( skip = 0; skip < quad_count; skip++ )
{
// get bounding rectangle
CvPoint2D32f temp = centers[skip]; // temporarily make index 'skip' the same as
centers[skip] = center; // pattern center (so it is not counted for convex hull)
CvMat pointMat = cvMat(1, quad_count, CV_32FC2, centers);
CvSeq *hull = cvConvexHull2( &pointMat, temp_storage, CV_CLOCKWISE, 1 );
centers[skip] = temp;
double hull_area = fabs(cvContourArea(hull, CV_WHOLE_SEQ));
// remember smallest box area
if( hull_area < min_box_area )
{
min_box_area = hull_area;
min_box_area_index = skip;
}
cvClearMemStorage( temp_storage );
}
q0 = quad_group[min_box_area_index];
// remove any references to this quad as a neighbor
for( i = 0; i < quad_count; i++ )
{
q = quad_group[i];
for( j = 0; j < 4; j++ )
{
if( q->neighbors[j] == q0 )
{
q->neighbors[j] = 0;
q->count--;
for( k = 0; k < 4; k++ )
if( q0->neighbors[k] == q )
{
q0->neighbors[k] = 0;
q0->count--;
break;
}
break;
}
}
}
// remove the quad
quad_count--;
quad_group[min_box_area_index] = quad_group[quad_count];
centers[min_box_area_index] = centers[quad_count];
}
__END__;
cvReleaseMemStorage( &temp_storage );
cvFree( &centers );
return quad_count;
}
//=====================================================================================
static int
icvFindConnectedQuads( CvCBQuad *quad, int quad_count, CvCBQuad **out_group,
int group_idx, CvMemStorage* storage )
{
CvMemStorage* temp_storage = cvCreateChildMemStorage( storage );
CvSeq* stack = cvCreateSeq( 0, sizeof(*stack), sizeof(void*), temp_storage );
int i, count = 0;
// Scan the array for a first unlabeled quad
for( i = 0; i < quad_count; i++ )
{
if( quad[i].count > 0 && quad[i].group_idx < 0)
break;
}
// Recursively find a group of connected quads starting from the seed quad[i]
if( i < quad_count )
{
CvCBQuad* q = &quad[i];
cvSeqPush( stack, &q );
out_group[count++] = q;
q->group_idx = group_idx;
q->ordered = false;
while( stack->total )
{
cvSeqPop( stack, &q );
for( i = 0; i < 4; i++ )
{
CvCBQuad *neighbor = q->neighbors[i];
if( neighbor && neighbor->count > 0 && neighbor->group_idx < 0 )
{
cvSeqPush( stack, &neighbor );
out_group[count++] = neighbor;
neighbor->group_idx = group_idx;
neighbor->ordered = false;
}
}
}
}
cvReleaseMemStorage( &temp_storage );
return count;
}
//=====================================================================================
static int
icvCheckQuadGroup( CvCBQuad **quad_group, int quad_count,
CvCBCorner **out_corners, CvSize pattern_size )
{
const int ROW1 = 1000000;
const int ROW2 = 2000000;
const int ROW_ = 3000000;
int result = 0;
int i, out_corner_count = 0, corner_count = 0;
CvCBCorner** corners = 0;
CV_FUNCNAME( "icvCheckQuadGroup" );
__BEGIN__;
int j, k, kk;
int width = 0, height = 0;
int hist[5] = {0,0,0,0,0};
CvCBCorner* first = 0, *first2 = 0, *right, *cur, *below, *c;
CV_CALL( corners = (CvCBCorner**)cvAlloc( quad_count*4*sizeof(corners[0]) ));
// build dual graph, which vertices are internal quad corners
// and two vertices are connected iff they lie on the same quad edge
for( i = 0; i < quad_count; i++ )
{
CvCBQuad* q = quad_group[i];
/*CvScalar color = q->count == 0 ? cvScalar(0,255,255) :
q->count == 1 ? cvScalar(0,0,255) :
q->count == 2 ? cvScalar(0,255,0) :
q->count == 3 ? cvScalar(255,255,0) :
cvScalar(255,0,0);*/
for( j = 0; j < 4; j++ )
{
//cvLine( debug_img, cvPointFrom32f(q->corners[j]->pt), cvPointFrom32f(q->corners[(j+1)&3]->pt), color, 1, CV_AA, 0 );
if( q->neighbors[j] )
{
CvCBCorner *a = q->corners[j], *b = q->corners[(j+1)&3];
// mark internal corners that belong to:
// - a quad with a single neighbor - with ROW1,
// - a quad with two neighbors - with ROW2
// make the rest of internal corners with ROW_
int row_flag = q->count == 1 ? ROW1 : q->count == 2 ? ROW2 : ROW_;
if( a->row == 0 )
{
corners[corner_count++] = a;
a->row = row_flag;
}
else if( a->row > row_flag )
a->row = row_flag;
if( q->neighbors[(j+1)&3] )
{
if( a->count >= 4 || b->count >= 4 )
EXIT;
for( k = 0; k < 4; k++ )
{
if( a->neighbors[k] == b )
EXIT;
if( b->neighbors[k] == a )
EXIT;
}
a->neighbors[a->count++] = b;
b->neighbors[b->count++] = a;
}
}
}
}
if( corner_count != pattern_size.width*pattern_size.height )
EXIT;
for( i = 0; i < corner_count; i++ )
{
int n = corners[i]->count;
assert( 0 <= n && n <= 4 );
hist[n]++;
if( !first && n == 2 )
{
if( corners[i]->row == ROW1 )
first = corners[i];
else if( !first2 && corners[i]->row == ROW2 )
first2 = corners[i];
}
}
// start with a corner that belongs to a quad with a signle neighbor.
// if we do not have such, start with a corner of a quad with two neighbors.
if( !first )
first = first2;
if( !first || hist[0] != 0 || hist[1] != 0 || hist[2] != 4 ||
hist[3] != (pattern_size.width + pattern_size.height)*2 - 8 )
EXIT;
cur = first;
right = below = 0;
out_corners[out_corner_count++] = cur;
for( k = 0; k < 4; k++ )
{
c = cur->neighbors[k];
if( c )
{
if( !right )
right = c;
else if( !below )
below = c;
}
}
if( !right || (right->count != 2 && right->count != 3) ||
!below || (below->count != 2 && below->count != 3) )
EXIT;
cur->row = 0;
//cvCircle( debug_img, cvPointFrom32f(cur->pt), 3, cvScalar(0,255,0), -1, 8, 0 );
first = below; // remember the first corner in the next row
// find and store the first row (or column)
for(j=1;;j++)
{
right->row = 0;
out_corners[out_corner_count++] = right;
//cvCircle( debug_img, cvPointFrom32f(right->pt), 3, cvScalar(0,255-j*10,0), -1, 8, 0 );
if( right->count == 2 )
break;
if( right->count != 3 || out_corner_count >= MAX(pattern_size.width,pattern_size.height) )
EXIT;
cur = right;
for( k = 0; k < 4; k++ )
{
c = cur->neighbors[k];
if( c && c->row > 0 )
{
for( kk = 0; kk < 4; kk++ )
{
if( c->neighbors[kk] == below )
break;
}
if( kk < 4 )
below = c;
else
right = c;
}
}
}
width = out_corner_count;
if( width == pattern_size.width )
height = pattern_size.height;
else if( width == pattern_size.height )
height = pattern_size.width;
else
EXIT;
// find and store all the other rows
for( i = 1; ; i++ )
{
if( !first )
break;
cur = first;
first = 0;
for( j = 0;; j++ )
{
cur->row = i;
out_corners[out_corner_count++] = cur;
//cvCircle( debug_img, cvPointFrom32f(cur->pt), 3, cvScalar(0,0,255-j*10), -1, 8, 0 );
if( cur->count == 2 + (i < height-1) && j > 0 )
break;
right = 0;
// find a neighbor that has not been processed yet
// and that has a neighbor from the previous row
for( k = 0; k < 4; k++ )
{
c = cur->neighbors[k];
if( c && c->row > i )
{
for( kk = 0; kk < 4; kk++ )
{
if( c->neighbors[kk] && c->neighbors[kk]->row == i-1 )
break;
}
if( kk < 4 )
{
right = c;
if( j > 0 )
break;
}
else if( j == 0 )
first = c;
}
}
if( !right )
EXIT;
cur = right;
}
if( j != width - 1 )
EXIT;
}
if( out_corner_count != corner_count )
EXIT;
// check if we need to transpose the board
if( width != pattern_size.width )
{
CV_SWAP( width, height, k );
memcpy( corners, out_corners, corner_count*sizeof(corners[0]) );
for( i = 0; i < height; i++ )
for( j = 0; j < width; j++ )
out_corners[i*width + j] = corners[j*height + i];
}
// check if we need to revert the order in each row
{
CvPoint2D32f p0 = out_corners[0]->pt, p1 = out_corners[pattern_size.width-1]->pt,
p2 = out_corners[pattern_size.width]->pt;
if( (p1.x - p0.x)*(p2.y - p1.y) - (p1.y - p0.y)*(p2.x - p1.x) < 0 )
{
if( width % 2 == 0 )
{
for( i = 0; i < height; i++ )
for( j = 0; j < width/2; j++ )
CV_SWAP( out_corners[i*width+j], out_corners[i*width+width-j-1], c );
}
else
{
for( j = 0; j < width; j++ )
for( i = 0; i < height/2; i++ )
CV_SWAP( out_corners[i*width+j], out_corners[(height - i - 1)*width+j], c );
}
}
}
result = corner_count;
__END__;
if( result <= 0 && corners )
{
corner_count = MIN( corner_count, pattern_size.width*pattern_size.height );
for( i = 0; i < corner_count; i++ )
out_corners[i] = corners[i];
result = -corner_count;
if (result == -pattern_size.width*pattern_size.height)
result = -result;
}
cvFree( &corners );
return result;
}
//=====================================================================================
static void icvFindQuadNeighbors( CvCBQuad *quads, int quad_count )
{
const float thresh_scale = 1.f;
int idx, i, k, j;
float dx, dy, dist;
// find quad neighbors
for( idx = 0; idx < quad_count; idx++ )
{
CvCBQuad* cur_quad = &quads[idx];
// choose the points of the current quadrangle that are close to
// some points of the other quadrangles
// (it can happen for split corners (due to dilation) of the
// checker board). Search only in other quadrangles!
// for each corner of this quadrangle
for( i = 0; i < 4; i++ )
{
CvPoint2D32f pt;
float min_dist = FLT_MAX;
int closest_corner_idx = -1;
CvCBQuad *closest_quad = 0;
CvCBCorner *closest_corner = 0;
if( cur_quad->neighbors[i] )
continue;
pt = cur_quad->corners[i]->pt;
// find the closest corner in all other quadrangles
for( k = 0; k < quad_count; k++ )
{
if( k == idx )
continue;
for( j = 0; j < 4; j++ )
{
if( quads[k].neighbors[j] )
continue;
dx = pt.x - quads[k].corners[j]->pt.x;
dy = pt.y - quads[k].corners[j]->pt.y;
dist = dx * dx + dy * dy;
if( dist < min_dist &&
dist <= cur_quad->edge_len*thresh_scale &&
dist <= quads[k].edge_len*thresh_scale )
{
// check edge lengths, make sure they're compatible
// edges that are different by more than 1:4 are rejected
float ediff = cur_quad->edge_len - quads[k].edge_len;
if (ediff > 32*cur_quad->edge_len ||
ediff > 32*quads[k].edge_len)
{
PRINTF("Incompatible edge lengths\n");
continue;
}
closest_corner_idx = j;
closest_quad = &quads[k];
min_dist = dist;
}
}
}
// we found a matching corner point?
if( closest_corner_idx >= 0 && min_dist < FLT_MAX )
{
// If another point from our current quad is closer to the found corner
// than the current one, then we don't count this one after all.
// This is necessary to support small squares where otherwise the wrong
// corner will get matched to closest_quad;
closest_corner = closest_quad->corners[closest_corner_idx];
for( j = 0; j < 4; j++ )
{
if( cur_quad->neighbors[j] == closest_quad )
break;
dx = closest_corner->pt.x - cur_quad->corners[j]->pt.x;
dy = closest_corner->pt.y - cur_quad->corners[j]->pt.y;
if( dx * dx + dy * dy < min_dist )
break;
}
if( j < 4 || cur_quad->count >= 4 || closest_quad->count >= 4 )
continue;
// Check that each corner is a neighbor of different quads
for( j = 0; j < closest_quad->count; j++ )
{
if( closest_quad->neighbors[j] == cur_quad )
break;
}
if( j < closest_quad->count )
continue;
// check whether the closest corner to closest_corner
// is different from cur_quad->corners[i]->pt
for( k = 0; k < quad_count; k++ )
{
CvCBQuad* q = &quads[k];
if( k == idx || q == closest_quad )
continue;
for( j = 0; j < 4; j++ )
if( !q->neighbors[j] )
{
dx = closest_corner->pt.x - q->corners[j]->pt.x;
dy = closest_corner->pt.y - q->corners[j]->pt.y;
dist = dx*dx + dy*dy;
if( dist < min_dist )
break;
}
if( j < 4 )
break;
}
if( k < quad_count )
continue;
closest_corner->pt.x = (pt.x + closest_corner->pt.x) * 0.5f;
closest_corner->pt.y = (pt.y + closest_corner->pt.y) * 0.5f;
// We've found one more corner - remember it
cur_quad->count++;
cur_quad->neighbors[i] = closest_quad;
cur_quad->corners[i] = closest_corner;
closest_quad->count++;
closest_quad->neighbors[closest_corner_idx] = cur_quad;
}
}
}
}
//=====================================================================================
// returns corners in clockwise order
// corners don't necessarily start at same position on quad (e.g.,
// top left corner)
static int
icvGenerateQuads( CvCBQuad **out_quads, CvCBCorner **out_corners,
CvMemStorage *storage, CvMat *image, int flags )
{
int quad_count = 0;
CvMemStorage *temp_storage = 0;
if( out_quads )
*out_quads = 0;
if( out_corners )
*out_corners = 0;
CV_FUNCNAME( "icvGenerateQuads" );
__BEGIN__;
CvSeq *src_contour = 0;
CvSeq *root;
CvContourEx* board = 0;
CvContourScanner scanner;
int i, idx, min_size;
CV_ASSERT( out_corners && out_quads );
// empiric bound for minimal allowed perimeter for squares
min_size = cvRound( image->cols * image->rows * .03 * 0.01 * 0.92 );
// create temporary storage for contours and the sequence of pointers to found quadrangles
CV_CALL( temp_storage = cvCreateChildMemStorage( storage ));
CV_CALL( root = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvSeq*), temp_storage ));
// initialize contour retrieving routine
CV_CALL( scanner = cvStartFindContours( image, temp_storage, sizeof(CvContourEx),
CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ));
// get all the contours one by one
while( (src_contour = cvFindNextContour( scanner )) != 0 )
{
CvSeq *dst_contour = 0;
CvRect rect = ((CvContour*)src_contour)->rect;
// reject contours with too small perimeter
if( CV_IS_SEQ_HOLE(src_contour) && rect.width*rect.height >= min_size )
{
const int min_approx_level = 2, max_approx_level = MAX_CONTOUR_APPROX;
int approx_level;
for( approx_level = min_approx_level; approx_level <= max_approx_level; approx_level++ )
{
dst_contour = cvApproxPoly( src_contour, sizeof(CvContour), temp_storage,
CV_POLY_APPROX_DP, (float)approx_level );
// we call this again on its own output, because sometimes
// cvApproxPoly() does not simplify as much as it should.
dst_contour = cvApproxPoly( dst_contour, sizeof(CvContour), temp_storage,
CV_POLY_APPROX_DP, (float)approx_level );
if( dst_contour->total == 4 )
break;
}
// reject non-quadrangles
if( dst_contour->total == 4 && cvCheckContourConvexity(dst_contour) )
{
CvPoint pt[4];
double d1, d2, p = cvContourPerimeter(dst_contour);
double area = fabs(cvContourArea(dst_contour, CV_WHOLE_SEQ));
double dx, dy;
for( i = 0; i < 4; i++ )
pt[i] = *(CvPoint*)cvGetSeqElem(dst_contour, i);
dx = pt[0].x - pt[2].x;
dy = pt[0].y - pt[2].y;
d1 = sqrt(dx*dx + dy*dy);
dx = pt[1].x - pt[3].x;
dy = pt[1].y - pt[3].y;
d2 = sqrt(dx*dx + dy*dy);
// philipg. Only accept those quadrangles which are more square
// than rectangular and which are big enough
double d3, d4;
dx = pt[0].x - pt[1].x;
dy = pt[0].y - pt[1].y;
d3 = sqrt(dx*dx + dy*dy);
dx = pt[1].x - pt[2].x;
dy = pt[1].y - pt[2].y;
d4 = sqrt(dx*dx + dy*dy);
if( !(flags & CV_CALIB_CB_FILTER_QUADS) ||
(d3*4 > d4 && d4*4 > d3 && d3*d4 < area*1.5 && area > min_size &&
d1 >= 0.15 * p && d2 >= 0.15 * p) )
{
CvContourEx* parent = (CvContourEx*)(src_contour->v_prev);
parent->counter++;
if( !board || board->counter < parent->counter )
board = parent;
dst_contour->v_prev = (CvSeq*)parent;
//for( i = 0; i < 4; i++ ) cvLine( debug_img, pt[i], pt[(i+1)&3], cvScalar(200,255,255), 1, CV_AA, 0 );
cvSeqPush( root, &dst_contour );
}
}
}
}
// finish contour retrieving
cvEndFindContours( &scanner );
// allocate quad & corner buffers
CV_CALL( *out_quads = (CvCBQuad*)cvAlloc((root->total+root->total / 2) * sizeof((*out_quads)[0])));
CV_CALL( *out_corners = (CvCBCorner*)cvAlloc((root->total+root->total / 2) * 4 * sizeof((*out_corners)[0])));
// Create array of quads structures
for( idx = 0; idx < root->total; idx++ )
{
CvCBQuad* q = &(*out_quads)[quad_count];
src_contour = *(CvSeq**)cvGetSeqElem( root, idx );
if( (flags & CV_CALIB_CB_FILTER_QUADS) && src_contour->v_prev != (CvSeq*)board )
continue;
// reset group ID
memset( q, 0, sizeof(*q) );
q->group_idx = -1;
assert( src_contour->total == 4 );
for( i = 0; i < 4; i++ )
{
CvPoint2D32f pt = cvPointTo32f(*(CvPoint*)cvGetSeqElem(src_contour, i));
CvCBCorner* corner = &(*out_corners)[quad_count*4 + i];
memset( corner, 0, sizeof(*corner) );
corner->pt = pt;
q->corners[i] = corner;
}
q->edge_len = FLT_MAX;
for( i = 0; i < 4; i++ )
{
float dx = q->corners[i]->pt.x - q->corners[(i+1)&3]->pt.x;
float dy = q->corners[i]->pt.y - q->corners[(i+1)&3]->pt.y;
float d = dx*dx + dy*dy;
if( q->edge_len > d )
q->edge_len = d;
}
quad_count++;
}
__END__;
if( cvGetErrStatus() < 0 )
{
if( out_quads )
cvFree( out_quads );
if( out_corners )
cvFree( out_corners );
quad_count = 0;
}
cvReleaseMemStorage( &temp_storage );
return quad_count;
}
//=====================================================================================
static int is_equal_quad( const void* _a, const void* _b, void* )
{
CvRect a = (*((CvContour**)_a))->rect;
CvRect b = (*((CvContour**)_b))->rect;
int dx = MIN( b.x + b.width - 1, a.x + a.width - 1) - MAX( b.x, a.x);
int dy = MIN( b.y + b.height - 1, a.y + a.height - 1) - MAX( b.y, a.y);
int w = (a.width + b.width)>>1;
int h = (a.height + b.height)>>1;
if( dx > w*0.75 && dy > h*0.75 && dx < w*1.25 && dy < h*1.25 ) return 1;
return 0;
}
static int
icvGenerateQuadsEx( CvCBQuad **out_quads, CvCBCorner **out_corners,
CvMemStorage *storage, CvMat *image, CvMat *thresh_img, int dilations, int flags )
{
int l;
int quad_count = 0;
CvMemStorage *temp_storage = 0;
if( out_quads )
*out_quads = 0;
if( out_corners )
*out_corners = 0;
CV_FUNCNAME( "icvGenerateQuads" );
__BEGIN__;
CvSeq *src_contour = 0;
CvSeq *root, *root_tmp;
CvContourEx* board = 0;
CvContourScanner scanner;
int i, idx, min_size;
int step_level = 25;
CV_ASSERT( out_corners && out_quads );
// empiric bound for minimal allowed perimeter for squares
min_size = cvRound( image->cols * image->rows * .03 * 0.01 * 0.92 );
// create temporary storage for contours and the sequence of pointers to found quadrangles
CV_CALL( temp_storage = cvCreateChildMemStorage( storage ));
CV_CALL( root_tmp = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvSeq*), temp_storage ));
CV_CALL( root = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvSeq*), temp_storage ));
//perform contours slicing
cvEqualizeHist(image,image);
for(l = step_level; l < 256-step_level; l+= step_level)
{
cvThreshold( image, thresh_img, l, 255, CV_THRESH_BINARY );
cvDilate( thresh_img, thresh_img, 0, dilations );
//draw frame to extract edge quads
cvRectangle( thresh_img, cvPoint(0,0), cvPoint(thresh_img->cols-1,
thresh_img->rows-1), CV_RGB(255,255,255), 3, 8);
// initialize contour retrieving routine
CV_CALL( scanner = cvStartFindContours( thresh_img, temp_storage, sizeof(CvContourEx),
CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ));
// get all the contours one by one
while( (src_contour = cvFindNextContour( scanner )) != 0 )
{
CvSeq *dst_contour = 0;
CvRect rect = ((CvContour*)src_contour)->rect;
// reject contours with too small perimeter
if( CV_IS_SEQ_HOLE(src_contour) && rect.width*rect.height >= min_size )
{
const int min_approx_level = 2, max_approx_level = MAX_CONTOUR_APPROX;
int approx_level;
for( approx_level = min_approx_level; approx_level <= max_approx_level; approx_level++ )
{
dst_contour = cvApproxPoly( src_contour, sizeof(CvContour), temp_storage,
CV_POLY_APPROX_DP, (float)approx_level );
// we call this again on its own output, because sometimes
// cvApproxPoly() does not simplify as much as it should.
dst_contour = cvApproxPoly( dst_contour, sizeof(CvContour), temp_storage,
CV_POLY_APPROX_DP, (float)approx_level );
if( dst_contour->total == 4 )
break;
}
// reject non-quadrangles
if( dst_contour->total == 4 && cvCheckContourConvexity(dst_contour) )
{
CvPoint pt[4];
double d1, d2, p = cvContourPerimeter(dst_contour);
double area = fabs(cvContourArea(dst_contour, CV_WHOLE_SEQ));
double dx, dy;
for( i = 0; i < 4; i++ )
pt[i] = *(CvPoint*)cvGetSeqElem(dst_contour, i);
//check border condition. if this is edge square we will add this as is
int edge_flag = 0, eps = 2;
for( i = 0; i < 4; i++ )
if( pt[i].x <= eps || pt[i].y <= eps ||
pt[i].x >= image->width - eps ||
pt[i].y >= image->height - eps ) edge_flag = 1;
dx = pt[0].x - pt[2].x;
dy = pt[0].y - pt[2].y;
d1 = sqrt(dx*dx + dy*dy);
dx = pt[1].x - pt[3].x;
dy = pt[1].y - pt[3].y;
d2 = sqrt(dx*dx + dy*dy);
// philipg. Only accept those quadrangles which are more square
// than rectangular and which are big enough
double d3, d4;
dx = pt[0].x - pt[1].x;
dy = pt[0].y - pt[1].y;
d3 = sqrt(dx*dx + dy*dy);
dx = pt[1].x - pt[2].x;
dy = pt[1].y - pt[2].y;
d4 = sqrt(dx*dx + dy*dy);
if( edge_flag ||
(!(flags & CV_CALIB_CB_FILTER_QUADS) ||
(d3*4 > d4 && d4*4 > d3 && d3*d4 < area*1.5 && area > min_size &&
d1 >= 0.15 * p && d2 >= 0.15 * p)) )
{
CvContourEx* parent = (CvContourEx*)(src_contour->v_prev);
parent->counter++;
if( !board || board->counter < parent->counter )
board = parent;
dst_contour->v_prev = (CvSeq*)parent;
//for( i = 0; i < 4; i++ ) cvLine( debug_img, pt[i], pt[(i+1)&3], cvScalar(200,255,255), 1, CV_AA, 0 );
cvSeqPush( root_tmp, &dst_contour );
}
}
}
}
// finish contour retrieving
cvEndFindContours( &scanner );
}
// Perform clustering of extracted quads
// Same quad can be extracted from different binarization levels
if( root_tmp->total )
{
CvSeq* idx_seq = 0;
int n_quads = cvSeqPartition( root_tmp, temp_storage, &idx_seq, is_equal_quad, 0 );
for( i = 0; i < n_quads; i++ )
{
//extract biggest quad in group
int max_size = 0;
CvSeq* max_seq = 0;
for( int j = 0; j < root_tmp->total; j++ )
{
int index = *(int*)cvGetSeqElem(idx_seq, j);
if(index!=i) continue;
CvContour* cnt = *(CvContour**)cvGetSeqElem(root_tmp, j);
if(cnt->rect.width > max_size)
{
max_size = cnt->rect.width;
max_seq = (CvSeq*)cnt;
}
}
cvSeqPush( root, &max_seq);
}
}
// allocate quad & corner buffers
CV_CALL( *out_quads = (CvCBQuad*)cvAlloc((root->total+root->total / 2) * sizeof((*out_quads)[0])));
CV_CALL( *out_corners = (CvCBCorner*)cvAlloc((root->total+root->total / 2) * 4 * sizeof((*out_corners)[0])));
// Create array of quads structures
for( idx = 0; idx < root->total; idx++ )
{
CvCBQuad* q = &(*out_quads)[quad_count];
src_contour = *(CvSeq**)cvGetSeqElem( root, idx );
if( (flags & CV_CALIB_CB_FILTER_QUADS) && src_contour->v_prev != (CvSeq*)board )
continue;
// reset group ID
memset( q, 0, sizeof(*q) );
q->group_idx = -1;
assert( src_contour->total == 4 );
for( i = 0; i < 4; i++ )
{
CvPoint2D32f pt = cvPointTo32f(*(CvPoint*)cvGetSeqElem(src_contour, i));
CvCBCorner* corner = &(*out_corners)[quad_count*4 + i];
memset( corner, 0, sizeof(*corner) );
corner->pt = pt;
q->corners[i] = corner;
}
q->edge_len = FLT_MAX;
for( i = 0; i < 4; i++ )
{
float dx = q->corners[i]->pt.x - q->corners[(i+1)&3]->pt.x;
float dy = q->corners[i]->pt.y - q->corners[(i+1)&3]->pt.y;
float d = dx*dx + dy*dy;
if( q->edge_len > d )
q->edge_len = d;
}
quad_count++;
}
__END__;
if( cvGetErrStatus() < 0 )
{
if( out_quads )
cvFree( out_quads );
if( out_corners )
cvFree( out_corners );
quad_count = 0;
}
cvReleaseMemStorage( &temp_storage );
return quad_count;
}
CV_IMPL void
cvDrawChessboardCorners( CvArr* _image, CvSize pattern_size,
CvPoint2D32f* corners, int count, int found )
{
CV_FUNCNAME( "cvDrawChessboardCorners" );
__BEGIN__;
const int shift = 0;
const int radius = 4;
const int r = radius*(1 << shift);
int i;
CvMat stub, *image;
double scale = 1;
int type, cn, line_type;
CV_CALL( image = cvGetMat( _image, &stub ));
type = CV_MAT_TYPE(image->type);
cn = CV_MAT_CN(type);
if( cn != 1 && cn != 3 && cn != 4 )
CV_ERROR( CV_StsUnsupportedFormat, "Number of channels must be 1, 3 or 4" );
switch( CV_MAT_DEPTH(image->type) )
{
case CV_8U:
scale = 1;
break;
case CV_16U:
scale = 256;
break;
case CV_32F:
scale = 1./255;
break;
default:
CV_ERROR( CV_StsUnsupportedFormat,
"Only 8-bit, 16-bit or floating-point 32-bit images are supported" );
}
line_type = type == CV_8UC1 || type == CV_8UC3 ? CV_AA : 8;
if( !found )
{
CvScalar color = {{0,0,255}};
if( cn == 1 )
color = cvScalarAll(200);
color.val[0] *= scale;
color.val[1] *= scale;
color.val[2] *= scale;
color.val[3] *= scale;
for( i = 0; i < count; i++ )
{
CvPoint pt;
pt.x = cvRound(corners[i].x*(1 << shift));
pt.y = cvRound(corners[i].y*(1 << shift));
cvLine( image, cvPoint( pt.x - r, pt.y - r ),
cvPoint( pt.x + r, pt.y + r ), color, 1, line_type, shift );
cvLine( image, cvPoint( pt.x - r, pt.y + r),
cvPoint( pt.x + r, pt.y - r), color, 1, line_type, shift );
cvCircle( image, pt, r+(1<<shift), color, 1, line_type, shift );
}
}
else
{
int x, y;
CvPoint prev_pt = {0, 0};
const int line_max = 7;
static const CvScalar line_colors[line_max] =
{
{{0,0,255}},
{{0,128,255}},
{{0,200,200}},
{{0,255,0}},
{{200,200,0}},
{{255,0,0}},
{{255,0,255}}
};
for( y = 0, i = 0; y < pattern_size.height; y++ )
{
CvScalar color = line_colors[y % line_max];
if( cn == 1 )
color = cvScalarAll(200);
color.val[0] *= scale;
color.val[1] *= scale;
color.val[2] *= scale;
color.val[3] *= scale;
for( x = 0; x < pattern_size.width; x++, i++ )
{
CvPoint pt;
pt.x = cvRound(corners[i].x*(1 << shift));
pt.y = cvRound(corners[i].y*(1 << shift));
if( i != 0 )
cvLine( image, prev_pt, pt, color, 1, line_type, shift );
cvLine( image, cvPoint(pt.x - r, pt.y - r),
cvPoint(pt.x + r, pt.y + r), color, 1, line_type, shift );
cvLine( image, cvPoint(pt.x - r, pt.y + r),
cvPoint(pt.x + r, pt.y - r), color, 1, line_type, shift );
cvCircle( image, pt, r+(1<<shift), color, 1, line_type, shift );
prev_pt = pt;
}
}
}
__END__;
}
/* End of file. */