前面学习了霍夫直线检测,现在把它推广一下用来检测圆,不过它的思路还是用投票来计算交点的方式,但是霍夫空间的坐标系作了改变。让我们来回忆一下初中的几何,如果给出不共线三个点,怎么样画一个圆?如果用尺规作图的方法,如下图:
这里是采用垂直平分线的方法来找圆,显然速度很快。但是还没有别的法来找圆呢?仔细考虑一下,其实还有别的方法来找圆心的,比如下图:
在这图里可以发现使用同一个半径不停地画圆,比如以A点、B点、D点画圆,这三个圆相关于C点,那么C点就这三个点共圆的圆心点。从这里会发一个规律,如果以相同的半径在圆周上作圆,所有的圆都会相交于圆心,那么就可以利用这个规律来找圆心了,如下图:
因此,可以在一个图片上(比如左图),遍历所有黑色的像素,每个像素都作一个给半径r的圆,如果这些圆相交数量越多,那么这么点就是圆周上的圆心,圆的半径为r。这里相交的数量换成霍夫变换的思路就是投票数量。但是还有一个问题,因为给出图像之后怎么知道半径r呢?其实也没有别的好办法,只能使用蛮力,枚举所有半径,从0开始直到图片的像素大小。另外再来看圆的数学公式:
这个是直角坐标系下的公式,一幅图片里只能给出我们什么已知的条件呢?可以把上面的公式改写一下:
图片里已知的条件就是x、y、θ,未知有a,b,r这三个,因此需要把图像的二维空间进行升维变换,在三维空间里来处理,用a,b,r三个变量为作坐标轴,如下图所示:
从这里可以看到半径r是从0开始,不断地向上变大。如果在XY图像里每一个点作不同的半径,变换到参数空间里就成为一个圆锥,这样不同XY里的点,全部变换过来,就变成圆锥进行相交了。如果在某一个半径r上相交的点最多,就是投票最多,那么这个半径r就是要找的半径,这个(a,b)的值就是圆心,这样就完成从图片里寻找半径和圆心的任务。
根据上面的原理,就可以使用代码进行实现:
#python 3.7.4,opencv4.1
#蔡军生 https://blog.csdn.net/caimouse/article/details/51749579
#
import cv2
import numpy as np
from scipy import signal
import math
#图片的路径
imgname = "rmb2.png"
#读取图片
image = cv2.imread(imgname, cv2.IMREAD_GRAYSCALE)
#图片的高度和宽度
h,w = image.shape[:2]
print('imagesize={}-{}'.format(w,h))
output = image.copy()
blur_image = cv2.GaussianBlur(image,(3,3),0)
cv2.imshow('Gaussian Blurred Image',blur_image)
edged_image = cv2.Canny(blur_image,75,150)
cv2.imshow('Edged Image', edged_image)
#
height,width = edged_image.shape
radii = 100
acc_array = np.zeros(((height+2*radii,width+2*radii,radii)))
filter3D = np.zeros((30,30,radii))
filter3D[:,:,:]=1
print(acc_array.shape)
#变换为霍夫参数空间
def fill_acc_array(x0,y0,radius):
for theta in np.linspace(0,360,180):
a = x0 - radius*math.cos(theta/180.0*math.pi)
b = y0 - radius*math.sin(theta/180.0*math.pi)
a = int(round(a))
b = int(round(b))
acc_array[b,a,radius]+=1 #投票
#遍历所有边缘元素
edges = np.where(edged_image==255)
for i in range(0,len(edges[0])):
y=edges[0][i] #行
x=edges[1][i] #列
for radius in range(20,55):
fill_acc_array(x,y,radius)
#找到投票多的点
i=0
j=0
while(i<height-30):
while(j<width-30):
filter3D=acc_array[i:i+30,j:j+30,:]*filter3D
max_pt = np.where(filter3D==filter3D.max())
a = max_pt[0]
b = max_pt[1]
c = max_pt[2]
b=b+j
a=a+i
if(filter3D.max()>90):
cv2.circle(output,(b,a),c,(0,255,0),2)#标记圆
j=j+30
filter3D[:,:,:]=1
j=0
i=i+30
cv2.imshow('Detected circle',output)
#
cv2.imshow("Image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果输出如下:
输入图片
高斯平滑后图片
Canny边缘识别
检测到圆,并标记出来
https://blog.csdn.net/caimouse/article/details/51749579
转载:https://blog.csdn.net/caimouse/article/details/102363788