第一步,局部平均自适应阈值化方法对亮度不均匀情况适应性强,因此用该方法对图像二值化,均衡化后得到了理想的门限,效果如图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<Point2f> 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);

}

else

corners.release();

return ok;

}


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

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

InputArray这个接口类可以是Mat、Mat_<T>、Mat_<T, m, n>、vector<T>、vector<vector<T>>、vector<Mat>。也就意味着如果看见函数的参数类型是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-place

SHOW("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 histogram

if (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 METHOD

dilate( 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 failed

if (!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 time

double 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 k

int 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 binary

adaptiveThreshold( 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 boundary

if( 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

原文链接:https://www.douban.com/note/541379633/?from=tag

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

  1. 相机模型与标定(五)--opencv棋盘格角点检测算法

    原文: http://blog.csdn.net/b5w2p0/article/details/18446961 很简单,作者写的差不多对,不高兴改了... 刚接触图像处理是从摄像机标定开始,一直好奇 ...

  2. 【OpenCV3】棋盘格角点检测与绘制——cv::findChessboardCorners()与cv::drawChessboardCorners()详解

    棋盘格法是摄像机标定中常用的一种方法,在使用该方法时需要对棋盘格的角点进行检测.opencv中封装了一个专门用于棋盘格角点检测的函数即cv::findChessboardCorners(),同时,也提 ...

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

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

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

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

  5. harris角点检测c语言,Harris角点检测原理及实现

    为便于理解,先简要介绍角点的概念和角点检测背景 一.角点及角点检测背景: 角点概念: 角点,通常可理解为两条边的角点,也可理解为像素值在多个方向有显著变化的点或局部区域内某个属性明显的点.如多个轮廓的 ...

  6. opencv harris 角点检测

    一.图像特征的分类 [OpenCV入门教程之十六]OpenCV角点检测之Harris角点检测_[浅墨的游戏编程Blog]毛星云(浅墨)的专栏-CSDN博客_基于轮廓曲线的角点检测 下面部分图像来自与B ...

  7. Harris 角点检测原理详解

    var html = document.getElementById("artContent").innerHTML; document.getElementById(" ...

  8. MFC+OPENCV实现角点检测

    MFC+OPENCV实现角点检测: // 角点检测 // 根据<基于OpenCV的计算机视觉技术实现>#define max_corners 200; // 限定的最大角点数IplImag ...

  9. OpenCV ChArUco角的检测

    OpenCV ChArUco角的检测 ChArUco角的检测 目标 源代码 ChArUco Board创建 ChArUco板检测 拍摄输入图像 读取相机校准参数(仅用于相机校准检测) 检测标记 ChA ...

最新文章

  1. What is Wiki?
  2. Silverlight Blend动画设计系列五:故事板(StoryBoards)和动画(Animations)
  3. HK-2000 数采仪 航空头接口定义
  4. Django——2 路由分配设置 re_path正则匹配 include总路由 url传参 name使用 模板渲染render方法 模板渲染方法...
  5. ffmpeg 怎么用
  6. C和汇编----字符串
  7. Postman下载与安装
  8. xpath下面的xpath_深入研究XPATH查询
  9. ubuntu安装composer
  10. ios整理(五)小应用-重力感应
  11. 4.15Day20加密模块,表格操作模块
  12. 数字信号处理第二章:Z变换及离散时间系统系统分析
  13. ECS架构 Entitas-CSharp学习之路(三)
  14. 00截断原理核心分析(重点)
  15. 【洛谷P3651】展翅翱翔之时
  16. 0083-Zipkin耗时分析
  17. mermaid流程图工具_基于 Mermaid 的时序图、流程图和甘特图
  18. 【操作系统 - 1】先来先服务FCFS和短作业优先SJF进程调度算法
  19. 数据应用案例之“客户画像体系”
  20. R语言学习笔记8_回归分析和相关分析

热门文章

  1. 道听途说Struts2之20:OGNL投影
  2. C++のeasyx特辑2:星空
  3. java中的el表达式_简单介绍Java中的EL表达式(图)
  4. 浅谈×××技术的应用与发展
  5. 厦门大学计算机学院面试题,【图片】一战厦大计算机上岸,经验帖。慢更【考研吧】_百度贴吧...
  6. mac brew 安装 thrift 0.13
  7. [gdb] unhandled dwarf expression opcode 0x9c 解决方式 .
  8. 课后练习 第4章 递归
  9. 【zcu106】zcu106裸机Hello_world调试运行
  10. python3杀死进程_Python多处理:优雅地杀死进程