本次我们来看OpenCV中的霍夫线变换,它可以用于检测图像中的直线进而标注出来。

基本原理

一条直线可由两个点A=(X1,Y1)和B=(X2,Y2)确定(笛卡尔坐标):

python已知两点绘制直线代码_计算机视觉


另一方面,

python已知两点绘制直线代码_opencv_02

也可以写成关于(k,q)的函数表达式(霍夫空间):

python已知两点绘制直线代码_opencv_03

对应的变换可以通过图形直观表示:

python已知两点绘制直线代码_机器学习_04


变换后的空间成为霍夫空间。即:笛卡尔坐标系中一条直线,对应霍夫空间的一个点。反过来同样成立(霍夫空间的一条直线,对应笛卡尔坐标系的一个点):

python已知两点绘制直线代码_python_05


再来看看A、B两个点,对应霍夫空间的情形:

python已知两点绘制直线代码_opencv_06


一步步来,再看一下三个点共线的情况:

python已知两点绘制直线代码_python_07


可以看出如果笛卡尔坐标系的点共线,这些点在霍夫空间对应的直线交于一点:这也是必然,共线只有一种取值可能。如果不止一条直线呢?再看看多个点的情况(有两条直线):

python已知两点绘制直线代码_机器学习_08


其实(3,2)与(4,1)也可以组成直线,只不过它有两个点确定,而图中A、B两点是由三条直线汇成,这也是霍夫变换的后处理的基本方式:选择由尽可能多直线汇成的点。看看,霍夫空间:选择由三条交汇直线确定的点(中间图),对应的笛卡尔坐标系的直线(右图)。

python已知两点绘制直线代码_opencv_09


到这里问题似乎解决了,已经完成了霍夫变换的求解,但是如果像下图这种情况呢?

python已知两点绘制直线代码_机器学习_10


k=∞是不方便表示的,而且q怎么取值呢,这样不是办法。因此考虑将笛卡尔坐标系换为:极坐标表示。

python已知两点绘制直线代码_python_11


在极坐标系下,其实是一样的:极坐标的点→霍夫空间的直线,只不过霍夫空间不再是[k,q]的参数,而是image.png的参数,给出对比图:

python已知两点绘制直线代码_计算机视觉_12


我们来看霍夫变换的算法步骤:

python已知两点绘制直线代码_python_13

OpenCV中的霍夫变换

在OpenCV中,我们可以使用相应的函数API,先来看函数原型:
lines=cv.HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])

第一个参数,输入图像应该是一个二值图像,因此在应用hough变换之前应用阈值或使用Canny边缘检测.

第二和第三个参数分别是ρ和θ的精度.

第四个参数是阈值,这意味着它应该被视为一条直线.

记住,线条的数量取决于直线上的点的数量,所以它表示应该检测到的最小长度。

我们来看代码:

view plaincopy to clipboardprint?
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()

python已知两点绘制直线代码_计算机视觉_14


可以看到,线条被检测出来,不过精度并不好,接下来我们介绍另一种直线检测。

概率霍夫线变换

上面介绍的标准霍夫变换,其本质上就是把图像中的边缘像素映射到它的霍夫空间,比如一共有M个边缘像素,则所有的边缘像素都需要进行映射,其运算量和所需内存都会很大。

如果只处理图像的m(m<M)个边缘像素点,则这m个边缘像素点的选取是具有一定概率的,因此该方法就是概率霍夫变换。该方法有一个重要的特点就是能够检测出线段,即能够检测出图像中直线的两个端点,从而定位图像中的直线。

下面是概率霍夫变换的简易步骤:

随机抽取图像中的一个边缘像素点,如果已经被标定为是某一条直线上的点,则继续在剩下的边缘点中随机抽取一个边缘点,直到所有边缘点都抽取完为止;
对该点进行霍夫变换,并进行累加计算;
选取在霍夫空间内累加值最大的点,如果该点的值大于阈值,则进行步骤4,否则回到步骤1;
对于累加值大于阈值的点,从该点出发,沿着图像中的直线的方向位移,从而找到直线的两个端点;
计算直线的长度,如果大于某个阈值,则被认为是直线并输出。

OpenCV提供了函数API:

lines=cv.HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]])

其参数跟上面的一样,这里不再过多赘述,函数cv2.HoughLinesP()是一种概率直线检测,我们知道,原理上讲hough变换是一个耗时耗力的算法,尤其是每一个点计算,即使经过了canny转换了有的时候点的个数依然是庞大的,这个时候我们采取一种概率挑选机制,不是所有的点都计算,而是随机的选取一些个点来计算,相当于降采样了。这样的话我们的阈值设置上也要降低一些。在参数输入输出上,输入不过多了两个参数:minLineLengh(线的最短长度,比这个短的都被忽略)和MaxLineCap(两条直线之间的最大间隔,小于此值,认为是一条直线)。输出上也变了,不再是直线参数的,这个函数输出的直接就是直线点的坐标位置,这样可以省去一系列for循环中的由参数空间到图像的实际坐标点的转换。

我们来看代码:

view plaincopy to clipboardprint?
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()

python已知两点绘制直线代码_python已知两点绘制直线代码_15


可以看到,这个直线检测是比之前的要好很多的,我们将在下一次中介绍霍夫圆变换,用途更为广泛。查看文章汇总页