OpenCV-Python教程:霍夫变换~圆形(HoughCircles)

原文链接:http://www.juzicode.com/opencv-python-houghcircles

返回OpenCV-Python教程

在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仍然查找半径。

扩展阅读:

  1. OpenCV-Python教程

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注