【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处。

文章链接:http://blog.csdn.net/poem_qianmo/article/details/26977557

作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

知乎:http://www.zhihu.com/people/mao-xing-yun

邮箱: happylifemxy@163.com

写作当前博文时配套使用的OpenCV版本号: 2.4.9

本篇文章中。我们一起探讨了OpenCV中霍夫变换相关的知识点,以及了解了OpenCV中实现霍夫线变换的HoughLines、HoughLinesP函数的用法,实现霍夫圆变换的HoughCircles函数的用法。此博文一共同拥有四个配套的简短的演示样例程序,其具体凝视过的代码都在文中贴出,且文章最后提供了综合演示样例程序的下载。

先尝鲜一下当中一个演示样例程序的执行截图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

一、引言

在图像处理和计算机视觉领域中。怎样从当前的图像中提取所须要的特征信息是图像识别的关键所在。

在很多应用场合中须要高速准确地检測出直线或者圆。当中一种非常有效的解决这个问题的方法是霍夫(Hough)变换,其为图像处理中从图像中识别几何形状的基本方法之中的一个,应用非常广泛,也有非常多改进算法。

最主要的霍夫变换是从黑白图像中检測直线(线段)。这篇文章就将介绍OpenCV中霍夫变换的用法和相关知识。

二、霍夫变换概述

霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,该过程在一个參数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果。

霍夫变换于1962年由PaulHough首次提出,最初的Hough变换是设计用来检測直线和曲线,起初的方法要求知道物体边界线的解析方程,但不须要有关区域位置的先验知识。

这个方案的一个突出长处是切割结果的Robustness,即对数据的不全然或噪声不是非常敏感。然而。要获得描写叙述边界的解析表达经常是不可能的。

 后于1972年由Richard Duda & Peter Hart推广使用,经典霍夫变换用来检測图像中的直线,后来霍夫变换扩展到随意形状物体的识别,多为圆和椭圆。

霍夫变换运用两个坐标空间之间的变换将在一个空间中具有同样形状的曲线或直线映射到还有一个坐标空间的一个点上形成峰值。从而把检測随意形状的问题转化为统计峰值问题。

霍夫变换在OpenCV中分为霍夫线变换和霍夫圆变换两种。我们以下将分别进行介绍。

三、霍夫线变换

3.1  OpenCV中的霍夫线变换

我们知道,霍夫线变换是一种用来寻找直线的方法. 在使用霍夫线变换之前, 首先要对图像进行边缘检測的处理,也即霍夫线变换的直接输入仅仅能是边缘二值图像.

OpenCV支持三种不同的霍夫线变换,它们各自是:标准霍夫变换(Standard Hough Transform。SHT)和多尺度霍夫变换(Multi-Scale Hough Transform,MSHT)累计概率霍夫变换(Progressive Probabilistic Hough Transform ,PPHT)。

当中,多尺度霍夫变换(MSHT)为经典霍夫变换(SHT)在多尺度下的一个变种。

累计概率霍夫变换(PPHT)算法是标准霍夫变换(SHT)算法的一个改进,它在一定的范围内进行霍夫变换,计算单独线段的方向以及范围。从而降低计算量,缩短计算时间。

之所以称PPHT为“概率”的,是由于并不将累加器平面内的全部可能的点累加,而仅仅是累加当中的一部分。该想法是假设峰值假设足够高,仅仅用一小部分时间去寻找它就够了。这样猜想的话,能够实质性地降低计算时间。

在OpenCV中,我们能够用HoughLines函数来调用标准霍夫变换SHT和多尺度霍夫变换MSHT。

而HoughLinesP函数用于调用累计概率霍夫变换PPHT。累计概率霍夫变换执行效率非常高,全部相比于HoughLines函数,我们更倾向于使用HoughLinesP函数。

总结一下,OpenCV中的霍夫线变换有例如以下三种:

<1>标准霍夫变换(StandardHough Transform。SHT)。由HoughLines函数调用。

<2>多尺度霍夫变换(Multi-ScaleHough Transform,MSHT),由HoughLines函数调用。

<3>累计概率霍夫变换(ProgressiveProbabilistic Hough Transform,PPHT),由HoughLinesP函数调用。

3.2 霍夫线变换的原理

【1】众所周知, 一条直线在图像二维空间可由两个变量表示. 如:

<1>在笛卡尔坐标系: 可由參数: 斜率和截距(m,b) 表示。

<2>在极坐标系: 可由參数: 极径和极角【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" width="45" height="20" alt="" />表示。

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

对于霍夫变换, 我们将採用另外一种方式极坐标系来表示直线. 因此, 直线的表达式可为:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

化简便可得到:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

【2】一般来说对于点【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑, 我们能够将通过这个点的一族直线统一定义为:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="300" height="30" alt="" />

这就意味着每一对【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑代表一条通过点【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" width="80" height="25" style="font-family: 'Microsoft YaHei';font-size:14px;" alt="" />的直线。

【3】假设对于一个给定点【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑我们在极坐标对极径极角平面绘出全部通过它的直线, 将得到一条正弦曲线. 比如, 对于给定点X_0= 8 和Y_0= 6 我们能够绘出下图 (在平面):

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

仅仅绘出满足下列条件的点 【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 和  【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 .

【4】我们能够对图像中全部的点进行上述操作. 假设两个不同点进行上述操作后得到的曲线在平面【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑相交, 这就意味着它

们通过同一条直线. 比如,接上面的样例我们继续对点 【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 和点 【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 画图, 得到下图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" width="450" height="300" alt="" />

这三条曲线在平面相交于点 (0.925, 9.6), 坐标表示的是參数对 【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 或者是说点【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑, 点【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="80" height="25" alt="" />和点【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑组成的平面内的的直线。

【5】以上的说明表明,一般来说, 一条直线能够通过在平面 【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 寻找交于一点的曲线数量来检測。而越多曲线交于一点也就意味着这个交点表示的直线由很多其它的点组成. 一般来说我们能够通过设置直线上点的阈值来定义多少条曲线交于一点我们才觉得检測到了一条直线。

【6】这就是霍夫线变换要做的. 它追踪图像中每个点相应曲线间的交点. 假设交于一点的曲线的数量超过了阈值, 那么能够觉得这个交点所代表的參数对【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑在原图像中为一条直线。

3.3 HoughLines( )函数具体解释

此函数能够找出採用标准霍夫变换的二值图像线条。

在OpenCV中,我们能够用其来调用标准霍夫变换SHT和多尺度霍夫变换MSHT的OpenCV内建算法。

C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )

  • 第一个參数,InputArray类型的image,输入图像,即源图像,需为8位的单通道二进制图像。能够将随意的源图加载进来后由函数改动成此格式后,再填在这里。
  • 第二个參数,InputArray类型的lines,经过调用HoughLines函数后储存了霍夫线变换检測到线条的输出矢量。

    每一条线由具有两个元素的矢量【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" width="48" height="22" alt="" />表示。当中,【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑是离坐标原点((0,0)(也就是图像的左上角)的距离。

    【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑是弧度线条旋转角度(0~垂直线,π/2~水平线)。

  • 第三个參数,double类型的rho,以像素为单位的距离精度。还有一种形容方式是直线搜索时的进步尺寸的单位半径。PS:Latex中/rho就表示 【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑
  • 第四个參数,double类型的theta。以弧度为单位的角度精度。

    还有一种形容方式是直线搜索时的进步尺寸的单位角度。

  • 第五个參数。int类型的threshold,累加平面的阈值參数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才干够被检測通过并返回到结果中。
  • 第六个參数,double类型的srn。有默认值0。对于多尺度的霍夫变换。这是第三个參数进步尺寸rho的除数距离。

    粗略的累加器进步尺寸直接是第三个參数rho,而精确的累加器进步尺寸为rho/srn。

  • 第七个參数,double类型的stn,有默认值0,对于多尺度霍夫变换,srn表示第四个參数进步尺寸的单位角度theta的除数距离。且假设srn和stn同一时候为0,就表示使用经典的霍夫变换。否则,这两个參数应该都为正数。

另外,关于霍夫变换的具体解释,能够看此英文页面:

http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm

在学完函数解析,看看浅墨为大家准备的以HoughLines为核心的演示样例程序。就能够全方位了解HoughLines函数的用法了:

//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp> //-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】加载原始图和Mat变量定义
Mat srcImage = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图
Mat midImage,dstImage;//暂时变量和目标图的定义 //【2】进行边缘检測和转化为灰度图
Canny(srcImage, midImage, 50, 200, 3);//进行一此canny边缘检測
cvtColor(midImage,dstImage, CV_GRAY2BGR);//转化边缘检測后的图为灰度图 //【3】进行霍夫线变换
vector<Vec2f> lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughLines(midImage, lines, 1, CV_PI/180, 150, 0, 0 ); //【4】依次在图中绘制出每条线段
for( size_t i = 0; i < lines.size(); i++ )
{
float rho = lines[i][0], theta = lines[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line( dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);
} //【5】显示原始图
imshow("【原始图】", srcImage); //【6】边缘检測后的图
imshow("【边缘检測后的图】", midImage); //【7】显示效果图
imshow("【效果图】", dstImage); waitKey(0); return 0;
}

执行截图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" alt="" />

来一张大图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

PS:能够通过调节line(dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);一句Scalar(55,100,195)參数中G、B、R颜色值的数值,得到图中想要的线条颜色。

3.4 HoughLinesP( )函数具体解释

此函数在HoughLines的基础上末尾加了一个代表Probabilistic(概率)的P,表明它能够採用累计概率霍夫变换(PPHT)来找出二值图像中的直线。

C++: void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
  • 第一个參数,InputArray类型的image,输入图像。即源图像,需为8位的单通道二进制图像。能够将随意的源图加载进来后由函数改动成此格式后。再填在这里。
  • 第二个參数。InputArray类型的lines,经过调用HoughLinesP函数后后存储了检測到的线条的输出矢量,每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2)  表示。当中。(x_1, y_1)和(x_2, y_2) 是是每个检測到的线段的结束点。
  • 第三个參数,double类型的rho,以像素为单位的距离精度。还有一种形容方式是直线搜索时的进步尺寸的单位半径。
  • 第四个參数,double类型的theta,以弧度为单位的角度精度。

    还有一种形容方式是直线搜索时的进步尺寸的单位角度。

  • 第五个參数,int类型的threshold,累加平面的阈值參数。即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才干够被检測通过并返回到结果中。
  • 第六个參数,double类型的minLineLength,有默认值0,表示最低线段的长度,比这个设定參数短的线段就不能被显现出来。
  • 第七个參数。double类型的maxLineGap。有默认值0,同意将同一行点与点之间连接起来的最大的距离。

对于此函数,依旧是为大家准备了演示样例程序:

//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp> //-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】加载原始图和Mat变量定义
Mat srcImage = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图
Mat midImage,dstImage;//暂时变量和目标图的定义 //【2】进行边缘检測和转化为灰度图
Canny(srcImage, midImage, 50, 200, 3);//进行一此canny边缘检測
cvtColor(midImage,dstImage, CV_GRAY2BGR);//转化边缘检測后的图为灰度图 //【3】进行霍夫线变换
vector<Vec4i> lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughLinesP(midImage, lines, 1, CV_PI/180, 80, 50, 10 ); //【4】依次在图中绘制出每条线段
for( size_t i = 0; i < lines.size(); i++ )
{
Vec4i l = lines[i];
line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186,88,255), 1, CV_AA);
} //【5】显示原始图
imshow("【原始图】", srcImage); //【6】边缘检測后的图
imshow("【边缘检測后的图】", midImage); //【7】显示效果图
imshow("【效果图】", dstImage); waitKey(0); return 0;
}

执行截图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

来一张大图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" alt="" />

四、霍夫圆变换

霍夫圆变换的基本原理和上面讲的霍夫线变化大体上是非常相似的,仅仅是点相应的二维极径极角空间被三维的圆心点x, y还有半径r空间代替。说“大体上相似”的原因是,假设全然用同样的方法的话。累加平面会被三维的累加容器所代替:在这三维中,一维是x,一维是y,另外一维是圆的半径r。这就意味着须要大量的内存而且执行效率会非常低,速度会非常慢。

对直线来说, 一条直线能由參数极径极角【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑表示. 而对圆来说, 我们须要三个參数来表示一个圆, 也就是:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

这里的 表示圆心的位置 (下图中的绿点) 而 r 表示半径, 这样我们就能唯一的定义一个圆了, 见下图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

在OpenCV中。我们一般通过一个叫做“霍夫梯度法”的方法来解决圆变换的问题。

4.1 霍夫梯度法的原理

霍夫梯度法的原理是这种。

【1】首先对图像应用边缘检測,比方用canny边缘检測。

【2】然后,对边缘图像中的每个非零点,考虑其局部梯度,即用Sobel()函数计算x和y方向的Sobel一阶导数得到梯度。

【3】利用得到的梯度。由斜率指定的直线上的每个点都在累加器中被累加,这里的斜率是从一个指定的最小值到指定的最大值的距离。

【4】同一时候,标记边缘图像中每个非0像素的位置。

【5】然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值而且大于其全部近邻。这些候选的中心依照累加值降序排列,以便于最支持像素的中心首先出现。

【6】接下来对每个中心,考虑全部的非0像素。

【7】这些像素依照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。

8.假设一个中心收到边缘图像非0像素最充分的支持,而且到前期被选择的中心有足够的距离,那么它就会被保留下来。

这个实现能够使算法执行起来更高效。也许更加重要的是,能够帮助解决三维累加器中会产生很多噪声而且使得结果不稳定的稀疏分布问题。

人无完人。金无足赤。

同样。这个算法也并非十全十美的,还有很多须要指出的缺点。

4.2 霍夫梯度法的缺点

<1>在霍夫梯度法中,我们使用Sobel导数来计算局部梯度,那么随之而来的假设是,其能够视作等同于一条局部切线,并这个不是一个数值稳定的做法。在大多数情况下,这样做会得到正确的结果,但也许会在输出中产生一些噪声。

<2>在边缘图像中的整个非0像素集被看做每个中心的候选部分。因此,假设把累加器的阈值设置偏低,算法将要消耗比較长的时间。第三,由于每个中心仅仅选择一个圆,假设有同心圆,就仅仅能选择当中的一个。

<3>由于中心是依照其关联的累加器值的升序排列的,而且假设新的中心过于接近之前已经接受的中心的话,就不会被保留下来。且当有很多同心圆或者是近似的同心圆时,霍夫梯度法的倾向是保留最大的一个圆。能够说这是一种比較极端的做法。由于在这里默认Sobel导数会产生噪声,若是对于无穷分辨率的平滑图像而言的话,这才是必须的。

4.3 HoughCircles( )函数具体解释

HoughCircles函数能够利用霍夫变换算法检測出灰度图中的圆。它和之前的HoughLines和HoughLinesP比較明显的一个差别是它不须要源图是二值的,而HoughLines和HoughLinesP都须要源图为二值图像。

C++: void HoughCircles(InputArray image,OutputArray circles, int method, double dp, double minDist, double param1=100,double param2=100, int minRadius=0, int maxRadius=0 )
  • 第一个參数。InputArray类型的image,输入图像,即源图像,需为8位的灰度单通道图像。
  • 第二个參数,InputArray类型的circles,经过调用HoughCircles函数后此參数存储了检測到的圆的输出矢量,每个矢量由包括了3个元素的浮点矢量(x, y, radius)表示。
  • 第三个參数,int类型的method。即使用的检測方法,眼下OpenCV中就霍夫梯度法一种能够使用,它的标识符为CV_HOUGH_GRADIENT。在此參数处填这个标识符就可以。
  • 第四个參数,double类型的dp,用来检測圆心的累加器图像的分辨率于输入图像之比的倒数,且此參数同意创建一个比输入图像分辨率低的累加器。

    上述文字不好理解的话,来看样例吧。

    比如,假设dp= 1时。累加器和输入图像具有同样的分辨率。假设dp=2,累加器便有输入图像一半那么大的宽度和高度。

  • 第五个參数。double类型的minDist,为霍夫变换检測到的圆的圆心之间的最小距离。即让我们的算法能明显区分的两个不同圆之间的最小距离。这个參数假设太小的话,多个相邻的圆可能被错误地检測成了一个重合的圆。

    反之,这个參数设置太大的话,某些圆就不能被检測出来了。

  • 第六个參数。double类型的param1。有默认值100。

    它是第三个參数method设置的检測方法的相应的參数。

    对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT。它表示传递给canny边缘检測算子的高阈值。而低阈值为高阈值的一半。

  • 第七个參数,double类型的param2,也有默认值100。

    它是第三个參数method设置的检測方法的相应的參数。

    对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在检測阶段圆心的累加器阈值。

    它越小的话。就能够检測到很多其它根本不存在的圆,而它越大的话。能通过检測的圆就更加接近完美的圆形了。

  • 第八个參数。int类型的minRadius,有默认值0。表示圆半径的最小值。
  • 第九个參数,int类型的maxRadius,也有默认值0,表示圆半径的最大值。

须要注意的是。使用此函数能够非常easy地检測出圆的圆心,可是它可能找不到合适的圆半径。我们能够通过第八个參数minRadius和第九个參数maxRadius指定最小和最大的圆半径,来辅助圆检測的效果。或者。我们能够直接忽略返回半径,由于它们都有着默认值0。单单用HoughCircles函数检測出来的圆心。然后用额外的一些步骤来进一步确定半径。

依旧是为大家准备了基于此函数的演示样例程序:

//-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp> //-----------------------------------【命名空间声明部分】---------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数,我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】加载原始图和Mat变量定义
Mat srcImage = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图
Mat midImage,dstImage;//暂时变量和目标图的定义 //【2】显示原始图
imshow("【原始图】", srcImage); //【3】转为灰度图,进行图像平滑
cvtColor(srcImage,midImage, CV_BGR2GRAY);//转化边缘检測后的图为灰度图
GaussianBlur( midImage, midImage, Size(9, 9), 2, 2 ); //【4】进行霍夫圆变换
vector<Vec3f> circles;
HoughCircles( midImage, circles, CV_HOUGH_GRADIENT,1.5, 10, 200, 100, 0, 0 ); //【5】依次在图中绘制出圆
for( size_t i = 0; i < circles.size(); i++ )
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
//绘制圆心
circle( srcImage, center, 3, Scalar(0,255,0), -1, 8, 0 );
//绘制圆轮廓
circle( srcImage, center, radius, Scalar(155,50,255), 3, 8, 0 );
} //【6】显示效果图
imshow("【效果图】", srcImage); waitKey(0); return 0;
}

执行截图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" width="787" height="810" alt="" />

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

五、源代码部分

这个部分就是贴出OpenCV中本文相关函数的源代码实现细节。来给想了解实现细节的小伙伴们參考的,浅墨就暂时不在源代码的细节上挖深作具体凝视了。

5.1 OpenCV2.X中HoughLines( )函数源代码

void cv::HoughLines( InputArray _image,OutputArray _lines,
double rho, double theta,int threshold,
double srn, double stn )
{
Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
Mat image = _image.getMat();
CvMat c_image = image;
CvSeq* seq = cvHoughLines2( &c_image, storage, srn == 0 &&stn == 0 ?
CV_HOUGH_STANDARD :CV_HOUGH_MULTI_SCALE,
rho, theta, threshold, srn,stn );
seqToMat(seq, _lines);
}

能够发现其内部实现是基于OpenCV 1.X旧版的cvHoughLines2函数,我们再来看看其旧版cvHoughLines2的函数源代码。

5.1.1 OpenCV2.X中cvHoughLines2()函数源代码

CV_IMPL CvSeq*
cvHoughLines2( CvArr* src_image, void*lineStorage, int method,
double rho, double theta, intthreshold,
double param1, double param2 )
{
CvSeq* result = 0; CvMat stub, *img = (CvMat*)src_image;
CvMat* mat = 0;
CvSeq* lines = 0;
CvSeq lines_header;
CvSeqBlock lines_block;
int lineType, elemSize;
int linesMax = INT_MAX;
int iparam1, iparam2; img = cvGetMat( img, &stub ); if( !CV_IS_MASK_ARR(img))
CV_Error( CV_StsBadArg, "The source image must be 8-bit,single-channel" ); if( !lineStorage )
CV_Error( CV_StsNullPtr, "NULL destination" ); if( rho <= 0 || theta <= 0 || threshold <= 0 )
CV_Error( CV_StsOutOfRange, "rho, theta and threshold must bepositive" ); if( method != CV_HOUGH_PROBABILISTIC )
{
lineType = CV_32FC2;
elemSize = sizeof(float)*2;
}
else
{
lineType = CV_32SC4;
elemSize = sizeof(int)*4;
} if( CV_IS_STORAGE( lineStorage ))
{
lines = cvCreateSeq( lineType, sizeof(CvSeq), elemSize,(CvMemStorage*)lineStorage );
}
else if( CV_IS_MAT( lineStorage ))
{
mat = (CvMat*)lineStorage; if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 &&mat->cols != 1) )
CV_Error( CV_StsBadArg,
"The destination matrix should be continuous and have a single rowor a single column" ); if( CV_MAT_TYPE( mat->type ) != lineType )
CV_Error( CV_StsBadArg,
"The destination matrix data type is inappropriate, see themanual" ); lines = cvMakeSeqHeaderForArray( lineType, sizeof(CvSeq), elemSize,mat->data.ptr,
mat->rows + mat->cols - 1, &lines_header, &lines_block );
linesMax = lines->total;
cvClearSeq( lines );
}
else
CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* norCvMat*" ); iparam1 = cvRound(param1);
iparam2 = cvRound(param2); switch( method )
{
case CV_HOUGH_STANDARD:
icvHoughLinesStandard( img, (float)rho,
(float)theta, threshold,lines, linesMax );
break;
case CV_HOUGH_MULTI_SCALE:
icvHoughLinesSDiv( img, (float)rho, (float)theta,
threshold, iparam1, iparam2,lines, linesMax );
break;
case CV_HOUGH_PROBABILISTIC:
icvHoughLinesProbabilistic( img, (float)rho, (float)theta,
threshold, iparam1, iparam2,lines, linesMax );
break;
default:
CV_Error( CV_StsBadArg, "Unrecognized method id" );
} if( mat )
{
if( mat->cols > mat->rows )
mat->cols = lines->total;
else
mat->rows = lines->total;
}
else
result = lines; return result;
}

5.2 OpenCV2.X中HoughLinesP()函数源代码

void cv::HoughLinesP( InputArray _image,OutputArray _lines,
double rho, double theta,int threshold,
double minLineLength,double maxGap )
{
Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
Mat image = _image.getMat();
CvMat c_image = image;
CvSeq*seq = cvHoughLines2( &c_image, storage, CV_HOUGH_PROBABILISTIC,
rho, theta, threshold,minLineLength, maxGap );
seqToMat(seq, _lines);
}

能够发现其内部内部实现依旧是基于旧版OpenCV 1.X的cvHoughLines2函数的,上面我们已经将cvHoughLines2()贴出来了,这里就不再次贴出了。

5.3 OpenCV2.X中HoughCircles()函数源代码

void cv::HoughCircles( InputArray _image,OutputArray _circles,
int method, double dp,double min_dist,
double param1, doubleparam2,
int minRadius, int maxRadius )
{
Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
Mat image = _image.getMat();
CvMat c_image = image;
CvSeq* seq = cvHoughCircles( &c_image, storage, method,
dp, min_dist, param1,param2, minRadius, maxRadius );
seqToMat(seq, _circles);
}

能够发现其内部内部实现是基于旧版OpenCV 1.X的cvHoughCircles,我们再来看看其旧版cvHoughCircles( )的函数源代码。

5.3.1 OpenCV2.X中cvHoughCircles()函数源代码

CV_IMPL CvSeq*
cvHoughCircles( CvArr* src_image, void*circle_storage,
int method, double dp, doublemin_dist,
double param1, double param2,
int min_radius, int max_radius)
{
CvSeq* result = 0; CvMat stub, *img = (CvMat*)src_image;
CvMat* mat = 0;
CvSeq* circles = 0;
CvSeq circles_header;
CvSeqBlock circles_block;
int circles_max = INT_MAX;
int canny_threshold = cvRound(param1);
int acc_threshold = cvRound(param2); img = cvGetMat( img, &stub ); if( !CV_IS_MASK_ARR(img))
CV_Error( CV_StsBadArg, "The source image must be 8-bit,single-channel" ); if( !circle_storage )
CV_Error( CV_StsNullPtr, "NULL destination" ); if( dp <= 0 || min_dist <= 0 || canny_threshold <= 0 ||acc_threshold <= 0 )
CV_Error( CV_StsOutOfRange, "dp, min_dist, canny_threshold andacc_threshold must be all positive numbers" ); min_radius = MAX( min_radius, 0 );
if( max_radius <= 0 )
max_radius = MAX( img->rows, img->cols );
else if( max_radius <= min_radius )
max_radius = min_radius + 2; if( CV_IS_STORAGE( circle_storage ))
{
circles = cvCreateSeq( CV_32FC3, sizeof(CvSeq),
sizeof(float)*3, (CvMemStorage*)circle_storage );
}
else if( CV_IS_MAT( circle_storage ))
{
mat = (CvMat*)circle_storage; if( !CV_IS_MAT_CONT( mat->type ) || (mat->rows != 1 &&mat->cols != 1) ||
CV_MAT_TYPE(mat->type) != CV_32FC3 )
CV_Error( CV_StsBadArg,
"The destination matrix should be continuous and have a single rowor a single column" ); circles = cvMakeSeqHeaderForArray( CV_32FC3, sizeof(CvSeq),sizeof(float)*3,
mat->data.ptr, mat->rows +mat->cols - 1, &circles_header, &circles_block );
circles_max = circles->total;
cvClearSeq( circles );
}
else
CV_Error( CV_StsBadArg, "Destination is not CvMemStorage* norCvMat*" ); switch( method )
{
case CV_HOUGH_GRADIENT:
icvHoughCirclesGradient( img, (float)dp, (float)min_dist,
min_radius,max_radius, canny_threshold,
acc_threshold,circles, circles_max );
break;
default:
CV_Error( CV_StsBadArg, "Unrecognized method id" );
} if( mat )
{
if( mat->cols > mat->rows )
mat->cols = circles->total;
else
mat->rows = circles->total;
}
else
result = circles; return result;
}

五、综合演示样例部分

这次的综合演示样例。浅墨在HoughLinesP函数的基础上,为其加入了用于控制其第五个參数阈值threshold的滚动栏。

于是便能通过调节滚动栏,改变阈值,动态地控制霍夫线变换检測的线条多少。

废话不多说。直接上具体凝视的代码:

//-----------------------------------【程序说明】----------------------------------------------
// 程序名称::《【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 》 博文配套源代码
// 开发所用IDE版本号:Visual Studio 2010
// 开发所用OpenCV版本号: 2.4.9
// 2014年5月26日 Created by 浅墨
//---------------------------------------------------------------------------------------------- //-----------------------------------【头文件包括部分】---------------------------------------
// 描写叙述:包括程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp> //-----------------------------------【命名空间声明部分】--------------------------------------
// 描写叙述:包括程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv; //-----------------------------------【全局变量声明部分】--------------------------------------
// 描写叙述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage,g_midImage;//原始图、中间图和效果图
vector<Vec4i> g_lines;//定义一个矢量结构g_lines用于存放得到的线段矢量集合
//变量接收的TrackBar位置參数
int g_nthreshold=100; //-----------------------------------【全局函数声明部分】--------------------------------------
// 描写叙述:全局函数声明
//----------------------------------------------------------------------------------------------- static void on_HoughLines(int, void*);//回调函数
static void ShowHelpText(); //-----------------------------------【main( )函数】--------------------------------------------
// 描写叙述:控制台应用程序的入口函数。我们的程序从这里開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//改变console字体颜色
system("color 3F"); ShowHelpText(); //加载原始图和Mat变量定义
Mat g_srcImage = imread("1.jpg"); //project文件夹下应该有一张名为1.jpg的素材图 //显示原始图
imshow("【原始图】", g_srcImage); //创建滚动栏
namedWindow("【效果图】",1);
createTrackbar("值", "【效果图】",&g_nthreshold,200,on_HoughLines); //进行边缘检測和转化为灰度图
Canny(g_srcImage, g_midImage, 50, 200, 3);//进行一次canny边缘检測
cvtColor(g_midImage,g_dstImage, CV_GRAY2BGR);//转化边缘检測后的图为灰度图 //调用一次回调函数,调用一次HoughLinesP函数
on_HoughLines(g_nthreshold,0);
HoughLinesP(g_midImage, g_lines, 1, CV_PI/180, 80, 50, 10 ); //显示效果图
imshow("【效果图】", g_dstImage); waitKey(0); return 0; } //-----------------------------------【on_HoughLines( )函数】--------------------------------
// 描写叙述:【顶帽运算/黑帽运算】窗体的回调函数
//----------------------------------------------------------------------------------------------
static void on_HoughLines(int, void*)
{
//定义局部变量储存全局变量
Mat dstImage=g_dstImage.clone();
Mat midImage=g_midImage.clone(); //调用HoughLinesP函数
vector<Vec4i> mylines;
HoughLinesP(midImage, mylines, 1, CV_PI/180, g_nthreshold+1, 50, 10 ); //循环遍历绘制每一条线段
for( size_t i = 0; i < mylines.size(); i++ )
{
Vec4i l = mylines[i];
line( dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(23,180,55), 1, CV_AA);
}
//显示图像
imshow("【效果图】",dstImage);
} //-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描写叙述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出一些帮助信息
printf("\n\n\n\t请调整滚动栏观察图像效果~\n\n");
printf("\n\n\t\t\t\t\t\t\t\t by浅墨"
);
}

放一些执行截图吧。

原始图:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

阈值为95时:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" alt="" />

阈值为35时:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" data-pinit="registered" alt="" />

阈值为200时:

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

本篇文章的配套源代码请点击这里下载:

【浅墨OpenCV新手教程之十四】配套源代码下载

【OpenCV新手教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

OK。今天的内容大概就是这些,我们下篇文章见:)

上一篇:【OpenCV新手教程之十二】OpenCV边缘检測:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑


下一篇:【OpenCV新手教程之十七】OpenCV重映射 & SURF特征点检測合辑