OpenCV  4.10.0
开源计算机视觉
加载中...
搜索中...
未找到匹配项
霍夫线变换

目标

在本教程中,

  • 我们将了解霍夫变换的概念。
  • 我们将了解如何使用它来检测图像中的线。
  • 我们将了解以下函数:cv.HoughLines()cv.HoughLinesP()

理论

如果可以用数学形式表示某种形状,霍夫变换是一种流行的检测任意形状的技术。即使形状破损或轻微变形,它也可以检测出该形状。我们来看看它如何对线起作用。

一条线可以表示为\(y = mx+c\),或者以参数形式表示为\(\rho = x \cos \theta + y \sin \theta\),其中\(\rho\)是从原点到线的垂直距离,\(\theta\)是由该垂直线与水平轴形成的角度,并逆时针测量(该方向取决于坐标系的表示方式。OpenCV 中使用此表示法)。查看下图

图片

因此,如果该线经过原点下方,它将具有一个正的 rho 和一个小于 180 的角度。如果它在原点上方,则角度小于 180,而不是取大于 180 的角度,并且 rho 取负值。任何垂直线将有 0 度,而水平线将有 90 度。

现在让我们看看霍夫变换如何对线起作用。任何一行都可以用这两个术语\(((\rho, \theta))\)表示。因此,它首先创建一个 2D 数组或累加器(用于保存两个参数的值),并且最初将其设置为 0。让行表示\(\rho\),列表示\(\theta\)。数组的大小取决于所需的精度。假设您希望角度的精度为 1 度,您将需要 180 列。对于 \(\rho\),最大可能距离是图像的对角线长度。因此,以一个像素的精度,行的数量可以是图像的对角线长度。

考虑一张 100x100 的图像,其中水平线位于中间。取直线上的第一个点。你已知其 (x,y) 值。现在,在直线方程中输入值 \(\theta = 0,1,2,....,180\) 并检查你得到的 \(\rho\) 值。对于每组 \((\rho, \theta)\) ,在其对应的 \((\rho, \theta)\) 单元格中将累加器的值增加 1。所以现在,在累加器单元中,单元格 (50,90) = 1,还有一些其他单元格。

现在,取直线上的第二个点。如上所述进行同样的操作。增加所得到的 (rho, theta) 对应单元中的值。此时,单元格 (50,90) = 2。实际上的操作是投票 \((\rho, \theta)\) 值。对直线上的每个点继续此过程。在每个点,单元格 (50,90) 将被增加或投票加一,而其他单元格可能不会被投票加一。这样,最后,单元格 (50,90) 将获得最高票数。所以,如果你在累加器中查找最高票数,你将得到值 (50,90),它表示此图像中存在一条距离原点 50 且角度为 90 度的直线。下方的动画对此进行了很好的说明(图像致谢:Amos Storkey

这是霍夫变换对直线进行工作的方式。它很简单,你也许可以通过自己使用 NumPy 来实现它。下方是显示累加器的图像。某些位置的亮斑表明它们是图像中可能直线的参数。(图像致谢:维基百科

OpenCV 中的霍夫变换

上述所有解释都封装在 OpenCV 函数 cv.HoughLines() 中。它简单地返回一个 :math:(rho, theta)` 值的数组。\(\rho\) 以像素为单位测量,\(\theta\) 以弧度为单位测量。第一个参数,输入图像应该是二进制图像,因此在应用霍夫变换之前应用阈值或使用 Canny 边缘检测。第二个和第三个参数分别是 \(\rho\) 和 \(\theta\) 精度。第四个参数是阈值,这意味着它是作为直线考虑时应该获得的最低票数。记住,票数取决于直线上的点数。所以它表示应该检测到的最小直线长度。

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)
cv::String findFile(const cv::String &relative_path, bool required=true, bool silentMode=false)
尝试查找请求的数据文件。
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
将图像保存到指定的文件。
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件中加载图像。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
将图像从一个颜色空间转换到另一个颜色空间。
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
绘制连接两个点的线段。
void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false)
使用 Canny 算法在图像中查找边缘。
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0, double min_theta=0, double max_theta=CV_PI)
使用标准霍夫变换在二进制图像中查找线。

查看以下结果

图片

概率霍夫变换

在霍夫变换中,你可以看到即使对于包含两个参数的线,它的计算量也非常大。概率霍夫变换是对我们所看到的霍夫变换的优化。它不考虑所有点。相反,它只考虑随机子集的点,对于线检测来说,这已经足够了。我们只需降低阈值。请看下图,它比较了霍夫变换与概率霍夫变换在霍夫空间中的差异。(图片由:Franck Bettinger's home page 提供)

图片

OpenCV 实现基于 Matas、J. 和 Galambos、C. 和 Kittler、J.V. 撰写的使用渐进概率霍夫变换鲁棒检测线。 [185]。使用的函数是 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)
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0)
使用概率霍夫变换在二进制图像中查找线段。

查看下面的结果

图片

额外的资源

  1. 维 Wikipedia 上的霍夫变换

练习