opencv 十一 霍夫圆检测原理及高级使用案例(含优化步骤)

霍夫圆检测能检测出目标图像中存在的圆,但在实际使用中,参数调节存在很大的困难,故在本博文中对霍夫圆检测的原理、参数列表、优化经验进行分析总结。详细的列出了各个参数的调节依据,实现了在复杂背景下的霍夫圆检测。

1. 原理介绍

1.1 基本原理

相关知识:
霍夫圆检测与霍夫变换密切相关,霍夫变换是基于极坐标系(是由半径与夹角所描述的一种坐标系)与笛卡尔坐标系(普通的平面坐标系)的相互转变而实现的。笛卡尔坐标系上的一个点,变换到极坐标系上就变成了一条线;反之亦然。
在这里插入图片描述

然而,基于霍夫变换的霍夫圆检测方法计算量极大,不适合实际应用。在opencv的实现中,是使用霍夫梯度算法进行圆检测。
参考链接:https://www.cnblogs.com/bjxqmy/p/12333022.html

1.2 霍夫梯度法的原理

1.把原图做一次 Canny 边缘检测,得到边缘检测的二值图。
2.对原始图像执行一次 Sobel 算子,计算出所有像素的邻域梯度值。
3.初始化圆心空间 N(a,b),令所有的 N(a,b)=0。若要求圆心在图像中,则a,b值的范围分别对应图像的宽高,N(a,b)表示一共有a*b个。
4.遍历 Canny 边缘二值图中的所有非零像素点,沿着梯度方向 (切线的垂直方向,根据Sobel 算子计算出的垂直梯度及水平梯度得来)固定搜索半径范围画线,将线段经过的所有累加器中的点 (a,b) 的 N(a,b)+=1。
在这里插入图片描述

5.统计排序 N(a,b),得到可能的圆心(N(a,b) 越大,越有可能是圆心)。
在这里插入图片描述
参考链接:https://zhuanlan.zhihu.com/p/427270299

1.3 霍夫梯度法缺点

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

2.在边缘图像中的整个非0像素集被看做每个中心的候选部分,若没有合理设置梯度方向的搜索半径范围,则会导致计算量巨大。

3.因为霍夫梯度算法通过圆弧切线的垂直线进行圆心累加检测,当存在同心圆时,会累加到同一个圆心,这导致只会选择到累加值最大的圆。

1.4 参数详情

霍夫圆检测的使用代码如下

method=cv2.HOUGH_GRADIENT
circles=cv2.HoughCircles(image, method, dp=1, minDist=10, param1=None, param2=None, minRadius=None, maxRadius=None)

其参数列表详情如下:

Circles:用来存储HoughCircles的结果,类型为list,list中对象格式为x,y,r;
image:输入图像,即源图像,8位单通道图像,如果使用彩色图像,需要先转换成灰度图像;
method:定义检测图像中圆的方法。目前唯一实现的方法是cv2.HOUGH_GRADIENT;
dp:图像像素分辨率与参数空间分辨率的比值(官方文档上写的是图像分辨率与累加器分辨率的比值,它把参数空间认为是一个累加器,毕竟里面存储的都是经过的像素点的数量),dp=1,则参数空间与图像像素空间(分辨率)一样大,dp=2,参数空间的分辨率只有像素空间的一半大;#通过设置dp可以减少计算量
minDist:检测到的圆中心(x,y)坐标之间的最小距离。如果minDist太小,则会保留大部分圆心相近的圆。如果minDist太大,则会将圆心相近的圆进行合并(若两圆心距离 < minDist,则认为是同一个圆)。
param1:canny 边缘检测的高阈值,低阈值被自动置为高阈值的一半,默认为 100。;
param2:累加平面某点是否是圆心的判定阈值。大于该阈值才判断为圆。当值设置的很小是,检测到的圆越多。默认值为 100;
minRadius:半径的最小大小(以像素为单位)默认为 0;
maxRadius:半径的最大大小(以像素为单位)默认为 0。

2. 实际应用

2.1 基本运行代码

以下代码,实现了读取图像进行霍夫圆检测,试图检测出下图车辆中的车轮。但其参数设置有待调整优化。

import cv2
import numpy as np
import time
if __name__ == "__main__":
    name="test.jpg"
    img = cv2.imread(name)
    t1=time.time()
    GrayImage=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    circles = cv2.HoughCircles(GrayImage, cv2.HOUGH_GRADIENT, 1, 40, param1=70, param2=30, minRadius=0,maxRadius=0)
    t2=time.time()
    print("运行时间:",t2-t1)

    circles = np.uint16(np.around(circles))
    for i in circles[0,:]:
        x,y,r=i[0],i[1],i[2]
        #draw the outer circle
        cv2.circle(img,(x,y),r,(0,255,0),2)
        cv2.circle(img,(x,y),2,(0,0,255),3)
    cv2.imshow("img",img)
    cv2.waitKey()

原始图像内容如下:
请添加图片描述

2.2 优化方案

在运行以上代码时,因计算量巨大,电脑直接卡死。(原因1:图像缺乏处理优化、原因2:霍夫圆参数不合理、)

2.2.1 图像优化

图像优化是为了减少霍夫圆检测过程中的干扰点,因为3个点就可以构成一个圆。

边缘噪声滤除

1、对图像进行中值|均值滤波优化,减少图像中存在的边缘噪声。
所增加的代码如下,在16s内终于出了结果

    #GrayImage= cv2.medianBlur(GrayImage,7)#中值滤波 滤除背景噪声
    GrayImage= cv2.blur(GrayImage,(7,7))#均值滤波 滤除背景噪声

在这里插入图片描述

计算量削减

2、对图像size进行优化,因为进行霍夫圆检测时,图像size对检测精度影响不大。但对图像进行下采样(缩小后)可以大幅度减少计算量。
所增加的代码如下,在0.76s内出了结果,但也不是很准。若缩放到0.25倍,则可在0.06秒内跑出结果

    GrayImage=cv2.resize(GrayImage,None,fx=0.5,fy=0.5)

在这里插入图片描述

正圆保持

此步骤非必须。
在某些特殊情况,如钢卷的圆心检测,由于重力作用,钢卷的侧面圆被压成了椭圆,需要对其y方向(垂直方向)进行拉伸。如以下代码,将图像在y方向拉伸了1.2倍,此时钢卷的侧面椭圆被拉伸成了正圆。
img=cv2.resize(img,None,fx=1,fy=1.2)

2.2.2 检测参数优化

检索半径优化

通过对上图分析,发现检测到的圆太多了,且初步预估车轮的像素半径范围为20-80(通过多次设置范围,确定车轮像素在20~80之间)。
此时的霍夫圆检测参数如下:minRadius=20,maxRadius=80

circles = cv2.HoughCircles(GrayImage, cv2.HOUGH_GRADIENT, 1, minDist=40, param1=70, param2=30, minRadius=20,maxRadius=80)

此时检测效果如下,代码仅在0.03秒内跑出结果(比原先快了20倍),因此限定了霍夫圆检索半径。
在这里插入图片描述

圆心累加优化

经过上个步骤优化后,仍有实际中不存在的圆被检测出来。这是因为累加圆心(圆弧上的在圆心上的累加值,累加值超过该阈值则被认为是一个圆)设置不合理所导致的。
此时的霍夫圆检测参数如下:param2=50, minRadius=20,maxRadius=80

circles = cv2.HoughCircles(GrayImage, cv2.HOUGH_GRADIENT, 1, minDist=4, param1=70, param2=50, minRadius=20,maxRadius=80)

此时检测效果如下,仅在0.03秒内跑出结果(跟上一步骤相同),因此限定了霍夫圆检索半径
在这里插入图片描述

圆心距离优化

通过上述步骤后,发现针对某些圆会检测出多个圆心距离很近的圆。原先的圆心距设置很合理,但为了出优化效果,故将上一步的圆心距离设置的很小。这是由于检测步骤中的最小圆心距离设置不合理所导致的。
此时的霍夫圆检测参数如下:minDist=20, param2=50, minRadius=20,maxRadius=80
此时检测效果如下,已经能较好的定位到车轮区域。
在这里插入图片描述

最优完整代码

额外说明:若对检测到的圆效果存在疑惑时,可以自行对原图进行cany求边缘,以校验参数param1设置的是否合理,既根据cany运算的效果来调整param1的值(其为canny 边缘检测的高阈值),通常该值不需要进行调整,因为该值对效果影响不大。

import cv2
import numpy as np
import time
if __name__ == "__main__":
    name="test.jpg"
    img = cv2.imread(name)
    t1=time.time()
    img=cv2.resize(img,None,fx=0.5,fy=0.5)
    GrayImage=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    #GrayImage= cv2.medianBlur(GrayImage,7)#中值滤波 滤除背景噪声
    GrayImage= cv2.blur(GrayImage,(7,7))#均值滤波 滤除背景噪声
    circles = cv2.HoughCircles(GrayImage, cv2.HOUGH_GRADIENT, 1, minDist=20, param1=70, param2=50, minRadius=20,maxRadius=80)
    t2=time.time()
    print("运行时间:",t2-t1)#只统计霍夫圆检测时间
	
	#这里进行圆绘制
    circles = np.uint16(np.around(circles))
    for i in circles[0,:]:
        x,y,r=i[0],i[1],i[2]
        #draw the outer circle
        cv2.circle(img,(x,y),r,(0,255,0),2)
        cv2.circle(img,(x,y),2,(0,0,255),3)
    cv2.imshow("img1",img)
    cv2.waitKey()

猜你喜欢

转载自blog.csdn.net/m0_74259636/article/details/132655935