对应于Opencv源码文件:\sources\modules\calib3d\src\calibinit.cpp

图1 :原始图片
第一步,局部平均自适应阈值化方法对亮度不均匀情况适应性强,因此用该方法对图像二值化,均衡化后得到了理想的门限,效果如图2所示。



第二步,图像膨胀分离各个黑块四边形的衔接,由于膨胀的是白色像素点,因此能够缩小黑块四边形,断掉衔接,效果如图3所示。

第三步,检测四边形,计算每个轮廓的凸包,多边形检测,以及判断是否只有四个顶点,若是则为四边形,再用长宽比、周长和面积等约束去除一些干扰四边形,效果如图4所示。

第四步,将每个四边形作为一个单元,它分别有邻近的四边形,无邻近四边形的为干扰四边形,两个邻近四边形为边界处四边形,四个邻近四边形为内部四边形。每个四边形的序号可按邻近关系排序,然后按对角两个四边形相对的两个点,取其连线的中间点作为角点, 效果如图5 所示。

整个棋盘定位过程是一个循环过程,先对读入的棋盘图像直方图均衡化,接着自适应(取决于flag参数)二值化,再对二值化后的图像膨胀。为了定位的鲁棒性,自适应二值化和膨胀所采用核的大小不能是唯一的,故不断的循环用不同的参数用对棋盘图像处理,膨胀所采用核的大小逐渐变大。

在每次的循环过程都需要,经过以下步骤。

1、在二值化后图像外围画一白色的矩形框(方便轮廓提取),然后进行轮廓提取cvFindContours。经过膨胀后的二值图像,每个黑色的方格已经被分开,轮廓提取后可以得到每个方格的轮廓,当然还有很多干扰轮廓。对轮廓进行多边形拟合cvApproxPoly,排除不是矩形的轮廓,利用矩形的其他性质,再排除一些干扰轮廓。这些工作主要由icvGenerateQuads函数完成。

2、寻找每个方格的相邻方格,并记相邻方格的个数,连同相邻方格的信息存在相应CvCBQuad结构体中。二值图像在膨胀后原本相邻的方格,分开了,原来相连部分有一个公共点,现在分开变成了两个点。找到相邻的方格之后,计算出原来的公共点,用公共点替代膨胀后分开的点。这主要由icvFindQuadNeighborhors函数完成。

3、对所有“方格”(包括被误判的)分类,分类的原则是类内所有方格是相邻的。由icvFindConnectedQuads函数完成。

4、根据已知所求的角点个数,判别每个类中方格是否为所求的棋盘方格,并对棋盘方格排序,即该方格位于哪行那列。在这个过程中,可以添加每类方格总缺少的方格,也可以删除每类方格中多余的方格。icvOrderFoundConnetedQuads函数完成该过程。

5、icvCleanFoundConnectedQuads函数、icvCheckQuadGroup函数根据已知棋盘的方格个数(由棋盘的角点数计算出来)确认方格位置及个数是否正确,并确定粗略强角点的位置(两个方格的相连位置)。icvCheckBoardMonotony再次检验棋盘方格是否提取正确。

6、以上如果有一步所有方格都不符合要求,则进入一个新的循环。若循环结束,还尚未找到符合要求的方格,则棋盘定位失败,退出函数。

最后,cvFindCornerSubpix()根据上步的强角点位置,确定强角点的精确位置。

bool cv::findChessboardCorners ( InputArray image, Size patternSize, OutputArray corners, int flags = CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)

这个函数用来检测一幅图像中是否含有棋盘格,如果图像中不含有指定数目的棋盘格角点(即黑色方块相交的点为角点,因此制作标定板时,最好选用大一点白色或浅色木板作为棋盘格背景)或对它们排序失败时,函数返回0; 如果图像中棋盘格内部角点都被确定了位置并准确排列的话,该函数将它们按制定行列排序并存储在corners向量中。该函数确定的角点的大概位置(approximate),如果想更准确地确定角点的位置,你可以该函数检测角点成功后继续调用cornerSubPix函数。

参数:

image:8位灰度图像或彩色图像。

patternSizeNumber: 棋盘格内部角点的行和列,( patternSize = cvSize(points_per_row, points_per_colum) = cvSize(columns,rows) )。

cornersOutput:检测到的角点存储的数组,

flags:棋盘格检测角点方法设置标置位

CALIB_CB_ADAPTIVE_THRESH 使用自适应阈值将灰度图像转化为二值图像,而不是固定的由图像的平均亮度计算出来的阈值
CALIB_CB_NORMALIZE_IMAGE 在利用固定阈值或者自适应的阈值进行二值化之前,先使用equalizeHist来均衡化图像gamma值。
CALIB_CB_FILTER_QUADS 使用其他的准则(如轮廓面积,周长,类似方形的形状)来去除在轮廓检测阶段检测到的错误方块。
CALIB_CB_FAST_CHECK 在图像上快速检测一下棋盘格角点,如果没有棋盘格焦点被发现,绕过其它费时的函数调用,这可以在图像没有棋盘格被观测以及恶劣情况下缩短整个函数执行时间。
检测棋盘格角点并在图像中画出来的例子:
Size patternsize(8,6); //interior number of corners

Mat gray = …; //source image

vector corners; //this will be filled by the detected corners

// CALIB_CB_FAST_CHECK saves a lot of time on images that do not contain any chessboard corners

bool patternfound = findChessboardCorners(gray, patternsize, corners, CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK);

if(patternfound)

cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));

drawChessboardCorners(img, patternsize, Mat(corners), patternfound);

OpenCV findChessboardCorners 实现代码阅读, 主要实现在calibinit.cpp

bool cv::findChessboardCorners( InputArray _image, Size patternSize, OutputArray corners, int flags ){

CV_INSTRUMENT_REGION()int count = patternSize.area()*2;std::vector<Point2f> tmpcorners(count+1);Mat image = _image.getMat(); CvMat c_image = image;bool ok = cvFindChessboardCorners(&c_image, patternSize, (CvPoint2D32f*)&tmpcorners[0], &count, flags ) > 0;if( count > 0 ) {tmpcorners.resize(count);Mat(tmpcorners).copyTo(corners);}elsecorners.release();return ok;

}

InputArray和OutputArray两个类都是代理数据类型,用来接收Mat和Vector<>作为输入参数,OutputArray继承自InputArray。

InputArray作为输入参数的时候,传入的参数加了const限定符,即它只接收参数作为纯输入参数,无法更改输入参数的内容。而OutputArray则没有加入限定符,可以对参数的内容进行更改。

InputArray这个接口类可以是Mat、Mat_、Mat_<T, m, n>、vector、vector<vector>、vector。也就意味着如果看见函数的参数类型是InputArray型时,把上诉几种类型作为参数都是可以的。这个类只能作为函数的形参参数使用,不要试图声明一个InputArray类型的变量,可以用cv::Mat()或cv::noArray()作为空参。在函数的内部可以使用InputArray::getMat()函数将传入的参数转换为Mat的结构,方便函数内的操作。可能还需要InputArray::kind()用来区分Mat结构或者vector<>结构.

CV_IMPL int cvFindChessboardCorners( const void* arr, CvSize pattern_size,

                         CvPoint2D32f* out_corners, int* out_corner_count,  int flags )

{

int found = 0;    CvCBQuad *quads = 0;    CvCBCorner *corners = 0;cv::Ptr<CvMemStorage> storage;try{int k = 0;    const int min_dilations = 0;    const int max_dilations = 7;if( out_corner_count )*out_corner_count = 0;Mat img = cvarrToMat((CvMat*)arr).clone();if( img.depth() != CV_8U || (img.channels() != 1 && img.channels() != 3) )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" );if (img.channels() != 1)  cvtColor(img, img, COLOR_BGR2GRAY);Mat thresh_img_new = img.clone();icvBinarizationHistogramBased( thresh_img_new ); // process image in-placeSHOW("New binarization", thresh_img_new);if( flags & CV_CALIB_CB_FAST_CHECK){//perform new method for checking chessboard using a binary image.//image is binarised using a threshold dependent on the image histogramif (checkChessboardBinary(thresh_img_new, pattern_size) <= 0) //fall back to the old method{if (checkChessboard(img, pattern_size) <= 0){return found;}}}storage.reset(cvCreateMemStorage(0));int prev_sqr_size = 0;// 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( int dilations = min_dilations; dilations <= max_dilations; dilations++ ){if (found)break;      // already found it//USE BINARY IMAGE COMPUTED USING icvBinarizationHistogramBased METHODdilate( thresh_img_new, thresh_img_new, Mat(), Point(-1, -1), 1 );// So we can find rectangles that go to the edge, we draw a white line around the image edge.// Otherwise FindContours will miss those clipped rectangle contours.// The border color will be the image mean, because otherwise we risk screwing up filters like cvSmooth()...rectangle( thresh_img_new, Point(0,0), Point(thresh_img_new.cols-1, thresh_img_new.rows-1), Scalar(255,255,255), 3, LINE_8);int max_quad_buf_size = 0;cvFree(&quads);cvFree(&corners);int quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img_new, flags, &max_quad_buf_size );PRINTF("Quad count: %d/%d\n", quad_count, (pattern_size.width/2+1)*(pattern_size.height/2+1));SHOW_QUADS("New quads", thresh_img_new, quads, quad_count);if (processQuads(quads, quad_count, pattern_size, max_quad_buf_size, storage, corners, out_corners, out_corner_count, prev_sqr_size))found = 1;}PRINTF("Chessboard detection result 0: %d\n", found);// revert to old, slower, method if detection failedif (!found){if( flags & CV_CALIB_CB_NORMALIZE_IMAGE ){equalizeHist( img, img );}Mat thresh_img;prev_sqr_size = 0;PRINTF("Fallback to old algorithm\n");const bool useAdaptive = flags & CV_CALIB_CB_ADAPTIVE_THRESH;if (!useAdaptive){// empiric threshold level// thresholding performed here and not inside the cycle to save processing timedouble mean = cv::mean(img).val[0];int thresh_level = MAX(cvRound( mean - 10 ), 10);threshold( img, thresh_img, thresh_level, 255, THRESH_BINARY );}//if flag CV_CALIB_CB_ADAPTIVE_THRESH is not set it doesn't make sense to iterate over kint max_k = useAdaptive ? 6 : 1;for( k = 0; k < max_k; k++ ){for( int dilations = min_dilations; dilations <= max_dilations; dilations++ ){if (found)break;      // already found it// convert the input grayscale image to binary (black-n-white)if (useAdaptive){int block_size = cvRound(prev_sqr_size == 0? MIN(img.cols, img.rows) * (k % 2 == 0 ? 0.2 : 0.1): prev_sqr_size * 2);block_size = block_size | 1;// convert to binaryadaptiveThreshold( img, thresh_img, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, block_size, (k/2)*5 );if (dilations > 0)dilate( thresh_img, thresh_img, Mat(), Point(-1, -1), dilations-1 );}else{dilate( thresh_img, thresh_img, Mat(), Point(-1, -1), 1 );}SHOW("Old binarization", thresh_img);// 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()...rectangle( thresh_img, Point(0,0), Point(thresh_img.cols-1, thresh_img.rows-1), Scalar(255,255,255), 3, LINE_8);int max_quad_buf_size = 0;cvFree(&quads);cvFree(&corners);int quad_count = icvGenerateQuads( &quads, &corners, storage, thresh_img, flags, &max_quad_buf_size);PRINTF("Quad count: %d/%d\n", quad_count, (pattern_size.width/2+1)*(pattern_size.height/2+1));SHOW_QUADS("Old quads", thresh_img, quads, quad_count);if (processQuads(quads, quad_count, pattern_size, max_quad_buf_size, storage, corners, out_corners, out_corner_count, prev_sqr_size))found = 1;}}}PRINTF("Chessboard detection result 1: %d\n", found);if( found )found = icvCheckBoardMonotony( out_corners, pattern_size );PRINTF("Chessboard detection result 2: %d\n", found);// check that none of the found corners is too close to the image boundaryif( found ){const int BORDER = 8;for( k = 0; k < pattern_size.width*pattern_size.height; k++ ){if( out_corners[k].x <= BORDER || out_corners[k].x > img.cols - BORDER ||out_corners[k].y <= BORDER || out_corners[k].y > img.rows - BORDER )break;}found = k == pattern_size.width*pattern_size.height;}PRINTF("Chessboard detection result 3: %d\n", found);if( found ){if ( pattern_size.height % 2 == 0 && pattern_size.width % 2 == 0 ){int last_row = (pattern_size.height-1)*pattern_size.width;double dy0 = out_corners[last_row].y - out_corners[0].y;if( dy0 < 0 ){int n = pattern_size.width*pattern_size.height;for(int i = 0; i < n/2; i++ ){CvPoint2D32f temp;CV_SWAP(out_corners[i], out_corners[n-i-1], temp);}}}int wsize = 2;CvMat old_img(img);cvFindCornerSubPix( &old_img, out_corners, pattern_size.width*pattern_size.height,cvSize(wsize, wsize), cvSize(-1,-1),cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 15, 0.1));}}catch(...){cvFree(&quads);cvFree(&corners);throw;}cvFree(&quads);cvFree(&corners);return found;

}

cornerSubPix
功能:在角点检测中精确化角点位置
函数原型:void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria);
C: void cvFindCornerSubPix(const CvArr* image, CvPoint2D32f* corners, int count, CvSize win, CvSize zero_zone, CvTermCriteria criteria);
参数:

image:输入图像
corners:输入角点的初始坐标以及精准化后的坐标用于输出
winSize:搜索窗口边长的一半,例如如果winSize=Size(5,5),则一个大小为:的搜索窗口将被使用。
zeroZone:搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
criteria:角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。
CvTermCriteria 类:迭代算法的终止准则

原型:

`typedef struct CvTermCriteria
{
int type; /* CV_TERMCRIT_ITER 和CV_TERMCRIT_EPS二值之一,或者二者的组合 /
int max_iter; /
最大迭代次数 /
double epsilon; /
结果的精确性 */
宏定义:

CV_TERMCRIT_ITER:代终止条件为达到最大迭代次数终止
CV_TERMCRIT_EPS:迭代到阈值终止
A corner can be defined as the intersection of two edges. A corner can also be defined as a point for which there are two dominant and different edge directions in a local neighbourhood of the point。

下面是opencv使用的harris cornor 检测法

首先, (x , y)像素点的与周围临近点像素差的均方值的和为R ≈ ∑ ( I ( x + u , y + v ) − I ( x, y) )^2  对I(x+u, y+v)进行泰勒展开R≈∑ ( I ( x, y) +(δI/δx) +(δI/δy)−  I ( x, y) )^2

OpenCV棋盘角点检测原理总结相关推荐

  1. opencv棋盘格角点检测原理总结

    第一步,局部平均自适应阈值化方法对亮度不均匀情况适应性强,因此用该方法对图像二值化,均衡化后得到了理想的门限,效果如图2所示. 第二步,图像膨胀分离各个黑块四边形的衔接,由于膨胀的是白色像素点,因此能 ...

  2. 利用OpenCV的霍夫变换线检测函数HoughLines()得到直线的ρ和θ值后绘制直线的原理详解

    为了更好地看懂本文,大家可以先看下我写的另一篇博文,链接如下: https://blog.csdn.net/wenhao_ir/article/details/51774444 OpenCV的霍夫变换 ...

  3. 基于Opencv的车距检测系统(源码&教程)

    1.研究背景 对本车前方车辆的识别及距离检测是计算机视觉技术在智能网联汽车中的一个重要内容.要想在错综繁杂的交通环境中实现无人驾驶,对行车环境中其他车辆信息的获取是十分重要的.因此,对本车前方车辆的识 ...

  4. 用C++ 和OpenCV 实现视频目标检测(YOLOv4模型)

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 据说,现在很多小区都上线了AI抓拍高空抛物的黑科技,可以自动分析抛物 ...

  5. OpenCV Fast角点检测

    FAST是一种用于角点检测的算法,该算法的原理是取图像中检测点,以改点为圆心的周围邻域内像素点判断监测点是否为角点. OpenCV中的FAST检测算法是用传统方法实现的. 实例化fast fast = ...

  6. Harris角点检测原理详解(转载)

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/lwzkiller/article/details/54633670 关于角点的应用在图像处理上比较广 ...

  7. YOLOv4 | 用C++ 和OpenCV 实现视频目标检测

    据说,现在很多小区都上线了AI抓拍高空抛物的黑科技,可以自动分析抛物轨迹,用来协助检查很多不文明行为. 你想不想知道,这类检测视频中目标物的黑科技是怎么实现的呢? 虽然不同场景下的目标检测模型训练不同 ...

  8. python 检测直线 交点_Python+OpenCV图像处理——实现直线检测

    简介: 1.霍夫变换(Hough Transform) 霍夫变换是图像处理中从图像中识别几何形状的基本方法之一,应用很广泛,也有很多改进算法.主要用来从图像中分离出具有某种相同特征的几何形状(如,直线 ...

  9. 特征提取与检测(一)---Harris与Shi-Tomasi角点检测原理

    一.Harris角点检测原理 1. 何为角点? 下面有两幅不同视角的图像,通过找出对应的角点进行匹配. 我们可以直观的概括下角点所具有的特征: >轮廓之间的交点: >对于同一场景,即使视角 ...

最新文章

  1. [置顶] 面向业务开发应用
  2. 网站建设需要遵守的三大原则!
  3. 有没有通俗易懂的python课程-有没有简单易懂的入门级Python辅导书或网络课程?...
  4. Python多线程学习教程
  5. linux deepin换源:换成国内镜像源(上海交大镜像源)
  6. 架构师必须掌握的 10 条设计原则
  7. solr4.6本地数据提交异常
  8. 如何让程序异常退出后重启
  9. SPFA和FLOYD算法如何打印路径
  10. 收藏商品表设计_babycare商品价格及销售情况分析
  11. assign ur here php,ecshop源码分析01
  12. Android获取所在城市坐标及城市信息(逆地理位置编码)
  13. 整体大于部分_整体叶盘球头鼓锥形铣刀五轴加工技术
  14. java求三角形的面积_java编程中求三角形面积怎么写?
  15. Java多线程复习整理(二)
  16. Android:基于OpenCV实现身份证识别(C++)——移植图像算法
  17. 阿里云MAVEN/华为MAVEN/腾讯云MAVEN 仓库地址
  18. JDBCUtils工具类的基础学习笔记
  19. python opencv读mp4视频
  20. [CSDN 视频--新闻分析第二期]:Baidu事件、MySpaceVSYahoo、Flashget被收购

热门文章

  1. 杰理之启用消人声之后声音变调【篇】
  2. 对懂球帝的一点用户感想
  3. 成才之路(4):广泛的兴趣
  4. 如何把极坐标化为直角坐标_直角坐标怎么化极坐标
  5. 做网站,一般是什么流程?
  6. 一个困扰许久的Word嵌入文档问题(求助)
  7. 记一次「失败」的 Redis 面试
  8. 【Redis】Redis各大版本最新版以及系统兼容性测试
  9. HTML学习之tr|td|th标签
  10. 姚明确诊非移位性胫骨骨裂 至少缺席未来六周比赛