实战:基于霍夫变换进行线检测

重磅干货,第一时间送达

霍夫变换算法线检测

一、目的

最近,我们发现自己不得不在应用程序中加入文档扫描功能。在做了一些研究之后,我们偶然发现了一篇熊英写的文章,他是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 cv2import numpy as npimport matplotlib.pyplot as pltimport matplotlib.lines as mlines

def 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_thetas drho = (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] += 1 ys.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_half y0 = (b * rho) + edge_height_half x1 = 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, thetas

if __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 cv2import numpy as npimport matplotlib.pyplot as pltimport matplotlib.lines as mlines

def 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_thetas drho = (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 = line rho = rhos[y] theta = thetas[x] a = np.cos(np.deg2rad(theta)) b = np.sin(np.deg2rad(theta)) x0 = (a * rho) + edge_width_half y0 = (b * rho) + edge_height_half x1 = 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, thetas

if __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:OpenCV-Contrib扩展模块中文版教程
(0)

相关推荐

  • Python机器学习算法:线性回归

    https://m.toutiao.com/is/JTpGVoD/ 线性回归可能是最常见的算法之一,线性回归是机器学习实践者必须知道的.这通常是初学者第一次接触的机器学习算法,了解它的操作方式对于更好 ...

  • HTC U11发布:Edge Sense边缘触控、DxO 90分相机、卖649刀

    昨天是HTC成为20周年纪念日,而在今天HTC带来了新款年度旗舰手机HTC U11,用到前后玻璃设计,配备IP67防尘防水,机身有Edge Sense边缘触控技术,5.5英寸2K屏幕,内部搭载高通骁龙 ...

  • python进阶—OpenCV之图像处理(一)

    文章目录 颜色空间转换 RGB色彩空间 HSV色彩空间 YUV色彩空间 简单的物体跟踪示例 HSV空间目标阈值选取 图像几何变换 图像的缩放 图像的位移 图像的旋转 图像的仿射 图像的投射 图像阈值( ...

  • [OpenCV]经典霍夫变换原理

    本文主要讲述的是霍夫变换的一些内容,并加入一些在生活中的应用,希望能对读者对于霍夫变换的内容有所了解. 首先我先说的是,霍夫变换是一个特征提取技术.其可用于隔离图像中特定形状的特征的技术,应用在图像分 ...

  • 频域图像增强-锐化

    图像增强技术根据增强处理过程所在的空间不同,可分为基于频域的算法和基于空域的算法两大类.基于频域的算法是在图像的某种变换域内对图像的变换系数值进行某种修正,是一种间接增强的算法,把图像看成一种二维信号 ...

  • python数字图像处理(三)边缘检测常用算子

    在该文将介绍基本的几种应用于边缘检测的滤波器,首先我们读入saber用来做为示例的图像 #读入图像代码,在此之前应当引入必要的opencv matplotlib numpysaber = cv2.im ...

  • python+opencv图像处理(十二)

    图像仿射变换和透视变换 天晴了...... 1.仿射变换 图像的仿射变换就是图像的旋转加上拉升,说直白点,就是把矩形变成平行四边形. 要把矩形变成平行四边行,只需要拉伸其四个角点就行了,事实上,只需要 ...

  • python+opencv图像处理(四十二)

    Kirsch算子 1.Kirsch算子 Kirsch算子是R.Kirsch提出来一种边缘检测新算法,它采用8个模板对图像上的每一个像素点进行卷积求导数,这8个模板代表8个方向,对图像上的8个特定边缘方 ...

  • 实战:基于OpenCV的人眼检测

    重磅干货,第一时间送达 一.背景 无论学习什么,实践都非常重要.如果打算学习OpenCV.Numpy等Python库,那么这简单的12行代码很适合实践并体验这些库的实时使用. 二.OpenCV库 Op ...

  • DETR:基于 Transformers 的目标检测

    设为星标,干货直达! 编辑:我是小将 前言 最近可以说是随着 ViT 的大火,几乎可以说是一天就能看到一篇基于 Transformers 的 CV 论文,今天给大家介绍的是另一篇由Facebook 在 ...

  • 疫情期间,瑜伽馆如何通过直播进行线上拓客?

    虽然因为疫情暂时不能开门,但是我们可以通过直播课程的方式进行拓客. 在讲如何通过线上直播课程维护会员(可点击查看原文)时我们提到过,抖音等直播平台是有引流功能的.那么今天我们就来讲一讲,如何通过线上直 ...

  • 网络首发|基于图像和机器学习检测大豆作物幼苗期玉米杂苗

    基于图像和机器学习检测大豆作物幼苗期玉米杂苗 Paulo FLORES1,张 昭1*,Jithin MATHEW2,Nusrat JAHAN1,John STENGER1 (1. 北达科他州州立大学 ...

  • 技术 | 现代摩比斯开发基于雷达的乘客检测系统,提升后排安全

    现代摩比斯宣布已成功开发出带有 "雷达"(ROA,后乘人员警报)的后座乘客检测系统,并计划向全球汽车制造商推荐该系统. ROA 是用于防止乘客无人照管后排座椅的装置.过去,通常使用 ...

  • 面膜品牌怎么有效地进行线上营销推广?

    品牌是企业最重要的无形资产,近年来,一些意识超前的企业纷纷运用品牌战略利器,取得了竞争优势并逐渐发展壮大,从而确保企业的长远发展. 面膜产品也不例外,从线下到线上的推广越来越重要,那么面膜要怎么推广才 ...

  • 基于质谱的糖尿病检测系统的研发及应用

    近日,深圳市工业和信息化局公示了2021年新兴产业扶持计划拟资助项目,绘云生物的"基于质谱的糖尿病检测系统的研发及应用"项目通过层层评定后,荣获生物医药领域最高资助. 深圳市工信局 ...

  • 10万+标注数据开放!驾驶员不良驾驶识别/电动车进电梯检测/渣土车车牌识别/反光衣识别等8大赛题详解来了!

    ECV-2021极市计算机视觉开发者榜单大赛(以下简称ECV-2021)已于2021年7月6日正式开赛! ECV-2021由青岛市人民政府指导,极视角科技有限公司. 青岛市委台港澳办.青岛市工业和信息 ...

  • opencv基于DNN的人脸检测

    from:https://blog.csdn.net/minstyrain/article/details/78907425 opencv3.4 版之前自带的人脸检测器是基于Haar+Adaboost ...