第十六章: 霍夫变换
霍夫变换是一种在图像中寻找直线、圆、椭圆等简单的几何形状的方法。
极好的参考文章: 一文解读经典霍夫变换(Hough Transform)-电子发烧友网
一、霍夫变换原理
- 霍夫变换的原理就是,把笛卡尔坐标系下的点或者直线映射到霍夫空间。
左图是笛卡尔空间下的笛卡尔坐标系,也称x-y坐标系
右图是霍夫空间下的霍夫坐标系,也称k-b坐标系
映射的过程是:
假如笛卡尔坐标系下有一条直线:y=k0x+b0, 这条直线映射到霍夫空间就是一个点(k0,b0)
假如笛卡尔坐标系下有一个点(x0,y0), 则在笛卡尔坐标系下经过该点的直线是:y0=kx0+b, 那么k和b之间的关系就是:b=-kx0+y0,那么k、b在霍夫空间的关系就是b=-kx0+y0,是一条直线!!!
- 结论:
1、笛卡尔空间的一条直线映射到霍夫空间就是一个点,同理,霍夫空间的一个点就是笛卡尔空间的一条直线。
2、笛卡尔空间的一个点映射到霍夫空间就是一条直线,同理,霍夫空间的一条直线就是笛卡尔空间的一个点。
3、同理,笛卡尔空间中的多个共线点,就对应霍夫空间的多个共点线,而这个共点就是笛卡尔空间的线的k、b。
所以,要找笛卡尔空间中的线条,我们就把笛卡尔空间转换到霍夫空间,在霍夫空间找交点最多的点,这个点的霍夫坐标值就是笛卡尔空间中的线条的k和b。
或者说用霍夫变换找原图中的线的基本思路是:将原图从笛卡尔空间映射到霍夫空间,然后找霍夫空间中尽可能多直线的交汇点的点,这个点的坐标就是原图中的直线的斜率和截距。这样我们就得到了原图中的直线了
- 但是:如果笛卡尔空间存在垂直线,垂直x轴的直线,其斜率k无穷大、截距b无法取值,此时垂直线就无法映射到霍夫空间。
此时我们就把笛卡尔空间的x-y坐标系换成极坐标r-θ,即用(r,θ)的形式来表示,同时霍夫空间我们用r-θ来表示霍夫空间,r是原点到直线的垂直距离,θ是直线的垂线与横轴逆时针方向的夹角,垂直线的角度为0,水平线的角度为180度。
此时两个空间的映射关系变成:
1、极坐标系内的一个点映射到霍夫空间中是一条直线(曲线)
2、极坐标系内的一条直线映射到霍夫空间中是一个点。
只要我们求得霍夫空间中的交点位置,就得到了原坐标系下的直线了。
二、实现流程
假设有一个100x100像素的图片,现在使用霍夫变换检测图片中的直线:
1、创建一个二维数组,初始化所有值为0。数组的行表示r,列表示θ。数组尺寸的大小决定着结果的准确性。如果我们希望直线的角度的精度为1度,那这个数组的列就设置为180列,每列是一度,依次从0度到180度。数组的行表示r,如果我们希望直线的精度达到像素级别的,r的最大值就应该等于图像的对角线距离,所以r就从0取到图像的对角线长度值。这个数组我们又称为累加器。
2、遍历原图中的每一个点,计算每个点在θ取0-180之间的每个r,如果这个数值在上述累加器中存在相应的位置,则在该位置上加1。
3、搜索累加器中的最大值,并找到这个最大值对应的r和θ,就可以将图像中直线表示出来了。
三、API
-
1、霍夫变换
lines = cv2.HoughLines(img, rho, theta, threshold)
img:源图像,必须是8位的单通道二值图像。所以在进行霍夫变换之前要先把源图像二值化,或者进行canny边缘检测。
rho:就是r,一般情况下使用的精度是1。 theta:就是θ, 一般情况下使用pi除以180,表示要搜索所有可能的角度。
threshold:是阈值,是判断要多少个点落在直线上,这个参数和我们前面的累加器里面的结果相对应。如果我们想检测多条直线,这个阈值就设置的小一点,反之设置的大一点。
返回值lines是(r,θ),是一对儿浮点数,是numpy.ndarray类型的。 -
2、概率霍夫变换
上面的霍夫变换检测直线时,非常容易出现误检测,并且检测出很多重复的结果,为了解决这些缺点,科学家们又提出了霍夫变换的改进版——概率霍夫变换,是霍夫变换算法的优化。
概率霍夫变换没有考虑所有的点,它只需要一个足以进行线检测的随机点子集即可。此外概率霍夫变换算法还对选取直线的方法进行了2点改进:
(1)所接受直线的最小长度。如果有超过阈值个数的像素点构成了一条直线,但是这条直线很短,那么就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原图中并不存在这条直线。
(2)接受直线时允许的最大像素点间距。如果有超过阈值个数的像素点构成了一条直线,但是这组像素点之间的距离都很远,就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原始图像中并不存在这条直线。
- lines = cv2.HoughLinesP(img, rho, theta, threshold, minLineLength, maxLineGap)
img:必须是二值图像。
rho:r
theta:θ
threshold:阈值,该值越小,判定出的直线越多,反之,判断出的直线越少。
minLineLength:默认值是0,用来控制"接受直线的最小长度"
maxLineGap:默认值是0,用来控制接受公共线段之间的最小间隔,即在一条线中两点的最大间隔。如果两点间的间隔超过了该参数值,就认为这两个点不在一条直线上。
返回值lines是两个点的坐标。#例16.1 对图像进行霍夫变换、概率霍夫变换,并观察变换效果 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r'C:\Users\25584\Desktop\computer.jpg',0) #img.shape返回(460, 460) edges = cv2.Canny(img, 50, 150) lines1 = cv2.HoughLines(edges, 1, np.pi/180, 200) #霍夫变换 lines2 = cv2.HoughLinesP(edges, 1, np.pi/180, 1, minLineLength=100, maxLineGap=10) #概率霍夫变换 #绘制霍夫变换的图像 result1 = img.copy() for i in lines1: rho, theta = i[0] x0 = rho * np.cos(theta) y0 = rho * np.sin(theta) x1 = int(x0+1000*(-np.sin(theta))) y1 = int(y0+1000*np.cos(theta)) x2 = int(x0-1000*(-np.sin(theta))) y2 = int(y0-1000*np.cos(theta)) cv2.line(result1, (x1,y1), (x2, y2), 255, 2) #绘制概率霍夫变换的图像 result2 = img.copy() for i in lines2: x1,y1,x2,y2 = i[0] cv2.line(result2, (x1,y1), (x2,y2), 255, 2) #可视化: plt.figure(figsize=(12,3)) plt.subplot(141), plt.imshow(img, cmap='gray') #原图 plt.subplot(142), plt.imshow(edges, cmap='gray') #模板 plt.subplot(143), plt.imshow(result1, cmap='gray') #霍夫变换结果 plt.subplot(144), plt.imshow(result2, cmap='gray') #概率霍夫变换结果 plt.show()
四、霍夫圆环变换
霍夫变换除了可以用来检测直线外,还可以检测其他几何对象。
用霍夫圆变换来检测图像中的圆,与使用霍夫直线变换检测直线的原理类似。在霍夫圆变换中,需要考虑圆的半径和圆形(x坐标和y坐标)三个参数。 在opencv中采用的策略是两轮筛选,第一轮筛选是找出可能存在圆的位置(也就是圆心),第二轮筛选是根据第一轮的结果筛选出半径。
与用来决定是否接受直线的两个参数"接受直线的最小长度(minLineLength)"和"接受直线时允许的最大像素点间距(MaxLineGap)"类似,霍夫圆变换也有几个用于决定是否接受圆的参数:圆心间的最小距离、圆的最小半径、圆的最大半径。 - circles = cv2.HoughCircles(img, method, dp, minDist, param1, param2, minRadius, maxRadius)
img:8位单通道灰度图像
method:检测方法。目前的版本只有HOUGH_GRADIENT是唯一可用的参数值,就是上面说的两轮检测的方法。
dp:累计器分辨率,用来指定图像分辨率与圆心累加器分辨率的比例。例如,pd=1表示输入图像和累加器具有相同的分辨率。
minDist:圆心间的最小间距。如果该参数太大,可能会在检测时漏掉一些圆,如果该参数太小,会有多个临近的圆被检测出来。
param1:该参数时缺省的,在缺省时默认值是100。它对应的是canny边缘检测器的高阈值(低阈值是高阈值的二分之一)
param2:圆心位置必须收到的投票数。只有在第一轮筛选过程中,投票数超过该值的圆,才有资格进入第二轮的筛选。因此,该值越大,检测到的圆越少,该值越小,检测到的圆越多。这个参数也是缺省的,缺省时的默认值是100。
minRadius:圆半径的最小值,小于该值的圆不会被检测出来。该参数也是缺省的,在缺省时默认值是0,此时这个参数不起作用。
maxRadius:圆半径的最大值,大于该值的圆不会被检测出来。该参数也是缺省的,在缺省时默认值是0,此时这个参数不起作用。
返回值circles是由圆心坐标和半径构成的numpy.ndarray -
说明:在调用这个api之前,要对源图像进行平滑操作,以减少图像中的噪声,避免发生误判。
#例16.2 对图像进行霍夫变换、概率霍夫变换,并观察变换效果 import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread(r'C:\Users\25584\Desktop\chess.jpg',0) #img.shape返回(460, 460) img_blur = cv2.medianBlur(img, 5) #中值滤波平滑一下 circles = cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, 1, 300, param1=50, param2=30, minRadius=100, maxRadius=200) circles = np.uint16(np.around(circles)) result = img.copy() for i in circles[0]: cv2.circle(result, (i[0], i[1]), i[2], 255, 5) cv2.circle(result, (i[0], i[1]), 2, 255, 15) #可视化: plt.figure(figsize=(10,3)) plt.subplot(131), plt.imshow(img, cmap='gray') plt.subplot(132), plt.imshow(img_blur, cmap='gray') plt.subplot(133), plt.imshow(result, cmap='gray') plt.show()