函数: cv.HoughLines(), cv.HoughLinesP()
如果你能以数学形式表示一个形状,霍夫变换就可以检测出该形状。它可以检测形状,即使该形状是破碎或扭曲了一点。我们来看看霍夫直线变换是如何工作的。
一条直线可以表示成 y = m x + c y = mx+c y=mx+c或参数式,为 ρ = x cos θ + y sin θ \rho = x \cos \theta + y \sin \theta ρ=xcosθ+ysinθ。其中ρ是原点到直线的垂直距离,θ角是由这个垂直的线和水平轴以逆时针方向形成的角度。(不同坐标系统有不同的表达。OpenCV中使用这种表达)。看看下面的图片:
该公式具体推导可参考:https://en.wikipedia.org/wiki/Hough_transform
推导过程:
所以如果这条线在原点以下,它的角是正值,角度小于180度。如果它在原点上方,不是取大于180度的角,而是取小于180度的角,但是取负值。任何垂直线都是0度,水平线是90度。
现在让我们看看霍夫变换是如何检测直线的。任何直线都可以用这两项表示:(ρ,θ)。因此,它首先创建一个2D数组或累加器(保存两个参数的值),并在初始时将其设置为0。设行表示ρ,列表示θ。数组的大小取决于你需要的精度。假设你想让角度的精度是1度,你需要180列。对于ρ,可能的最大距离是图像的对角线长度。所以取一个像素的精度,行数可以是图像的对角线长度。
考虑一张100x100的图像,中间有一条水平线。取直线的第一个点。你知道它的(x,y)值。在直线方程中,θ=0,1,2 …,180,并检查你得到的ρ。对于每一对(ρ,θ),你在相应的(ρ,θ)单元的累加器中增加一个值。所以现在在累加器中,单元格(50,90)= 1,还有一些其他的单元格。
再取直线上的第二个点。按照上面的方法做。增加你得到的(rho, theta)对应单元格中的值。这一次,单元格(50,90)= 2。实际上你要做的是选出(ρ,θ)值。对直线上的每个点都继续这个过程。在每一点上,单元格(50,90)将被增加或支持,而其他单元格可能被支持,也可能不被支持。这样,最后,单元格(50,90)将获得最大投票数。所以如果你在累加器中搜索最大投票数,你得到的值是(50,90),这表示在这个图像中有一条距离原点50,角度90度的线。它在下面的动画中显示得很好(图像来源: Amos Storkey)
这就是hough变换检测直线的原理。它很简单,你可以自己使用Numpy来实现它。下图是一个显示累加器的图片。某些位置的亮点表示它们最有可能是图像中直线的参数。(图片来源:Wikipedia)
一点自己的理解:对于每一个点(x,y),给定一个θ都能计算出一个ρ,所以θ=0,1,2……180,每一个点在(ρ,θ)坐标系中就是一个曲线。
判断这些点是否共线(concurrent lines)的问题,经由霍夫变换之后,变成判断一堆曲线(每一个点在 ( ρ , θ ) \displaystyle (\rho,\theta ) (ρ,θ)平面上代表一条曲线)是否 在 ( ρ , θ ) \displaystyle (\rho,\theta ) (ρ,θ)平面上相交于同一点的问题(concurrent curves)
上面解释的一切都封装在OpenCV函数cv.HoughLines()中。它只是返回一个:math:(rho, theta) '值的数组。ρ值以像素为单位,θ以弧度为单位。第一个参数,输入的图像应该是二值图像,在应用霍夫变换前应用阈值或canny边缘检测。第二个和第三个参数分别是ρ和θ的精度。第四个参数是临界值,这意味着它应该被视为一条线的最低投票数。记住,投票的数量取决于直线上的点的数量。所以它表示最小值
import cv2 as cv
import numpy as np
img = cv.imread(cv.samples.findFile('sudoku.png'))
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray,50,150,apertureSize = 3)
lines = cv.HoughLines(edges,1,np.pi/180,200)
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))
cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)
cv.imwrite('houghlines3.jpg',img)
在霍夫变换中,你可以看到,即使一条直线只有两个参数,也需要大量的计算。概率霍夫变换是霍夫变换的一个优化。它没有把所有的点都考虑进去。相反,它只取一个足以进行直线检测的点的随机子集。我们只需要降低阈值。下图是霍夫变换和概率霍夫变换在霍夫空间中的对比。(图片来源:Franck Bettinger’s home page)
OpenCV的实现是基于Matas, J.和Galambos, C.和Kittler, J.V.的渐进概率Hough变换的鲁棒检测。使用的函数是**cv.HoughLinesP()**。它有两个新参数。
minLineLength:最小线长。比它短的线段将不认为是一条直线。
maxLineGap:段之间的最大允许间隙,以将它们视为单独的一条线。
最好的情况是,它直接返回直线的两个端点。在之前的例子中,你只有直线的参数,你必须找到所有的点。在这里,一切都是直接和简单的。
import cv2 as cv
import numpy as np
img = cv.imread(cv.samples.findFile('sudoku.png'))
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray,50,150,apertureSize = 3)
lines = cv.HoughLinesP(edges,1,np.pi/180,100,minLineLength=100,maxLineGap=10)
for line in lines:
x1,y1,x2,y2 = line[0]
cv.line(img,(x1,y1),(x2,y2),(0,255,0),2)
cv.imwrite('houghlines5.jpg',img)
函数: cv.HoughCircles()
圆的数学表达式为 ( x − x c e n t e r ) 2 + ( y − y c e n t e r ) 2 = r 2 (x-x_{center})^2 + (y - y_{center})^2 = r^2 (x−xcenter)2+(y−ycenter)2=r2,其中 ( x c e n t e r , y c e n t e r ) (x_{center},y_{center}) (xcenter,ycenter)是圆心,r是圆的半径。从方程可以看出,我们有3个参数,所以我们需要一个3D累加器用于hough变换,这是非常低效的。所以OpenCV使用了更有技巧的方法,霍夫梯度法,它利用了边缘的梯度信息。
我们这里使用的函数是 cv.HoughCircles()。它有很多参数,在文档中有很好的解释。
注意:通常函数能很好地检测圆心。但是,它可能无法找到正确的半径。如果您知道该函数,可以通过指定半径范围(minRadius和maxRadius)来辅助该函数。或者,在HOUGH_GRADIENT方法的情况下,您可以将maxRadius设置为负数,以只返回中心而不进行半径搜索,并使用额外的过程找到正确的半径。
目前 HOUGH_GRADIENT and HOUGH_GRADIENT_ALT.法可用
import numpy as np
import cv2 as cv
img = cv.imread('opencv-logo-white.png',0)
img = cv.medianBlur(img,5)
cimg = cv.cvtColor(img,cv.COLOR_GRAY2BGR)
circles = cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv.imshow('detected circles',cimg)
cv.waitKey(0)
cv.destroyAllWindows()