前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenCV:霍夫直线变换和霍夫圆变换

OpenCV:霍夫直线变换和霍夫圆变换

作者头像
用户3578099
发布2023-09-01 09:19:17
3510
发布2023-09-01 09:19:17
举报
文章被收录于专栏:AI科技时讯AI科技时讯

目标1

在这一章当中,将学习

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

理论

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

一条线可以表示为

y = mx+c

或以参数形式表示

ρ = xcosθ+ysinθ

$,其中ρ是从原点到该线的垂直距离,而θ是由该垂直线和水平轴形成的角度以逆时针方向测量(该方向随如何表示坐标系而变化。此表示形式在OpenCV中使用)。如下图所示:

因此,如果线在原点下方通过,则它将具有正的ρ 且角度小于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度

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

OpenCV中的霍夫曼变换

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

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

代码语言:javascript
复制
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)。请参阅https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html 上的图表。但是线本身的方向与该方向成 90 度,其角度由 给出-1/tan(theta) = -cos(theta)/sin(theta) = -a/b or a/-b。即它的斜率是 (a/-b)=(y-yo)/(x-xo)=delY/delX。请参阅https://byjus.com/maths/slope-of-line/。因此,要获得两个端点,您可以从 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的实现基于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 - 线段之间允许将它们视为一条线的最大间隙。如果有超过阈值个数的像素点构成了一条直线,但是这组像素点之间的距离都很远,就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原始图像中并不存在这条直线。

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

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

实现

代码语言:javascript
复制
# 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()

看到如下结果:

可以看到在这张图上,cv2.HoughLines效果比cv2.HoughLinesP要好,但是不一定一直好,比如下面一张

代码语言:javascript
复制
# 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()

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

附加资源

  • https://docs.opencv.org/4.1.2/d6/d10/tutorial_py_houghlines.html
  • Hough Transform on Wikipedia
  • https://docs.opencv.org/4.1.2/dd/d1a/group__imgproc__feature.html#ga46b4e588934f6c8dfd509cc6e0e4545a
  • https://docs.opencv.org/4.1.2/dd/d1a/group__imgproc__feature.html#ga8618180a5948286384e3b7ca02f6feeb
  • https://blog.csdn.net/ftimes/article/details/106816736
  • https://stackoverflow.com/questions/62585845/why-add-and-subtract-1000-for-hough-line-transformation
  • https://www.cnblogs.com/kk17/p/9693132.html

学习目标2

在本章中,将学习

  • 使用霍夫变换来查找图像中的圆
  • 函数:cv2.HoughCircles()

理论

圆在数学上表示为

( x − x_{center}) ^2 + ( y − y_{center})^ 2 = r^ 2

,其中

(x_{center}, y_{center})

是圆的中心,r是圆的半径。从等式中,可以看到我们有3个参数,因此需要3D累加器进行霍夫变换,这是非常低效的。因此,OpenCV使用更加技巧性的方法,即使用边缘的梯度信息的Hough梯度方法

OpenCv中实现的函数是cv2.HoughCircles()。它有很多参数。

circles = cv.HoughCircles( image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]] )

  • image: 8-bit灰度输入图
  • circles:输出找到的圆. 3 or 4 element floating-point vector (x,y,radius) or (x,y,radius,votes) .
  • method:检测方法 HOUGH_GRADIENT
  • dp:检测内侧圆心的累加器图像的分辨率于输入图像之比的倒数, 如dp=1,累加器和输入图像具有相同的分辨率,如果dp=2,累计器便有输入图像一半那么大的宽度和高度.
  • minDist: 表示两个圆之间圆心的最小距离.
  • param1:默认值100,它是method设置的检测方法的对应的参数,对当前唯一的方法霍夫梯度法cv2.HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半
  • param2:默认值100,它是method设置的检测方法的对应的参数,对当前唯一的方法霍夫梯度法cv2.HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值,它越小,就越可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了
  • minRadius:默认值0,圆半径的最小值
  • maxRadius:默认值0,圆半径的最大值

例子:

代码语言:javascript
复制
import cv2
import numpy as np

img = cv2.imread('origin.png', 0)
img = cv2.medianBlur(img, 5)
cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 10, param1=50, param2=30, minRadius=0, maxRadius=0)
# param2越小,能找到的圆更多,

circles = np.uint16(np.around(circles))
for i in circles[0,:]:
    # draw the outer circle
    cv2.circle(cimg, (i[0],i[1]), i[2], (0,255,0), 2)
    # draw the center of the circle
    cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)

cv2.imshow('circle', cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果如下:

附加资源

  • https://docs.opencv.org/4.1.2/da/d53/tutorial_py_houghcircles.html
  • https://docs.opencv.org/4.1.2/dd/d1a/group__imgproc__feature.html#ga47849c3be0d0406ad3ca45db65a25d2d
  • https://www.cnblogs.com/jsxyhelu/p/13191015.html
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-05-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI科技时讯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目标1
  • 理论
  • OpenCV中的霍夫曼变换
  • 概率霍夫变换
  • 附加资源
  • 学习目标2
  • 理论
  • 附加资源
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档