目标
理解霍夫变换的概念
学习如何在一张图片中检测直线
学习函数:
cv2.HoughLines()
,cv2.HoughLinesP()
原理
霍夫变换在检测各种形状的的技术中非常流行,
如果要检测的形状可以用数学表达式写出,
就可以是使用霍夫变换检测它。
及时要检测的形状存在一点破坏或者扭曲也可以使用。
下面就看看如何使用霍夫变换检测直线。
一条直线可以用数学表达式 $y = mx + c$
或者 $ρ = xcosθ + y sinθ$
表示。
ρ 是从原点到直线的垂直距离, θ 是直线的垂线与横轴顺时针方向的夹角(如果使用的坐标系不同方向也可能不同, 是按 OpenCV 使用的坐标系描述的)。 如下图所示:
所以如果一条线在原点下方经过,ρ 的值就应该大于 0, 角度小于 180。 但是如果从原点上方经过的话,角度不是大于 180, 也是小于 180,但 ρ 的值小于 0。垂直的线角度为 0 度, 水平线的角度为 90 度。 接下来看看霍夫变换是如何工作的。 每一条直线都可以用 (ρ,θ) 表示。
所以首先创建一个 2D 数组(累加器), 初始化累加器,所有的值都为 0。行表示 ρ,列表示 θ。 这个数组的大小决定了最后结果的准确性。 如果希望角度精确到 1 度,就需要 180 列。 对于 ρ,最大值为图片对角线的距离。 所以如果精确度要达到一个像素的级别, 行数就应该与图像对角线的距离相等。
想象一下有一个大小为 100x100 的直线位于图像的中央。 取直线上的第一个点,知道此处的(x,y)值。 把 x 和 y 带入上边的方程组, 然后遍历 θ 的取值:0,1,2,3,...,180。 分别求出与其对应的 ρ 的值,这样就得到一系列(ρ,θ)的数值对, 如果这个数值对在累加器中也存在相应的位置, 就在这个位置上加 1。所以现在累加器中的(50,90)=1。(一个点可能存在与多条直线中, 所以对于直线上的每一个点可能是累加器中的多个值同时加 1)。
现在取直线上的第二个点。重复上边的过程。 更新累加器中的值。现在累加器中(50,90)的值为 2。 每次做的就是更新累加器中的值。 对直线上的每个点都执行上边的操作,每次操作完成之后, 累加器中的值就加 1,但其他地方有时会加 1, 有时不会。 按照这种方式下去,到最后累加器中(50,90)的值肯定是最大的。 如果搜索累加器中的最大值,并找到其位置(50,90), 这就说明图像中有一条直线, 这条直线到原点的距离为 50,它的垂线与横轴的夹角为 90 度。 下面的动画很好的演示了这个过程(Image Courtesy: AmosStorkey )。
这就是霍夫直线变换工作的方式。很简单, 也许自己就可以使用 Numpy搞定它。 下图显示了一个累加器。 其中最亮的两个点代表了图像中两条直线的参数。(Image courtesy: Wikipedia)。
OpenCV 中的霍夫变换
上面介绍的整个过程在 OpenCV 中都被封装进了一个函数: cv2.HoughLines()
。
返回值就是(ρ,θ)。ρ 的单位是像素,θ 的单位是弧度。 这个函数的第一个参数是一个二值化图像, 所以在进行霍夫变换之前要首先进行二值化, 或者进行Canny 边缘检测。第二和第三个值分别代表 ρ 和 θ 的精确度。 第四个参数是阈值,只有累加其中的值高于阈值时才被认为是一条直线, 也可以把它看成能检测到的直线的最短长度(以像素点为单位)。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('img4.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,60,160,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)
plt.subplot(121),plt.imshow(img),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(cv2.imread("houghlines3.jpg",0))
plt.show()
Probabilistic Hough Transform
从上边的过程可以发现:仅仅是一条直线都需要两个参数,
这需要大量的计算。
Probabilistic_Hough_Transform
是对霍夫变换的一种优化。
它不会对每一个点都进行计算,而是从一幅图像中随机选取(是不是也可以使用图像
金字塔呢?)一个点集进行计算,对于直线检测来说这已经足够了。
但是使用这种变换必须要降低阈值(总的点数都少了,阈值肯定也要小呀!)。
下图是对两种方法的对比。(Image Courtesy : Franck Bettinger’s homepage)
OpenCV 中使用的 Matas, J. ,Galambos, C. 和 Kittler, J.V. 提出的Progressive Probabilistic Hough Transform。
这个函数是 cv2.HoughLinesP()
。
两个参数如下:
minLineLength
- 线的最短长度。比这个短的线都会被忽略。MaxLineGap
- 两条线段之间的最大间隔,如果小于此值,这两条直线就被看成是一条直线。
更加给力的是,这个函数的返回值就是直线的起点和终点。而在前面的例子中, 只得到了直线的参数,而且必须要找到所有的直线。而在这里一切都很直接很简单。
import cv2
import numpy as np
img = cv2.imread('globs.png')
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('houghlines6.jpg',img)
plt.subplot(121),plt.imshow(img),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(cv2.imread("houghlines6.jpg",0))
plt.show()