首页 > 学院 > 开发设计 > 正文

opencv学习笔记(二十五)霍夫圆变换

2019-11-08 02:59:11
字体:
来源:转载
供稿:网友

霍夫圆变换与之前所描述的霍夫直线变换是大体上是类似的。说“大体上类似”的原因是——如果想要尝试完全类似——累加平面会被三维的累加容器所代替:在这三维中,一维是x,一维是y,另一维是圆的半径r。这就意味着需要大量的内存但速度却很慢。在OpenCV的应用中可以通过一个比较灵活的霍夫梯度法来解决圆变换的这一问题。 霍夫梯度法的原理如下。首先对图像应用边缘检测(这里用cvCanny ( )。然后,对边缘图像中每一个非0点,考虑其局部梯度(通过cvSobel( )函数计算x和Y方向的Sobel一阶导数得到梯度)。利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,这里斜率是从一个指定的最小值到指定的最大值的距离。同时,标记边缘图像中每一个非0像素的位置。然后从(二维)累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像素的中心首先出现。接下来对每一个中心,考虑所有的非0像素(回想一下这个清单在早期已经建立)。这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素最支持的一条半径。如果一个中心受到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,它将会被保留。

这个实现可以使算法执行起来更快,或许更重要的是,能够帮助解决三维累加器中其他稀疏分布问题,这个问题会产生许多噪声并使结果不稳定。另一方面,这个算法有许多需要注意的缺点。 首先,使用Sobel导数计算局部梯度——随之而来的假设是这个可以看作等同于一条局部切线—并不是一个数值稳定做法。在大多数时间这个命题可能是真的,但你可能认为这样会在输出中产生一些噪声。 其次,在边缘图像中的整个非0像素集被认为是每个中心的候选。因此,加器的阈值设置偏低,算法将要消耗比较长的时间。 第三,因为每一个中心只选择一个圆,如果有同心圆,就只能选择其中的一个。 最后,因为中心是被认为是按照与其关联的累加器的值升序排列的,并且如果新的中心过于接近以前接受的中心将不会被保留。这里有一个倾向就是当有许多同心圆或者是近似同心圆时,保留最大的一个圆。(只是个“偏见”,因为从Sobcl导数产生了噪声;若是在无穷分辨率的平滑图像,这才是确定的)。 霍夫圆变换函数evHoughCircles()与直线变换有相似的变量。输入的image也是8位的。cvHoughCircles()与cvHoughLines2()一个明显的不同是后者需要二值图像。cvHoughCircles()函数将会在内部(自动)调用。cvSobel()①——注释:①内部调用的是函数cvSobel()而不是cvCanny ( )。因为cvHoughCircles()需要估计每一个像素梯度的方向,但二值边缘图像的处理是比较难的,所以可以提供更加普通的灰度图。 circle_storage既可以是数组,也可以是内存存储器(memory storage),这取决于我们希望返回什么结果。如果使用数组,则应该是CV_32FC3类型的单列数组,三个通道分别存储圆的位置及其半径。如果使用内存存储器(memory storage) ,圆将会变成OpenCV的一个序列Cvseq,由cvHoughCircles()返回一个指向这个序列的指针(给定一个指向圆存储的数组指针值,cvHoughCircles()的返回值将为空)。 在这个方法中,参数必须设置为CV_HOUGH_GRADIENT。

cvHoughCircles()

注意:尽管cvHoughCircles()能很好地捕获到圆心,但有时也会找不到圆的半径。因此,在只需找到圆的中心(或者有其他一些技术可以准确找到圆的半径)的实际应用中,cvHoughCircles()返回的圆半径可以忽略。

利用 Hough 变换在灰度图像中找圆 定义: CvSeq* cvHoughCircles( CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1=100, double param2=100, int min_radius=0, int max_radius=0 ); 参数: image 输入 8比特、单通道灰度图像。 circle_storage 检测到的圆存储仓。可以是内存存储仓 (此种情况下,一个线段序列在存储仓中被创建,并且由函数返回)或者是包含圆参数的特殊类型的具有单行/单列的CV_32FC3型矩阵(CvMat*)。矩阵头为函数所修改,使得它的 cols/rows 将包含一组检测到的圆。如果 circle_storage 是矩阵,而实际圆的数目超过矩阵尺寸,那么最大可能数目的圆被返回。每个圆由三个浮点数表示:圆心坐标(x,y)和半径r。 method Hough 变换方式,目前只支持CV_HOUGH_GRADIENT。 dp 累加器图像的分辨率。这个参数允许创建一个比输入图像分辨率低的累加器。(这样做是因为有理由认为图像中存在的圆会自然降低到与图像宽高相同数量的范畴)。如果dp设置为1,则分辨率是相同的;如果设置为更大的值(比如2),累加器的分辨率受此影响会变小(此情况下为一半)。dp的值不能比1小。 自己试验之后发现,当dp=1时,好像检测不出圆。1.5时正好,一旦大于1.5,就有N个圆出现了。数越大,圆越多。 min_dist 该参数是让算法能明显区分的两个不同圆之间的最小距离。 param1 用于Canny的边缘阀值上限,下限被置为上限的一半。 param2 累加器的阀值。 min_radius 最小圆半径,默认值0。 max_radius 最大圆半径,默认值0。

程序实例:

#include <opencv2/opencv.hpp>int main(int argc, char* argv[]) { iplImage* image0=cvLoadImage("d.jpg",CV_LOAD_IMAGE_GRAYSCALE); IplImage* image= cvLoadImage("d.jpg",CV_LOAD_IMAGE_GRAYSCALE); CvMemStorage* storage=cvCreateMemStorage(0); cvSmooth(image0,image,CV_GAUSSIAN,5,5); CvSeq* results=cvHoughCircles(image,storage,CV_HOUGH_GRADIENT,1.8, 10, 200, 100, 0, 0 ); for(int i=0;i<results->total ;i++) { float* p=(float*) cvGetSeqElem(results,i); CvPoint pt=cvPoint(cvRound(p[0]),cvRound(p[1])); cvCircle(image,pt,cvRound(p[2]),CV_RGB(0,0,0), 3, 8, 0); } cvNamedWindow("source",1); cvShowImage("source",image0); cvNamedWindow("cvHoughCircles",1); cvShowImage("cvHoughCircles",image); cvWaitKey(0); return 0; }

程序使用的图片: 这里写图片描述

程序理解: 这里面有一个 float* p=(float*) cvGetSeqElem(results,i); CvPoint pt=cvPoint(cvRound(p[0]),cvRound(p[1])); cvCircle(image,pt,cvRound(p[2]),CV_RGB(0,0,0), 3, 8, 0); 我们需要注意一下的是: 调用cvGetSeqElem之后得到的是相应圆的圆心(x,y)和半径信息。 所以才有下的cvCircle中的引用pt(圆心坐标)和cvRound(p[2])——半径。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表