赞
踩
首先,这是一篇英文博客的翻译,原地址:https://www.learnopencv.com/hough-transform-with-opencv-c-python/ 。
通过这篇博客,你可以知道什么是霍夫变换,如何检测直线和圆,以及如何使用C++/Python和OpenCV实现图像的直线和圆检测。
但是,这篇博客并不是通俗易懂,至少我开始是没搞明白到底什么是霍夫变换,它是怎么检测直线的。所以,建议先看这篇博客,图文并茂,讲得很好。
霍夫变换是用于检测图像中的简单形状(诸如圆形,线条等)的特征提取方法。
“简单”形状是指可以仅由几个参数表示的形状。例如,一条线可以用两个参数(斜率,截距)表示,一个圆有三个参数——圆心坐标和半径(x,y,r)。霍夫变换在图像中找到这样的形状方面做得很好。
使用霍夫变换的主要优点是它对遮挡不敏感。
让我们通过一个例子来看看霍夫变换是如何工作的。
从高中数学课上我们知道一条直线的极坐标形式为:
(1) ρ = x c o s ( θ ) + y s i n ( θ ) ρ=xcos(θ)+ysin(θ) \tag1 ρ=xcos(θ)+ysin(θ)(1)
这里, ρ ρ ρ表示线与原点的垂直距离(以像素为单位), θ θ θ是以弧度为单位测量的角度,该线与原点一起如上图所示。
您可能会想问我们为什么不使用下面给出的熟悉的等式:
y = m x + c y=mx+c y=mx+c
原因是斜率 m m m可以取 − ∞ -∞ −∞到 + ∞ +∞ +∞之间的值。然而,对于Hough变换,参数需要有界限。
您可能还有一个后续问题。在 ( ρ , θ ) (ρ,θ) (ρ,θ)形式上, θ θ θ是有界的,但是 ρ ρ ρ不能取0到 + ∞ +∞ +∞之间的值?这在理论上可能是正确的,但在实践中,也是有限的,因为图像本身是有限的。
当我们说二维空间中的一条直线用 ρ ρ ρ和 θ θ θ参数化时,意味着如果我们选择一个 ( ρ , θ ) (ρ,θ) (ρ,θ),它就对应一条直线。
想象一个二维数组,其中x轴具有所有可能的 θ θ θ值,y轴具有所有可能的 ρ ρ ρ值。该2D数组中的任何元素对应于一条直线。
这个2D数组称为累加器,因为我们将使用此数组的元素来收集有关图像中存在哪些直线的证据。左上角的单元格对应于
(
−
R
,
0
)
(-R,0)
(−R,0),右下角对应于
(
R
,
π
)
(R,π)
(R,π)。
稍后我们将看到元素 ( ρ , θ ) (ρ,θ) (ρ,θ)内的值将随着有关存在参数 ρ ρ ρ和 θ θ θ表示的直线的更多证据的收集而增加。
执行以下步骤以检测图像中的线。
首先,我们需要创建一个累加器数组。元素数量的选取是一个设计决策。假设选择10×10大小的累加器。这意味着 ρ ρ ρ只能取10个不同的值,并且 θ θ θ可以取10个不同的值,因此将能够检测100条不同的直线。累加器的大小也取决于图像的分辨率。但如果你刚刚开始,不要担心完全正确。选择一个20×20的累加器,看看你得到了什么结果。
现在我们已经设置了累加器,我们希望收集累加器的每个元素的证据,因为累加器的每个元素对应于一条直线。
我们如何收集证据?
这个想法是,如果图像中有可见的直线,边缘检测器应该作用于直线的边界处。这些边缘像素提供了直线存在的证据。
边缘检测的输出是边缘像素的数组:
[ ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x n , y n ) ] [(x_1,y_1),(x_2,y_2),...,(x_n,y_n)] [(x1,y1),(x2,y2),...,(xn,yn)]
对于上述数组中的每个边缘像素 ( x , y ) (x,y) (x,y),我们令 θ θ θ取从 0 0 0到 π π π的值,并将其代入等式(1)中以获得 ρ ρ ρ。
下图表示图像中三个边缘像素分别对应霍夫空间(即累加器)中的三条曲线。(原文写的不是很清楚,实在受不了了,这里是自己的理解)
如您所见,这些曲线相交于一点,该点表示的直线即边缘像素所在的直线。
通常,图像中有数百个边缘像素,累加器用于查找边缘像素对应的所有曲线的交点。
让我们看看这是如何完成的。
假设我们的累加器尺寸为20×20。因此, θ θ θ存在20个不同的值,对于每个边缘像素 ( x , y ) (x,y) (x,y),可以通过等式(1)来计算20对 ( ρ , θ ) (ρ,θ) (ρ,θ)。如果曲线经过累加器中的元素,那么这个元素的值就加1。
我们为每个边缘像素执行此操作,现在我们有一个累加器,其中包含有关图像中所有可能直线的所有证据。
我们可以简单地选择累加器中的特定阈值以上的区域来查找图像中的线条。如果阈值较高,您会发现较少的强线,如果较低,您会发现包含一些弱线的大量线。(个人理解,强线表示存在较多边缘像素的直线,弱线表示存在较少边缘像素的直线)
在OpenCV中,使用Hough变换的直线检测在函数HoughLines和HoughLinesP[Probabilistic Hough Transform]中实现。此函数采用以下参数:
Python:
# Read image
img = cv2.imread('lanes.jpg', cv2.IMREAD_COLOR) # road.png is the filename
# 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)
C++:
// Read the image as gray-scale Mat img = imread('lanes.jpg', IMREAD_COLOR); // Convert to gray-scale Mat gray = cvtColor(img, COLOR_BGR2GRAY); // Store the edges Mat edges; // Find the edges in the image using canny detector Canny(gray, edges, 50, 200); // Create a vector to store lines of the image vector<Vec4i> lines; // Apply Hough Transform HoughLinesP(edges, lines, 1, CV_PI/180, thresh, 10, 250); // Draw lines on the image for (size_t i=0; i<lines.size(); i++) { Vec4i l = lines[i]; line(src, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(255, 0, 0), 3, LINE_AA); } // Show result image imshow("Result Image", img);
下面我们展示了使用霍夫变换进行直线检测的结果。请记住,检测到的线条的质量在很大程度上取决于边缘图的质量。因此,在现实世界中,当您可以控制环境并因此获得一致的边缘图时,或者当您可以为您正在寻找的特定类型的边缘训练边缘检测器时,使用霍夫变换。
在直线霍夫变换的情况下,我们需要两个参数,但是为了检测圆,我们需要三个参数:
可以想象,圆形探测器需要一个3D累加器,每个参数一个。
圆的方程由下式给出:
( x − x 0 ) 2 + ( y − y 0 ) 2 = r 2 (x-x_0)^2+(y-y_0)^2=r^2 (x−x0)2+(y−y0)2=r2
按照以下步骤检测图像中的圆圈:
在OpenCV中使用HoughCircles函数来检测图像中的圆圈。它需要以下参数:
Python:
# 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)
C++:
// Read the image as gray-scale img = imread("circles.png", IMREAD_COLOR); // Convert to gray-scale gray = cvtColor(img, COLOR_BGR2GRAY); // Blur the image to reduce noise Mat img_blur; medianBlur(gray, img_blur, 5); // Create a vector for detected circles vector<Vec3f> circles; // Apply Hough Transform HoughCircles(img_blur, circles, HOUGH_GRADIENT, 1, img.rows/64, 200, 10, 5, 30); // Draw detected circles for(size_t i=0; i<circles.size(); i++) { Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); int radius = cvRound(circles[i][2]); circle(img, center, radius, Scalar(255, 255, 255), 2, 8, 0); }
HoughCircles函数具有内置的canny检测功能,因此不需要在其中明确检测边缘。
使用霍夫变换的圆检测结果如下所示。结果的质量在很大程度上取决于您可以找到的边缘质量,以及您对要检测的圆的大小有多少先验知识。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。