理论
Hough Transform 是一个用来检测任意形状的流行技术,如果你能用数理形式表达一个形状,即便它是残缺不全的或者略微扭曲的,也能检测出来,我们来看看对于直线是怎么做的。
一个直线可以表达为y = mx + c 或者参数形式p = x cosθ + y sinθ, 其中p是从原点到直线的垂直距离,θ是垂线和水平坐标轴的夹角。
任何直线都可以表示成(ρ,θ). 首先它创建一个2D数组或者是收集器(拿着两个参数)初始化是0 。用行来代表ρ列代表θ.数组的大小由你需要的准确率来决定。假设你想要角度的准确率是1度,你需要180列。对于ρ,最大的可能距离是图的对角线的距离。所以对于一个像素的精确度来说,行数就是图像对角线的长度。
假设一个100x100的图像,中间有一个水平线,对于线的第一个点,你知道他的(x,y)的值。在直线方程里,把θ = 0,1,2,...,180带入然后看ρ的值。对于每个(ρ,θ)对,你每次在收集器里加1,看他对应的(ρ,θ)。(50, 90)= 1
现在看直线的第二个点,仍然按上面的做法,增加值得到对应的(ρ,θ).这次(50, 90) = 2, 你持续这个过程知道直线上的每个点,对于每个点,(50,90)会被增加或者投票,其他单元也有可能被投票,这样到了最后,(50, 90)会得到最大投票。如果你搜索收集器找最大投票,你会得到(50, 90)。也就是说,在这个图片里距离远点50有一个直线,角度是90度。
这就是霍夫线变换。很简单,你可以自己用Numpy实现,下面的图片展示了收集器。两点表示他们是图像中可能的直线。
OpenCV里的霍夫变换
在OpenCV里,上面的过程被封装在了函数cv2.HoughLines()里。它返回数组(ρ,θ). ρ的单位是像素,θ的单位是弧度。第一个参数是图像,应该是个二元图像。所以可以用canny边缘检测法或者使用阈值然后再做霍夫变换。第二个和第三个参数是ρ和θ的准确度。第四个参数是阈值,是它认为是个直线的最小的票数。记住,投票数依赖线上的点数,所以它表示了我们要检测的直线的最小长度。
import cv2
import numpy as np
img = cv2.imread('dave.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)lines = cv2.HoughLines(edges,1,np.pi/180,200)
for rho,theta in lines[0]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img,(x1,y1),(x2,y2),(0,0,255),2)cv2.imwrite('houghlines3.jpg',img)
概率霍夫变换
在霍夫变换里,你可以看到即便对于一个有两个参数的直线,也要做一堆计算。概率霍夫变换是优化的霍夫变换,它不考虑所有点,只取一个随机的子集点,所以对于检测直线来说更有效,只是我们要减低阈值。
OpenCV的实现使用cv2.HoughLinesP()。它有两个新参数:
·minLineLength - 直线最小长度,小于这个的线段就被忽略了。
·maxLineGap - 最大线隙, 如果两根线中间缝隙小于这个就认为是一根直线。
最好的事情是它直接返回直线的两个端点,在前面的例子里,你只是得到直线的参数,你还得找到左右点,这里,所有事情的简单直接。
import cv2
import numpy as npimg = cv2.imread('dave.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap)
for x1,y1,x2,y2 in lines[0]:
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),2)cv2.imwrite('houghlines5.jpg',img)
END