前言

通过这篇博客,你可以知道什么是霍夫变换,如何检测直线和圆,以及如何使用C++/Python和OpenCV实现图像的直线和圆检测。

但是,这篇博客并不是通俗易懂,至少我开始是没搞明白到底什么是霍夫变换,它是怎么检测直线的。所以,建议先看这篇博客,图文并茂,讲得很好。

什么是霍夫变换?

霍夫变换是用于检测图像中的简单形状(诸如圆形,线条等)的特征提取方法。

“简单”形状是指可以仅由几个参数表示的形状。例如,一条线可以用两个参数(斜率,截距)表示,一个圆有三个参数——圆心坐标和半径(x,y,r)。霍夫变换在图像中找到这样的形状方面做得很好。

使用霍夫变换的主要优点是它对遮挡不敏感

让我们通过一个例子来看看霍夫变换是如何工作的。

使用霍夫变换检测图像中的线条

opencv 霍夫直线 python python opencv霍夫变换检测圆_opencv 霍夫直线 python

极坐标中的直线方程

从高中数学课上我们知道一条直线的极坐标形式为:

opencv 霍夫直线 python python opencv霍夫变换检测圆_opencv 霍夫直线 python_02

这里,opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_03表示线与原点的垂直距离(以像素为单位),opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04是以弧度为单位测量的角度,该线与原点一起如上图所示。

您可能会想问我们为什么不使用下面给出的熟悉的等式:

opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_05

原因是斜率opencv 霍夫直线 python python opencv霍夫变换检测圆_opencv 霍夫直线 python_06可以取opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_07opencv 霍夫直线 python python opencv霍夫变换检测圆_opencv 霍夫直线 python_08之间的值。然而,对于Hough变换,参数需要有界限。

您可能还有一个后续问题。在opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_09形式上,opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04是有界的,但是opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_03不能取0到opencv 霍夫直线 python python opencv霍夫变换检测圆_opencv 霍夫直线 python_08之间的值?这在理论上可能是正确的,但在实践中,也是有限的,因为图像本身是有限的。

累加器

当我们说二维空间中的一条直线用opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_03opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04参数化时,意味着如果我们选择一个opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_09,它就对应一条直线。

想象一个二维数组,其中x轴具有所有可能的opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04值,y轴具有所有可能的opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_03值。该2D数组中的任何元素对应于一条直线。

opencv 霍夫直线 python python opencv霍夫变换检测圆_opencv 霍夫直线 python_18


这个2D数组称为累加器,因为我们将使用此数组的元素来收集有关图像中存在哪些直线的证据。左上角的单元格对应于opencv 霍夫直线 python python opencv霍夫变换检测圆_数组_19,右下角对应于opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_20

稍后我们将看到元素opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_09内的值将随着有关存在参数opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_03opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04表示的直线的更多证据的收集而增加。

执行以下步骤以检测图像中的线。

第1步:初始化累加器

首先,我们需要创建一个累加器数组。元素数量的选取是一个设计决策。假设选择10×10大小的累加器。这意味着opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_03只能取10个不同的值,并且opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04可以取10个不同的值,因此将能够检测100条不同的直线。累加器的大小也取决于图像的分辨率。但如果你刚刚开始,不要担心完全正确。选择一个20×20的累加器,看看你得到了什么结果。

第2步:检测边缘

现在我们已经设置了累加器,我们希望收集累加器的每个元素的证据,因为累加器的每个元素对应于一条直线。

我们如何收集证据?

这个想法是,如果图像中有可见的直线,边缘检测器应该作用于直线的边界处。这些边缘像素提供了直线存在的证据。

边缘检测的输出是边缘像素的数组:

opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_26

第3步:边缘像素投票

对于上述数组中的每个边缘像素opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_27,我们令opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04取从opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_29opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_30的值,并将其代入等式(1)中以获得opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_03

下图表示图像中三个边缘像素分别对应霍夫空间(即累加器)中的三条曲线。(原文写的不是很清楚,实在受不了了,这里是自己的理解)

如您所见,这些曲线相交于一点,该点表示的直线即边缘像素所在的直线。

opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_32

通常,图像中有数百个边缘像素,累加器用于查找边缘像素对应的所有曲线的交点。

让我们看看这是如何完成的。

假设我们的累加器尺寸为20×20。因此,opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_04存在20个不同的值,对于每个边缘像素opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_27,可以通过等式(1)来计算20对opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_09如果曲线经过累加器中的元素,那么这个元素的值就加1。

我们为每个边缘像素执行此操作,现在我们有一个累加器,其中包含有关图像中所有可能直线的所有证据。

我们可以简单地选择累加器中的特定阈值以上的区域来查找图像中的线条。如果阈值较高,您会发现较少的强线,如果较低,您会发现包含一些弱线的大量线。(个人理解,强线表示存在较多边缘像素的直线,弱线表示存在较少边缘像素的直线)

如何使用OpenCV检测直线

在OpenCV中,使用Hough变换的直线检测在函数HoughLinesHoughLinesP[Probabilistic Hough Transform]中实现。此函数采用以下参数:

  • edge:边缘检测器的输出。
  • lines:用于存储线段的起点和终点坐标的向量。
  • rho:参数opencv 霍夫直线 python python opencv霍夫变换检测圆_opencv 霍夫直线 python_36的分辨率,以像素为单位。
  • theta:参数opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_37的分辨率,以弧度为单位。
  • threshold:用于检测直线的最小交叉点的数量。

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

直线检测结果

下面我们展示了使用霍夫变换进行直线检测的结果。请记住,检测到的线条的质量在很大程度上取决于边缘图的质量。因此,在现实世界中,当您可以控制环境并因此获得一致的边缘图时,或者当您可以为您正在寻找的特定类型的边缘训练边缘检测器时,使用霍夫变换。

opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_38

使用OpenCV检测图像中的圆圈

在直线霍夫变换的情况下,我们需要两个参数,但是为了检测圆,我们需要三个参数:

  • 圆心的坐标opencv 霍夫直线 python python opencv霍夫变换检测圆_霍夫变换_39
  • 半径

可以想象,圆形探测器需要一个3D累加器,每个参数一个。

圆的方程由下式给出:

opencv 霍夫直线 python python opencv霍夫变换检测圆_边缘像素_40

按照以下步骤检测图像中的圆圈:

  1. 借助边缘检测器(Canny)找到给定图像中的边缘。
  2. 为了检测图像中的圆,我们设置半径的最大值和最小值的阈值。
  3. 证据收集在3D累加器数组中,以确定是否存在具有不同圆心和半径的圆。

在OpenCV中使用HoughCircles函数来检测图像中的圆圈。它需要以下参数:

  • image:输入图像。
  • method:检测方法。
  • dp:累加器分辨率和图像分辨率的反比。
  • mindst:检测到圆圈的中心之间的最小距离。
  • param_1和param_2:这些是特定于方法的参数。
  • min_Radius:要检测的圆的最小半径。
  • max_Radius:要检测的最大半径。

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检测功能,因此不需要在其中明确检测边缘。

圆检测结果

使用霍夫变换的圆检测结果如下所示。结果的质量在很大程度上取决于您可以找到的边缘质量,以及您对要检测的圆的大小有多少先验知识。

opencv 霍夫直线 python python opencv霍夫变换检测圆_数组_41


opencv 霍夫直线 python python opencv霍夫变换检测圆_数组_42