霍夫变换检测直线(HoughLines)

霍夫变换用来检测任意能够用数学公式表达的形状,即使这个形状被破坏或者有点扭曲。霍夫变换的原理是将特定图形上的点变换到一组参数空间上,根据参数空间点的累计结果找到一个极大值对应的解,那么这个解就对应着要寻找的几何形状的参数(比如说直线,那么就会得到直线的斜率k与常熟b,圆就会得到圆心与半径等等)。

关于霍夫变换核心以及难点就是关于就是有原始空间到参数空间的变换上。以直线检测为例,假设有一条直线L,原点到该直线的垂直距离为ρ,垂线与x轴夹角为θ ,那么这条直线是唯一的,且直线的方程为 ρ=xcosθ+ysinθ , 如下图所示:

一条直线在极坐标系下只有一个(ρ,θ) 与之对应,随便改变ρ或θ任何一个参数的大小,变换到空间域上的这个直线将会改变。

L直线上的所有点都必然是在极坐标为(ρ,θ) 所表示的直线上的,但是,它们也可以出现在其他的(ρ,直线上((ρ1,θ1),(ρ2,θ2)...),就比如L直线上的点(x,y)吧,它可以在很多直线上,准确的说,在经过这个点的直线上,随便画两条如下:

我们可以看到θ 无非是从0-360度(0−2π )变化,假设我们每1度取一个直线并保证(x,y)在这个直线上,那么(x,y)会在360条直线上出现。现在我们把这个情况画在极坐标轴上是什么样子的呢?

这个图体现了空间域上的一个点(x,y),可以出现的每一条可能的直线。每一个红点代表了一个(ρ,θ),也就代表着一条通过(x,y)点的直线。根据θ的变换步长不同而数量不同,如果θ的步长变化值为1,就有360个红点,如果θ的步长变化值为10,就有36个红点。

那么如果把空间域中每个点都这么找一圈并绘制在极坐标系下呢?也就是每个点在参数空间上都对应一系列的(ρ,θ)。现在把它们画在同一个坐标系下会怎么样呢?

为了方便,假设在这个直线上取3个点画一下:

在极坐标下,空间域中的每一个点都会存在一个周期曲线来表示通过这个点的直线。可以发现这三个极坐标系曲线同时经过一个点(ρ',θ')。极坐标上每一个点对应空间坐标上一条直线的,这就表示在空间坐标系下,有一条直线可以经过点1,经过点2,经过点3,也就说明这三个点是在一条直线上的,这条直线就是(ρ',θ')。反过来再来看这个极坐标系下的曲线,那么我们只需要找到交点最多的点,把它返回到空间域就是要找的直线了。一条直线上的所有点绘成的曲线交点势必是曲线相交次数最多的点。

         可以看到霍夫变换就是参数映射变换。对每一个点都进行映射,并且映射还不止一次。(ρ,θ) 是存在步长的,以θ 取步长为例,当θ 取得步长大的时候,映射的(ρ,θ) 对少些,反之则多。但是我们看到,映射后的点对是需要求交点的,上述画出来的曲线是连续的,然而实际上因为θ 步长的存在,他不可能是连续的,是离散的。当θ 步长取得比较大的时候,你还想有很多交点是不可能的,所以说θ 步长不能太大,理论上是越小效果越好,因为越小,越接近于连续曲线,也就越容易相交。但是越小带来的问题就是计算量越大,假设一副100*100的图像(很小吧),就有10000个点,对每个点假设就映射36组(θ步长值为10),那么总共需要映射360000次,在考虑每次映射计算的时间,可想而知霍夫的计算是耗时耗力的。所以必须对其进行改进。首先就是对图像进行改进,100*100的图像,10000个点,是不是每个点都要计算?大可不必,我们只需要在开始把图像进行一个边缘提取,一般使用canny算子就可以,生成黑白二值图像,白的是边缘,那么在映射的时候,只需要把边缘上的点进行参数空间变换就可以。为什么提取边缘?想想无论检测图像中存在的直线呀圆呀,它们必然都是轮廓鲜明的。那么需要变换的点可能就从10000个点降到可能1000个点了,这也就是为什么看到许多霍夫变换提取形状时为什么要把图像提取边缘,变成二值图像了。

那么一个霍夫变换在算法设计上就可以如下步骤:
(1)将参数空间(ρ,θ) 量化,赋初值一个二维矩阵M,M(ρ,θ) 就是一个累加器了。这个二维数组的行代表不同的ρ,而列代表θ;初始时所有值均为0。数组的大小取决于算法的精度。假设所需角度的精度精确到1度,那么就需要360列。对于ρ,最大的可能距离是图像的对角长度,因此若需要一个像素的精度,那么行数就是图像对角线的长度。

(2)然后对图像边缘上的每一个点进行变换,变换到属于哪一组(ρ,θ) ,就把该组(ρ,θ) 对应的值增加1(这里的需要变换的点就是上面说的经过边缘提取以后的图像)。
(3)当所有点处理完成后,就来分析得到的M(ρ,θ) ,设置一个阈值T,认为当M(ρ,θ)>T ,就认为有一条直线存在。而对应的(ρ,θ) 就是这组直线的参数,至于T是多少,自己去式,试的比较合适为止。
(4)有了(ρ,θ) 和点(x,y)就可以计算出来这个直线了。

Opencv中使用霍夫变换检测直线的函数有cv2.HoughLines(),cv2.HoughLinesP()。
cv2.HoughLines()函数有四个输入,第一个是二值图像,也就是canny变换后的图像,二三参数分别是ρ和θ的精确度,也就是两者的步长,步长决定了累加器二维数组的大小。第四个参数为阈值T,累加器中的值高于T是才认为是一条直线。函数的返回值是一个numpy数组,shape为(N,1,2),表述的就是根据二值图像得到了N个(ρ,θ)。ρ的单位是像素长度(也就是直线到图像原点(0,0)点的距离),而θ的单位是弧度

以下是代码示例:

import cv2
import numpy as np
'''图像中的直线检测
'''
img = cv2.imread('img/computer.jpg')

gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray_img,50,150,apertureSize=3)
minLineLength = 30
maxLineGap = 5
print(np.pi/180)
lines = cv2.HoughLines(edges,1,np.pi/180,150)
for line in lines:
    
    r,theta = line[0]
    # Stores the value of cos(theta) in a
    a = np.cos(theta)
    # Stores the value of sin(theta) in b
    b = np.sin(theta)
    # x0 stores the value rcos(theta)
    x0 = a*r
    # y0 stores the value rsin(theta)
    y0 = b*r
    # x1 stores the rounded off value of (rcos(theta)-1000sin(theta))
    x1 = int(x0 + 1000*(-b))
    # y1 stores the rounded off value of (rsin(theta)+1000cos(theta))
    y1 = int(y0 + 1000*(a))
    # x2 stores the rounded off value of (rcos(theta)+1000sin(theta))
    x2 = int(x0 - 1000*(-b))
    # y2 stores the rounded off value of (rsin(theta)-1000cos(theta))
    y2 = int(y0 - 1000*(a))
    # cv2.line draws a line in img from the point(x1,y1) to (x2,y2).
    # (0,0,255) denotes the colour of the line to be 
    #drawn. In this case, it is red. 
    cv2.line(img,(x1,y1), (x2,y2), (0,0,255),1)
 

cv2.imshow('edges',edges)
cv2.imshow('lines',img)
cv2.waitKey(-1)
cv2.destroyAllWindows()

运行结果:

可以修改cv2.HoughLines函数的第四个参数,也就是阈值T,会得到不同数量的直线检测效果。

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

import cv2
import numpy as np
img = cv2.imread('img/computer.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize =3)
lines = cv2.HoughLinesP(edges,1,np.pi/180,160,minLineLength=100,maxLineGap=10)
for line in lines:
    x1,y1,x2,y2 = line[0]
    cv2.line(img,(x1,y1),(x2,y2),(0,0,255),1)
    
cv2.imshow('edges',edges)
cv2.imshow('lines',img)
cv2.waitKey(-1)
cv2.destroyAllWindows()

运行效果:

本文参考:

Hough变换,同一条直线上的点对应的(r,θ)都是相同的,怎么理解?

Python下opencv使用笔记(十一)(详解hough变换检测直线与圆)


 

  • 15
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值