深入OpenCV图像处理:霍夫线与霍夫圆变换合辑教程

更新:11-21 名人轶事 我要投稿 纠错 投诉

老铁们,大家好,相信还有很多朋友对于深入OpenCV图像处理:霍夫线与霍夫圆变换合辑教程和的相关问题不太懂,没关系,今天就由我来为大家分享分享深入OpenCV图像处理:霍夫线与霍夫圆变换合辑教程以及的问题,文章篇幅可能偏长,希望可以帮助到大家,下面一起来看看吧!

由于简书的编辑不太方便,如果您想查看完整的规格信息,请前往原文。

本系列文章由@青色_马星云制作。转载时请注明出处。

作者:钱默_毛星云

【关于淡墨】

毛星云,网络ID“千沫”,是一位热爱游戏开发、游戏引擎、计算机图形学、实时渲染等技术的90后,就职于腾讯互娱。 * 微软最有价值专家* 书籍《Windows游戏编程之从零开始》、《OpenCV3编程入门》 * 开源电子书《Real-Time Rendering 3rd》 提炼总结》* 南京航空航天大学航天学院硕士(2013级硕士),2016年3月毕业。毕业于南京航空航天大学中乌航空航天联合培训班,并获得乌克兰国立航空航天大学和南京航空航天大学双学位我一定会回复的,请见谅。 * 经常活动的地方:

1知乎2知乎专栏3GitHub4微博本文我们讨论了OpenCV中Hough变换相关的知识点,了解了OpenCV中HoughLines和HoughLinesP函数的使用来实现Hough直线变换。如何使用HoughCircles函数实现霍夫圆变换。这篇博文一共有四个配套的简短示例程序,文中贴出了它们的详细注释代码,文末提供了综合示例程序的下载。

让我们尝试一下正在运行的示例程序之一的屏幕截图:

img1.jpeg 1. 简介

在图像处理和计算机视觉领域,如何从当前图像中提取所需的特征信息是图像识别的关键。在许多应用中,需要快速准确地检测直线或圆。解决该问题非常有效的方法之一就是霍夫变换,它是图像处理中从图像中识别几何形状的基本方法之一。它被广泛使用并且有许多改进的算法。最基本的霍夫变换是从黑白图像中检测直线(线段)。本文将介绍OpenCV中霍夫变换的使用以及相关知识。

2.霍夫变换概述

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

霍夫变换最早由Paul Hough于1962年提出。最初的霍夫变换是为了检测直线和曲线而设计的。原始方法需要知道物体边界线的解析方程,但不需要先验了解该区域的位置。该方法的一个突出优点是分割结果的鲁棒性,即对不完整数据或噪声不是很敏感。然而,通常不可能获得描述边界的解析表达式。经典的霍夫变换后来被Richard Duda Peter Hart 于1972 年推广并使用,用于检测图像中的直线。后来,霍夫变换被扩展到任意形状物体的识别,主要是圆形和椭圆形。霍夫变换利用两个坐标空间之间的变换,将一个空间中形状相同的曲线或直线映射到另一个坐标空间中的一点,形成峰值,从而将检测任意形状的问题转化为统计峰值问题。

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

3.霍夫线变换

3.1 OpenCV中的霍夫线变换

我们知道霍夫线变换是一种用来寻找直线的方法。在使用霍夫线变换之前,首先必须对图像进行边缘检测,即霍夫线变换的直接输入只能是边缘二进制值。图像。

OpenCV支持三种不同的霍夫线变换,它们是:标准霍夫变换(SHT)和多尺度霍夫变换(MSHT)渐进概率霍夫变换(PPHT)。

其中,多尺度霍夫变换(MSHT)是经典霍夫变换(SHT)的多尺度变体。累积概率霍夫变换(PPHT)算法是标准霍夫变换(SHT)算法的改进。它在一定范围内进行霍夫变换,计算各个线段的方向和范围,从而减少计算量,缩短计算时间。 PPHT之所以被称为“概率性”,是因为它不会累加累加器平面上所有可能的点,而只是累加其中的一部分。这个想法是,如果峰值足够高,只需要一小部分时间就能找到它。够了。如果你这样猜测,你可以大大减少计算时间。

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

HoughLinesP函数用于调用累积概率霍夫变换PPHT。累积概率霍夫变换非常高效,因此相比于HoughLines函数,我们更喜欢使用HoughLinesP函数。

总结一下,OpenCV 中的霍夫线变换分为三种类型:

1StandardHough Transform (SHT),由HoughLines函数调用。

2多尺度Hough变换(MSHT),由HoughLines函数调用。

3.渐进概率霍夫变换(PPHT),由HoughLinesP函数调用。

3.2 霍夫线变换原理

【1】众所周知,一条直线可以用二维图像空间中的两个变量来表示。如:

1 在笛卡尔坐标系:中,可以用参数:斜率和截距(m,b)来表示。

2 在极坐标系:中,可以用参数:极半径和极角来表示。

rQ.png

img2.jpeg 对于霍夫变换,我们将使用极坐标系的第二种方式来表示直线。因此,直线的表达式可以为:

img3.gif可以简化得到:

img4.gif[2] 一般来说,对于点(x0,y0),我们可以统一定义通过该点的一族直线为:

img5.gif 这意味着每对代表一条通过点的直线。

【3】对于给定的一点,如果我们在极半径和极角平面上的极坐标中画出所有经过该点的直线,我们将得到一条正弦曲线。例如,对于给定的点X_0=8和Y_0=6,我们可以绘制下图(在平面上):

仅绘制点和。

【4】我们可以对图像中的所有点进行上述操作。如果对两个不同的点进行上述运算后得到的曲线在平面上相交,则表示

它们经过同一条直线。比如继续上面的例子,我们继续画点点,得到下图:

这三条曲线相交于平面上的点(0.925, 9.6)。坐标代表参数对或点,由点与点组成的平面中的直线。

【5】上面的描述表明,一般来说,可以通过找出平面上一点相交的曲线的条数来检测直线。交于一点的曲线越多,就意味着这个交点所代表的直线由更多的点组成。一般来说,在考虑检测到一条直线之前,我们可以通过设置直线上点的阈值来定义有多少条曲线相交于一点。

【6】这就是霍夫线变换的作用。它跟踪图像中每个点对应的曲线之间的交点。如果在一点相交的曲线数量超过了阈值,那么就可以认为这个交点所代表的参数对在原始图像的中心是一条直线。

3.3 HoughLines()函数详解

该函数使用标准霍夫变换查找二值图像线。在OpenCV中,我们可以用它来调用OpenCV内置的标准霍夫变换SHT和多尺度霍夫变换MSHT的算法。

C++: void HoughLines(InputArray 图像、OutputArray 线、double rho、double theta、int 阈值、double srn=0、double stn=0)

第一个参数是InputArray 类型的图像。输入图像,即源图像,需要是8位单通道二值图像。你可以加载任何源图像并通过函数将其修改为这种格式,然后在这里填写。

第二个参数,类型为InputArray的lines,存储调用HoughLines函数后霍夫线变换检测到的线的输出向量。每条线由一个具有两个元素的向量表示,其中是距坐标原点((0,0)(即图像的左上角))的距离。是以弧度为单位的线旋转角度(0 垂直线,/2 水平线)。

第三个参数,double类型的rho,是以像素为单位的距离精度。另一种描述方式是搜索直线时渐进尺寸的单位半径。 /rho 在PS:Latex 中的意思是.

第四个参数,double类型的theta,是以弧度为单位的角度精度。另一种描述方式是搜索直线时渐进尺寸的单位角度。

第五个参数int类型的threshold,是累积平面的阈值参数,即在累积平面中必须达到的值才能识别出图中的某一部分是直线。只有大于阈值的线段才能被检测到并返回到结果中。

第六个参数,double类型的srn,默认值为0。对于多尺度霍夫变换,这是第三个参数渐进大小rho的除数距离。粗略累加器前进大小直接是第三个参数rho,精确累加器前进大小是rho/srn。

第七个参数stn为double类型,默认值为0。对于多尺度霍夫变换,srn表示第四个参数渐进大小的单位角度theta的除数距离。而如果srn和stn同时为0,则表示使用经典的霍夫变换。否则,两个参数都应为正。

另外,关于霍夫变换的详细解释,可以看这个英文页面:

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

学习完函数分析后,看一下千墨为您准备的以HoughLines为核心的示例程序,让您充分了解如何使用HoughLines函数:

//--------------------------------[头文件包含部分]-------- - -------------------------------------------

//说明:包含程序依赖的头文件

//------------------------------------------------ --------------------------------------------------

include

include

//------------------------------------------------[命名空间声明部分]- -- --------------------------------------------------------

//描述:包含程序使用的命名空间

//------------------------------------------------ --------------------------------------------------

使用命名空间cv;

//--------------------------------【main( ) 函数】------ -- --------------------------------------

//说明:控制台应用程序的入口函数,我们的程序从这里开始

//------------------------------------------------ --------------------------------------------------

int main()

{

//【1】加载原始图和Mat变量定义

Mat srcImage=imread("1.jpg"); //项目目录下应该有一个名为1.jpg的素材图片

Mat midImage,dstImage;//临时变量和目标图像的定义

//【2】进行边缘检测并转换为灰度图

Canny(srcImage, midImage, 50, 200, 3);//进行canny边缘检测

cvtColor(midImage,dstImage, CV_GRAY2BGR);//将边缘检测后的图像转换为灰度图像

//【3】进行霍夫线变换

vectorlines;//定义一个向量结构lines来存储获得的线段向量集合

HoughLines(midImage, 线, 1, CV_PI/180, 150, 0, 0);

//【4】依次绘制图中的每条线段

for( size_t i=0; i 行.size(); i++ )

{

浮点rho=行[i][0], theta=行[i][1];

点pt1、pt2;

双a=cos(theta), b=sin(theta);

双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));

行(dstImage,pt1,pt2,标量(55,100,195),1,CV_AA);

}

//【5】显示原图

imshow("[原始图像]", srcImage);

//【6】边缘检测后的图片

imshow("[边缘检测后的图像]", midImage);

//【7】显示效果图

imshow("[渲染]", dstImage);

等待键(0);

返回0;}

运行截图:

这是一张大图:

PS:通过调整线条中的G、B、R颜色值即可得到图片中想要的线条颜色(dstImage, pt1, pt2, Scalar(55,100,195), 1, CV_AA);标量(55,100,195) 参数。

3.4 HoughLinesP()函数详解

该函数在HoughLines末尾添加了一个代表Probabilistic(概率)的P,表明它可以使用累积概率霍夫变换(PPHT)来寻找二值图像中的直线。

C++: void HoughLinesP(InputArray 图像、OutputArray 线、双rho、双theta、int 阈值、双minLineLength=0、双maxLineGap=0 )

第一个参数是InputArray 类型的图像。输入图像,即源图像,需要是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,是累积平面的阈值参数,即在累积平面中必须达到的值才能识别出图中的某一部分是直线。只有大于阈值的线段才能被检测到并返回到结果中。

第六个参数,double类型的minLineLength,默认值为0,代表最低线段的长度。无法显示短于该设定参数的线段。

第七个参数,double类型的maxLineGap,默认值为0,即同一直线上的点允许连接的最大距离。

对于这个功能,还是给大家准备了一个示例程序:

//--------------------------------[头文件包含部分]-------- - -------------------------------------------

//说明:包含程序依赖的头文件

//------------------------------------------------ --------------------------------------------------

include

include

//------------------------------------------------[命名空间声明部分]- -- --------------------------------------------------------

//描述:包含程序使用的命名空间

//------------------------------------------------ --------------------------------------------------

使用命名空间cv;

//--------------------------------【main( ) 函数】------ -- --------------------------------------

//说明:控制台应用程序的入口函数,我们的程序从这里开始

//------------------------------------------------ --------------------------------------------------

int main()

{

//【1】加载原始图和Mat变量定义

Mat srcImage=imread("1.jpg"); //项目目录下应该有一个名为1.jpg的素材图片

Mat midImage,dstImage;//临时变量和目标图像的定义

//【2】进行边缘检测并转换为灰度图

Canny(srcImage, midImage, 50, 200, 3);//进行canny边缘检测

cvtColor(midImage,dstImage, CV_GRAY2BGR);//将边缘检测后的图像转换为灰度图像

//【3】进行霍夫线变换

vectorlines;//定义一个向量结构lines来存储获得的线段向量集合

HoughLinesP(midImage, 线, 1, CV_PI/180, 80, 50, 10);

//【4】依次绘制图中的每条线段

for( size_t i=0; i 行.size(); i++ )

{

Vec4i l=行[i];

线( dstImage, 点(l[0], l[1]), 点(l[2], l[3]), 标量(186,88,255), 1, CV_AA);

}

//【5】显示原图

imshow("[原始图像]", srcImage);

//【6】边缘检测后的图片

imshow("[边缘检测后的图像]", midImage);

//【7】显示效果图

imshow("[渲染]", dstImage);

等待键(0);

返回0;}

运行截图:

这是一张大图:

4.霍夫圆变换

霍夫圆变换的基本原理大体上和上面提到的霍夫线变换很相似,只不过将点对应的二维极半径和极角空间换成了三维中心点x、y和半径r空间。之所以说“大体相似”,是因为如果采用同样的方法,累积平面就会被三维的累积容器所取代:这三个维度中,一维是x,一维是y,另一维是x是一个圆。半径河这意味着需要大量的内存,执行效率会很低,速度会很慢。

对于直线来说,直线可以用参数极半径和极角来表示。对于圆来说,我们需要三个参数来表示一个圆,即:

这里代表圆心的位置(下图中的绿色点),r代表半径,这样我们就可以唯一定义一个圆,见下图:

在OpenCV中,我们一般通过一种称为“霍夫梯度法”的方法来解决圆形变换问题。

4.1 霍夫梯度法原理

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

[1] 首先对图像应用边缘检测,例如使用canny边缘检测。

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

【3】利用获得的斜率,将斜率指定的直线上的各点累加到累加器中,其中斜率是从指定最小值到指定最大值的距离。

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

[5]然后从二维累加器中的这些点中选择候选中心,这些点大于给定的阈值并且大于其所有邻居。这些候选中心按照累积值的降序排列,使得最支持该像素的中心首先出现。

【6】接下来,对于每个中心,考虑所有非0像素。

【7】这些像素按照距中心的距离排序。从最小距离到最大半径开始,选择最受支持的非零像素半径。 8. 如果一个中心从边缘图像中的非零像素获得最充分的支持,并且距离先前选择的中心足够远,那么它将被保留。

这种实现可以使算法执行更高效,也许更重要的是,可以帮助解决三维累加器中的稀疏分布问题,该问题会产生大量噪声并使结果不稳定。

没有人是完美的,也没有金子是纯净的。同样,这个算法也并不完美,还有很多不足之处需要指出。

4.2 霍夫梯度法的缺点

1在霍夫梯度法中,我们使用Sobel导数来计算局部梯度,随后的假设是可以将其视为等价于局部切线,并且这不是一种数值稳定的方法。在大多数情况下,这会给出正确的结果,但可能会在输出中产生一些噪音。

2边缘图像中的整组非零像素被认为是每个中心的候选部分。因此,如果累加器的阈值设置得较低,则算法将花费较长的时间。第三,由于每个圆心只选取一个圆,因此如果有同心圆,则只能选取其中一个。

3 因为中心是按照其关联累加器值的升序排序的,如果新中心与先前接受的中心太接近,则不会保留新中心。而当同心圆或近似同心圆较多时,霍夫梯度法往往会保留最大的圆。可以说,这是一种比较极端的做法,因为这里默认的索贝尔导数会产生噪声,而这对于无限分辨率的平滑图像是必需的。

4.3 HoughCircles()函数详解

HoughCircles函数可以使用霍夫变换算法来检测灰度图像中的圆。它与之前的HoughLines和HoughLinesP的一个明显区别是,它不要求源图像是二值图像,而HoughLines和HoughLinesP都要求源图像是二值图像。

C++: void HoughCircles(InputArray 图像,OutputArray 圆,int 方法,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、半径)表示。

第三个参数int类型的method,是使用的检测方法。目前OpenCV中可以使用Hough梯度法。它的标识符是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,表示圆半径的最大值。 需要注意的是,使用此函数可以很容易地检测出圆的圆心,但是它可能找不到合适的圆半径。我们可以通过第八个参数minRadius和第九个参数maxRadius指定最小和最大的圆半径,来辅助圆检测的效果。或者,我们可以直接忽略返回半径,因为它们都有着默认值0,单单用HoughCircles函数检测出来的圆心,然后用额外的一些步骤来进一步确定半径。 依然是为大家准备了基于此函数的示例程序: //-----------------------------------【头文件包含部分】--------------------------------------- // 描述:包含程序所依赖的头文件 //----------------------------------------------------------------------------------------------

include

include

//-----------------------------------【命名空间声明部分】--------------------------------------- // 描述:包含程序所使用的命名空间 //----------------------------------------------------------------------------------------------- using namespace cv; //-----------------------------------【main( )函数】-------------------------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始 //----------------------------------------------------------------------------------------------- int main( ) { //【1】载入原始图和Mat变量定义 Mat srcImage = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图 Mat midImage,dstImage;//临时变量和目标图的定义 //【2】显示原始图 imshow("【原始图】", srcImage); //【3】转为灰度图,进行图像平滑 cvtColor(srcImage,midImage, CV_BGR2GRAY);//转化边缘检测后的图为灰度图 GaussianBlur( midImage, midImage, Size(9, 9), 2, 2 ); //【4】进行霍夫圆变换 vectorcircles; 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中本文相关函数的源码实现细节,来给想了解实现细节的小伙伴们参考的,浅墨就暂时不在源码的细节上挖深作详细注释了。 5.1 OpenCV2.X中HoughLines( )函数源码 void cv::HoughLines( InputArray _image,OutputArray _lines, double rho, double theta,int threshold, double srn, double stn ) { Ptrstorage = 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, voidlineStorage, int method, double rho, double theta, intthreshold, double param1, double param2 ) { CvSeqresult = 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 ) { Ptrstorage = 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 ) { Ptrstorage = 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, voidcircle_storage, int method, double dp, doublemin_dist, double param1, double param2, int min_radius, int max_radius) { CvSeqresult = 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

include

include

//-----------------------------------【命名空间声明部分】-------------------------------------- // 描述:包含程序所使用的命名空间 //----------------------------------------------------------------------------------------------- using namespace std; using namespace cv; //-----------------------------------【全局变量声明部分】-------------------------------------- // 描述:全局变量声明 //----------------------------------------------------------------------------------------------- Mat g_srcImage, g_dstImage,g_midImage;//原始图、中间图和效果图 vectorg_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"); //工程目录下应该有一张名为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函数 vectormylines; 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("nnnt请调整滚动条观察图像效果~nn"); printf("nntttttttt by浅墨" ); } 放一些运行截图吧。 原始图: 阈值为95时: 阈值为35时: 阈值为200时: 本篇文章的配套源代码请点击这里下载:

关于深入OpenCV图像处理:霍夫线与霍夫圆变换合辑教程和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

用户评论

红尘滚滚

终于轮到霍夫变换了!好久没更新教程了,赶紧去看看这篇教程吧。

    有5位网友表示赞同!

╯念抹浅笑

这个系列的教程写的真棒!希望能通俗易懂地讲解霍夫变换。

    有18位网友表示赞同!

无寒

以前一直对霍夫线变换好奇,不知道怎么用,现在可以终于学起来了!

    有7位网友表示赞同!

伱德柔情是我的痛。

我听说霍夫圆变换也是很实用的,期待看到具体的应用案例。

    有20位网友表示赞同!

别在我面前犯贱

学习霍夫变换是为了做图像处理项目吗?我的项目剛好需要用到。

    有7位网友表示赞同!

夜晟洛

这个合辑太棒了!包含了霍夫线和圆变换,一站式解决问题。

    有7位网友表示赞同!

金橙橙。-

之前看了一些相关的资料,觉得霍夫变换的原理挺复杂的研究一下看看。

    有7位网友表示赞同!

冷眼旁观i

希望能详细解释霍夫变换的代码实现过程,这样学习起来更轻松。

    有9位网友表示赞同!

屌国女农

我对图像识别的研究需要用到霍夫变换,这个教程正好可以帮我解决问题!

    有19位网友表示赞同!

歆久

我已经看到这系列教程的第一部分了,每个主题都很有深度!

    有18位网友表示赞同!

话扎心

期待能看到一些实际的应用例子,更直观地理解霍夫变换的作用。

    有19位网友表示赞同!

淡淡の清香

我觉得学习 OpenCV 的关键是掌握它的主要算法,比如霍夫变换就是其中之一!

    有17位网友表示赞同!

各自安好ぃ

我要开始我的图像处理之旅,感谢作者分享这些宝贵的教程资源!

    有19位网友表示赞同!

看我发功喷飞你

我会把这个教程分享给我的同学,我们一起学习 OpenCV!

    有17位网友表示赞同!

安之若素

我觉得霍夫变换是图像识别中的神器,这门技术必须掌握!

    有14位网友表示赞同!

逾期不候

这个合集应该能让对 OpenCV 很新的人快速入门。

    有9位网友表示赞同!

酒笙倾凉

学习新的技能永远都不嫌晚,我现在开始学习 OpenCV 不迟吧?

    有12位网友表示赞同!

闲肆

感谢作者的用心创作,希望未来能看到更多高质量的教程!

    有18位网友表示赞同!

【深入OpenCV图像处理:霍夫线与霍夫圆变换合辑教程】相关文章:

1.蛤蟆讨媳妇【哈尼族民间故事】

2.米颠拜石

3.王羲之临池学书

4.清代敢于创新的“浓墨宰相”——刘墉

5.“巧取豪夺”的由来--米芾逸事

6.荒唐洁癖 惜砚如身(米芾逸事)

7.拜石为兄--米芾逸事

8.郑板桥轶事十则

9.王献之被公主抢亲后的悲惨人生

10.史上真实张三丰:在棺材中竟神奇复活

上一篇:《飞鸟集》诗篇翻译赏析——泰戈尔名作鉴赏 下一篇:人生:接力赛的精彩瞬间而非马拉松的漫长征途