OpenCV-Python教程:霍夫变换~圆形(HoughCircles)
原文链接:http://www.juzicode.com/opencv-python-houghcircles
在OpenCV中HoughCircles()方法可以用来查找圆形,找到的圆形通过圆心位置和半径进行描述。
1、接口
接口形式:
cv2.HoughCircles(image,method,dp,minDist[,circles[,param1[,param2[,minRadius[,maxRadius]]]]])->circles
- 参数含义:
- image:输入图像,8bit单通道图像。
- method:检测方法,当前有cv2.HOUGH_GRADIENT和cv2.HOUGH_GRADIENT_ALT 2种方法,后者是前者的改进方法。
- dp:检测圆心的累加器精度和图像精度比的倒数,比如dp=1时累加器和输入图像有相同的分辨率,dp=2时累加器是输入图像一半大的宽高;method=cv2.HOUGH_GRADIENT_ALT时推荐设置dp=1.5。
- minDist:检测到圆心的间距,设置的越小可能检测的圆形越多,设置的越大可能会错过一些圆形的检测。
- param1:特定方法参数,和method配合;当method=cv2.HOUGH_GRADIENT或method=cv2.HOUGH_GRADIENT_ALT时,该参数是canny检测的高阈值,低阈值是该参数的一半;method=cv2.HOUGH_GRADIENT_ALT时,内部使用Scharr计算图像梯度,这个值通常要设置得更大。
- param2:特定方法参数,和method配合;当method=cv2.OUGH_GRADIENT,它表示检测阶段圆心的累加器阈值,越小就会检测到更多的圆,越大能通过检测的圆就更加精确。当method=cv2.HOUGH_GRADIENT_ALT时,该参数可以看做是圆的“完美性”度量,它越接近1算法选择的圆形形状越好,一般可以设置在0.9。如果想要更好地检测小圆,可以设置在0.85、0.8甚至更小,通过限制搜索范围[minRadius,maxRadius]可以避免出现许多假圆。
- minRadius:最小圆半径。
- maxRadius:最大圆半径,如果设置为<=0,使用最大图像尺寸;如果<0时且method=cv2.HOUGH_GRADIENT用来查找圆心而忽略半径的查找,method=cv2.HOUGH_GRADIENT_ALT不受影响,始终会去找半径。
- circles:返回的圆形的点,是一个三维数组,HOUGH_GRADIENT和HOUGH_GRADIENT_ALT 2种不同方法返回的圆形数组形式有差异,后文有详细介绍。
用HoughCircles()方法找到的圆形,通常圆心的准确率比较高,半径的准确率比较低,可以借助minRadius和maxRadius 2个参数来限制半径的范围提高查找的精确度。
2、HOUGH_GRADIENT方法
因为method参数不一样,param1或param2参数的含义不一样,内部实现找圆的方法也不一样,我们先来看下HOUGH_GRADIENT方法找圆形。
用HOUGH_GRADIENT方法时,先分别找x和y方向的Sobel梯度,再用Canny找边沿:
Sobel(_image, dx, CV_16S, 1, 0, kernelSize, 1, 0, BORDER_REPLICATE);
Sobel(_image, dy, CV_16S, 0, 1, kernelSize, 1, 0, BORDER_REPLICATE);
Canny(dx, dy, edges, std::max(1, cannyThreshold / 2), cannyThreshold, false);
下面这个例子找出图中的圆形并在原图中绘制出来:
import numpy as np
import matplotlib.pyplot as plt
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
#读入图像
img_src = cv2.imread('..\\samples\\picture\\houghcircle-basic.bmp')
print('img_src.shape:',img_src.shape)
img_disp=img_src.copy()
#转为灰度图
img_gray = cv2.cvtColor(img_src,cv2.COLOR_BGR2GRAY)
#找出圆形
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=0,maxRadius=0)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
#画出圆形
circles = np.uint16(np.around(circles))
for cir in circles[0]:
print('circle.shape:',cir.shape,'circle:',cir)
cv2.circle(img_disp,(cir[0],cir[1]),cir[2],(0,255,0),2)
cv2.circle(img_disp,(cir[0],cir[1]),2,(0,0,255),3)
#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('img_src')
ax[0,0].imshow(cv2.cvtColor(img_src,cv2.COLOR_BGR2RGB))
ax[0,1].set_title('img_disp')
ax[0,1].imshow(cv2.cvtColor(img_disp,cv2.COLOR_BGR2RGB))
plt.show()
运行结果:
VX公众号: 桔子code / juzicode.com
cv2.__version__: 4.5.3
img_src.shape: (312, 584, 3)
circles.shape: (1, 5, 3)
circles:
[[[346.5 215.5 67.6]
[160.5 189.5 66.8]
[ 59.5 38.5 29.9]
[442.5 43.5 28.6]
[534.5 42.5 29. ]]]
circle.shape: (3,) circle: [346 216 68]
circle.shape: (3,) circle: [160 190 67]
circle.shape: (3,) circle: [60 38 30]
circle.shape: (3,) circle: [442 44 29]
circle.shape: (3,) circle: [534 42 29]
从运行结果看,返回的圆形是一个3维numpy数组,shape属性为(1, 5, 3),shape[0]固定为1,shape[1]为5表示包含5组圆形的参数,shape[2]的3个数值包含圆形的x,y坐标和半径的值。
参数dp
回到前面的例子,仅仅修改dp参数为0.5:
#找出圆形 dp设置为1.5
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,0.5,20,param1=50,param2=30,minRadius=0,maxRadius=0)
print('circles.shape:',circles.shape)
当dp=0.5时,仍然是原来的5个圆形:
img_src.shape: (312, 584, 3)
circles.shape: (1, 5, 3)
circles:
[[[346.5 215.5 67.6]
[160.5 189.5 66.8]
[ 59.5 38.5 29.9]
[442.5 43.5 28.6]
[534.5 42.5 29. ]]]
但是当修改dp=1.5时:
#找出圆形 dp设置为1.5
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1.5,20,param1=50,param2=30,minRadius=0,maxRadius=0)
print('circles.shape:',circles.shape)
这时能找到33个圆形:
circles.shape: (1, 33, 3)
circles:
[[[159.75 191.25 65.399994]
[345.75 215.25 67.8 ]
[ 59.25 38.25 29.699999]
[534.75 42.75 29.699999]
[441.75 44.25 28.650002]
[321.75 192.75 98.25 ]
[318.75 216.75 93. ]
[176.25 177.75 47.699997]......
这些非预期的“圆形”都是从其他的圆形上“借来”的点构成的。
参数minDist
接下来看下minDist参数的改变引起的变化。
因为该参数表示检测圆形的最小间距,如果将该间距增大,就会导致在该参数minDist范围内只会检测到一个圆形。比如在上例中,用画图板程序观察右上角的2个小圆圆心间距为90个像素,我们将minDist设置为100时,就会导致只能检测到一个圆形:
#找出圆形,minDist设置为100
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1,100,param1=50,param2=30,minRadius=0,maxRadius=0)
将间距再增大到200时,下方的2个大圆也只能找到1个:
#找出圆形,minDist设置为100
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1,100,param1=50,param2=30,minRadius=0,maxRadius=0)
参数param2
仍然回到最开始的例子,仅仅修改param2参数,当method=cv2.OUGH_GRADIENT,它表示检测阶段圆心的累加器阈值,越小就会检测到更多的圆,越大能通过检测的圆就更加精确,下面的例子将param2从30修改为25:
#找出圆形 param2设置为25
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=25,minRadius=0,maxRadius=0)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
运行结果:
circles.shape: (1, 11, 3)
circles:
[[[346.5 215.5 67.6]
[160.5 189.5 66.8]
[ 59.5 38.5 29.9]
[442.5 43.5 28.6]
[534.5 42.5 29. ]
[151.5 207.5 50. ]
[341.5 193.5 47.8]
[366.5 216.5 47.8]
[326.5 209.5 47.7]
[174.5 211.5 43.6]
[350.5 244.5 38.6]]]
参数minRadius,maxRadius
同样在原来的代码基础上,只修改minRadius:
#找出圆形 限制minRadius maxRadius
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=50,maxRadius=0)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
运行结果:
circles.shape: (1, 2, 3)
circles:
[[[346.5 215.5 67.6]
[160.5 189.5 66.8]]]
circle.shape: (3,) circle: [346 216 68]
circle.shape: (3,) circle: [160 190 67]
这时只找到2个半径大于50的圆,其他3个半径小于50的圆被忽略掉了:
同样的用法,我们把maxRadius限制在35,就只会找到3个半径小于35的圆形:
当maxRadius的值设置小于0时,HoughCircles()只找圆心不找半径:
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1,20,param1=50,param2=30,minRadius=0,maxRadius=-1)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
运行结果:
circles.shape: (1, 5, 3)
circles:
[[[160.5 191.5 0. ]
[347.5 212.5 0. ]
[534.5 42.5 0. ]
[ 59.5 38.5 0. ]
[442.5 45.5 0. ]]]
circle.shape: (3,) circle: [160 192 0]
circle.shape: (3,) circle: [348 212 0]
circle.shape: (3,) circle: [534 42 0]
circle.shape: (3,) circle: [60 38 0]
circle.shape: (3,) circle: [442 46 0]
返回的圆形数组中,每组圆形数据中表示半径的第3个数值为0,表示半径为0,所以圆形的外围就没有再被标注为绿色,只标注了圆心的位置:
3、HOUGH_GRADIENT_ALT方法
用HOUGH_GRADIENT_ALT方法时,先分别找x和y方向的Scharr梯度,再用Canny找边沿:
Scharr(img, Dx, CV_16S, 1, 0);
Scharr(img, Dy, CV_16S, 0, 1);
Canny(Dx, Dy, edges, cannyThreshold/2, cannyThreshold, true);
源图片仍然是用HOUGH_GRADIENT方法的例子中使用的同一个图片,method参数这时改为HOUGH_GRADIENT_ALT,param2=0.9,另外需要注意返回圆形数组形式的差异:
import numpy as np
import matplotlib.pyplot as plt
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
#读入图像
img_src = cv2.imread('..\\samples\\picture\\houghcircle-basic.bmp')
print('img_src.shape:',img_src.shape)
img_disp=img_src.copy()
#转为灰度图
img_gray = cv2.cvtColor(img_src,cv2.COLOR_BGR2GRAY)
#找出圆形 HOUGH_GRADIENT_ALT 方法
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT_ALT,1,20,param1=50,param2=0.9,minRadius=0,maxRadius=0)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
#画出圆形
circles = np.uint16(np.around(circles))
for cir_ in circles:
cir = cir_[0]
print('circle.shape:',cir.shape,'circle:',cir)
cv2.circle(img_disp,(cir[0],cir[1]),cir[2],(0,255,0),2)
cv2.circle(img_disp,(cir[0],cir[1]),2,(0,0,255),3)
img_bin = cv2.Canny(img_gray,50,150,apertureSize = 3)
#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('img_src')
ax[0,0].imshow(cv2.cvtColor(img_src,cv2.COLOR_BGR2RGB))
ax[0,1].set_title('img_disp')
ax[0,1].imshow(cv2.cvtColor(img_disp,cv2.COLOR_BGR2RGB))
ax[1,0].set_title('img_bin')
ax[1,0].imshow(img_bin,'gray')
plt.show()
运行结果:
VX公众号: 桔子code / juzicode.com
cv2.__version__: 4.5.3
img_src.shape: (312, 584, 3)
circles.shape: (5, 1, 3)
circles:
[[[160. 192. 67.132744]]
[[348. 213. 67.159256]]
[[443. 45. 28.429234]]
[[535. 43. 28.45556 ]]
[[ 59. 38. 28.443163]]]
circle.shape: (3,) circle: [160 192 67]
circle.shape: (3,) circle: [348 213 67]
circle.shape: (3,) circle: [443 45 28]
circle.shape: (3,) circle: [535 43 28]
circle.shape: (3,) circle: [59 38 28]
从运行结果可以看到找到有的圆形位置和半径跟HOUGH_GRADIENT方法是一样的,但是要注意的是返回的结果组织形式是有差异的,HOUGH_GRADIENT_ALT方法找到的圆形数组的shape属性为(5, 1, 3),其shape[0]表示找到圆形的个数,shape[1]则固定为1,shape[2]是圆的3个参数,分别表示圆形的x,y坐标和半径;而HOUGH_GRADIENT方法找到的圆形数组的shape属性为(1, 5, 3),shape[0]固定为1,shape[1]为5表示包含5组圆形的参数,shape[2]是圆的3个参数。
入参dp
在上面的例子种修改dp的值为20:
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT_ALT,20,20,param1=50,param2=0.9,minRadius=0,maxRadius=0)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
运行结果:
img_src.shape: (312, 584, 3)
circles.shape: (4, 1, 3)
circles:
[[[340. 220. 72.84901 ]]
[[440. 40. 28.715666]]
[[ 60. 40. 28.445187]]
[[540. 40. 31.973608]]]
circle.shape: (3,) circle: [340 220 73]
circle.shape: (3,) circle: [440 40 29]
circle.shape: (3,) circle: [60 40 28]
circle.shape: (3,) circle: [540 40 32]
得到的圆心精确度下降,某些圆心已经偏离了原来的位置:
参数minDist
和HOUGH_GRADIENT方法一样,增大minDist入参的值会导致右上角的找到的圆形减少一个:
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT_ALT,1,100,param1=50,param2=0.9,minRadius=0,maxRadius=0)
运行结果:
circles.shape: (4, 1, 3)
circles:
[[[160. 192. 67.132744]]
[[348. 213. 67.159256]]
[[535. 43. 28.45556 ]]
[[ 59. 38. 28.443163]]]
circle.shape: (3,) circle: [160 192 67]
circle.shape: (3,) circle: [348 213 67]
circle.shape: (3,) circle: [535 43 28]
circle.shape: (3,) circle: [59 38 28]
参数param2
当method=HOUGH_GRADIENT_ALT时,param2参数的最大取值到1时表示最高精确度找圆,所以该数值越接近1,找到的圆就会越少,下面将该参数该为0.99:
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT_ALT,1,20,param1=50,param2=0.99,minRadius=0,maxRadius=0)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
运行结果:
circles.shape: (3, 1, 3)
circles:
[[[160. 192. 67.03482 ]]
[[348. 213. 67.37982 ]]
[[535. 43. 28.421442]]]
circle.shape: (3,) circle: [160 192 67]
circle.shape: (3,) circle: [348 213 67]
circle.shape: (3,) circle: [535 43 28]
这时只能找到3个圆形:
桔子菌尝试修改param2=1.0时一个圆形也找不到,这时要求被查找的圆形十分“完美”。
而当修改param2=0.1或0.01时,仍然只找到5个圆形,并没有像HOUGH_GRADIENT方法那样param2参数急剧变化后找到了非常多“错误”的圆形。
参数minRadius,maxRadius
这2个参数的用法和在method=HOUGH_GRADIENT方法中一样,可以限制圆形的半径。
但是maxRadius有个差异的地方在于即使设置为-1,HOUGH_GRADIENT_ALT方法仍然会找圆的半径:
circles = cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT_ALT,1,20,param1=50,param2=0.9,minRadius=0,maxRadius=-1)
print('circles.shape:',circles.shape)
print('circles:\n',circles)
运行结果:
circles.shape: (5, 1, 3)
circles:
[[[160. 192. 67.132744]]
[[348. 213. 67.159256]]
[[443. 45. 28.429234]]
[[535. 43. 28.45556 ]]
[[ 59. 38. 28.443163]]]
小结:houghCircles()找圆的方法有2种:HOUGH_GRADIENT和HOUGH_GRADIENT_ALT,通过method参数传入来区分。二者返回结果的组织形式存在极大差别,其数组的shape属性下标0和1的含义相互做了调换,使用时需要特别注意。minDist、minRadius参数对于2种方法而言没有什么差异,maxRadius为负数时HOUGH_GRADIENT方法不找半径,HOUGH_GRADIENT_ALT仍然查找半径。