Path: blob/master/modules/calib3d/src/chessboard.cpp
16354 views
// This file is part of OpenCV project.1// It is subject to the license terms in the LICENSE file found in the top-level directory2// of this distribution and at http://opencv.org/license.html.34#include "precomp.hpp"5#include "opencv2/flann.hpp"6#include "chessboard.hpp"7#include "math.h"89//#define CV_DETECTORS_CHESSBOARD_DEBUG10#ifdef CV_DETECTORS_CHESSBOARD_DEBUG11#include <opencv2/highgui.hpp>12static cv::Mat debug_image;13#endif1415using namespace std;16namespace cv {17namespace details {1819/////////////////////////////////////////////////////////////////////////////20/////////////////////////////////////////////////////////////////////////////21// magic numbers used for chessboard corner detection22/////////////////////////////////////////////////////////////////////////////23static const float CORNERS_SEARCH = 0.5F; // percentage of the edge length to the next corner used to find new corners24static const float MAX_ANGLE = float(48.0/180.0*CV_PI); // max angle between line segments supposed to be straight25static const float MIN_COS_ANGLE = float(cos(35.0/180*CV_PI)); // min cos angle between board edges26static const float MIN_RESPONSE_RATIO = 0.1F;27static const float ELLIPSE_WIDTH = 0.35F; // width of the search ellipse in percentage of its length28static const float RAD2DEG = float(180.0/CV_PI);29static const int MAX_SYMMETRY_ERRORS = 5; // maximal number of failures during point symmetry test (filtering out lines)30/////////////////////////////////////////////////////////////////////////////31/////////////////////////////////////////////////////////////////////////////3233// some helper methods34static bool isPointOnLine(cv::Point2f l1,cv::Point2f l2,cv::Point2f pt,float min_angle);35static int testPointSymmetry(const cv::Mat& mat,cv::Point2f pt,float dist,float max_error);36static float calcSubpixel(const float &x_l,const float &x,const float &x_r);37static float calcSubPos(const float &x_l,const float &x,const float &x_r);38static void polyfit(const Mat& src_x, const Mat& src_y, Mat& dst, int order);39static float calcSignedDistance(const cv::Vec2f &n,const cv::Point2f &a,const cv::Point2f &pt);40static void normalizePoints1D(cv::InputArray _points,cv::OutputArray _T,cv::OutputArray _new_points);41static cv::Mat findHomography1D(cv::InputArray _src,cv::InputArray _dst);4243void normalizePoints1D(cv::InputArray _points,cv::OutputArray _T,cv::OutputArray _new_points)44{45cv::Mat points = _points.getMat();46if(points.cols > 1 && points.rows == 1)47points = points.reshape(1,points.cols);48CV_CheckChannelsEQ(points.channels(), 1, "points must have only one channel");4950// calc centroid51double centroid= cv::mean(points)[0];5253// shift origin to centroid54cv::Mat new_points = points-centroid;5556// calc mean distance57double mean_dist = cv::mean(cv::abs(new_points))[0];58if(mean_dist<= DBL_EPSILON)59CV_Error(Error::StsBadArg, "all given points are identical");60double scale = 1.0/mean_dist;616263// generate transformation64cv::Matx22d Tx(65scale, -scale*centroid,660, 167);68Mat(Tx, false).copyTo(_T);6970// calc normalized points;71_new_points.create(points.rows,1,points.type());72new_points = _new_points.getMat();73switch(points.type())74{75case CV_32FC1:76for(int i=0;i < points.rows;++i)77{78cv::Vec2d p(points.at<float>(i), 1.0);79p = Tx*p;80new_points.at<float>(i) = float(p(0)/p(1));81}82break;83case CV_64FC1:84for(int i=0;i < points.rows;++i)85{86cv::Vec2d p(points.at<double>(i), 1.0);87p = Tx*p;88new_points.at<double>(i) = p(0)/p(1);89}90break;91default:92CV_Error(Error::StsUnsupportedFormat, "unsupported point type");93}94}9596cv::Mat findHomography1D(cv::InputArray _src,cv::InputArray _dst)97{98// check inputs99cv::Mat src = _src.getMat();100cv::Mat dst = _dst.getMat();101if(src.cols > 1 && src.rows == 1)102src = src.reshape(1,src.cols);103if(dst.cols > 1 && dst.rows == 1)104dst = dst.reshape(1,dst.cols);105CV_CheckEQ(src.rows, dst.rows, "size mismatch");106CV_CheckChannelsEQ(src.channels(), 1, "data with only one channel are supported");107CV_CheckChannelsEQ(dst.channels(), 1, "data with only one channel are supported");108CV_CheckTypeEQ(src.type(), dst.type(), "src and dst must have the same type");109CV_Check(src.rows, src.rows >= 3,"at least three point pairs are needed");110111// normalize points112cv::Mat src_T,dst_T, src_n,dst_n;113normalizePoints1D(src,src_T,src_n);114normalizePoints1D(dst,dst_T,dst_n);115116int count = src_n.rows;117cv::Mat A = cv::Mat::zeros(count,3,CV_64FC1);118cv::Mat b = cv::Mat::zeros(count,1,CV_64FC1);119120// fill A;b and perform singular value decomposition121// it is assumed that w is one for both cooridnates122// h22 is kept to 1123switch(src_n.type())124{125case CV_32FC1:126for(int i=0;i<count;++i)127{128double s = src_n.at<float>(i);129double d = dst_n.at<float>(i);130A.at<double>(i,0) = s;131A.at<double>(i,1) = 1.0;132A.at<double>(i,2) = -s*d;133b.at<double>(i) = d;134}135break;136case CV_64FC1:137for(int i=0;i<count;++i)138{139double s = src_n.at<double>(i);140double d = dst_n.at<double>(i);141A.at<double>(i,0) = s;142A.at<double>(i,1) = 1.0;143A.at<double>(i,2) = -s*d;144b.at<double>(i) = d;145}146break;147default:148CV_Error(Error::StsUnsupportedFormat,"unsupported type");149}150151cv::Mat u,d,vt;152cv::SVD::compute(A,d,u,vt);153cv::Mat b_ = u.t()*b;154155cv::Mat y(b_.rows,1,CV_64FC1);156for(int i=0;i<b_.rows;++i)157y.at<double>(i) = b_.at<double>(i)/d.at<double>(i);158159cv::Mat x = vt.t()*y;160cv::Matx22d H_(x.at<double>(0), x.at<double>(1), x.at<double>(2), 1.0);161162// denormalize163Mat H = dst_T.inv()*Mat(H_, false)*src_T;164165// enforce frobeniusnorm of one166double scale = cv::norm(H);167CV_Assert(fabs(scale) > DBL_EPSILON);168scale = 1.0 / scale;169return H*scale;170}171void polyfit(const Mat& src_x, const Mat& src_y, Mat& dst, int order)172{173int npoints = src_x.checkVector(1);174int nypoints = src_y.checkVector(1);175CV_Assert(npoints == nypoints && npoints >= order+1);176Mat_<double> srcX(src_x), srcY(src_y);177Mat_<double> A = Mat_<double>::ones(npoints,order + 1);178// build A matrix179for (int y = 0; y < npoints; ++y)180{181for (int x = 1; x < A.cols; ++x)182A.at<double>(y,x) = srcX.at<double>(y)*A.at<double>(y,x-1);183}184cv::Mat w;185solve(A,srcY,w,DECOMP_SVD);186w.convertTo(dst, ((src_x.depth() == CV_64F || src_y.depth() == CV_64F) ? CV_64F : CV_32F));187}188189float calcSignedDistance(const cv::Vec2f &n,const cv::Point2f &a,const cv::Point2f &pt)190{191cv::Vec3f v1(n[0],n[1],0);192cv::Vec3f v2(pt.x-a.x,pt.y-a.y,0);193return v1.cross(v2)[2];194}195196bool isPointOnLine(cv::Point2f l1,cv::Point2f l2,cv::Point2f pt,float min_angle)197{198cv::Vec2f vec1(l1-pt);199cv::Vec2f vec2(pt-l2);200if(vec1.dot(vec2) < min_angle*cv::norm(vec1)*cv::norm(vec2))201return false;202return true;203}204205// returns how many tests fails out of 10206int testPointSymmetry(const cv::Mat& mat,cv::Point2f pt,float dist,float max_error)207{208cv::Rect image_rect(int(0.5*dist),int(0.5*dist),int(mat.cols-0.5*dist),int(mat.rows-0.5*dist));209cv::Size size(int(0.5*dist),int(0.5*dist));210int count = 0;211cv::Mat patch1,patch2;212cv::Point2f center1,center2;213for (int angle_i = 0; angle_i < 10; angle_i++)214{215double angle = angle_i * (CV_PI * 0.1);216cv::Point2f n(float(cos(angle)),float(-sin(angle)));217center1 = pt+dist*n;218if(!image_rect.contains(center1))219return false;220center2 = pt-dist*n;221if(!image_rect.contains(center2))222return false;223cv::getRectSubPix(mat,size,center1,patch1);224cv::getRectSubPix(mat,size,center2,patch2);225if(fabs(cv::mean(patch1)[0]-cv::mean(patch2)[0]) > max_error)226++count;227}228return count;229}230231float calcSubpixel(const float &x_l,const float &x,const float &x_r)232{233// prevent zero values234if(x_l <= 0)235return 0;236if(x <= 0)237return 0;238if(x_r <= 0)239return 0;240const float l0 = float(std::log(x_l+1e-6));241const float l1 = float(std::log(x+1e-6));242const float l2 = float(std::log(x_r+1e-6));243float delta = l2-l1-l1+l0;244if(!delta) // this happens if all values are identical245return 0;246delta = (l0-l2)/(delta+delta);247return delta;248}249250float calcSubPos(const float &x_l,const float &x,const float &x_r)251{252float val = 2.0F *(x_l-2.0F*x+x_r);253if(val == 0.0F)254return 0.0F;255val = (x_l-x_r)/val;256if(val > 1.0F)257return 1.0F;258if(val < -1.0F)259return -1.0F;260return val;261}262263FastX::FastX(const Parameters ¶)264{265reconfigure(para);266}267268void FastX::reconfigure(const Parameters ¶)269{270CV_Check(para.min_scale, para.min_scale >= 0 && para.min_scale <= para.max_scale, "invalid scale");271parameters = para;272}273274// rotates the image around its center275void FastX::rotate(float angle,const cv::Mat &img,cv::Size size,cv::Mat &out)const276{277if(angle == 0)278{279out = img;280return;281}282else283{284cv::Mat_<double> m = cv::getRotationMatrix2D(cv::Point2f(float(img.cols*0.5),float(img.rows*0.5)),float(angle/CV_PI*180),1);285m.at<double>(0,2) += 0.5*(size.width-img.cols);286m.at<double>(1,2) += 0.5*(size.height-img.rows);287cv::warpAffine(img,out,m,size);288}289}290291void FastX::calcFeatureMap(const Mat &images,Mat& out)const292{293if(images.empty())294CV_Error(Error::StsBadArg,"no rotation images");295int type = images.type(), depth = CV_MAT_DEPTH(type);296CV_CheckType(type,depth == CV_8U,297"Only 8-bit grayscale or color images are supported");298if(!images.isContinuous())299CV_Error(Error::StsBadArg,"image must be continuous");300301float signal,noise,rating;302int count1;303unsigned char val1,val2,val3;304const unsigned char* wrap_around;305const unsigned char* pend;306const unsigned char* pimages = images.data;307const int channels = images.channels();308if(channels < 4)309CV_Error(Error::StsBadArg,"images must have at least four channels");310311// for each pixel312out = cv::Mat::zeros(images.rows,images.cols,CV_32FC1);313const float *pout_end = reinterpret_cast<const float*>(out.dataend);314for(float *pout=out.ptr<float>(0,0);pout != pout_end;++pout)315{316//reset values317rating = 0.0; count1 = 0;318noise = 255; signal = 0;319320//calc rating321pend = pimages+channels;322val1 = *(pend-1); // wrap around (last value)323wrap_around = pimages++; // store for wrap around (first value)324val2 = *wrap_around; // first value325for(;pimages != pend;++pimages)326{327val3 = *pimages;328if(val1 <= val2)329{330if(val3 < val2) // maxima331{332if(signal < val2)333signal = val2;334++count1;335}336}337else if(val1 > val2 && val3 >= val2) // minima338{339if(noise > val2)340noise = val2;341++count1;342}343val1 = val2;344val2 = val3;345}346// wrap around347if(val1 <= val2) // maxima348{349if(*wrap_around < val2)350{351if(signal < val2)352signal = val2;353++count1;354}355}356else if(val1 > val2 && *wrap_around >= val2) // minima357{358if(noise > val2)359noise = val2;360++count1;361}362363// store rating364if(count1 == parameters.branches)365{366rating = signal-noise;367*pout = rating*rating; //store rating in the feature map368}369}370}371372std::vector<std::vector<float> > FastX::calcAngles(const std::vector<cv::Mat> &rotated_images,std::vector<cv::KeyPoint> &keypoints)const373{374// validate rotated_images375if(rotated_images.empty())376CV_Error(Error::StsBadArg,"no rotated images");377std::vector<cv::Mat>::const_iterator iter = rotated_images.begin();378for(;iter != rotated_images.end();++iter)379{380if(iter->empty())381CV_Error(Error::StsBadArg,"empty rotated images");382if(iter->channels() < 4)383CV_Error(Error::StsBadArg,"rotated images must have at least four channels");384}385386// assuming all elements of the same channel387const int channels = rotated_images.front().channels();388int channels_1 = channels-1;389float resolution = float(CV_PI/channels);390391float angle;392float val1,val2,val3,wrap_around;393const unsigned char *pimages1,*pimages2,*pimages3,*pimages4;394std::vector<std::vector<float> > angles;395angles.resize(keypoints.size());396float scale = float(parameters.super_resolution)+1.0F;397398// for each keypoint399std::vector<cv::KeyPoint>::iterator pt_iter = keypoints.begin();400for(int id=0;pt_iter != keypoints.end();++pt_iter,++id)401{402int scale_id = pt_iter->octave - parameters.min_scale;403if(scale_id>= int(rotated_images.size()) ||scale_id < 0)404CV_Error(Error::StsBadArg,"no rotated image for requested keypoint octave");405const cv::Mat &s_rotated_images = rotated_images[scale_id];406407float x2 = pt_iter->pt.x*scale;408float y2 = pt_iter->pt.y*scale;409int row = int(y2);410int col = int(x2);411x2 -= col;412y2 -= row;413float x1 = 1.0F-x2; float y1 = 1.0F-y2;414float a = x1*y1; float b = x2*y1; float c = x1*y2; float d = x2*y2;415pimages1 = s_rotated_images.ptr<unsigned char>(row,col);416pimages2 = s_rotated_images.ptr<unsigned char>(row,col+1);417pimages3 = s_rotated_images.ptr<unsigned char>(row+1,col);418pimages4 = s_rotated_images.ptr<unsigned char>(row+1,col+1);419std::vector<float> &angles_i = angles[id];420421//calc rating422val1 = a**(pimages1+channels_1)+b**(pimages2+channels_1)+423c**(pimages3+channels_1)+d**(pimages4+channels_1); // wrap around (last value)424wrap_around = a**(pimages1++)+b**(pimages2++)+c**(pimages3++)+d**(pimages4++); // first value425val2 = wrap_around; // first value426for(int i=0;i<channels-1;++pimages1,++pimages2,++pimages3,++pimages4,++i)427{428val3 = a**(pimages1)+b**(pimages2)+c**(pimages3)+d**(pimages4);429if(val1 <= val2)430{431if(val3 < val2)432{433angle = float((calcSubPos(val1,val2,val3)+i)*resolution);434if(angle < 0)435angle += float(CV_PI);436else if(angle > CV_PI)437angle -= float(CV_PI);438angles_i.push_back(angle);439pt_iter->angle = 360.0F-angle*RAD2DEG;440}441}442else if(val1 > val2 && val3 >= val2)443{444angle = float((calcSubPos(val1,val2,val3)+i)*resolution);445if(angle < 0)446angle += float(CV_PI);447else if(angle > CV_PI)448angle -= float(CV_PI);449angles_i.push_back(-angle);450pt_iter->angle = 360.0F-angle*RAD2DEG;451}452val1 = val2;453val2 = val3;454}455// wrap around456if(val1 <= val2)457{458if(wrap_around< val2)459{460angle = float((calcSubPos(val1,val2,wrap_around)+channels-1)*resolution);461if(angle < 0)462angle += float(CV_PI);463else if(angle > CV_PI)464angle -= float(CV_PI);465angles_i.push_back(angle);466pt_iter->angle = 360.0F-angle*RAD2DEG;467}468}469else if(val1 > val2 && wrap_around >= val2)470{471angle = float((calcSubPos(val1,val2,wrap_around)+channels-1)*resolution);472if(angle < 0)473angle += float(CV_PI);474else if(angle > CV_PI)475angle -= float(CV_PI);476angles_i.push_back(-angle);477pt_iter->angle = 360.0F-angle*RAD2DEG;478}479}480return angles;481}482483void FastX::findKeyPoints(const std::vector<cv::Mat> &feature_maps, std::vector<KeyPoint>& keypoints,const Mat& _mask) const484{485//TODO check that all feature_maps have the same size486int num_scales = parameters.max_scale-parameters.min_scale;487CV_CheckGE(int(feature_maps.size()), num_scales, "missing feature maps");488if (!_mask.empty())489{490CV_CheckTypeEQ(_mask.type(), CV_8UC1, "wrong mask type");491CV_CheckEQ(_mask.size(), feature_maps.front().size(),"wrong mask type or size");492}493keypoints.clear();494495cv::Mat mask;496if(!_mask.empty())497mask = _mask;498else499mask = cv::Mat::ones(feature_maps.front().size(),CV_8UC1);500501int super_res = int(parameters.super_resolution);502int super_scale = super_res+1;503float super_comp = 0.25F*super_res;504505// for each scale506float strength = parameters.strength;507std::vector<int> windows;508cv::Point pt,pt2;509double min,max;510cv::Mat src;511for(int scale=parameters.max_scale;scale>=parameters.min_scale;--scale)512{513int window_size = (1 << (scale + super_res)) + 1;514float window_size2 = 0.5F*window_size;515float window_size4 = 0.25F*window_size;516int window_size2i = cvRound(window_size2);517518const cv::Mat &feature_map = feature_maps[scale-parameters.min_scale];519int y = ((feature_map.rows)/window_size)-6;520int x = ((feature_map.cols)/window_size)-6;521for(int row=5;row<y;++row)522{523for(int col=5;col<x;++col)524{525Rect rect(col*window_size,row*window_size,window_size,window_size);526src = feature_map(rect);527cv::minMaxLoc(src,&min,&max,NULL,&pt);528if(min == max || max < strength)529continue;530531cv::Point pos(pt.x+rect.x,pt.y+rect.y);532if(mask.at<unsigned char>(pos.y,pos.x) == 0)533continue;534535Rect rect2(int(pos.x-window_size2),int(pos.y-window_size2),window_size,window_size);536src = feature_map(rect2);537cv::minMaxLoc(src,NULL,NULL,NULL,&pt2);538if(pos.x == pt2.x+rect2.x && pos.y == pt2.y+rect2.y)539{540// the point is the best one on the current scale541// check all larger scales if there is a stronger one542double max2;543int scale2= scale-1;544//parameters.min_scale;545for(;scale2>=parameters.min_scale;--scale2)546{547cv::minMaxLoc(feature_maps[scale2-parameters.min_scale](rect),NULL,&max2,NULL,NULL);548if(max2 > max)549break;550}551if(scale2<parameters.min_scale && pos.x+1 < feature_map.cols && pos.y+1 < feature_map.rows)552{553float sub_x = float(calcSubpixel(feature_map.at<float>(pos.y,pos.x-1),554feature_map.at<float>(pos.y,pos.x),555feature_map.at<float>(pos.y,pos.x+1)));556float sub_y = float(calcSubpixel(feature_map.at<float>(pos.y-1,pos.x),557feature_map.at<float>(pos.y,pos.x),558feature_map.at<float>(pos.y+1,pos.x)));559cv::KeyPoint kpt(sub_x+pos.x,sub_y+pos.y,float(window_size),0.F,float(max),scale);560int x2 = std::max(0,int(kpt.pt.x-window_size4));561int y2 = std::max(0,int(kpt.pt.y-window_size4));562int w = std::min(int(mask.cols-x2),window_size2i);563int h = std::min(int(mask.rows-y2),window_size2i);564mask(cv::Rect(x2,y2,w,h)) = 0.0;565if(super_scale != 1)566{567kpt.pt.x /= super_scale;568kpt.pt.y /= super_scale;569kpt.pt.x -= super_comp;570kpt.pt.y -= super_comp;571kpt.size /= super_scale;572}573keypoints.push_back(kpt);574}575}576}577}578}579}580581void FastX::detectAndCompute(cv::InputArray image,cv::InputArray mask,std::vector<cv::KeyPoint>& keypoints,582cv::OutputArray _descriptors,bool useProvidedKeyPoints)583{584useProvidedKeyPoints = false;585detectImpl(image.getMat(),keypoints,mask.getMat());586if(!_descriptors.needed())587return;588589// generate descriptors based on their position590_descriptors.create(int(keypoints.size()),2,CV_32FC1);591cv::Mat descriptors = _descriptors.getMat();592std::vector<cv::KeyPoint>::const_iterator iter = keypoints.begin();593for(int row=0;iter != keypoints.end();++iter,++row)594{595descriptors.at<float>(row,0) = iter->pt.x;596descriptors.at<float>(row,1) = iter->pt.y;597}598if(!useProvidedKeyPoints) // suppress compiler warning599return;600return;601}602603void FastX::detectImpl(const cv::Mat& _gray_image,604std::vector<cv::Mat> &rotated_images,605std::vector<cv::Mat> &feature_maps,606const cv::Mat &_mask)const607{608if(!_mask.empty())609CV_Error(Error::StsBadSize, "Mask is not supported");610CV_CheckTypeEQ(_gray_image.type(), CV_8UC1, "Unsupported image type");611612// up-sample if needed613cv::Mat gray_image;614int super_res = int(parameters.super_resolution);615if(super_res)616cv::resize(_gray_image,gray_image,cv::Size(),2,2);617else618gray_image = _gray_image;619620//for each scale621int num_scales = parameters.max_scale-parameters.min_scale+1;622rotated_images.resize(num_scales);623feature_maps.resize(num_scales);624parallel_for_(Range(parameters.min_scale,parameters.max_scale+1),[&](const Range& range){625for(int scale=range.start;scale < range.end;++scale)626{627// calc images628// for each angle step629int scale_id = scale-parameters.min_scale;630cv::Mat rotated,filtered_h,filtered_v;631int diag = int(sqrt(gray_image.rows*gray_image.rows+gray_image.cols*gray_image.cols));632cv::Size size(diag,diag);633int num = int(0.5001*CV_PI/parameters.resolution);634std::vector<cv::Mat> images;635images.resize(2*num);636int scale_size = int(1+pow(2.0,scale+1+super_res));637int scale_size2 = int((scale_size/10)*2+1);638for(int i=0;i<num;++i)639{640float angle = parameters.resolution*i;641rotate(-angle,gray_image,size,rotated);642cv::blur(rotated,filtered_h,cv::Size(scale_size,scale_size2));643cv::blur(rotated,filtered_v,cv::Size(scale_size2,scale_size));644645// rotate filtered images back646rotate(angle,filtered_h,gray_image.size(),images[i]);647rotate(angle,filtered_v,gray_image.size(),images[i+num]);648}649cv::merge(images,rotated_images[scale_id]);650651// calc feature map652calcFeatureMap(rotated_images[scale_id],feature_maps[scale_id]);653// filter feature map to improve impulse responses654if(parameters.filter)655{656cv::Mat high,low;657cv::blur(feature_maps[scale_id],low,cv::Size(scale_size,scale_size));658int scale2 = int((scale_size/6))*2+1;659cv::blur(feature_maps[scale_id],high,cv::Size(scale2,scale2));660feature_maps[scale_id] = high-0.8*low;661}662}663});664}665666void FastX::detectImpl(const cv::Mat& image,std::vector<cv::KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,const cv::Mat &mask)const667{668std::vector<cv::Mat> rotated_images;669detectImpl(image,rotated_images,feature_maps,mask);670findKeyPoints(feature_maps,keypoints,mask);671}672673void FastX::detectImpl(InputArray image, std::vector<KeyPoint>& keypoints, InputArray mask)const674{675std::vector<cv::Mat> feature_maps;676detectImpl(image.getMat(),keypoints,feature_maps,mask.getMat());677}678679void FastX::detectImpl(const Mat& src, std::vector<KeyPoint>& keypoints, const Mat& mask)const680{681std::vector<cv::Mat> feature_maps;682detectImpl(src,keypoints,feature_maps,mask);683}684685686Ellipse::Ellipse():687angle(0),688cosf(0),689sinf(0)690{691}692693Ellipse::Ellipse(const cv::Point2f &_center, const cv::Size2f &_axes, float _angle):694center(_center),695axes(_axes),696angle(_angle),697cosf(cos(-_angle)),698sinf(sin(-_angle))699{700}701702Ellipse::Ellipse(const Ellipse &other)703{704center = other.center;705axes= other.axes;706angle= other.angle;707cosf = other.cosf;708sinf = other.sinf;709}710711const cv::Size2f &Ellipse::getAxes()const712{713return axes;714}715716cv::Point2f Ellipse::getCenter()const717{718return center;719}720721void Ellipse::draw(cv::InputOutputArray img,const cv::Scalar &color)const722{723cv::ellipse(img,center,axes,360-angle/CV_PI*180,0,360,color);724}725726bool Ellipse::contains(const cv::Point2f &pt)const727{728cv::Point2f ptc = pt-center;729float x = cosf*ptc.x+sinf*ptc.y;730float y = -sinf*ptc.x+cosf*ptc.y;731if(x*x/(axes.width*axes.width)+y*y/(axes.height*axes.height) <= 1.0)732return true;733return false;734}735736737// returns false if the angle from the line pt1-pt2 to the line pt3-pt4 is negative738static bool checkOrientation(const cv::Point2f &pt1,const cv::Point2f &pt2,739const cv::Point2f &pt3,const cv::Point2f &pt4)740{741cv::Point3f p1(pt2.x-pt1.x,pt2.y-pt1.y,0);742cv::Point3f p2(pt4.x-pt3.x,pt4.y-pt3.y,0);743return p1.cross(p2).z > 0;744}745746static bool sortKeyPoint(const cv::KeyPoint &pt1,const cv::KeyPoint &pt2)747{748// used as comparison function for partial sort749// the keypoints with the best score should be first750return pt1.response > pt2.response;751}752753cv::Mat Chessboard::getObjectPoints(const cv::Size &pattern_size,float cell_size)754{755cv::Mat result(pattern_size.width*pattern_size.height,1,CV_32FC3);756for(int row=0;row < pattern_size.height;++row)757{758for(int col=0;col< pattern_size.width;++col)759{760cv::Point3f &pt = *result.ptr<cv::Point3f>(row*pattern_size.width+col);761pt.x = cell_size*col;762pt.y = cell_size*row;763pt.z = 0;764}765}766return result;767}768769bool Chessboard::Board::Cell::empty()const770{771// check if one of its corners has NaN772if(top_left->x != top_left->x || top_left->y != top_left->y)773return true;774if(top_right->x != top_right->x || top_right->y != top_right->y)775return true;776if(bottom_right->x != bottom_right->x || bottom_right->y != bottom_right->y)777return true;778if(bottom_left->x != bottom_left->x || bottom_left->y != bottom_left->y)779return true;780return false;781}782783int Chessboard::Board::Cell::getRow()const784{785int row = 0;786Cell const* temp = this;787for(;temp->top;temp=temp->top,++row);788return row;789}790791int Chessboard::Board::Cell::getCol()const792{793int col = 0;794Cell const* temp = this;795for(;temp->left;temp=temp->left,++col);796return col;797}798799Chessboard::Board::Cell::Cell() :800top_left(NULL), top_right(NULL), bottom_right(NULL), bottom_left(NULL),801left(NULL), top(NULL), right(NULL), bottom(NULL),black(false)802{}803804Chessboard::Board::PointIter::PointIter(Cell *_cell,CornerIndex _corner_index):805corner_index(_corner_index),806cell(_cell)807{808}809810Chessboard::Board::PointIter::PointIter(const PointIter &other)811{812this->operator=(other);813}814815void Chessboard::Board::PointIter::operator=(const PointIter &other)816{817corner_index = other.corner_index;818cell = other.cell;819}820821Chessboard::Board::Cell* Chessboard::Board::PointIter::getCell()822{823return cell;824}825826bool Chessboard::Board::PointIter::valid()const827{828return cell != NULL;829}830831bool Chessboard::Board::PointIter::isNaN()const832{833const cv::Point2f *pt = operator*();834if(pt->x != pt->x || pt->y != pt->y) // NaN check835return true;836return false;837}838839bool Chessboard::Board::PointIter::checkCorner()const840{841if(!cell->empty())842return true;843// test all other cells844switch(corner_index)845{846case BOTTOM_LEFT:847if(cell->left)848{849if(!cell->left->empty())850return true;851if(cell->left->bottom && !cell->left->bottom->empty())852return true;853}854if(cell->bottom)855{856if(!cell->bottom->empty())857return true;858if(cell->bottom->left && !cell->bottom->left->empty())859return true;860}861break;862case TOP_LEFT:863if(cell->left)864{865if(!cell->left->empty())866return true;867if(cell->left->top && !cell->left->top->empty())868return true;869}870if(cell->top)871{872if(!cell->top->empty())873return true;874if(cell->top->left && !cell->top->left->empty())875return true;876}877break;878case TOP_RIGHT:879if(cell->right)880{881if(!cell->right->empty())882return true;883if(cell->right->top && !cell->right->top->empty())884return true;885}886if(cell->top)887{888if(!cell->top->empty())889return true;890if(cell->top->right && !cell->top->right->empty())891return true;892}893break;894case BOTTOM_RIGHT:895if(cell->right)896{897if(!cell->right->empty())898return true;899if(cell->right->bottom && !cell->right->bottom->empty())900return true;901}902if(cell->bottom)903{904if(!cell->bottom->empty())905return true;906if(cell->bottom->right && !cell->bottom->right->empty())907return true;908}909break;910default:911CV_Assert(false);912}913return false;914}915916917bool Chessboard::Board::PointIter::left(bool check_empty)918{919switch(corner_index)920{921case BOTTOM_LEFT:922if(cell->left && (!check_empty || !cell->left->empty()))923cell = cell->left;924else if(check_empty && cell->bottom && cell->bottom->left && !cell->bottom->left->empty())925{926cell = cell->bottom->left;927corner_index = TOP_LEFT;928}929else930return false;931break;932case TOP_LEFT:933if(cell->left && (!check_empty || !cell->left->empty()))934cell = cell->left;935else if(check_empty && cell->top && cell->top->left && !cell->top->left->empty())936{937cell = cell->top->left;938corner_index = BOTTOM_LEFT;939}940else941return false;942break;943case TOP_RIGHT:944corner_index = TOP_LEFT;945break;946case BOTTOM_RIGHT:947corner_index = BOTTOM_LEFT;948break;949default:950CV_Assert(false);951}952return true;953}954955bool Chessboard::Board::PointIter::top(bool check_empty)956957{958switch(corner_index)959{960case TOP_RIGHT:961if(cell->top && (!check_empty || !cell->top->empty()))962cell = cell->top;963else if(check_empty && cell->right && cell->right->top&& !cell->right->top->empty())964{965cell = cell->right->top;966corner_index = TOP_LEFT;967}968else969return false;970break;971case TOP_LEFT:972if(cell->top && (!check_empty || !cell->top->empty()))973cell = cell->top;974else if(check_empty && cell->left && cell->left->top&& !cell->left->top->empty())975{976cell = cell->left->top;977corner_index = TOP_RIGHT;978}979else980return false;981break;982case BOTTOM_LEFT:983corner_index = TOP_LEFT;984break;985case BOTTOM_RIGHT:986corner_index = TOP_RIGHT;987break;988default:989CV_Assert(false);990}991return true;992}993994bool Chessboard::Board::PointIter::right(bool check_empty)995{996switch(corner_index)997{998case TOP_RIGHT:999if(cell->right && (!check_empty || !cell->right->empty()))1000cell = cell->right;1001else if(check_empty && cell->top && cell->top->right && !cell->top->right->empty())1002{1003cell = cell->top->right;1004corner_index = BOTTOM_RIGHT;1005}1006else1007return false;1008break;1009case BOTTOM_RIGHT:1010if(cell->right && (!check_empty || !cell->right->empty()))1011cell = cell->right;1012else if(check_empty && cell->bottom && cell->bottom->right && !cell->bottom->right->empty())1013{1014cell = cell->bottom->right;1015corner_index = TOP_RIGHT;1016}1017else1018return false;1019break;1020case TOP_LEFT:1021corner_index = TOP_RIGHT;1022break;1023case BOTTOM_LEFT:1024corner_index = BOTTOM_RIGHT;1025break;1026default:1027CV_Assert(false);1028}1029return true;1030}10311032bool Chessboard::Board::PointIter::bottom(bool check_empty)1033{1034switch(corner_index)1035{1036case BOTTOM_LEFT:1037if(cell->bottom && (!check_empty || !cell->bottom->empty()))1038cell = cell->bottom;1039else if(check_empty && cell->left && cell->left->bottom && !cell->left->bottom->empty())1040{1041cell = cell->left->bottom;1042corner_index = BOTTOM_RIGHT;1043}1044else1045return false;1046break;1047case BOTTOM_RIGHT:1048if(cell->bottom && (!check_empty || !cell->bottom->empty()))1049cell = cell->bottom;1050else if(check_empty && cell->right && cell->right->bottom && !cell->right->bottom->empty())1051{1052cell = cell->right->bottom;1053corner_index = BOTTOM_LEFT;1054}1055else1056return false;1057break;1058case TOP_LEFT:1059corner_index = BOTTOM_LEFT;1060break;1061case TOP_RIGHT:1062corner_index = BOTTOM_RIGHT;1063break;1064default:1065CV_Assert(false);1066}1067return true;1068}106910701071const cv::Point2f* Chessboard::Board::PointIter::operator*()const1072{1073switch(corner_index)1074{1075case TOP_LEFT:1076return cell->top_left;1077case TOP_RIGHT:1078return cell->top_right;1079case BOTTOM_RIGHT:1080return cell->bottom_right;1081case BOTTOM_LEFT:1082return cell->bottom_left;1083}1084CV_Assert(false);1085}10861087const cv::Point2f* Chessboard::Board::PointIter::operator->()const1088{1089return operator*();1090}10911092cv::Point2f* Chessboard::Board::PointIter::operator*()1093{1094const cv::Point2f *pt = const_cast<const PointIter*>(this)->operator*();1095return const_cast<cv::Point2f*>(pt);1096}10971098cv::Point2f* Chessboard::Board::PointIter::operator->()1099{1100return operator*();1101}11021103Chessboard::Board::Board(float _white_angle,float _black_angle):1104top_left(NULL),1105rows(0),1106cols(0),1107white_angle(_white_angle),1108black_angle(_black_angle)1109{1110}111111121113Chessboard::Board::Board(const Chessboard::Board &other):1114top_left(NULL),1115rows(0),1116cols(0)1117{1118*this = other;1119}11201121Chessboard::Board::Board(const cv::Size &size, const std::vector<cv::Point2f> &points,float _white_angle,float _black_angle):1122top_left(NULL),1123rows(0),1124cols(0),1125white_angle(_white_angle),1126black_angle(_black_angle)1127{1128if(size.width*size.height != int(points.size()))1129CV_Error(Error::StsBadArg,"size mismatch");1130if(size.width < 3 || size.height < 3)1131CV_Error(Error::StsBadArg,"at least 3 rows and cols are needed to initialize the board");11321133// init board with 3x31134// TODO write function speeding up the copying1135cv::Mat data = cv::Mat(points).reshape(2,size.height);1136cv::Mat temp;1137data(cv::Rect(0,0,3,3)).copyTo(temp);1138std::vector<cv::Point2f> ipoints = temp.reshape(2,1);1139if(!init(ipoints))1140return;11411142// add all cols1143for(int col=3 ; col< data.cols;++col)1144{1145data(cv::Rect(col,0,1,3)).copyTo(temp);1146ipoints = temp.reshape(2,1);1147addColumnRight(ipoints);1148}11491150// add all rows1151for(int row=3; row < data.rows;++row)1152{1153data(cv::Rect(0,row,cols,1)).copyTo(temp);1154ipoints = temp.reshape(2,1);1155addRowBottom(ipoints);1156}1157}11581159Chessboard::Board::~Board()1160{1161clear();1162}11631164std::vector<cv::Point2f> Chessboard::Board::getCellCenters()const1165{1166int icols = int(colCount());1167int irows = int(rowCount());1168if(icols < 3 || irows < 3)1169throw std::runtime_error("getCellCenters: Chessboard must be at least consist of 3 rows and cols to calcualte the cell centers");11701171std::vector<cv::Point2f> points;1172cv::Matx33d H(estimateHomography(DUMMY_FIELD_SIZE));1173cv::Vec3d pt1,pt2;1174pt1[2] = 1;1175for(int row = 0;row < irows;++row)1176{1177pt1[1] = (0.5+row)*DUMMY_FIELD_SIZE;1178for(int col= 0;col< icols;++col)1179{1180pt1[0] = (0.5+col)*DUMMY_FIELD_SIZE;1181pt2 = H*pt1;1182points.push_back(cv::Point2f(float(pt2[0]/pt2[2]),float(pt2[1]/pt2[2])));1183}1184}1185return points;1186}11871188void Chessboard::Board::draw(cv::InputArray m,cv::OutputArray out,cv::InputArray _H)const1189{1190cv::Mat H = _H.getMat();1191if(H.empty())1192H = estimateHomography();1193cv::Mat image = m.getMat().clone();1194if(image.type() == CV_32FC1)1195{1196double maxVal,minVal;1197cv::minMaxLoc(image, &minVal, &maxVal);1198double scale = 255.0/(maxVal-minVal);1199image.convertTo(image,CV_8UC1,scale,-scale*minVal);1200cv::applyColorMap(image,image,cv::COLORMAP_JET);1201}12021203// draw all points and search areas1204std::vector<cv::Point2f> points = getCorners();1205std::vector<cv::Point2f>::const_iterator iter1 = points.begin();1206int icols = int(colCount());1207int irows = int(rowCount());1208int count=0;1209for(int row=0;row<irows;++row)1210{1211for(int col=0;col<icols;++col,++iter1)1212{1213if(iter1->x != iter1->x) // NaN check1214{1215// draw search ellipse1216Ellipse ellipse = estimateSearchArea(H,row,col,0.4F);1217ellipse.draw(image,cv::Scalar::all(200));1218}1219else1220{1221cv::circle(image,*iter1,4,cv::Scalar(count*20,count*20,count*20,255),-1);1222++count;1223}1224}1225}12261227// draw field colors1228for(int row=0;row<irows-1;++row)1229{1230for(int col=0;col<icols-1;++col)1231{1232const Cell *cell = getCell(row,col);1233cv::Point2f center = *cell->top_left+*cell->top_right+*cell->bottom_left+*cell->bottom_right;1234center.x /=4;1235center.y /=4;1236int size = 4;1237if(row==0&&col==0)1238size=8;1239if(row==0&&col==1)1240size=7;1241if(cell->black)1242cv::circle(image,center,size,cv::Scalar::all(255),-1);1243else1244cv::circle(image,center,size,cv::Scalar(0,0,10,255),-1);1245}1246}12471248out.create(image.rows,image.cols,image.type());1249image.copyTo(out.getMat());1250}12511252bool Chessboard::Board::estimatePose(const cv::Size2f &real_size,cv::InputArray _K,cv::OutputArray rvec,cv::OutputArray tvec)const1253{1254cv::Mat K = _K.getMat();1255CV_CheckTypeEQ(K.type(), CV_64FC1, "wrong K type");1256CV_CheckEQ(K.size(), Size(3, 3), "wrong K size");1257if(isEmpty())1258return false;12591260int icols = int(colCount());1261int irows = int(rowCount());1262float field_width = real_size.width/(icols+1);1263float field_height= real_size.height/(irows+1);1264// the center of the board is placed at (0,0,1)1265int offset_x = int(-(icols-1)*field_width*0.5F);1266int offset_y = int(-(irows-1)*field_width*0.5F);12671268std::vector<cv::Point2f> image_points;1269std::vector<cv::Point3f> object_points;1270std::vector<cv::Point2f> corners_temp = getCorners(true);1271std::vector<cv::Point2f>::const_iterator iter = corners_temp.begin();1272for(int row = 0;row < irows;++row)1273{1274for(int col= 0;col<icols;++col,++iter)1275{1276if(iter == corners_temp.end())1277CV_Error(Error::StsInternal,"internal error");1278if(iter->x != iter->x) // NaN check1279continue;1280image_points.push_back(*iter);1281object_points.push_back(cv::Point3f(field_width*col-offset_x,field_height*row-offset_y,1.0));1282}1283}1284return cv::solvePnP(object_points,image_points,K,cv::Mat(),rvec,tvec);//,cv::SOLVEPNP_P3P);1285}12861287float Chessboard::Board::getBlackAngle()const1288{1289return black_angle;1290}12911292float Chessboard::Board::getWhiteAngle()const1293{1294return white_angle;1295}12961297void Chessboard::Board::swap(Chessboard::Board &other)1298{1299corners.swap(other.corners);1300cells.swap(other.cells);1301std::swap(rows,other.rows);1302std::swap(cols,other.cols);1303std::swap(top_left,other.top_left);1304std::swap(white_angle,other.white_angle);1305std::swap(black_angle,other.black_angle);1306}13071308Chessboard::Board& Chessboard::Board::operator=(const Chessboard::Board &other)1309{1310if(this == &other)1311return *this;1312clear();1313rows = other.rows;1314cols = other.cols;1315white_angle = other.white_angle;1316black_angle = other.black_angle;1317cells.reserve(other.cells.size());1318corners.reserve(other.corners.size());13191320//copy all points and generate mapping1321std::map<cv::Point2f*,cv::Point2f*> point_point_mapping;1322point_point_mapping[NULL] = NULL;1323std::vector<cv::Point2f*>::const_iterator iter = other.corners.begin();1324for(;iter != other.corners.end();++iter)1325{1326cv::Point2f *pt = new cv::Point2f(**iter);1327point_point_mapping[*iter] = pt;1328corners.push_back(pt);1329}13301331//copy all cells using mapping1332std::map<Cell*,Cell*> cell_cell_mapping;1333std::vector<Cell*>::const_iterator iter2 = other.cells.begin();1334for(;iter2 != other.cells.end();++iter2)1335{1336Cell *cell = new Cell;1337cell->top_left = point_point_mapping[(*iter2)->top_left];1338cell->top_right= point_point_mapping[(*iter2)->top_right];1339cell->bottom_right= point_point_mapping[(*iter2)->bottom_right];1340cell->bottom_left = point_point_mapping[(*iter2)->bottom_left];1341cell->black = (*iter2)->black;1342cell_cell_mapping[*iter2] = cell;1343cells.push_back(cell);1344}13451346//set cell connections using mapping1347cell_cell_mapping[NULL] = NULL;1348iter2 = other.cells.begin();1349std::vector<Cell*>::iterator iter3 = cells.begin();1350for(;iter2 != other.cells.end();++iter2,++iter3)1351{1352(*iter3)->left = cell_cell_mapping[(*iter2)->left];1353(*iter3)->top = cell_cell_mapping[(*iter2)->top];1354(*iter3)->right = cell_cell_mapping[(*iter2)->right];1355(*iter3)->bottom= cell_cell_mapping[(*iter2)->bottom];1356}1357top_left = cell_cell_mapping[other.top_left];1358return *this;1359}13601361void Chessboard::Board::normalizeOrientation(bool bblack)1362{1363// fix ordering1364cv::Point2f y = getCorner(0,1)-getCorner(2,1);1365cv::Point2f x = getCorner(1,2)-getCorner(1,0);1366cv::Point3f y3d(y.x,y.y,0);1367cv::Point3f x3d(x.x,x.y,0);1368if(x3d.cross(y3d).z > 0)1369flipHorizontal();13701371//normalize orientation so that first element is black or white1372const Cell* cell = getCell(0,0);1373if(cell->black != bblack && colCount()%2 != 0)1374rotateLeft();1375else if(cell->black != bblack && rowCount()%2 != 0)1376{1377rotateLeft();1378rotateLeft();1379}13801381//find closest point to top left image corner1382//in case of symmetric checkerboard1383if(colCount() == rowCount())1384{1385PointIter iter_top_right(top_left,TOP_RIGHT);1386while(iter_top_right.right());1387PointIter iter_bottom_right(iter_top_right);1388while(iter_bottom_right.bottom());1389PointIter iter_bottom_left(top_left,BOTTOM_LEFT);1390while(iter_bottom_left.bottom());1391// check if one of the cell is empty and do not normalize if so1392if(top_left->empty() || iter_top_right.getCell()->empty() ||1393iter_bottom_left.getCell()->empty() || iter_bottom_right.getCell()->empty())1394return;13951396float d1 = pow(top_left->top_left->x,2)+pow(top_left->top_left->y,2);1397float d2 = pow((*iter_top_right)->x,2)+pow((*iter_top_right)->y,2);1398float d3 = pow((*iter_bottom_left)->x,2)+pow((*iter_bottom_left)->y,2);1399float d4 = pow((*iter_bottom_right)->x,2)+pow((*iter_bottom_right)->y,2);1400if(d2 <= d1 && d2 <= d3 && d2 <= d4) // top left is top right1401rotateLeft();1402else if(d3 <= d1 && d3 <= d2 && d3 <= d4) // top left is bottom left1403rotateRight();1404else if(d4 <= d1 && d4 <= d2 && d4 <= d3) // top left is bottom right1405{1406rotateLeft();1407rotateLeft();1408}1409}1410}14111412void Chessboard::Board::rotateRight()1413{1414PointIter p_iter(top_left,BOTTOM_LEFT);1415while(p_iter.bottom());14161417std::vector<Cell*>::iterator iter = cells.begin();1418for(;iter != cells.end();++iter)1419{1420Cell *temp = (*iter)->bottom;1421(*iter)->bottom = (*iter)->right;1422(*iter)->right= (*iter)->top;1423(*iter)->top= (*iter)->left;1424(*iter)->left = temp;14251426cv::Point2f *ptemp = (*iter)->bottom_left;1427(*iter)->bottom_left= (*iter)->bottom_right;1428(*iter)->bottom_right= (*iter)->top_right;1429(*iter)->top_right= (*iter)->top_left;1430(*iter)->top_left= ptemp;1431}1432int temp = rows;1433rows = cols;1434cols = temp;1435top_left = p_iter.getCell();1436}143714381439void Chessboard::Board::rotateLeft()1440{1441PointIter p_iter(top_left,TOP_RIGHT);1442while(p_iter.right());14431444std::vector<Cell*>::iterator iter = cells.begin();1445for(;iter != cells.end();++iter)1446{1447Cell *temp = (*iter)->top;1448(*iter)->top = (*iter)->right;1449(*iter)->right= (*iter)->bottom;1450(*iter)->bottom= (*iter)->left;1451(*iter)->left = temp;14521453cv::Point2f *ptemp = (*iter)->top_left;1454(*iter)->top_left = (*iter)->top_right;1455(*iter)->top_right= (*iter)->bottom_right;1456(*iter)->bottom_right = (*iter)->bottom_left;1457(*iter)->bottom_left = ptemp;1458}1459int temp = rows;1460rows = cols;1461cols = temp;1462top_left = p_iter.getCell();1463}14641465void Chessboard::Board::flipHorizontal()1466{1467PointIter p_iter(top_left,TOP_RIGHT);1468while(p_iter.right());14691470std::vector<Cell*>::iterator iter = cells.begin();1471for(;iter != cells.end();++iter)1472{1473Cell *temp = (*iter)->right;1474(*iter)->right= (*iter)->left;1475(*iter)->left = temp;14761477cv::Point2f *ptemp = (*iter)->top_left;1478(*iter)->top_left = (*iter)->top_right;1479(*iter)->top_right = ptemp;14801481ptemp = (*iter)->bottom_left;1482(*iter)->bottom_left = (*iter)->bottom_right;1483(*iter)->bottom_right = ptemp;1484}1485top_left = p_iter.getCell();1486}14871488void Chessboard::Board::flipVertical()1489{1490PointIter p_iter(top_left,BOTTOM_LEFT);1491while(p_iter.bottom());14921493std::vector<Cell*>::iterator iter = cells.begin();1494for(;iter != cells.end();++iter)1495{1496Cell *temp = (*iter)->top;1497(*iter)->top= (*iter)->bottom;1498(*iter)->bottom = temp;14991500cv::Point2f *ptemp = (*iter)->top_left;1501(*iter)->top_left = (*iter)->bottom_left;1502(*iter)->bottom_left = ptemp;15031504ptemp = (*iter)->top_right;1505(*iter)->top_right = (*iter)->bottom_right;1506(*iter)->bottom_right = ptemp;1507}1508top_left = p_iter.getCell();1509}15101511// returns the best found score1512// if NaN is returned for a point no point at all was found1513// if 0 is returned the point lies outside of the ellipse1514float Chessboard::Board::findMaxPoint(cv::flann::Index &index,const cv::Mat &data,const Ellipse &ellipse,float white_angle,float black_angle,cv::Point2f &point)1515{1516// flann data type enriched with angles (third column)1517CV_CheckType(data.type(), CV_32FC1, "type of flann data is not supported");1518CV_CheckEQ(data.cols, 4, "4-cols flann data is expected");15191520std::vector<float> query,dists;1521std::vector<int> indices;1522query.resize(2);1523point = ellipse.getCenter();1524query[0] = point.x;1525query[1] = point.y;1526index.knnSearch(query,indices,dists,4,cv::flann::SearchParams(64));1527std::vector<int>::const_iterator iter = indices.begin();1528float best_score = -std::numeric_limits<float>::max();1529point.x = std::numeric_limits<float>::quiet_NaN();1530point.y = std::numeric_limits<float>::quiet_NaN();1531for(;iter != indices.end();++iter)1532{1533const float *val = data.ptr<float>(*iter);1534const float &response = *(val+3);1535if(response < best_score)1536continue;1537const float &a0 = *(val+2);1538float a1 = std::fabs(a0-white_angle);1539float a2 = std::fabs(a0-black_angle);1540if(a1 > CV_PI*0.5)1541a1 = std::fabs(float(a1-CV_PI));1542if(a2> CV_PI*0.5)1543a2 = std::fabs(float(a2-CV_PI));1544if(a1 < MAX_ANGLE || a2 < MAX_ANGLE )1545{1546cv::Point2f pt(val[0], val[1]);1547if(point.x != point.x) // NaN check1548point = pt;1549if(best_score < response && ellipse.contains(pt))1550{1551best_score = response;1552point = pt;1553}1554}1555}1556if(best_score == -std::numeric_limits<float>::max())1557return 0;1558else1559return best_score;1560}15611562void Chessboard::Board::clear()1563{1564top_left = NULL; rows = 0; cols = 0;1565std::vector<Cell*>::iterator iter = cells.begin();1566for(;iter != cells.end();++iter)1567delete *iter;1568cells.clear();1569std::vector<cv::Point2f*>::iterator iter2 = corners.begin();1570for(;iter2 != corners.end();++iter2)1571delete *iter2;1572corners.clear();1573}15741575// p0 p1 p21576// p3 p4 p51577// p6 p7 p81578bool Chessboard::Board::init(const std::vector<cv::Point2f> points)1579{1580clear();1581if(points.size() != 9)1582CV_Error(Error::StsBadArg,"exact nine points are expected to initialize the board");15831584// generate cells1585corners.resize(9);1586for(int i=0;i < 9;++i)1587corners[i] = new cv::Point2f(points[i]);1588cells.resize(4);1589for(int i=0;i<4;++i)1590cells[i] = new Cell();15911592//cell 01593cells[0]->top_left = corners[0];1594cells[0]->top_right = corners[1];1595cells[0]->bottom_right = corners[4];1596cells[0]->bottom_left = corners[3];1597cells[0]->right = cells[1];1598cells[0]->bottom = cells[2];15991600//cell 11601cells[1]->top_left = corners[1];1602cells[1]->top_right = corners[2];1603cells[1]->bottom_right = corners[5];1604cells[1]->bottom_left = corners[4];1605cells[1]->left = cells[0];1606cells[1]->bottom = cells[3];16071608//cell 21609cells[2]->top_left = corners[3];1610cells[2]->top_right = corners[4];1611cells[2]->bottom_right = corners[7];1612cells[2]->bottom_left = corners[6];1613cells[2]->top = cells[0];1614cells[2]->right = cells[3];16151616//cell 31617cells[3]->top_left = corners[4];1618cells[3]->top_right = corners[5];1619cells[3]->bottom_right = corners[8];1620cells[3]->bottom_left = corners[7];1621cells[3]->top = cells[1];1622cells[3]->left= cells[2];16231624top_left = cells.front();1625rows = 3;1626cols = 3;16271628// set inital cell colors1629Point2f pt1 = *(cells[0]->top_right)-*(cells[0]->bottom_left);1630pt1 /= cv::norm(pt1);1631cv::Point2f pt2(cos(white_angle),-sin(white_angle));1632cv::Point2f pt3(cos(black_angle),-sin(black_angle));1633if(fabs(pt1.dot(pt2)) < fabs(pt1.dot(pt3)))1634{1635cells[0]->black = false;1636cells[1]->black = true;1637cells[2]->black = true;1638cells[3]->black = false;1639}1640else1641{1642cells[0]->black = true;1643cells[1]->black = false;1644cells[2]->black = false;1645cells[3]->black = true;1646}1647return true;1648}16491650//TODO magic number1651bool Chessboard::Board::estimatePoint(const cv::Point2f &p0,const cv::Point2f &p1,const cv::Point2f &p2, cv::Point2f &p3)1652{1653// use cross ration to find new point1654if(p0 == p1 || p0 == p2 || p1 == p2)1655return false;1656cv::Point2f p01 = p1-p0;1657cv::Point2f p12 = p2-p1;1658float a = float(cv::norm(p01));1659float b = float(cv::norm(p12));1660float t = (0.75F*a-0.25F*b);1661if(t <= 0)1662return false;1663float c = 0.25F*b*(a+b)/t;1664if(c < 0.1F)1665return false;1666p01 = p01/a;1667p12 = p12/b;1668// check angle between p01 and p12 < 25°1669if(p01.dot(p12) < 0.9)1670return false;1671// calc mean1672// p12 = (p01+p12)*0.5;1673// p3 = p2+p12*c;1674p3 = p2+p12*c;16751676// compensate radial distortion by fitting polynom1677std::vector<double> x,y;1678x.resize(3,0); y.resize(3,0);1679x[1] = b;1680x[2] = b+a;1681y[2] = calcSignedDistance(-p12,p2,p0);1682cv::Mat dst;1683polyfit(cv::Mat(x),cv::Mat(y),dst,2);1684double d = dst.at<double>(0)-dst.at<double>(1)*c+dst.at<double>(2)*c*c;1685cv::Vec3f v1(p12.x,p12.y,0);1686cv::Vec3f v2(0,0,1);1687cv::Vec3f v3 = v1.cross(v2);1688cv::Point2f n2(v3[0],v3[1]);1689p3 += d*n2;1690return true;1691}16921693bool Chessboard::Board::estimatePoint(const cv::Point2f &p0,const cv::Point2f &p1,const cv::Point2f &p2, const cv::Point2f &p3, cv::Point2f &p4)1694{1695// use 1D homography to find fith point minimizing square error1696if(p0 == p1 || p0 == p2 || p0 == p3 || p1 == p2 || p1 == p3 || p2 == p3 )1697return false;1698static const cv::Mat src = (cv::Mat_<double>(1,4) << 0,10,20,30);1699cv::Point2f p01 = p1-p0;1700cv::Point2f p02 = p2-p0;1701cv::Point2f p03 = p3-p0;1702float a = float(cv::norm(p01));1703float b = float(cv::norm(p02));1704float c = float(cv::norm(p03));1705cv::Mat dst = (cv::Mat_<double>(1,4) << 0,a,b,c);1706cv::Mat h = findHomography1D(src,dst);1707float d = float((h.at<double>(0,0)*40+h.at<double>(0,1))/(h.at<double>(1,0)*40+h.at<double>(1,1)));1708cv::Point2f p12 = p2-p1;1709cv::Point2f p23 = p3-p2;1710p01 = p01/a;1711p12 = p12/cv::norm(p12);1712p23 = p23/cv::norm(p23);1713p4 = p3+(d-c)*p23;17141715// compensate radial distortion by fitting polynom1716std::vector<double> x,y;1717x.resize(4,0); y.resize(4,0);1718x[1] = c-b;1719x[2] = c-a;1720x[3] = c;1721y[2] = calcSignedDistance(-p23,p3,p1);1722y[3] = calcSignedDistance(-p23,p3,p0);1723polyfit(cv::Mat(x),cv::Mat(y),dst,2);1724d = d-c;1725double e = dst.at<double>(0)-dst.at<double>(1)*fabs(d)+dst.at<double>(2)*d*d;1726cv::Vec3f v1(p23.x,p23.y,0);1727cv::Vec3f v2(0,0,1);1728cv::Vec3f v3 = v1.cross(v2);1729cv::Point2f n2(v3[0],v3[1]);1730p4 += e*n2;1731return true;1732}17331734// H is describing the transformation from dummy to reality1735Ellipse Chessboard::Board::estimateSearchArea(cv::Mat _H,int row, int col,float p,int field_size)1736{1737cv::Matx31d point1,point2,center;1738center(0) = (1+col)*field_size;1739center(1) = (1+row)*field_size;1740center(2) = 1.0;1741point1(0) = center(0)-p*field_size;1742point1(1) = center(1);1743point1(2) = center(2);1744point2(0) = center(0);1745point2(1) = center(1)-p*field_size;1746point2(2) = center(2);17471748cv::Matx33d H(_H);1749point1 = H*point1;1750point2 = H*point2;1751center = H*center;1752cv::Point2f pt(float(center(0)/center(2)),float(center(1)/center(2)));1753cv::Point2f pt1(float(point1(0)/point1(2)),float(point1(1)/point1(2)));1754cv::Point2f pt2(float(point2(0)/point2(2)),float(point2(1)/point2(2)));17551756cv::Point2f p01(pt1-pt);1757cv::Point2f p02(pt2-pt);1758float norm1 = float(cv::norm(p01));1759float norm2 = float(cv::norm(p02));1760float angle = float(acos(p01.dot(p02)/norm1/norm2));1761cv::Size2f axes(norm1,norm2);1762return Ellipse(pt,axes,angle);1763}17641765bool Chessboard::Board::estimateSearchArea(const cv::Point2f &p1,const cv::Point2f &p2,const cv::Point2f &p3,float p,Ellipse &ellipse,const cv::Point2f *p0)1766{1767cv::Point2f p4,n;1768if(p0)1769{1770// use 1D homography1771if(!estimatePoint(*p0,p1,p2,p3,p4))1772return false;1773n = p4-*p0;1774}1775else1776{1777// use cross ratio1778if(!estimatePoint(p1,p2,p3,p4))1779return false;1780n = p4-p1;1781}1782float norm = float(cv::norm(n));1783n = n/norm;1784float angle = acos(n.x);1785if(n.y > 0)1786angle = float(2.0F*CV_PI-angle);1787n = p4-p3;1788norm = float(cv::norm(n));1789double delta = std::max(3.0F,p*norm);1790ellipse = Ellipse(p4,cv::Size(int(delta),int(std::max(2.0,delta*ELLIPSE_WIDTH))),angle);1791return true;1792}17931794bool Chessboard::Board::checkRowColumn(const std::vector<cv::Point2f> &points)1795{1796if(points.size() < 4)1797{1798if(points.size() == 3)1799return true;1800else1801return false;1802}1803std::vector<cv::Point2f>::const_iterator iter = points.begin();1804std::vector<cv::Point2f>::const_iterator iter2 = iter+1;1805std::vector<cv::Point2f>::const_iterator iter3 = iter2+1;1806std::vector<cv::Point2f>::const_iterator iter4 = iter3+1;1807Ellipse ellipse;1808if(!estimateSearchArea(*iter4,*iter3,*iter2,CORNERS_SEARCH*3,ellipse))1809return false;1810if(!ellipse.contains(*iter))1811return false;18121813std::vector<cv::Point2f>::const_iterator iter5 = iter4+1;1814for(;iter5 != points.end();++iter5)1815{1816if(!estimateSearchArea(*iter2,*iter3,*iter4,CORNERS_SEARCH,ellipse,&(*iter)))1817return false;1818if(!ellipse.contains(*iter5))1819return false;1820iter = iter2;1821iter2 = iter3;1822iter3 = iter4;1823iter4 = iter5;1824}1825return true;1826}18271828cv::Point2f &Chessboard::Board::getCorner(int _row,int _col)1829{1830int _rows = int(rowCount());1831int _cols = int(colCount());1832if(_row >= _rows || _col >= _cols)1833CV_Error(Error::StsBadArg,"out of bound");1834if(_row == 0)1835{1836PointIter iter(top_left,TOP_LEFT);1837int count = 0;1838do1839{1840if(count == _col)1841return *(*iter);1842++count;1843}while(iter.right());1844}1845else1846{1847Cell *row_start = top_left;1848int count = 1;1849do1850{1851if(count == _row)1852{1853PointIter iter(row_start,BOTTOM_LEFT);1854int count2 = 0;1855do1856{1857if(count2 == _col)1858return *(*iter);1859++count2;1860}while(iter.right());1861}1862++count;1863row_start = row_start->bottom;1864}while(_row);1865}1866CV_Error(Error::StsInternal,"cannot find corner");1867// return *top_left->top_left; // never reached1868}18691870bool Chessboard::Board::isCellBlack(int row,int col)const1871{1872return getCell(row,col)->black;1873}18741875bool Chessboard::Board::isCellEmpty(int row,int col)1876{1877return getCell(row,col)->empty();1878}18791880Chessboard::Board::Cell* Chessboard::Board::getCell(int row,int col)1881{1882const Cell *cell = const_cast<const Board*>(this)->getCell(row,col);1883return const_cast<Cell*>(cell);1884}18851886const Chessboard::Board::Cell* Chessboard::Board::getCell(int row,int col)const1887{1888if(row > rows-1 || row < 0 || col > cols-1 || col < 0)1889CV_Error(Error::StsBadArg,"out of bound");1890PointIter p_iter(top_left,BOTTOM_RIGHT);1891for(int i=0; i< row; p_iter.bottom(),++i);1892for(int i=0; i< col; p_iter.right(),++i);1893return p_iter.getCell();1894}189518961897bool Chessboard::Board::isEmpty()const1898{1899return cells.empty();1900}19011902size_t Chessboard::Board::colCount()const1903{1904return cols;1905}19061907size_t Chessboard::Board::rowCount()const1908{1909return rows;1910}19111912cv::Size Chessboard::Board::getSize()const1913{1914return cv::Size(int(colCount()),int(rowCount()));1915}19161917void Chessboard::Board::drawEllipses(const std::vector<Ellipse> &ellipses)1918{1919// currently there is no global image find way to store global image1920// without polluting namespace1921if(ellipses.empty())1922return; //avoid compiler warning1923#ifdef CV_DETECTORS_CHESSBOARD_DEBUG1924cv::Mat img;1925draw(debug_image,img);1926std::vector<Ellipse>::iterator iter;1927for(;iter != ellipses.end();++iter)1928iter->draw(img);1929cv::imshow("chessboard",img);1930cv::waitKey(-1);1931#endif1932}193319341935void Chessboard::Board::growLeft()1936{1937if(isEmpty())1938CV_Error(Error::StsInternal,"Board is empty");1939PointIter iter(top_left,TOP_LEFT);1940std::vector<cv::Point2f> points;1941cv::Point2f pt;1942do1943{1944PointIter iter2(iter);1945cv::Point2f *p0 = *iter2;1946iter2.right();1947cv::Point2f *p1 = *iter2;1948iter2.right();1949cv::Point2f *p2 = *iter2;1950if(iter2.right())1951estimatePoint(**iter2,*p2,*p1,*p0,pt);1952else1953estimatePoint(*p2,*p1,*p0,pt);1954points.push_back(pt);1955}1956while(iter.bottom());1957addColumnLeft(points);1958}19591960bool Chessboard::Board::growLeft(const cv::Mat &map,cv::flann::Index &flann_index)1961{1962#ifdef CV_DETECTORS_CHESSBOARD_DEBUG1963std::vector<Ellipse> ellipses;1964#endif1965if(isEmpty())1966CV_Error(Error::StsInternal,"growLeft: Board is empty");1967PointIter iter(top_left,TOP_LEFT);1968std::vector<cv::Point2f> points;1969int count = 0;1970Ellipse ellipse;1971cv::Point2f pt;1972do1973{1974PointIter iter2(iter);1975cv::Point2f *p0 = *iter2;1976iter2.right();1977cv::Point2f *p1 = *iter2;1978iter2.right();1979cv::Point2f *p2 = *iter2;1980cv::Point2f *p3 = NULL;1981if(iter2.right())1982p3 = *iter2;1983if(!estimateSearchArea(*p2,*p1,*p0,CORNERS_SEARCH,ellipse,p3))1984return false;1985float result = findMaxPoint(flann_index,map,ellipse,white_angle,black_angle,pt);1986if(pt == *p0)1987{1988++count;1989points.push_back(ellipse.getCenter());1990}1991else if(result != 0)1992{1993points.push_back(pt);1994if(result < 0)1995++count;1996}1997else1998{1999++count;2000if(pt.x != pt.x) // NaN check2001points.push_back(ellipse.getCenter());2002else2003points.push_back(pt);2004}2005#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2006ellipses.push_back(ellipse);2007#endif2008}2009while(iter.bottom());2010#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2011drawEllipses(ellipses);2012#endif2013if(points.size()-count <= 2)2014return false;2015if(count > points.size()*0.5 || !checkRowColumn(points))2016return false;2017addColumnLeft(points);2018return true;2019}20202021void Chessboard::Board::growTop()2022{2023if(isEmpty())2024CV_Error(Error::StsInternal,"Board is empty");2025PointIter iter(top_left,TOP_LEFT);2026std::vector<cv::Point2f> points;2027cv::Point2f pt;2028do2029{2030PointIter iter2(iter);2031cv::Point2f *p0 = *iter2;2032iter2.bottom();2033cv::Point2f *p1 = *iter2;2034iter2.bottom();2035cv::Point2f *p2 = *iter2;2036if(iter2.bottom())2037estimatePoint(**iter2,*p2,*p1,*p0,pt);2038else2039estimatePoint(*p2,*p1,*p0,pt);2040points.push_back(pt);2041}2042while(iter.right());2043addRowTop(points);2044}20452046bool Chessboard::Board::growTop(const cv::Mat &map,cv::flann::Index &flann_index)2047{2048#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2049std::vector<Ellipse> ellipses;2050#endif2051if(isEmpty())2052CV_Error(Error::StsInternal,"Board is empty");20532054PointIter iter(top_left,TOP_LEFT);2055std::vector<cv::Point2f> points;2056int count = 0;2057Ellipse ellipse;2058cv::Point2f pt;2059do2060{2061PointIter iter2(iter);2062cv::Point2f *p0 = *iter2;2063iter2.bottom();2064cv::Point2f *p1 = *iter2;2065iter2.bottom();2066cv::Point2f *p2 = *iter2;2067cv::Point2f *p3 = NULL;2068if(iter2.bottom())2069p3 = *iter2;2070if(!estimateSearchArea(*p2,*p1,*p0,CORNERS_SEARCH,ellipse,p3))2071return false;2072float result = findMaxPoint(flann_index,map,ellipse,white_angle,black_angle,pt);2073if(pt == *p0)2074{2075++count;2076points.push_back(ellipse.getCenter());2077}2078else if(result != 0)2079{2080points.push_back(pt);2081if(result < 0)2082++count;2083}2084else2085{2086++count;2087if(pt.x != pt.x) // NaN check2088points.push_back(ellipse.getCenter());2089else2090points.push_back(pt);2091}2092#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2093ellipses.push_back(ellipse);2094#endif2095}2096while(iter.right());2097#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2098drawEllipses(ellipses);2099#endif2100if(count > points.size()*0.5 || !checkRowColumn(points))2101return false;2102addRowTop(points);2103return true;2104}21052106void Chessboard::Board::growRight()2107{2108if(isEmpty())2109CV_Error(Error::StsInternal,"Board is empty");2110PointIter iter(top_left,TOP_RIGHT);2111while(iter.right());2112std::vector<cv::Point2f> points;2113cv::Point2f pt;2114do2115{2116PointIter iter2(iter);2117cv::Point2f *p0 = *iter2;2118iter2.left();2119cv::Point2f *p1 = *iter2;2120iter2.left();2121cv::Point2f *p2 = *iter2;2122if(iter2.left())2123estimatePoint(**iter2,*p2,*p1,*p0,pt);2124else2125estimatePoint(*p2,*p1,*p0,pt);2126points.push_back(pt);2127}2128while(iter.bottom());2129addColumnRight(points);2130}21312132bool Chessboard::Board::growRight(const cv::Mat &map,cv::flann::Index &flann_index)2133{2134#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2135std::vector<Ellipse> ellipses;2136#endif2137if(isEmpty())2138CV_Error(Error::StsInternal,"Board is empty");21392140PointIter iter(top_left,TOP_RIGHT);2141while(iter.right());2142std::vector<cv::Point2f> points;2143cv::Point2f pt;2144Ellipse ellipse;2145int count = 0;2146do2147{2148PointIter iter2(iter);2149cv::Point2f *p0 = *iter2;2150iter2.left();2151cv::Point2f *p1 = *iter2;2152iter2.left();2153cv::Point2f *p2 = *iter2;2154cv::Point2f *p3 = NULL;2155if(iter2.left())2156p3 = *iter2;2157if(!estimateSearchArea(*p2,*p1,*p0,CORNERS_SEARCH,ellipse,p3))2158return false;2159float result = findMaxPoint(flann_index,map,ellipse,white_angle,black_angle,pt);2160if(pt == *p0)2161{2162++count;2163points.push_back(ellipse.getCenter());2164}2165else if(result != 0)2166{2167points.push_back(pt);2168if(result < 0)2169++count;2170}2171else2172{2173++count;2174if(pt.x != pt.x) // NaN check2175points.push_back(ellipse.getCenter());2176else2177points.push_back(pt);2178}2179#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2180ellipses.push_back(ellipse);2181#endif2182}2183while(iter.bottom());2184#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2185drawEllipses(ellipses);2186#endif2187if(count > points.size()*0.5 || !checkRowColumn(points))2188return false;2189addColumnRight(points);2190return true;2191}21922193void Chessboard::Board::growBottom()2194{2195if(isEmpty())2196CV_Error(Error::StsInternal,"Board is empty");21972198PointIter iter(top_left,BOTTOM_LEFT);2199while(iter.bottom());2200std::vector<cv::Point2f> points;2201cv::Point2f pt;2202do2203{2204PointIter iter2(iter);2205cv::Point2f *p0 = *iter2;2206iter2.top();2207cv::Point2f *p1 = *iter2;2208iter2.top();2209cv::Point2f *p2 = *iter2;2210if(iter2.top())2211estimatePoint(**iter2,*p2,*p1,*p0,pt);2212else2213estimatePoint(*p2,*p1,*p0,pt);2214points.push_back(pt);2215}2216while(iter.right());2217addRowBottom(points);2218}22192220bool Chessboard::Board::growBottom(const cv::Mat &map,cv::flann::Index &flann_index)2221{2222#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2223std::vector<Ellipse> ellipses;2224#endif2225if(isEmpty())2226CV_Error(Error::StsInternal,"Board is empty");22272228PointIter iter(top_left,BOTTOM_LEFT);2229while(iter.bottom());2230std::vector<cv::Point2f> points;2231cv::Point2f pt;2232Ellipse ellipse;2233int count = 0;2234do2235{2236PointIter iter2(iter);2237cv::Point2f *p0 = *iter2;2238iter2.top();2239cv::Point2f *p1 = *iter2;2240iter2.top();2241cv::Point2f *p2 = *iter2;2242cv::Point2f *p3 = NULL;2243if(iter2.top())2244p3 = *iter2;2245if(!estimateSearchArea(*p2,*p1,*p0,CORNERS_SEARCH,ellipse,p3))2246return false;2247float result = findMaxPoint(flann_index,map,ellipse,white_angle,black_angle,pt);2248if(pt == *p0)2249{2250++count;2251points.push_back(ellipse.getCenter());2252}2253else if(result != 0)2254{2255points.push_back(pt);2256if(result < 0)2257++count;2258}2259else2260{2261++count;2262if(pt.x != pt.x) // NaN check2263points.push_back(ellipse.getCenter());2264else2265points.push_back(pt);2266}2267#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2268ellipses.push_back(ellipse);2269#endif2270}2271while(iter.right());2272#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2273drawEllipses(ellipses);2274#endif2275if(count > points.size()*0.5 || !checkRowColumn(points))2276return false;2277addRowBottom(points);2278return true;2279}22802281void Chessboard::Board::addColumnLeft(const std::vector<cv::Point2f> &points)2282{2283if(points.empty() || points.size() != rowCount())2284CV_Error(Error::StsBadArg,"wrong number of points");22852286int offset = int(cells.size());2287cells.resize(offset+points.size()-1);2288for(int i = offset;i < (int) cells.size();++i)2289cells[i] = new Cell();2290corners.push_back(new cv::Point2f(points.front()));22912292Cell *cell = top_left;2293std::vector<cv::Point2f>::const_iterator iter = points.begin()+1;2294for(int pos=offset;iter != points.end();++iter,cell = cell->bottom,++pos)2295{2296cell->left = cells[pos];2297cells[pos]->black = !cell->black;2298if(pos != offset)2299cells[pos]->top = cells[pos-1];2300cells[pos]->right = cell;2301if(pos +1 < (int)cells.size())2302cells[pos]->bottom= cells[pos+1];2303cells[pos]->top_left = corners.back();2304corners.push_back(new cv::Point2f(*iter));2305cells[pos]->bottom_left = corners.back();2306cells[pos]->top_right=cell->top_left;2307cells[pos]->bottom_right=cell->bottom_left;2308}2309top_left = cells[offset];2310++cols;2311}23122313void Chessboard::Board::addRowTop(const std::vector<cv::Point2f> &points)2314{2315if(points.empty() || points.size() != colCount())2316CV_Error(Error::StsBadArg,"wrong number of points");23172318int offset = int(cells.size());2319cells.resize(offset+points.size()-1);2320for(int i = offset;i < (int) cells.size();++i)2321cells[i] = new Cell();2322corners.push_back(new cv::Point2f(points.front()));23232324Cell *cell = top_left;2325std::vector<cv::Point2f>::const_iterator iter = points.begin()+1;2326for(int pos=offset;iter != points.end();++iter,cell = cell->right,++pos)2327{2328cell->top = cells[pos];2329cells[pos]->black = !cell->black;2330if(pos != offset)2331cells[pos]->left= cells[pos-1];2332cells[pos]->bottom= cell;2333if(pos +1 <(int) cells.size())2334cells[pos]->right= cells[pos+1];23352336cells[pos]->top_left = corners.back();2337corners.push_back(new cv::Point2f(*iter));2338cells[pos]->top_right = corners.back();2339cells[pos]->bottom_left = cell->top_left;2340cells[pos]->bottom_right = cell->top_right;2341}2342top_left = cells[offset];2343++rows;2344}23452346void Chessboard::Board::addColumnRight(const std::vector<cv::Point2f> &points)2347{2348if(points.empty() || points.size() != rowCount())2349CV_Error(Error::StsBadArg,"wrong number of points");23502351int offset = int(cells.size());2352cells.resize(offset+points.size()-1);2353for(int i = offset;i < (int) cells.size();++i)2354cells[i] = new Cell();2355corners.push_back(new cv::Point2f(points.front()));23562357Cell *cell = top_left;2358for(;cell->right;cell = cell->right);2359std::vector<cv::Point2f>::const_iterator iter = points.begin()+1;2360for(int pos=offset;iter != points.end();++iter,cell = cell->bottom,++pos)2361{2362cell->right = cells[pos];2363cells[pos]->black = !cell->black;2364if(pos != offset)2365cells[pos]->top= cells[pos-1];2366cells[pos]->left = cell;2367if(pos +1 <(int) cells.size())2368cells[pos]->bottom= cells[pos+1];23692370cells[pos]->top_right = corners.back();2371corners.push_back(new cv::Point2f(*iter));2372cells[pos]->bottom_right = corners.back();2373cells[pos]->top_left =cell->top_right;2374cells[pos]->bottom_left =cell->bottom_right;2375}2376++cols;2377}23782379void Chessboard::Board::addRowBottom(const std::vector<cv::Point2f> &points)2380{2381if(points.empty() || points.size() != colCount())2382CV_Error(Error::StsBadArg,"wrong number of points");23832384int offset = int(cells.size());2385cells.resize(offset+points.size()-1);2386for(int i = offset;i < (int) cells.size();++i)2387cells[i] = new Cell();2388corners.push_back(new cv::Point2f(points.front()));23892390Cell *cell = top_left;2391for(;cell->bottom;cell = cell->bottom);2392std::vector<cv::Point2f>::const_iterator iter = points.begin()+1;2393for(int pos=offset;iter != points.end();++iter,cell = cell->right,++pos)2394{2395cell->bottom = cells[pos];2396cells[pos]->black = !cell->black;2397if(pos != offset)2398cells[pos]->left = cells[pos-1];2399cells[pos]->top = cell;2400if(pos +1 < (int)cells.size())2401cells[pos]->right= cells[pos+1];24022403cells[pos]->bottom_left = corners.back();2404corners.push_back(new cv::Point2f(*iter));2405cells[pos]->bottom_right = corners.back();2406cells[pos]->top_left = cell->bottom_left;2407cells[pos]->top_right = cell->bottom_right;2408}2409++rows;2410}24112412bool Chessboard::Board::checkUnique()const2413{2414std::vector<cv::Point2f> points = getCorners(false);2415std::vector<cv::Point2f>::const_iterator iter = points.begin();2416for(;iter != points.end();++iter)2417{2418std::vector<cv::Point2f>::const_iterator iter2 = iter+1;2419for(;iter2 != points.end();++iter2)2420{2421if(*iter == *iter2)2422return false;2423}2424}2425return true;2426}24272428int Chessboard::Board::validateCorners(const cv::Mat &data,cv::flann::Index &flann_index,const cv::Mat &h,float min_response)2429{2430// TODO check input2431if(isEmpty() || h.empty())2432return 0;2433int count = 0; int icol = 0;2434// first row2435PointIter iter(top_left,TOP_LEFT);2436cv::Point2f point;2437do2438{2439if((*iter)->x == (*iter)->x)2440++count;2441else2442{2443Ellipse ellipse = estimateSearchArea(h,0,icol,0.4F);2444float result = findMaxPoint(flann_index,data,ellipse,white_angle,black_angle,point);2445if(fabs(result) >= min_response)2446{2447++count;2448**iter = point;2449}2450}2451++icol;2452}while(iter.right());24532454// all other rows2455int irow = 1;2456Cell *row = top_left;2457do2458{2459PointIter iter2(row,BOTTOM_LEFT);2460icol = 0;2461do2462{2463if((*iter2)->x == (*iter2)->x)2464++count;2465else2466{2467Ellipse ellipse = estimateSearchArea(h,irow,icol,0.4F);2468if(min_response <= findMaxPoint(flann_index,data,ellipse,white_angle,black_angle,point))2469{2470++count;2471**iter2 = point;2472}2473}2474++icol;2475}while(iter2.right());2476row = row->bottom;2477++irow;2478}while(row);24792480// check that there are no points with the same coordinate2481std::vector<cv::Point2f> points = getCorners(false);2482std::vector<cv::Point2f>::const_iterator iter1 = points.begin();2483for(;iter1 != points.end();++iter1)2484{2485// we do not have to check for NaN because of getCorners(flase)2486std::vector<cv::Point2f>::const_iterator iter2 = iter1+1;2487for(;iter2 != points.end();++iter2)2488if(*iter1 == *iter2)2489return -1; // one corner is there twice -> not valid configuration2490}2491return count;2492}24932494bool Chessboard::Board::validateContour()const2495{2496std::vector<cv::Point2f> contour = getContour();2497if(contour.size() != 4)2498{2499return false;2500}2501cv::Point2f n1 = contour[1]-contour[0];2502cv::Point2f n2 = contour[2]-contour[1];2503cv::Point2f n3 = contour[3]-contour[2];2504cv::Point2f n4 = contour[0]-contour[3];2505n1 = n1/cv::norm(n1);2506n2 = n2/cv::norm(n2);2507n3 = n3/cv::norm(n3);2508n4 = n4/cv::norm(n4);2509// a > b => cos(a) < cos(b)2510if(fabs(n1.dot(n2)) > MIN_COS_ANGLE||2511fabs(n2.dot(n3)) > MIN_COS_ANGLE||2512fabs(n3.dot(n4)) > MIN_COS_ANGLE||2513fabs(n4.dot(n1)) > MIN_COS_ANGLE)2514return false;2515return true;2516}25172518std::vector<cv::Point2f> Chessboard::Board::getContour()const2519{2520std::vector<cv::Point2f> points;2521if(isEmpty())2522return points;25232524//find start cell part of the contour2525Cell* start_cell = NULL;2526PointIter iter(top_left,TOP_LEFT);2527do2528{2529PointIter iter2(iter);2530do2531{2532if(!iter2.getCell()->empty())2533{2534start_cell = iter2.getCell();2535iter = iter2;2536break;2537}2538}while(iter2.right());2539}while(!start_cell && iter.bottom());2540if(start_cell == NULL)2541return points;25422543// trace contour2544const cv::Point2f *start_pt = *iter;2545int mode = 2; int last = -1;2546do2547{2548PointIter current_iter(iter);2549switch(mode)2550{2551case 1: // top2552if(iter.top(true))2553{2554if(last != 1)2555points.push_back(**current_iter);2556mode = 4;2557last = 1;2558break;2559}2560/* fallthrough */2561case 2: // right2562if(iter.right(true))2563{2564if(last != 2)2565points.push_back(**current_iter);2566mode = 1;2567last = 2;2568break;2569}2570/* fallthrough */2571case 3: // bottom2572if(iter.bottom(true))2573{2574if(last != 3)2575points.push_back(**current_iter);2576mode = 2;2577last = 3;2578break;2579}2580/* fallthrough */2581case 4: // left2582if(iter.left(true))2583{2584if(last != 4)2585points.push_back(**current_iter);2586mode = 3;2587last = 4;2588break;2589}2590mode = 1;2591break;2592default:2593CV_Error(Error::StsInternal,"cannot retrieve contour");2594}2595}while(*iter != start_pt);2596return points;2597}259825992600cv::Mat Chessboard::Board::estimateHomography(cv::Rect rect,int field_size)const2601{2602int _rows = int(rowCount());2603int _cols = int(colCount());2604if(_rows < 3 || _cols < 3)2605return cv::Mat();2606if(rect.width <= 0)2607rect.width= _cols;2608if(rect.height <= 0)2609rect.height= _rows;26102611int col_end = std::min(rect.x+rect.width,_cols);2612int row_end = std::min(rect.y+rect.height,_rows);2613std::vector<cv::Point2f> points = getCorners(true);26142615// build src and dst2616std::vector<cv::Point2f> src,dst;2617for(int row =rect.y;row < row_end;++row)2618{2619for(int col=rect.x;col <col_end;++col)2620{2621const cv::Point2f &pt = points[row*_rows+col];2622if(pt.x != pt.x) // NaN check2623continue;2624src.push_back(cv::Point2f(float(field_size)*(col+1),float(field_size)*(row+1)));2625dst.push_back(pt);2626}2627}2628if(dst.size() < 4)2629return cv::Mat();2630return cv::findHomography(src, dst,cv::LMEDS);2631}26322633cv::Mat Chessboard::Board::estimateHomography(int field_size)const2634{2635int _rows = int(rowCount());2636int _cols = int(colCount());2637if(_rows < 3 || _cols < 3)2638return cv::Mat();2639std::vector<cv::Point2f> src,dst;2640std::vector<cv::Point2f> points = getCorners(true);2641std::vector<cv::Point2f>::const_iterator iter = points.begin();2642for(int row =0;row < _rows;++row)2643{2644for(int col=0;col <_cols;++col,++iter)2645{2646const cv::Point2f &pt = *iter;2647if(pt.x == pt.x)2648{2649src.push_back(cv::Point2f(float(field_size)*(col+1),float(field_size)*(row+1)));2650dst.push_back(pt);2651}2652}2653}2654if(dst.size() < 4)2655return cv::Mat();2656return cv::findHomography(src, dst);2657}26582659bool Chessboard::Board::findNextPoint(cv::flann::Index &index,const cv::Mat &data,2660const cv::Point2f &pt1,const cv::Point2f &pt2, const cv::Point2f &pt3,2661float white_angle,float black_angle,float min_response,cv::Point2f &point)2662{2663Ellipse ellipse;2664if(!estimateSearchArea(pt1,pt2,pt3,0.4F,ellipse))2665return false;2666if(min_response > fabs(findMaxPoint(index,data,ellipse,white_angle,black_angle,point)))2667return false;2668return true;2669}26702671int Chessboard::Board::grow(const cv::Mat &map,cv::flann::Index &flann_index)2672{2673if(isEmpty())2674CV_Error(Error::StsInternal,"Board is empty");2675bool bleft = true;2676bool btop = true;2677bool bright = true;2678bool bbottom= true;2679int count = 0;2680do2681{2682// grow to the left2683if(bleft)2684{2685bleft = growLeft(map,flann_index);2686if(bleft)2687++count;2688}2689if(btop)2690{2691btop= growTop(map,flann_index);2692if(btop)2693++count;2694}2695if(bright)2696{2697bright= growRight(map,flann_index);2698if(bright)2699++count;2700}2701if(bbottom)2702{2703bbottom= growBottom(map,flann_index);2704if(bbottom)2705++count;2706}2707}while(bleft || btop || bright || bbottom );2708return count;2709}27102711std::map<int,int> Chessboard::Board::getMapping()const2712{2713std::map<int,int> map;2714std::vector<cv::Point2f> points = getCorners();2715std::vector<cv::Point2f>::iterator iter = points.begin();2716for(int idx1=0,idx2=0;iter != points.end();++iter,++idx1)2717{2718if(iter->x != iter->x) // NaN check2719continue;2720map[idx1] = idx2++;2721}2722return map;2723}27242725std::vector<cv::Point2f> Chessboard::Board::getCorners(bool ball)const2726{2727std::vector<cv::Point2f> points;2728if(isEmpty())2729return points;27302731// first row2732PointIter iter(top_left,TOP_LEFT);2733do2734{2735if(ball || !iter.isNaN())2736points.push_back(*(*iter));2737}while(iter.right());27382739// all other rows2740Cell *row = top_left;2741do2742{2743PointIter iter2(row,BOTTOM_LEFT);2744do2745{2746if(ball || !iter2.isNaN())2747points.push_back(*(*iter2));2748}while(iter2.right());2749row = row->bottom;2750}while(row);2751return points;2752}27532754std::vector<cv::KeyPoint> Chessboard::Board::getKeyPoints(bool ball)const2755{2756std::vector<cv::KeyPoint> keypoints;2757std::vector<cv::Point2f> points = getCorners(ball);2758std::vector<cv::Point2f>::const_iterator iter = points.begin();2759for(;iter != points.end();++iter)2760keypoints.push_back(cv::KeyPoint(iter->x,iter->y,1));2761return keypoints;2762}27632764Chessboard::Chessboard(const Parameters ¶)2765{2766reconfigure(para);2767}27682769void Chessboard::reconfigure(const Parameters &config)2770{2771parameters = config;2772}27732774Chessboard::Parameters Chessboard::getPara()const2775{2776return parameters;2777}27782779Chessboard::~Chessboard()2780{2781}27822783void Chessboard::findKeyPoints(const cv::Mat& image, std::vector<KeyPoint>& keypoints,std::vector<cv::Mat> &feature_maps,2784std::vector<std::vector<float> > &angles ,const cv::Mat& mask)const2785{2786keypoints.clear();2787angles.clear();2788vector<KeyPoint> keypoints_temp;2789FastX::Parameters para;27902791para.branches = 2; // this is always the case for checssboard corners2792para.strength = 10; // minimal threshold2793para.resolution = float(CV_PI*0.25); // this gives the best results taking interpolation into account2794para.filter = 1;2795para.super_resolution = parameters.super_resolution;2796para.min_scale = parameters.min_scale;2797para.max_scale = parameters.max_scale;27982799FastX detector(para);2800std::vector<cv::Mat> rotated_images;2801detector.detectImpl(image,rotated_images,feature_maps,mask);28022803//calculate seed chessboard corners2804detector.findKeyPoints(feature_maps,keypoints_temp,mask);28052806//sort points and limit number2807int max_seeds = std::min((int)keypoints_temp.size(),parameters.max_points);2808if(max_seeds < 9)2809return;28102811std::partial_sort(keypoints_temp.begin(),keypoints_temp.begin()+max_seeds-1,2812keypoints_temp.end(),sortKeyPoint);2813keypoints_temp.resize(max_seeds);2814std::vector<std::vector<float> > angles_temp = detector.calcAngles(rotated_images,keypoints_temp);28152816// filter out keypoints which are not symmetric2817std::vector<KeyPoint>::iterator iter1 = keypoints_temp.begin();2818std::vector<std::vector<float> >::const_iterator iter2 = angles_temp.begin();2819for(;iter1 != keypoints_temp.end();++iter1,++iter2)2820{2821cv::KeyPoint &pt = *iter1;2822const std::vector<float> &angles_i3 = *iter2;2823if(angles_i3.size() != 2)// || pt.response < noise)2824continue;2825int result = testPointSymmetry(image,pt.pt,pt.size*0.7F,std::max(10.0F,sqrt(pt.response)+0.5F*pt.size));2826if(result > MAX_SYMMETRY_ERRORS)2827continue;2828else if(result > 3)2829pt.response = - pt.response;2830angles.push_back(angles_i3);2831keypoints.push_back(pt);2832}2833}28342835cv::Mat Chessboard::buildData(const std::vector<KeyPoint>& keypoints)const2836{2837cv::Mat data(int(keypoints.size()),4,CV_32FC1); // x + y + angle + strength2838std::vector<cv::KeyPoint>::const_iterator iter = keypoints.begin();2839float *val = reinterpret_cast<float*>(data.data);2840for(;iter != keypoints.end();++iter)2841{2842(*val++) = iter->pt.x;2843(*val++) = iter->pt.y;2844(*val++) = float(2.0*CV_PI-iter->angle/180.0*CV_PI);2845(*val++) = iter->response;2846}2847return data;2848}28492850std::vector<cv::KeyPoint> Chessboard::getInitialPoints(cv::flann::Index &flann_index,const cv::Mat &data,const cv::KeyPoint ¢er,float white_angle,float black_angle,float min_response)const2851{2852CV_CheckTypeEQ(data.type(), CV_32FC1, "Unsupported source type");2853if(data.cols != 4)2854CV_Error(Error::StsBadArg,"wrong data format");28552856std::vector<float> query,dists;2857std::vector<int> indices;2858query.resize(2); query[0] = center.pt.x; query[1] = center.pt.y;2859flann_index.knnSearch(query,indices,dists,21,cv::flann::SearchParams(32));28602861// collect all points having a similar angle and response2862std::vector<cv::KeyPoint> points;2863std::vector<int>::const_iterator ids_iter = indices.begin()+1; // first point is center2864points.push_back(center);2865for(;ids_iter != indices.end();++ids_iter)2866{2867// TODO do more angle tests2868// test only one angle against the stored one2869const float &response = data.at<float>(*ids_iter,3);2870if(fabs(response) < min_response)2871continue;2872const float &angle = data.at<float>(*ids_iter,2);2873float angle_temp = fabs(angle-white_angle);2874if(angle_temp > CV_PI*0.5)2875angle_temp = float(fabs(angle_temp-CV_PI));2876if(angle_temp > MAX_ANGLE)2877{2878angle_temp = fabs(angle-black_angle);2879if(angle_temp > CV_PI*0.5)2880angle_temp = float(fabs(angle_temp-CV_PI));2881if(angle_temp >MAX_ANGLE)2882continue;2883}2884points.push_back(cv::KeyPoint(data.at<float>(*ids_iter,0),data.at<float>(*ids_iter,1),center.size,angle,response));2885}2886return points;2887}28882889Chessboard::BState Chessboard::generateBoards(cv::flann::Index &flann_index,const cv::Mat &data,2890const cv::KeyPoint ¢er,float white_angle,float black_angle,float min_response,const cv::Mat& img,2891std::vector<Chessboard::Board> &boards)const2892{2893// collect all points having a similar angle2894std::vector<cv::KeyPoint> kpoints= getInitialPoints(flann_index,data,center,white_angle,black_angle,min_response);2895if(kpoints.size() < 5)2896return MISSING_POINTS;28972898if(!img.empty())2899{2900#ifdef CV_DETECTORS_CHESSBOARD_DEBUG2901cv::Mat out;2902cv::drawKeypoints(img,kpoints,out,cv::Scalar(0,0,255,255),4);2903std::vector<cv::KeyPoint> temp;2904temp.push_back(kpoints.front());2905cv::drawKeypoints(out,temp,out,cv::Scalar(0,255,0,255),4);2906cv::imshow("chessboard",out);2907cv::waitKey(-1);2908#endif2909}29102911// use angles to filter out points2912std::vector<cv::KeyPoint> points;2913cv::Vec2f n1(cos(white_angle),-sin(white_angle));2914cv::Vec2f n2(cos(black_angle),-sin(black_angle));2915std::vector<cv::KeyPoint>::const_iterator iter1 = kpoints.begin()+1; // first point is center2916for(;iter1 != kpoints.end();++iter1)2917{2918// calc angle2919cv::Vec2f vec(iter1->pt-center.pt);2920vec = vec/cv::norm(vec);2921if(fabs(vec.dot(n1)) < 0.96 && fabs(vec.dot(n2)) < 0.96) //check that angle is bigger than 15°2922points.push_back(*iter1);2923}29242925// genreate pairs those connection goes through the center2926std::vector<std::pair<cv::KeyPoint,cv::KeyPoint> > pairs;2927iter1 = points.begin();2928for(;iter1 != points.end();++iter1)2929{2930std::vector<cv::KeyPoint>::const_iterator iter2 = iter1+1;2931for(;iter2 != points.end();++iter2)2932{2933if(isPointOnLine(iter1->pt,iter2->pt,center.pt,0.97F))2934{2935if(cv::norm(iter1->pt) < cv::norm(iter2->pt))2936pairs.push_back(std::make_pair(*iter1,*iter2));2937else2938pairs.push_back(std::make_pair(*iter2,*iter1));2939}2940}2941}29422943// generate all possible combinations consisting of two pairs2944if(pairs.size() < 2)2945return MISSING_PAIRS;2946std::vector<std::pair<cv::KeyPoint,cv::KeyPoint> >::iterator iter_pair1 = pairs.begin();29472948BState best_state = MISSING_PAIRS;2949for(;iter_pair1 != pairs.end();++iter_pair1)2950{2951cv::Point2f p1 = iter_pair1->second.pt-iter_pair1->first.pt;2952p1 = p1/cv::norm(p1);2953std::vector<std::pair<cv::KeyPoint,cv::KeyPoint> >::iterator iter_pair2 = iter_pair1+1;2954for(;iter_pair2 != pairs.end();++iter_pair2)2955{2956cv::Point2f p2 = iter_pair2->second.pt-iter_pair2->first.pt;2957p2 = p2/cv::norm(p2);2958if(p2.dot(p1) > 0.95)2959{2960if(best_state < WRONG_PAIR_ANGLE)2961best_state = WRONG_PAIR_ANGLE;2962}2963else2964{2965// check orientations2966if(checkOrientation(iter_pair1->first.pt,iter_pair1->second.pt,iter_pair2->first.pt,iter_pair2->second.pt))2967std::swap(iter_pair2->first,iter_pair2->second);29682969// minimal case2970std::vector<cv::Point2f> board_points;2971board_points.resize(9,cv::Point2f(std::numeric_limits<float>::quiet_NaN(),2972std::numeric_limits<float>::quiet_NaN()));29732974board_points[1] = iter_pair2->first.pt;2975board_points[3] = iter_pair1->first.pt;2976board_points[4] = center.pt;2977board_points[5] = iter_pair1->second.pt;2978board_points[7] = iter_pair2->second.pt;2979boards.push_back(Board(cv::Size(3,3),board_points,white_angle,black_angle));2980Board &board = boards.back();29812982if(board.isEmpty())2983{2984if(best_state < WRONG_CONFIGURATION)2985best_state = WRONG_CONFIGURATION;2986boards.pop_back(); // MAKE SURE board is no longer used !!!!2987continue;2988}2989best_state = FOUND_BOARD;2990}2991}2992}2993return best_state;2994}29952996void Chessboard::detectImpl(const Mat& image, vector<KeyPoint>& keypoints,std::vector<Mat> &feature_maps,const Mat& mask)const2997{2998keypoints.clear();2999Board board = detectImpl(image,feature_maps,mask);3000keypoints = board.getKeyPoints();3001return;3002}30033004Chessboard::Board Chessboard::detectImpl(const Mat& gray,std::vector<cv::Mat> &feature_maps,const Mat& mask)const3005{3006#ifdef CV_DETECTORS_CHESSBOARD_DEBUG3007debug_image = gray;3008#endif3009CV_CheckTypeEQ(gray.type(),CV_8UC1, "Unsupported image type");30103011cv::Size chessboard_size2(parameters.chessboard_size.height,parameters.chessboard_size.width);3012std::vector<KeyPoint> keypoints_seed;3013std::vector<std::vector<float> > angles;3014findKeyPoints(gray,keypoints_seed,feature_maps,angles,mask);3015if(keypoints_seed.empty())3016return Chessboard::Board();30173018// check how many points are likely a checkerbord corner3019float response = fabs(keypoints_seed.front().response*MIN_RESPONSE_RATIO);3020std::vector<KeyPoint>::const_iterator seed_iter = keypoints_seed.begin();3021int count = 0;3022int inum = chessboard_size2.width*chessboard_size2.height;3023for(;seed_iter != keypoints_seed.end() && count < inum;++seed_iter,++count)3024{3025// points are sorted based on response3026if(fabs(seed_iter->response) < response)3027{3028seed_iter = keypoints_seed.end();3029return Chessboard::Board();3030}3031}3032// just add dummy points or flann will fail during knnSearch3033if(keypoints_seed.size() < 21)3034keypoints_seed.resize(21, cv::KeyPoint(-99999.0F,-99999.0F,0.0F,0.0F,0.0F));30353036//build kd tree3037cv::Mat data = buildData(keypoints_seed);3038cv::Mat flann_data(data.rows,2,CV_32FC1);3039data(cv::Rect(0,0,2,data.rows)).copyTo(flann_data);3040cv::flann::Index flann_index(flann_data,cv::flann::KDTreeIndexParams(1),cvflann::FLANN_DIST_EUCLIDEAN);30413042// for each point3043std::vector<std::vector<float> >::const_iterator angles_iter = angles.begin();3044std::vector<cv::KeyPoint>::const_iterator points_iter = keypoints_seed.begin();3045cv::Rect bounding_box(5,5,gray.cols-10,gray.rows-10);3046int max_tests = std::min(parameters.max_tests,int(keypoints_seed.size()));3047for(count=0;count < max_tests;++angles_iter,++points_iter,++count)3048{3049// regard current point as center point3050// which must have two angles!!! (this was already checked)3051float min_response = points_iter->response*MIN_RESPONSE_RATIO;3052if(min_response <= 0)3053{3054if(max_tests+1 < int(keypoints_seed.size()))3055++max_tests;3056continue;3057}3058const std::vector<float> &angles_i = *angles_iter;3059float white_angle = fabs(angles_i.front()); // angle is negative if black --> clockwise3060float black_angle = fabs(angles_i.back()); // angle is negative if black --> clockwise3061if(angles_i.front() < 0) // ensure white angle is first3062swap(white_angle,black_angle);30633064std::vector<Board> boards;3065generateBoards(flann_index, data,*points_iter,white_angle,black_angle,min_response,gray,boards);3066parallel_for_(Range(0,(int)boards.size()),[&](const Range& range){3067for(int i=range.start;i <range.end;++i)3068{3069auto iter_boards = boards.begin()+i;3070cv::Mat h = iter_boards->estimateHomography();3071int size = iter_boards->validateCorners(data,flann_index,h,min_response);3072if(size != 9 || !iter_boards->validateContour())3073{3074iter_boards->clear();3075continue;3076}3077//grow based on kd-tree3078iter_boards->grow(data,flann_index);3079if(!iter_boards->checkUnique())3080{3081iter_boards->clear();3082continue;3083}30843085// check bounding box3086std::vector<cv::Point2f> contour = iter_boards->getContour();3087std::vector<cv::Point2f>::const_iterator iter = contour.begin();3088for(;iter != contour.end();++iter)3089{3090if(!bounding_box.contains(*iter))3091break;3092}3093if(iter != contour.end())3094{3095iter_boards->clear();3096continue;3097}30983099if(iter_boards->getSize() == parameters.chessboard_size ||3100iter_boards->getSize() == chessboard_size2)3101{3102iter_boards->normalizeOrientation(false);3103if(iter_boards->getSize() != parameters.chessboard_size)3104{3105if(iter_boards->isCellBlack(0,0) == iter_boards->isCellBlack(0,int(iter_boards->colCount())-1))3106iter_boards->rotateLeft();3107else3108iter_boards->rotateRight();3109}3110#ifdef CV_DETECTORS_CHESSBOARD_DEBUG3111cv::Mat img;3112iter_boards->draw(debug_image,img);3113cv::imshow("chessboard",img);3114cv::waitKey(-1);3115#endif3116}3117else3118{3119if(iter_boards->getSize().width*iter_boards->getSize().height < chessboard_size2.width*chessboard_size2.height)3120iter_boards->clear();3121else if(!parameters.larger)3122iter_boards->clear();3123}3124}3125});3126// check if a good board was found3127for(const auto &board : boards)3128{3129if(!board.isEmpty())3130return board;3131}3132}3133return Chessboard::Board();3134}31353136void Chessboard::detectAndCompute(cv::InputArray image,cv::InputArray mask,std::vector<cv::KeyPoint>& keypoints,3137cv::OutputArray descriptors,bool useProvidedKeyPoints)3138{3139descriptors.clear();3140useProvidedKeyPoints=false;3141std::vector<cv::Mat> maps;3142detectImpl(image.getMat(),keypoints,maps,mask.getMat());3143if(!useProvidedKeyPoints) // suppress compiler warning3144return;3145return;3146}31473148void Chessboard::detectImpl(const Mat& image, vector<KeyPoint>& keypoints,const Mat& mask)const3149{3150std::vector<cv::Mat> maps;3151detectImpl(image,keypoints,maps,mask);3152}31533154void Chessboard::detectImpl(InputArray image, std::vector<KeyPoint>& keypoints, InputArray mask)const3155{3156detectImpl(image.getMat(),keypoints,mask.getMat());3157}31583159} // end namespace details316031613162// public API3163bool findChessboardCornersSB(cv::InputArray image_, cv::Size pattern_size,3164cv::OutputArray corners_, int flags)3165{3166CV_INSTRUMENT_REGION();3167int type = image_.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);3168CV_CheckType(type, depth == CV_8U && (cn == 1 || cn == 3),3169"Only 8-bit grayscale or color images are supported");3170if(pattern_size.width <= 2 || pattern_size.height <= 2)3171{3172CV_Error(Error::StsOutOfRange, "Both width and height of the pattern should have bigger than 2");3173}3174if (!corners_.needed())3175CV_Error(Error::StsNullPtr, "Null pointer to corners");31763177Mat img;3178if (image_.channels() != 1)3179cvtColor(image_, img, COLOR_BGR2GRAY);3180else3181img = image_.getMat();31823183details::Chessboard::Parameters para;3184para.chessboard_size = pattern_size;3185para.min_scale = 2;3186para.max_scale = 4;3187para.max_tests = 25;3188para.max_points = std::max(100,pattern_size.width*pattern_size.height*2);3189para.super_resolution = false;31903191// setup search based on flags3192if(flags & CALIB_CB_NORMALIZE_IMAGE)3193{3194Mat tmp;3195cv::equalizeHist(img, tmp);3196swap(img, tmp);3197flags ^= CALIB_CB_NORMALIZE_IMAGE;3198}3199if(flags & CALIB_CB_EXHAUSTIVE)3200{3201para.max_tests = 100;3202para.max_points = std::max(500,pattern_size.width*pattern_size.height*2);3203flags ^= CALIB_CB_EXHAUSTIVE;3204}3205if(flags & CALIB_CB_ACCURACY)3206{3207para.super_resolution = true;3208flags ^= CALIB_CB_ACCURACY;3209}3210if(flags)3211CV_Error(Error::StsOutOfRange, cv::format("Invalid remaing flags %d", (int)flags));32123213std::vector<cv::KeyPoint> corners;3214details::Chessboard board(para);3215board.detect(img,corners);3216if(corners.empty())3217{3218corners_.release();3219return false;3220}3221std::vector<cv::Point2f> points;3222KeyPoint::convert(corners,points);3223Mat(points).copyTo(corners_);3224return true;3225}32263227} // namespace cv322832293230