likes
comments
collection
share

OpenCV 26: 霍夫直线变换

作者站长头像
站长
· 阅读数 7

目标

在这一章当中,将学习

  • 了解霍夫变换的概念
  • 使用它来检测图像中的线条
  • 函数:cv2.HoughLines()cv2.HoughLinesP()

理论

如果可以用数学形式表示形状,则霍夫变换是检测任何形状的一种比较流行的技术。即使形状有些破损或变形,也可以检测出形状。本文将讲解如何将它何作用于一条线。

一条线可以表示为y=mx+cy = mx+cy=mx+c或以参数形式表示ρ=xcosθ+ysinθρ = xcosθ+ysinθρ=xcosθ+ysinθ $,其中ρ是从原点到该线的垂直距离,而θ是由该垂直线和水平轴形成的角度以逆时针方向测量(该方向随如何表示坐标系而变化。此表示形式在OpenCV中使用)。如下图所示:

OpenCV 26: 霍夫直线变换 OpenCV 26: 霍夫直线变换

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

现在,看一下霍夫变换如何处理线条。任何一条线都可以用(ρ,θ)这两个术语表示。因此,首先创建2D数组或累加器(以保存两个参数的值),并将其初始设置为0。让行表示 ρ,列表示θ。阵列的大小取决于所需的精度。假设希望角度的精度为1度,则需要180列。对于ρ最大距离可能是图像的对角线长度。因此,以一个像素精度为准,行数可以是图像的对角线长度。

考虑一个100x100的图像,中间有一条水平线。取直线的第一点。此时知道它的(x,y)值。现在在线性方程式中,将值θ= 0,1,2,… 180放进去,然后检查得到ρ。对于每对( ρ, θ ),在累加器中对应的(ρ,θ )单元格将值增加1。假设此点是(50,90),则该点的值加1,其它点依此类推。

现在,对行的第二个点。执行与上述相同的操作。递增( ρ , θ ) 对应的单元格中的值。这次,单元格(50,90)=2。实际上,正在对( ρ, θ )值进行投票。对线路上的每个点都继续执行此过程。在每个点上,单元格(50,90)都会增加或投票,而其他单元格可能会或可能不会投票。这样一来,**最后,单元格(50,90)的投票数将最高。**因此,如果在累加器中搜索最大票数,则将获得(50,90)值,该值表示该图像中的一条线与原点的距离为50,角度为90度OpenCV 26: 霍夫直线变换

这就是霍夫变换对线条的工作方式,原理很简单。输入的图片中有两条粗直线,经过霍夫变换后的结果得到accumaltor矩阵,右图就是把accumaltor矩阵画出来,越亮值越大,越黑值越小。在右图中,有两个很明显的亮点, 这两个亮点分别代表两条不同参数的直线,与输入的图片(左图)吻合。然后读取矩阵的两个最大值就可以得出这两条线距画面中心距离以及角度。

OpenCV 26: 霍夫直线变换

OpenCV中的霍夫曼变换

上面说明的所有内容都封装在OpenCV函数cv2.HoughLines()中。

lines=cv2.HoughLines(image,rho,theta,threshold) 返回的lines:表示的是一个具有两个或三个元素的数组,math:(rho,theta), 其中ρ 以像素为单位,θ以弧度为单位 第一个参数,输入图像应该是二进制图像,因此在应用霍夫变换之前,请应用阈值或使用Canny边缘检测 第二和第三参数分别是ρθ精度 第四个参数是阈值,这意味着应该将其视为行的最低投票。请记住,票数取决于线上的点数。因此,它表示应检测到的最小线长。

import cv2
import numpy as np

img = cv2.imread('sudo.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)

lines = cv2.HoughLines(edges, 1, np.pi/180, 100)  # 最后一个数是阈值控制,不同的值效果不一样
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))  # 以(x0, y0)为起点,将线段延长
    y1 = int(y0 + 1000*(a))
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*(a))
    cv2.line(img, (x1, y1), (x2, y2), (0,0,255), 2)
cv2.imshow('houglines', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV HoughLines 中,它是定义线条的端点,以便它们在绘制线条时到达(并经过)图像的侧面。您使用的霍夫变换仅返回线与原始线的角度和距离。所以额外的计算是从原点垂直于这条线找到一条线的交点,这样它就可以识别这条线上的某个点。但它不知道这条线应该有多长。所以它沿着这条线从那个点延伸了这条线。由于它知道直线的角度和直线上的一个点,它只提供两个端点到直线上给定点的距离。如果您的图像尺寸大于约 21000 像素,那么如果您希望线条到达图像的两侧,则可能需要增加 1000 值。减号 (-b) 出现如下: 从原点到垂直于直线的方向由它的斜率给出b/a = sin(theta)/cos(theta)=tan(theta)。请参阅opencv-python-tutroals.readthedocs.io/en/latest/p… 上的图表。但是线本身的方向与该方向成 90 度,其角度由 给出-1/tan(theta) = -cos(theta)/sin(theta) = -a/b or a/-b。即它的斜率是 (a/-b)=(y-yo)/(x-xo)=delY/delX。请参阅byjus.com/maths/slope… xo, yo 给定的任意点开始,然后沿线向任一方向移动,这样端点 X 分量为 xo +- 1000 delX = xo +- 1000 cos(perp_angle) = xo +- 1000(-b) 终点 Y 分量是 yo +- 1000 delY = yo +- 1000sin(perp_angle) = yo +- 1000*a。其中 perp_angle 是沿实际线的方向。

分享

检查下面的结果

OpenCV 26: 霍夫直线变换

概率霍夫变换

在霍夫变换中,可以看到,即使对于带有两个参数的行,也需要大量计算。概率霍夫变换是霍夫变换的优化。它没有考虑所有要点。取而代之的是,它仅采用随机的点子集,足以进行直线检测。只是必须降低阈值。

OpenCV的实现基于Matas,J.和Galambos,C.和Kittler, J.V.使用渐进概率霍夫变换对行进行的稳健检测 。使用的函数是cv2.HoughLinesP()。它有两个新的论点。

lines = cv.HoughLines( image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]] )

  • minLineLength - 最小行长。小于此长度的线段将被拒绝。如果有超过阈值个数的像素点构成了一条直线,但是这条直线很短,那么就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原图中并不存在这条直线。
  • maxLineGap - 线段之间允许将它们视为一条线的最大间隙。如果有超过阈值个数的像素点构成了一条直线,但是这组像素点之间的距离都很远,就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原始图像中并不存在这条直线。

最好的是,它直接返回行的两个端点。在以前的情况下,仅获得线的参数,并且必须找到所有点。在这里,一切都是直接而简单的。

参见下图,比较了霍夫空间中的霍夫变换和概率霍夫变换。

OpenCV 26: 霍夫直线变换

实现

# p 霍夫变换
import cv2
import numpy as np

img = cv2.imread('sudo.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength=100, maxLineGap=10)
for line in lines:
    x1, y1, x2, y2 = line[0]  # 这里直接返回的是坐标,不用变换
    cv2.line(img, (x1, y1), (x2, y2), (0, 255,0), 2)
cv2.imshow('houghlinep', img)
cv2.waitKey()
cv2.destroyAllWindows()

看到如下结果: OpenCV 26: 霍夫直线变换

可以看到在这张图上,cv2.HoughLines效果比cv2.HoughLinesP要好,但是不一定一直好,比如下面一张 OpenCV 26: 霍夫直线变换

# compare
# p 霍夫变换
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('building.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 200, minLineLength=100, maxLineGap=10)
for line in lines:
   x1, y1, x2, y2 = line[0]
   cv2.line(img, (x1, y1), (x2, y2), (0, 255,0), 2)

img1= cv2.imread('building.png')
gray1= cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
edges1 = cv2.Canny(gray1, 50, 150, apertureSize=3)
lines1 = cv2.HoughLines(edges1, 1, np.pi/180, 200)
for line in lines1:
   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))
   cv2.line(img1, (x1, y1), (x2, y2), (0,0,255), 2)

plt.subplot(121)
plt.imshow(img1_rgb)
plt.title('HoughLines')
plt.xticks([])
plt.yticks([])

img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img1_rgb = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
plt.subplot(122)
plt.imshow(img_rgb)
plt.title('HoughLinesP')
plt.xticks([])
plt.yticks([])

plt.show()

OpenCV 26: 霍夫直线变换

实际上,主要是HoughLines是一条直线而非线段,而那些小树叶又容易被误检测成图片,所以才会画的面目。

附加资源

转载自:https://juejin.cn/post/7205875448575049784
评论
请登录