如何用OpenCV和Python理解和实现使用Hough变换的形状检测

627 阅读5分钟

用OpenCV和Python理解和实现使用Hough变换的形状检测

今天我们将学习如何借助一种叫做Hough变换的技术来检测图像中的线条和圆圈。

什么是Hough空间?

在我们开始对图像应用Hough变换之前,我们需要了解什么是Hough空间,我们将通过一个例子来学习。

参数空间

当我们处理图像时,我们可以把图像想象成一个在一些x和y坐标上的2维矩阵,在这个矩阵下,一条线可以被描述为y = mx + b

image.png

参数空间

但在参数空间中,我们将称之为Hough空间,我可以将同一条线表示为m ,而不是b ,因此,图像空间上的线的特征,将是Hough空间中位置m-b 的一个点。

image.png

霍夫空间

但是我们有一个问题,用y = mx + b ,我们不能表示一条垂直线,因为斜率是无限的。所以我们需要一个更好的方式来进行参数化,即极坐标(rho和theta)。

霍夫空间

  • rho:描述直线离原点的距离
  • theta:描述远离水平面的角度

image.png

线路极坐标

一个非常重要的观察是,当我们在一条线上取多个点,并将其转化为我们的Hough空间时会发生什么。

image.png

Hough空间中的点和线的关系

图像空间中的一个点可以转化为Hough空间中的一条曲线,其特点是图像空间中的线上的点将被代表为有一个接触点的多条曲线。

而这将是我们的目标,即找到一组曲线的相交点。

什么是Hough变换?

Hough变换是一种特征提取方法,用于检测图像中的简单形状,如圆形、直线等。

简单 "的特征是由参数方面的形状表示得出的。一个 "简单 "的形状只能用几个参数来表示,例如,一条直线可以用它的斜率和截距来表示,或者一个圆可以用x、y和半径来表示。

在我们的直线例子中,Hough变换将负责处理图像上的点并计算Hough空间中的值。

实现变换并随后找到相交曲线的算法有点复杂,因此不在本帖的讨论范围内。然而,我们将看一下这个算法的实现,它是OpenCV库的一部分。

使用OpenCV检测线条

在OpenCV中,使用Hough变换的线条检测是通过函数HoughLinesHoughLinesP (Probabilistic Hough Transform) 实现的。我们将重点讨论后者。

该函数希望有以下参数。

  • image: 8位,单通道的二进制源图像。该图像可以被该函数修改。
  • lines:输出的线条向量。每条线由一个4元素的向量(x_1, y_1, x_2, y_2)表示,其中(x_1,y_1)和(x_2, y_2)是每个检测到的线段的结束点。
  • rho:累积器的距离分辨率,单位为像素。
  • theta:累积器的角度分辨率,单位为弧度。
  • threshold:累积器阈值参数。只有那些得到足够票数的线条才会被返回
  • minLineLength:最小线段长度。短于该长度的线段被拒绝。
  • maxLineGap:同一条线上的各点之间允许的最大间隙,以便将它们连接起来。

太复杂了吗?用一个例子来说明会更容易。

# Read image 
img = cv2.imread('lanes.jpg', cv2.IMREAD_COLOR)
# Convert the image to gray-scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the edges in the image using canny detector
edges = cv2.Canny(gray, 50, 200)
# Detect points that form a line
lines = cv2.HoughLinesP(edges, 1, np.pi/180, max_slider, minLineLength=10, maxLineGap=250)
# Draw lines on the image
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0), 3)
# Show result
cv2.imshow("Result Image", img)

这里是结果。

image.png

线条检测实例

非常重要的是,我们实际上是用一个只有边缘的图像作为Hough变换的参数,否则算法就不能如期工作。

使用OpenCV检测圆圈

这个过程与线条检测的过程差不多,不同的是这次我们将使用OpenCV库中的一个不同的函数。我们现在将使用HoughCircles ,它接受以下参数。

  • image:8位,单通道,灰度输入图像。
  • circles:找到的圆的输出向量。每个向量被编码为一个3元素的浮点向量(x,y,半径)。
  • circle_storage:在C函数中,这是一个内存存储,将包含发现的圆的输出序列。
  • method:要使用的检测方法。目前,唯一实现的方法是CV_HOUGH_GRADIENT,它基本上是21HT
  • dp:累加器分辨率与图像分辨率的反比。例如,如果 dp=1 ,累加器的分辨率与输入图像相同。如果dp=2,累加器的宽度和高度都是一半。
  • minDist:检测到的圆的中心之间的最小距离。如果该参数太小,除了一个真正的圆之外,可能会错误地检测到多个邻居的圆。如果它过大,可能会遗漏一些圆圈。
  • param1:第一个方法特定参数。在CV_HOUGH_GRADIENT的情况下,它是传递给Canny()边缘检测器的两个阈值中较高的一个(较低的阈值要小两倍)。
  • param2:第二个方法专用参数。在CV_HOUGH_GRADIENT的情况下,它是检测阶段的圆心累积阈值。它越小,可能检测到更多的错误圆圈。对应于较大的累加器值的圆将被首先返回。
  • minRadius:最小圆半径。
  • maxRadius:最大圆半径。

记住参数需要不同,因为我们不能用我们用于线的参数化来描述一个圆,相反,我们需要使用一个类似于(x - x0)^^2 + (y - y0)^^2 = r^^2 的方程式。

再来看看代码。

# Read image as gray-scale
img = cv2.imread('circles.png', cv2.IMREAD_COLOR)
# Convert to gray-scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Blur the image to reduce noise
img_blur = cv2.medianBlur(gray, 5)
# Apply hough transform on the image
circles = cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, 1, img.shape[0]/64, param1=200, param2=10, minRadius=5, maxRadius=30)
# Draw detected circles
if circles is not None:
    circles = np.uint16(np.around(circles))
    for i in circles[0, :]:
        # Draw outer circle
        cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)
        # Draw inner circle
        cv2.circle(img, (i[0], i[1]), 2, (0, 0, 255), 3)

请注意,与之前的例子相比,我们在这里没有应用任何边缘检测函数。这是因为函数HoughCircles 有内置的canny检测。

以及结果。

image.png

圆形检测实例

总结

Hough变换是一种检测图像中简单形状的优秀技术,有多种应用,从X射线、CT和MRI分析等医疗应用,到自动驾驶汽车。