本次我们来看OpenCV中的霍夫线变换,它可以用于检测图像中的直线进而标注出来。
基本原理
一条直线可由两个点A=(X1,Y1)和B=(X2,Y2)确定(笛卡尔坐标):
另一方面,也可以写成关于(k,q)的函数表达式(霍夫空间):
对应的变换可以通过图形直观表示:
变换后的空间成为霍夫空间。即:笛卡尔坐标系中一条直线,对应霍夫空间的一个点。
反过来同样成立(霍夫空间的一条直线,对应笛卡尔坐标系的一个点):
再来看看A、B两个点,对应霍夫空间的情形:
一步步来,再看一下三个点共线的情况:
可以看出如果笛卡尔坐标系的点共线,这些点在霍夫空间对应的直线交于一点:这也是必然,共线只有一种取值可能。
如果不止一条直线呢?再看看多个点的情况(有两条直线):
其实(3,2)与(4,1)也可以组成直线,只不过它有两个点确定,而图中A、B两点是由三条直线汇成,这也是霍夫变换的后处理的基本方式:选择由尽可能多直线汇成的点。
看看,霍夫空间:选择由三条交汇直线确定的点(中间图),对应的笛卡尔坐标系的直线(右图)。
到这里问题似乎解决了,已经完成了霍夫变换的求解,但是如果像下图这种情况呢?
k=∞是不方便表示的,而且q怎么取值呢,这样不是办法。因此考虑将笛卡尔坐标系换为:极坐标表示。
在极坐标系下,其实是一样的:极坐标的点→霍夫空间的直线,只不过霍夫空间不再是[k,q]的参数,而是的参数,给出对比图:
我们来看霍夫变换的算法步骤:
OpenCV中的霍夫变换
在OpenCV中,我们可以使用相应的函数API,先来看函数原型:
lines=cv.HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])
第一个参数,输入图像应该是一个二值图像,因此在应用hough变换之前应用阈值或使用Canny边缘检测.
第二和第三个参数分别是ρ和θ的精度.
第四个参数是阈值,这意味着它应该被视为一条直线.
记住,线条的数量取决于直线上的点的数量,所以它表示应该检测到的最小长度。
我们来看代码:
def Hough(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray, 50, 150, apertureSize=3) lines = cv2.HoughLines(edges, 1, np.pi / 180, 90) for line in lines: rho, theta = line[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), 1) cv2.imshow('show', img) cv2.waitKey()
可以看到,线条被检测出来,不过精度并不好,接下来我们介绍另一种直线检测。
概率霍夫线变换
上面介绍的标准霍夫变换,其本质上就是把图像中的边缘像素映射到它的霍夫空间,比如一共有M个边缘像素,则所有的边缘像素都需要进行映射,其运算量和所需内存都会很大。
如果只处理图像的m(m<M)个边缘像素点,则这m个边缘像素点的选取是具有一定概率的,因此该方法就是概率霍夫变换。该方法有一个重要的特点就是能够检测出线段,即能够检测出图像中直线的两个端点,从而定位图像中的直线。
下面是概率霍夫变换的简易步骤:
1. 随机抽取图像中的一个边缘像素点,如果已经被标定为是某一条直线上的点,则继续在剩下的边缘点中随机抽取一个边缘点,直到所有边缘点都抽取完为止;
2. 对该点进行霍夫变换,并进行累加计算;
3. 选取在霍夫空间内累加值最大的点,如果该点的值大于阈值,则进行步骤4,否则回到步骤1;
4. 对于累加值大于阈值的点,从该点出发,沿着图像中的直线的方向位移,从而找到直线的两个端点;
5. 计算直线的长度,如果大于某个阈值,则被认为是直线并输出。
OpenCV提供了函数API:
lines=cv.HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]])
其参数跟上面的一样,这里不再过多赘述,函数cv2.HoughLinesP()是一种概率直线检测,我们知道,原理上讲hough变换是一个耗时耗力的算法,尤其是每一个点计算,即使经过了canny转换了有的时候点的个数依然是庞大的,这个时候我们采取一种概率挑选机制,不是所有的点都计算,而是随机的选取一些个点来计算,相当于降采样了。这样的话我们的阈值设置上也要降低一些。在参数输入输出上,输入不过多了两个参数:minLineLengh(线的最短长度,比这个短的都被忽略)和MaxLineCap(两条直线之间的最大间隔,小于此值,认为是一条直线)。输出上也变了,不再是直线参数的,这个函数输出的直接就是直线点的坐标位置,这样可以省去一系列for循环中的由参数空间到图像的实际坐标点的转换。
我们来看代码:
def HoughP(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) edges = cv2.Canny(gray, 50, 150, apertureSize=3) lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength=90, maxLineGap=10) for line in lines: x1, y1, x2, y2 = line[0] cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 1) cv2.imshow('show', img) cv2.waitKey()
可以看到,这个直线检测是比之前的要好很多的,我们将在下一次中介绍霍夫圆变换,用途更为广泛。