上个教程我们讨论了霍夫线变换,这次我们来看看霍夫圆变换。

原理

霍夫圆变换和霍夫线变换的原理类似。霍夫线变换是两个参数(r,θ),霍夫圆需要三个参数,圆心的x,y坐标和圆的半径.如下对应的三个参数c1,c2,c3:

opencv霍夫线去重_opencv

例如:

opencv霍夫线去重_opencv_02

其形状和:

opencv霍夫线去重_机器学习_03

类似,该函数是由z=x沿z轴旋转而成的圆锥曲面。

对于xy平面的一个点x0,y0(上述对应的点为(1,1)),则对应的由c1,c2,c3组成三维空间的空间曲面。对于c1,c2,c3平面的一个点,则对应的在xy平面它是一个圆。

对于在x,y平面上的三个点(x0,y0),(x1,y1),(x2,y2),在c1,c2,c3三维空间是对应的三个空间曲面(此时c1,c2,c3相当于常量)。

opencv霍夫线去重_opencv霍夫线去重_04

求解这三个方程,我们可以得到c1,c2,c3的值。这说明(x0,y0),(x1,y1),(x2,y2)这三个点在由c1,c2,c3所确定的圆上(即c1,c2,c3分别表示圆的圆心x坐标、圆心y坐标以及圆的半径),且三个点对应由c1,c2,c3确定的空间的三个空间曲面。进一步说明,在xy平面,三个点在同一个圆上,则它们对应的空间曲面相交于一点(即点(c1,c2,c3))。

故我们如果知道一个边界上的点的数目,足够多,且这些点与之对应的空间曲面相交于一点。则这些点构成的边界,就接近一个圆形。

上述描述的是标准霍夫圆变换的原理,由于三维空间的计算量大大增大的原因, 标准霍夫圆变化很难被应用到实际中。

OpenCV实现的是一个比标准霍夫圆变换更为灵活的检测方法: 霍夫梯度法, 也叫2-1霍夫变换(21HT)

它的原理依据是圆心一定是在圆上的每个点的模向量上, 这些圆上点模向量的交点就是圆心, 霍夫梯度法的第一步就是找到这些圆心, (圆心包含了圆心处的x和y坐标)这样三维的累加平面就又转化为二维累加平面. 第二步根据所有候选中心的边缘非0像素对其的支持程度来确定半径。

OpenCV中的cv2.HoughCircles()函数实现了圆形检测,它使用的算法也是改进的霍夫变换——2-1霍夫变换(21HT)。也就是把霍夫变换分为两个阶段,从而减小了霍夫空间的维数。

第一阶段用于检测圆心
第二阶段从圆心推导出圆半径

检测圆心的原理是圆心是它所在圆周所有法线的交汇处,因此只要找到这个交点,即可确定圆心,该方法所用的霍夫空间与图像空间的性质相同,因此它仅仅是二维空间。

检测圆半径的方法是从圆心到圆周上的任意一点的距离(即半径)是相同,只要确定一个阈值,只要相同距离的数量大于该阈值,我们就认为该距离就是该圆心所对应的圆半径,该方法只需要计算半径直方图,不使用霍夫空间。圆心和圆半径都得到了,那么通过公式1一个圆形就得到了。

从上面的分析可以看出,2-1霍夫变换把标准霍夫变换的三维霍夫空间缩小为二维霍夫空间,因此无论在内存的使用上还是在运行效率上,2-1霍夫变换都远远优于标准霍夫变换。但该算法有一个不足之处就是由于圆半径的检测完全取决于圆心的检测,因此如果圆心检测出现偏差,那么圆半径的检测肯定也是错误的。

2-1霍夫变换的具体步骤为:

1)首先对图像进行边缘检测,调用OpenCV自带的Canny()函数,将图像二值化,得到边缘图像。

2)对边缘图像上的每一个非零点。采用Sobel()函数,计算x方向导数和y方向的导数,从而得到梯度。从边缘点,沿着梯度和梯度的反方向,对参数指定的min_radius到max_radium的每一个像素,在累加器中被累加。同时记下边缘图像中每一个非0点的位置。

3)从(二维)累加器中这些点中选择候选中心。这些中心都大于给定的阈值和其相邻的四个邻域点的累加值。

4)对于这些候选中心按照累加值降序排序,以便于最支持的像素的中心首次出现。

5)对于每一个中心,考虑到所有的非0像素(非0,梯度不为0),这些像素按照与其中心的距离排序,从最大支持的中心的最小距离算起,选择非零像素最支持的一条半径。

6)如果一个中心受到边缘图像非0像素的充分支持,并且到前期被选择的中心有足够的距离。则将圆心和半径压入到序列中,得以保留。

我们先来看OpenCV中的函数API:

circles=cv.HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])

image:输入图像 (灰度图);

method:指定检测方法. 现在OpenCV中只有霍夫梯度法;

dp:累加器图像的反比分辨=1即可默认;

minDist = src_gray.rows/8: 检测到圆心之间的最小距离,这是一个经验值。这个大了,那么多个圆就是被认为一个圆;

param_1 = 200: Canny边缘函数的高阈值;

param_2 = 100: 圆心检测阈值.根据你的图像中的圆大小设置,当这张图片中的圆越小,那么此值就设置应该被设置越小。当设置的越小,那么检测出的圆越多,在检测较大的圆时则会产生很多噪声。所以要根据检测圆的大小变化;

min_radius = 0: 能检测到的最小圆半径, 默认为0;

max_radius = 0: 能检测到的最大圆半径, 默认为0。

我们使用图像做实验:

opencv霍夫线去重_python_05

图像中的杂乱信息太多,我们必须先进行滤波,如果不进行滤波的情况下,我们可以看代码:

view plaincopy to clipboardprint?
def Houghcircle(img):  
    cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)  
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20,  
                              param1=50, param2=30, minRadius=0, maxRadius=0)  
    circles = np.uint16(np.around(circles))  
    for i in circles[0, :]:  
        # draw the outer circle  
        cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)  
        # draw the center of the circle  
        cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)  
    cv2.imshow('detected circles', cimg)  
    cv2.waitKey(0)

opencv霍夫线去重_opencv霍夫线去重_06

结果让人感到乱七八糟的,现在我先采用中值滤波,继而采用高斯滤波,我们来看代码:

view plaincopy to clipboardprint?
def Houghcircle(img):  
    img = cv2.medianBlur(img, 3)  
    img = cv2.GaussianBlur(img,(17,19),0)  
    cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)  
    circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20,  
                              param1=50, param2=30, minRadius=0, maxRadius=0)  
    circles = np.uint16(np.around(circles))  
    for i in circles[0, :]:  
        # draw the outer circle  
        cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)  
        # draw the center of the circle  
        cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)  
    cv2.imshow('detected circles', cimg)  
    cv2.waitKey(0)

opencv霍夫线去重_opencv_07


可以看到,效果良好,霍夫变换通常情况下受图片的噪声信息干扰非常大,所以通常情况下我们需要对图像进行预处理操作。查看文章汇总页