本文转载自知乎专栏(四十八)通俗易懂理解——霍夫变换原理 - 知乎
霍夫变换主要用于,对付图片数据,能够提供一种方式找到直线/圆,广义上的霍夫变换可以找到你想要的任何你可以描述的特征。
学完霍夫变换,我认为精髓就在于把握特征变换。
一、霍夫变换(Hough)
A-基本原理
一条直线可由两个点A=(X1,Y1)和B=(X2,Y2)确定(笛卡尔坐标)
![](https://i-blog.csdnimg.cn/blog_migrate/b60f219c956166775f9e5de39ad4f67a.jpeg)
另一方面,y = kx + q,也可以写成关于(k,q)的函数表达式(霍夫空间):
![](https://i-blog.csdnimg.cn/blog_migrate/d23446470bc5cd26eca4e88e77cd87f6.png)
对应的变换可以通过图形直观表示:
![](https://i-blog.csdnimg.cn/blog_migrate/aa6635f03c966335176807d43bb17afa.jpeg)
变换后的空间成为霍夫空间。即:笛卡尔坐标系中一条直线,对应霍夫空间的一个点。
反过来同样成立(霍夫空间的一条直线,对应笛卡尔坐标系的一个点):
![](https://i-blog.csdnimg.cn/blog_migrate/288f5c599a9a5c3e12af24d9a80b6e09.jpeg)
再来看看A、B两个点,对应霍夫空间的情形:
![](https://i-blog.csdnimg.cn/blog_migrate/def95444ac3d794ba26dcb8fccdd7c12.jpeg)
一步步来,再看一下三个点共线的情况:
![](https://i-blog.csdnimg.cn/blog_migrate/22de07a0694c0974ab85aafe269718aa.jpeg)
可以看出如果笛卡尔坐标系的点共线,这些点在霍夫空间对应的直线交于一点:这也是必然,共线只有一种取值可能。
如果不止一条直线呢?再看看多个点的情况(有两条直线):
![](https://i-blog.csdnimg.cn/blog_migrate/86e4329c7c161528e62b7442ba087b7d.jpeg)
其实(3,2)与(4,1)也可以组成直线,只不过它有两个点确定,而图中A、B两点是由三条直线汇成,这也是霍夫变换的后处理的基本方式:选择由尽可能多直线汇成的点。
看看,霍夫空间:选择由三条交汇直线确定的点(中间图),对应的笛卡尔坐标系的直线(右图)。
到这里问题似乎解决了,已经完成了霍夫变换的求解,但是如果像下图这种情况呢?
![](https://i-blog.csdnimg.cn/blog_migrate/f13ea7fbc9c7bdfe011421208bdc4717.jpeg)
k=∞是不方便表示的,而且q怎么取值呢,这样不是办法。因此考虑将笛卡尔坐标系换为:极坐标表示。
在极坐标系下,其实是一样的:极坐标的点→霍夫空间的直线,只不过霍夫空间不再是[k,q]的参数,而是
![](https://i-blog.csdnimg.cn/blog_migrate/61393a77e32fa75f7bab34f35f19650b.png)
的参数,给出对比图:
在极坐标系中表示直线的方程为ρ=xCosθ+ySinθ(ρ为原点到直线的距离),如图所示:
如上图,假定在一个8*8的平面像素中有一条直线,并且从左上角(1,8)像素点开始分别计算θ为0°、45°、90°、135°、180°时的ρ,图中可以看出ρ分别为1、(9√2)/2、8、(7√2)/2、-1,并给这5个值分别记一票,同理计算像素点(3,6)点θ为0°、45°、90°、135°、180°时的ρ,再给计算出来的5个ρ值分别记一票,此时就会发现ρ = (9√2)/2的这个值已经记了两票了,以此类推,遍历完整个8*8的像素空间的时候ρ = (9√2)/2就记了5票, 别的ρ值的票数均小于5票,所以得到该直线在这个8*8的像素坐标中的极坐标方程为 (9√2)/2=x*Cos45°+y*Sin45°,到此该直线方程就求出来了。(PS:但实际中θ的取值不会跨度这么大,一般是1度)。
也就是说我们遍历θ的值,从0-180°,并且同时代入(x,y)的值,求得对应的ρ。
找到0-180°中,哪个度数下的ρ值相同的数量最多。这反向说明了,在一个ρ和θ组成的函数中,符合的点数最多。
python实现:
#生成80*160的图片
def data_img(data,row=40,col=160):
image = np.zeros((row*2,col))
for i in range(len(data)):
if abs(data[i][0]) > row:
continue
else:
image[int(data[i][0]) + row][int(data[i][1])] = 255
return image
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)
#返回非0位置索引
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
#从霍夫矩阵中找到最长的一条线
def longest_line(accumulator,rhos,thetas):
#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)
return k,b
大神博客:霍夫变换 - 疯狂奔跑 - 博客园