霍夫变换算法线检测

一、目的

最近,我们发现自己不得不在应用程序中加入文档扫描功能。在做了一些研究之后,我们偶然发现了一篇熊英写的文章,他是Dropbox机器学习团队的成员。该文章介绍了如何Dropbox的的机器学习团队通过强调他们通过去的步骤,并在每个步骤使用的算法来实现他们的文档扫描仪。通过那篇文章,我们了解了一种称为霍夫变换的方法, 以及如何将其用于检测图像中的线条。因此,在本文中,我们想解释Hough变换算法,并提供该算法在Python中的“从头开始”的实现。

二、霍夫变换

Hough变换是Paul VC Hough专利的一种算法,最初是为了识别照片中的复杂线条而发明的(Hough,1962)。自从创建以来,该算法已进行了修改和增强,使其能够识别其他形状,例如特定类型的圆形和四边形。为了了解霍夫变换算法的工作原理,重要的是要了解四个概念:边缘图像,霍夫空间以及边缘点到霍夫空间的映射,表示线的替代方法以及如何检测线。

边缘图像

坎尼边缘检测算法

边缘图像是边缘检测算法的输出。边缘检测算法通过确定图像的亮度/强度急剧变化的位置来检测图像中的边缘(“边缘检测-使用Python进行图像处理”,2020年)。边缘检测算法的示例包括:Canny,Sobel,Laplacian等。对边缘图像进行二值化是很常见的,意味着其所有像素值均为1或0。根据你们的情况,为1或0可以表示边缘像素。

霍夫空间和边缘点到霍夫空间的映射

霍夫空间是2D平面,其水平轴表示坡度,而垂直轴表示边缘图像上直线的截距。边缘图像上的一条线以y = ax + b的形式表示(Hough,1962年)。边缘图像上的一条线在霍夫空间上产生一个点,因为一条线的特征在于其斜率a和截距b。另一方面,边缘图像上的边缘点(xᵢ,yᵢ)可以有无数的线通过。因此,边缘点在Hough空间中以b =axᵢ+yᵢ的形式生成一条线(Leavers,1992)。在霍夫变换算法中,霍夫空间用于确定边缘图像中是否存在线条。

表示线的另一种方法

用y = ax + b形式的直线 和带有斜率和截距的霍夫空间代表着一种缺陷。在这种形式下,该算法将无法检测垂直线,因为斜率a对于垂直线是不确定的/无穷大(Leavers,1992)。编程,这意味着,一个计算机将需要的存储器的无限量来表示的所有可能的值一个。为避免此问题,一条直线由一条称为法线的线表示,该线穿过原点并垂直于该直线。法线的形式为ρ = x cos( θ )+ y sin( θ ),其中ρ 是法线的长度,θ是法线与x轴之间的角度。

使用此方法,不再用坡度a和截距b表示霍夫空间,而是用ρ和θ表示,其中水平轴表示θ值,垂直轴表示ρ值。边缘点到霍夫空间的映射以类似的方式工作,除了边缘点(x,y)现在在霍夫空间中生成余弦曲线,而不是直线(Leavers,1992)。线的这种正常表示消除了在处理垂直线时出现的a的无限值的问题。

线检测

如前所述,边缘点在霍夫空间中产生余弦曲线。由此,如果我们将边缘图像中的所有边缘点映射到霍夫空间上,它将生成许多余弦曲线。如果两个边缘点位于同一条线上,则它们对应的余弦曲线将在特定的(ρ,θ)对上彼此相交。因此,霍夫变换算法通过找到交叉点数量大于某个阈值的(ρ,θ)对来检测线。值得注意的是,如果不对霍夫空间进行邻域抑制等预处理以去除边缘图像中的相似线条,这种阈值化方法可能不会总是产生最佳结果。

三、算法

  1. 确定ρ和θ的范围。通常,θ的范围是[0,180]度,而ρ是[ -d,d ],其中d是边缘图像对角线的长度。量化ρ和θ的范围很重要,这意味着应该有数量有限的可能值。

  2. 创建一个称为累加器的二维数组,该数组表示维度为(num_rhos,num_thetas)的霍夫空间,并将其所有值初始化为零。

  3. 对原始图像执行边缘检测。可以使用你们选择的任何边缘检测算法来完成。

  4. 对于边缘图像上的每个像素,请检查该像素是否为边缘像素。如果是边缘像素,则循环遍历所有可能的θ值,计算对应的ρ,在累加器中找到θ和ρ索引,并基于这些索引对递增累加器。

  5. 循环遍历累加器中的所有值。如果该值大于某个阈值,则获取ρ和θ索引,从索引对获取ρ和θ的值,然后可以将其转换回y = ax + b的形式。

四、代码

非向量化解决方案

import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.lines as mlinesdef line_detection_non_vectorized(image, edge_image, num_rhos=180, num_thetas=180, t_count=220):edge_height, edge_width = edge_image.shape[:2]edge_height_half, edge_width_half = edge_height / 2, edge_width / 2#d = np.sqrt(np.square(edge_height) + np.square(edge_width))dtheta = 180 / num_thetasdrho = (2 * d) / num_rhos#thetas = np.arange(0, 180, step=dtheta)rhos = np.arange(-d, d, step=drho)#cos_thetas = np.cos(np.deg2rad(thetas))sin_thetas = np.sin(np.deg2rad(thetas))#accumulator = np.zeros((len(rhos), len(rhos)))#figure = plt.figure(figsize=(12, 12))subplot1 = figure.add_subplot(1, 4, 1)subplot1.imshow(image)subplot2 = figure.add_subplot(1, 4, 2)subplot2.imshow(edge_image, cmap="gray")subplot3 = figure.add_subplot(1, 4, 3)subplot3.set_facecolor((0, 0, 0))subplot4 = figure.add_subplot(1, 4, 4)subplot4.imshow(image)#for y in range(edge_height):for x in range(edge_width):if edge_image[y][x] != 0:edge_point = [y - edge_height_half, x - edge_width_half]ys, xs = [], []for theta_idx in range(len(thetas)):rho = (edge_point[1] * cos_thetas[theta_idx]) + (edge_point[0] * sin_thetas[theta_idx])theta = thetas[theta_idx]rho_idx = np.argmin(np.abs(rhos - rho))accumulator[rho_idx][theta_idx] += 1ys.append(rho)xs.append(theta)subplot3.plot(xs, ys, color="white", alpha=0.05)for y in range(accumulator.shape[0]):for x in range(accumulator.shape[1]):if accumulator[y][x] > t_count:rho = rhos[y]theta = thetas[x]a = np.cos(np.deg2rad(theta))b = np.sin(np.deg2rad(theta))x0 = (a * rho) + edge_width_halfy0 = (b * rho) + edge_height_halfx1 = int(x0 + 1000 * (-b))y1 = int(y0 + 1000 * (a))x2 = int(x0 - 1000 * (-b))y2 = int(y0 - 1000 * (a))subplot3.plot([theta], [rho], marker='o', color="yellow")subplot4.add_line(mlines.Line2D([x1, x2], [y1, y2]))subplot3.invert_yaxis()subplot3.invert_xaxis()subplot1.title.set_text("Original Image")subplot2.title.set_text("Edge Image")subplot3.title.set_text("Hough Space")subplot4.title.set_text("Detected Lines")plt.show()return accumulator, rhos, thetasif __name__ == "__main__":for i in range(3):image = cv2.imread(f"sample-{i+1}.png")edge_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)edge_image = cv2.GaussianBlur(edge_image, (3, 3), 1)edge_image = cv2.Canny(edge_image, 100, 200)edge_image = cv2.dilate(edge_image,cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)),iterations=1)edge_image = cv2.erode(edge_image,cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)),iterations=1)line_detection_non_vectorized(image, edge_image)

向量化解决方案

import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.lines as mlinesdef line_detection_vectorized(image, edge_image, num_rhos=180, num_thetas=180, t_count=220):edge_height, edge_width = edge_image.shape[:2]edge_height_half, edge_width_half = edge_height / 2, edge_width / 2#d = np.sqrt(np.square(edge_height) + np.square(edge_width))dtheta = 180 / num_thetasdrho = (2 * d) / num_rhos#thetas = np.arange(0, 180, step=dtheta)rhos = np.arange(-d, d, step=drho)#cos_thetas = np.cos(np.deg2rad(thetas))sin_thetas = np.sin(np.deg2rad(thetas))#accumulator = np.zeros((len(rhos), len(rhos)))#figure = plt.figure(figsize=(12, 12))subplot1 = figure.add_subplot(1, 4, 1)subplot1.imshow(image)subplot2 = figure.add_subplot(1, 4, 2)subplot2.imshow(edge_image, cmap="gray")subplot3 = figure.add_subplot(1, 4, 3)subplot3.set_facecolor((0, 0, 0))subplot4 = figure.add_subplot(1, 4, 4)subplot4.imshow(image)#edge_points = np.argwhere(edge_image != 0)edge_points = edge_points - np.array([[edge_height_half, edge_width_half]])#rho_values = np.matmul(edge_points, np.array([sin_thetas, cos_thetas]))#accumulator, theta_vals, rho_vals = np.histogram2d(np.tile(thetas, rho_values.shape[0]),rho_values.ravel(),bins=[thetas, rhos])accumulator = np.transpose(accumulator)lines = np.argwhere(accumulator > t_count)rho_idxs, theta_idxs = lines[:, 0], lines[:, 1]r, t = rhos[rho_idxs], thetas[theta_idxs]for ys in rho_values:subplot3.plot(thetas, ys, color="white", alpha=0.05)subplot3.plot([t], [r], color="yellow", marker='o')for line in lines:y, x = linerho = rhos[y]theta = thetas[x]a = np.cos(np.deg2rad(theta))b = np.sin(np.deg2rad(theta))x0 = (a * rho) + edge_width_halfy0 = (b * rho) + edge_height_halfx1 = int(x0 + 1000 * (-b))y1 = int(y0 + 1000 * (a))x2 = int(x0 - 1000 * (-b))y2 = int(y0 - 1000 * (a))subplot3.plot([theta], [rho], marker='o', color="yellow")subplot4.add_line(mlines.Line2D([x1, x2], [y1, y2]))subplot3.invert_yaxis()subplot3.invert_xaxis()subplot1.title.set_text("Original Image")subplot2.title.set_text("Edge Image")subplot3.title.set_text("Hough Space")subplot4.title.set_text("Detected Lines")plt.show()return accumulator, rhos, thetasif __name__ == "__main__":for i in range(3):image = cv2.imread(f"sample-{i+1}.png")edge_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)edge_image = cv2.GaussianBlur(edge_image, (3, 3), 1)edge_image = cv2.Canny(edge_image, 100, 200)edge_image = cv2.dilate(edge_image,cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)),iterations=1)edge_image = cv2.erode(edge_image,cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)),iterations=1)line_detection_vectorized(image, edge_image)

五、结论

综上所述,本文以最简单的形式展示了Hough变换算法,该算法可以扩展到检测直线以外。多年来,对该算法进行了许多改进,使其可以检测其他形状,例如圆形,三角形甚至特定形状的四边形。这导致了许多有用的现实世界应用,从文档扫描到自动驾驶汽车的车道检测。

实战:基于霍夫变换进行线检测相关推荐

  1. MTCNN目标检测实战—基于PyTorch的人脸检测算法实战

    目录 一.MTCNN简介: 1.什么是MTCNN 2.MTTCNN的作用 3.MTCNN的优缺点 1优点 2缺点 二.人脸检测 三.MTCN的网络模型 四.准备训练样本 1.获取原始数据集 2.准备训 ...

  2. 【NodeJs-5天学习】第三天实战篇③ ——基于MQTT的环境温度检测

    [NodeJs-5天学习]第三天实战篇③ --基于MQTT的环境温度检测 1. 前言 2.实现思路 2.1 NodeJs服务器代码 2.2.1 本地部署MQTT服务器,端口1883 2.2.1.1 用 ...

  3. 【物联网服务NodeJs-5天学习】第三天实战篇③ ——基于MQTT的环境温度检测

    [NodeJs-5天学习]第三天实战篇③ --基于MQTT的环境温度检测 1. 前言 2.实现思路 2.1 NodeJs服务器代码 2.2.1 本地部署MQTT服务器,端口1883 2.2.1.1 用 ...

  4. (六)基于霍夫变换的直线和圆检测

    1.Hough Transform https://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm 霍夫变换于1962年由Paul Hough首次提出,后于197 ...

  5. 目标检测YOLO实战应用案例100讲-基于FPGA的目标检测硬件加速技术及其应用研究

    目录 基于FPGA的目标检测加速器设计 目标检测算法与加速方法 2.1 YOLO v2算法

  6. 【目标检测】基于yolov6的钢筋检测和计数(附代码和数据集)

    写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌. Hello,大家好,我是augustqi. 今天给大家 ...

  7. 视觉SLAM如何基于深度学习闭环检测?

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:计算机视觉life 请问有做视觉SLAM基于深度学习闭环 ...

  8. 实战:OpenVINO+OpenCV 文本检测与识别

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|OpenCV学堂 模型介绍 文本检测模型 OpenVIN ...

  9. 快速完整的基于点云闭环检测的激光SLAM系统

    我原来总结过LOAM_Livox,这篇文章主要是解决LOAM在长时间运行的时累计误差的问题.本文提出的方法计算关键帧的2D直方图,局部地图patch,并使用2D直方图的归一化互相关(normalize ...

最新文章

  1. 潜移默化学会WPF(转载篇二)--退出应用程序
  2. 本科刚毕业有点迷茫,想入门单片机,应该怎么开始?
  3. windows编辑好的python代码在linux的vim编辑,缩进问题
  4. 企业网站如何具备亲和力?
  5. scrapy + selenium + chromedriver爬取动态数据
  6. Kubernetes的控制器类型即使用案例
  7. 现在编程语言的两大主流
  8. 【转】9、XAML名称空间详解
  9. 二级菜单打开一个时其他关闭_简介——菜单和工具栏
  10. VMware VMFS文件系统元数据不一致问题处理
  11. MySQL截取字符串的方法-substring_index
  12. Manacher's algorithm: 最长回文子串算法
  13. 考研数学预热(肖老师)-2019-12-21
  14. 计算机找不到链接打印机主机,电脑连接打印机厂商型号没有怎么办
  15. QML 语法(Syntax)
  16. 2012年2月51CTO壁纸点评活动获奖名单【已结束】
  17. robotframework打开ride.py 闪退,打不开
  18. 成功解决ImportError: cannot import name ‘imresize‘
  19. 电影票在线选座API接口电影排期场次
  20. 我与小娜(20):去LIGO,探秘光子接力赛

热门文章

  1. LVDS、CML、LVPECL不同逻辑电平之间的互连(二)
  2. 小程序自动换行text richtext
  3. macOS中SpaceVim搭建java开发环境
  4. 常见的服务器集群负载均衡技术:二三四七层负载均衡,DNS、LVS、F5、nginx负载均衡
  5. 生信初学者必备的基础知识
  6. Java程序员职业规划如何做?
  7. Input Events(输入事件)
  8. 买房子签合同要注意哪些细节
  9. OpenCV-Python图像运算变换处理:开运算和闭运算以及不同核矩阵的影响分析
  10. 1. windows上安装antlr4(code generation target为Java)