霍夫变换(Hough Transform)是图像处理中从图像中识别几何形状的基本方法之一,应用很广泛,也有很多改进算法。主要用来从图像中分离出具有某种相同特征的几何形状(如:直线、圆等)。最基本的霍夫变换是从黑白图像中检测直线。
霍夫变换是经典的检测直线的算法。其最初用来检测图像中的直线,同时也可以将其扩展,以用来检测图像中的简单结构。它最初是用于在二值化的图像中进行直线检测的。对于图像中的一条直线,利用直角坐标系,可以用公式表示为:
如果从k-b参数空间的角度来考虑,则该直线的任意一点(x,y)将变成一个“点”。也就是说,将图像空间中所有的非零像素转换到k-b参数空间,那么它们将聚焦在一个点上,而且参数空间中的一个局部峰值点就很有可能对应着原图像空间的一条直线。不过,由于直线的斜率可能为无穷大,或者无穷小,那么在k-b参数空间就不便于对直线进行描述和刻画。所以,有人提出采用极坐标参数空间进行直线检测。在极坐标系中,直线可以表述为以下形式:
上图(a)所示为原始的图像空间中的一个点() ;(b)中所示为直角坐标系当中为过同一点的四条直线;
(c)所示为这四条直线在极坐标参数空间可以表示为四个点。
在OpenCV中,支持两种两种不同的霍夫直线变换,the Standard Hough Transform(SHT,标准霍夫变换)和Progressive Probability Hough Transform(PPHT,渐进概率式霍夫变换)。
SHT就是上述的在极坐标空间进行参数表示的方法,而PPHT是SHT的改进,它是在一定的范围内进行霍夫变换,从而减少计算量,缩短计算时间。
在OpenCV中检测直线的函数有cv2.HoughLines()-----(标准霍夫变换),cv2.HoughLinesP()------(渐进概率式霍夫变换)。
函数cv2.HoughLines()返回值实际上是一个二维数据矩阵,表述的就是上述的,其中的单位是像素长度(即直线到图像原点直线的距离,从上述图b中可以看出),的单位是弧度,函数有四个参数输入:
通过调整边缘检测算子Canny阈值参数和标准霍夫变换阈值参数,来获取较好的检测效果。
# 标准霍夫变换
img = cv2.imread('malu.jpg')
house = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 获取灰度图
edges = cv2.Canny(house, 50, 200)
lines = cv2.HoughLines(edges, 1, np.pi/180, 260) # 霍夫变换返回的就是极坐标系中的两个参数 rho和theta
print(np.shape(lines))
lines = lines[:, 0, :] # 将数据转换到二维
for rho, theta in lines:
a = np.cos(theta)
b = np.sin(theta)
# 从图b中可以看出x0 = rho x cos(theta)
# y0 = rho x 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), 1)
cv2.imshow('img', img)
cv2.imshow('edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
函数cv2.HoughLinesP()是一种概率直线检测,从原理上讲hough变换是一个耗时耗力的算法,尤其是对每一个点的计算,即便经过了canny转换,但有的时候点的数量依然很庞大,这时候采取一种概率挑选机制,不是所有的点都进行计算,而是随机的选取一些点来进行计算,这样的话在阈值设置上也需要降低一些。
与标准霍夫变换函数相比,在参数的输入上多了两个参数:minLineLengh(线的最短长度,比这个线段短的都忽略掉)和maxLineGap(两条直线之间的最大间隔,小于此值,就认为是一条直线)。
这个函数的输出直接是直线点的坐标位置,这样可以省去一系列for循环中的由参数空间到图像的实际坐标点的转换。
# 渐进概率式霍夫变换
img = cv2.imread('house1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 250)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 30, minLineLength=60, maxLineGap=10)
lines = lines[:, 0, :]
for x1, y1, x2, y2 in lines:
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 1)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
再进行边缘检测之前如果对图像进行去噪声操作,图像显示的效果将会更好。
圆的数学的数学表达式为:
所以一个圆的确定需要三个参数,那么就需要三层循环来实现,从而把图像上的所有点映射到三维空间上。寻找参数空间累加器的最大(或者大于某一阈值)的值。那么理论上圆的检测将比直线更耗时,然而OpenCV对其进行了优化,用了霍夫梯度法。
cv2.HoughCircles(image, method, dp, minDist, circles, param1, param2, minRadius, maxRadius)
param1和param2就是和的精确度,最后两个参数是所要检测的圆的最大最小半径,不能盲目的检测,否则浪费时间和空间。输出就是三个参数空间矩阵。
利用霍夫检测对印章进行定位:
img = cv2.imread('yinzhang.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('img_yuantu', img)
gaussian = cv2.GaussianBlur(gray, (3, 3), 0)
circles1 = cv2.HoughCircles(gaussian, cv2.HOUGH_GRADIENT, 1, 100, param1=100, param2=30, minRadius=15, maxRadius=80)
print(np.shape(circles1)) # hough_gradient 霍夫梯度法
circles = circles1[0, :, :]
circles = np.uint16(np.around(circles))
for i in circles[:]:
cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 3)
cv2.circle(img, (i[0], i[1]), 2, (255, 0, 255), 10)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()