OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换

一、Hough 直线变换(Hough Line Transform

目标
• 理解霍夫变换的概念
• 学习如何在一张图片中检测直线
• 学习函数: cv2.HoughLines(), cv2.HoughLinesP()

原理
霍夫变换在检测各种形状的的技术中非常流行,如果你要检测的形状可以用数学表达式写出,你就可以是使用霍夫变换检测它,即使检测的形状存在一点破坏或者扭曲也可以使用。我们下面就看看如何使用霍夫变换检测直线。 

一条直线可以用数学表达式 y = mx+c 或者 \rho = x \cos \theta + y \sin \theta 表示。ρ 是从原点到直线的垂直距离, θ 是直线的垂线与横轴顺时针方向的夹角(如果你使用的坐标系不同方向也可能不同,这里是按 OpenCV 使用的坐标系描述的)。如下图所示: 

OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换_第1张图片

所以如果一条线在原点下方经过, ρ 的值就应该大于 0,角度小于 180。但是如果从原点上方经过的话,角度不是大于 180,而是小于 180,但 ρ 的值小于 0。垂直线的角度为 0 度,水平线的角度为 90 度。

让我们来看看霍夫变换是如何工作的。每一条直线都可以用 (ρ, θ) 表示。所以首先创建一个 2D 数组(累加器),初始化累加器,所有的值都为 0。行表示 ρ,列表示 θ。这个数组的大小决定了最后结果的准确性。如果你希望角度精确到 1 度,你就需要 180 列。对于 ρ,最大值为图片对角线的距离。所以如果精确度要达到一个像素的级别,行数就应该与图像对角线的距离相等。 

想象一下我们有一个大小为 100x100 的直线位于图像的中央。取直线上的第一个点,我们知道此处的(x, y)值。把 x 和 y 带入上边的方程组,然后遍历 θ 的取值: 0, 1, 2, 3, ..., 180。分别求出与其对应的 ρ 的值,这样我们就得到一系列(ρ,θ)的数值对,如果这个数值对在累加器中也存在相应的位置,就在这个位置上加 1。所以现在累加器中的(50, 90)=1。(一个点可能存在于多条直线中,所以对于直线上的每一个点可能是累加器中的多个值同时加 1)

现在取直线上的第二个点。重复上边的过程。更新累加器中的值。现在累加器中(50,90)的值为 2。你每次做的就是更新累加器中的值。对直线上的每个点都执行上边的操作,每次操作完成之后,累加器中的值就加 1,但其他地方有时会加 1, 有时不会。按照这种方式下去,到最后累加器中(50,90)的值肯定是最大的。如果你搜索累加器中的最大值,并找到其位置(50,90),这就说明图像中有一条直线,这条直线到原点的距离为 50,它的垂线与横轴的夹角为 90 度。下面的动画很好的演示了这个过程(Image Courtesy: Amos Storkey )。

OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换_第2张图片

这就是霍夫直线变换工作的方式。很简单,也许你自己就可以使用 Numpy搞定它。下图显示了一个累加器。其中最亮的两个点代表了图像中两条直线的参数。(Image courtesy: Wikipedia)。

OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换_第3张图片

1.1 OpenCV 中的霍夫变换

上面介绍的整个过程在 OpenCV 中都被封装进了一个函数:cv2.HoughLines()。返回值就是一个数组(ρ, θ)。 ρ 的单位是像素, θ 的单位是弧度。这个函数的第一个参数是一个二值化图像,所以在进行霍夫变换之前要首先进行二值化,或者进行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)

结果:

OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换_第4张图片

1.2 Probabilistic Hough Transform

从上边的过程我们可以发现:仅仅是一条直线都需要两个参数,这需要大量的计算。 Probabilistic_Hough_Transform 是对霍夫变换的一种优化。它不会对每一个点都进行计算,而是从一幅图像中随机选取一个点集进行计算,对于直线检测来说这已经足够了。但是使用这种变换我们必须要降低阈值(总的点数都少了,阈值肯定也要小呀!)。下图是对两种方法的对比。(Image Courtesy : Franck Bettinger’s homepage)

OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换_第5张图片

OpenCV 中使用由 Matas, J. , Galambos, C. 和 Kittler, J.V. 提出的Progressive Probabilistic Hough Transform。这个函数是 cv2.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)

 

OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换_第6张图片

 

二、Hough圆形变换(Hough Circle Transform)

目标
• 学习使用霍夫变换在图像中找圆形(环)。
• 学习函数: cv2.HoughCircles()


原理
圆形的数学表达式为 (x-x_{center})^2 + (y - y_{center})^2 = r^2,其中 (x_{center},y_{center}) 为圆心的坐标, r 为圆的半径。从这个方程中我们可以看出:一个圆环需要 3个参数来确定。所以进行圆形霍夫变换的累加器必须是 3 维的,这样的话效率就会很低。OpenCV 用了一个比较巧妙的办法,霍夫梯度法,它可以使用边界的梯度信息。我们要使用的函数为 cv2.HoughCircles()。文档中对它的参数有详细的解释。这里我们就直接看代码吧。 

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()

 结果:

OpenCV-Python学习笔记(十六):Hough直线变换、Hough圆变换_第7张图片

 

参考资料:

1. OpenCV-Python官方文档-Hough Line Transform

2. OpenCV-Python官方文档-Hough Circle Transform

3.《OpenCV-Python 中文教程》(段力辉 译)

 

 

你可能感兴趣的:(机器视觉,OpenCV)