Duda and Hart, 1972年提出了HoughTransform,最开始它用来检测直线,后来推广到检测二/三维曲线。
如上图,将(x,y)空间变到(m,b)空间,m代表斜率,b代表截距。可以看出(x,y)空间上的点在(m,b)空间是线,所以(m,b)空间上诸多直线的交点的坐标就是待检测直线的斜率和截距,其将全局搜索变为局部搜索,将找线问题变为找点问题。
但是,需要注意的是,当线是竖直线的时候,如
x=3,
这个时候
m
将不存在(除数为
0
)。为了避免这种情况,我们选择用另一个参数空间
-hough
空间,即角度
-
距离空间:
ρ = x cos θ + y sin θ
rho :代表原点到直线的距离,[-max_dist,max_dist],max_dist是图像对角线
sita:从原点开始到直线的角度,[-90,90]
为了更好的解释霍夫变换,采用一个简单的例子,一个大小为
30*30
的二值图像,图像上有两个点,(
5
,
25
),(
20
,
10
)。将其变换到霍夫空间,在角度
[-90,90]
区间之间计算各点对应得
rho
。
可以看出来在霍夫空间上,是两条正弦曲线。
两个边界点再不同角度下对应的
rho
如下:
可以得到,rho=21,sita=45
所以霍夫变换的算法如下:
1、角点/边界检测,如harris、soble、Laplacian、canny
2、创建rho和sita 的范围,方向个数根据需求确定,如精度/速度(方向个数越多,精度越高)
3、确定hough投票器的大小,其是个二维数组,其行大小是rho的长度(len(rho)),其列大小是len(sita)
4、投票
5、寻找峰值
代码如下:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
def hough_detectline(img):
thetas=np.deg2rad(np.arange(0,180))
row,cols=img.shape
diag_len=np.ceil(np.sqrt(row**2+cols**2))
rhos=np.linspace(-diag_len,diag_len,int(2*diag_len))
cos_t=np.cos(thetas)
sin_t=np.sin(thetas)
num_theta=len(thetas)
#vote
vote=np.zeros((int(2*diag_len),num_theta),dtype=np.uint64)
y_inx,x_inx=np.nonzero(img)
#vote in hough space
for i in range(len(x_inx)):
x=x_inx[i]
y=y_inx[i]
for j in range(num_theta):
rho=round(x*cos_t[j]+y*sin_t[j])+diag_len
if isinstance(rho,int):
vote[rho,j]+=1
else:
vote[int(rho),j]+=1
return vote,rhos,thetas
#image = cv2.imread(r'C:\Users\Y\Desktop\input_0.png')
#image_gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#image_binary=cv2.Canny(image_gray,150,255)
image = np.zeros((500,500))
image[10:100, 10:100] = np.eye(90)
accumulator, rhos,thetas= hough_detectline(image)
#look for peaks
idx = np.argmax(accumulator)
##下面两句是寻找投票器最大值所对应的行与列,最大值对应的行就是rho的索引,对应的列就是theta的索引
#可以用这句代替:row,col=np.unravel_index(idx,ccumulator.shape)
#rho=rho[row],theta=theta[col]
rho = rhos[int(idx/accumulator.shape[1])]
theta = thetas[idx % accumulator.shape[1]]
k=-np.cos(theta)/np.sin(theta)
b=rho/np.sin(theta)
x=np.float32(np.arange(1,150,2))
#要在image 上画必须用float32,要不然会报错(float不行)
y=np.float32(k*x+b)
cv2.imshow("original image",image),cv2.waitKey(0)
for i in range(len(x)-1):
cv2.circle(image,(x[i],y[i]),5,(255,0,0),1)
cv2.imshow("hough",image),cv2.waitKey(0)
print ("rho={0:.2f}, theta={1:.0f}".format(rho, np.rad2deg(theta)))
结果:rho=0.50, theta=135