OpenCV-Python快速入门(十五):霍夫变换

news2025/7/14 17:08:38

OpenCV-Python快速入门(十五):霍夫变换

    • 前言
    • 前提条件
    • 实验环境
    • 霍夫变换
      • 基本原理
      • 霍夫直线变换(cv2.HoughLines())
      • 概率霍夫变换(cv2.HoughLinesP())
      • 霍夫圆变换(cv2.HoughCircles())
    • 参考文献

前言

  • 本文是个人快速入门OpenCV-Python的电子笔记,由于水平有限,难免出现错漏,敬请批评改正。
  • 更多精彩内容,可点击进入OpenCV-Python快速入门专栏或我的个人主页查看

前提条件

  • 熟悉Python

实验环境

  • Python 3.x (面向对象的高级语言)
  • OpenCV 4.0(python第三方库)pip3 install opencv-python

霍夫变换

基本原理

  • 以笛卡儿坐标系来引出霍夫变换基本原理。
  • 笛卡儿空间中的一条直线在霍夫空间内的映射情况。
    在这里插入图片描述
  • 从上图可以看出,
    • 笛卡儿空间内的一条直线确定了霍夫空间内的一个点。
    • 霍夫空间内的一个点确定了笛卡儿空间内的一条直线。
  • 笛卡儿空间中的一个点在霍夫空间内的映射情况。在这里插入图片描述
  • 从上图可以看出,
    • 笛卡儿空间内的点 ( 𝑥 0 , 𝑦 0 ) (𝑥_0, 𝑦_0) (x0,y0)映射到霍夫空间,就是直线 𝑏 = − 𝑥 0 𝑘 + 𝑦 0 𝑏 = −𝑥_0𝑘 + 𝑦_0 b=x0k+y0
    • 霍夫空间内的直线 𝑏 = − 𝑥 0 𝑘 + 𝑦 0 𝑏 = −𝑥_0𝑘 + 𝑦_0 b=x0k+y0映射到笛卡儿空间,就是点 ( 𝑥 0 , 𝑦 0 ) (𝑥_0, 𝑦_0) (x0,y0)
  • 笛卡儿空间中的多个点映射到霍夫空间的情况。
    在这里插入图片描述
    在这里插入图片描述
  • 从上面图片可以看出,在霍夫空间内,经过一个点的直线越多,说明其在笛卡儿空间内映射的直线,是由越多的点所构成(穿过)的。
  • 两个点就能构成一条直线。但是,如果有一个点是因为计算错误而产生的,那么它和另外一个点,也会构成一条直线,此时就会凭空构造出一条实际上并不存在的直线。
  • 这种情况是要极力避免的。因此,在计算中,我们希望用更多的点构造一条直线,以提高直线的可靠性。也就是说,如果一条直线是由越多点所构成的,那么它实际存在的可能性就越大,它的可靠性也就越高。因此,霍夫变换选择直线的基本思路是:选择有尽可能多直线交汇的点。
  • 但是,笛卡儿空间有一定的局限性,在笛卡儿空间中,如下图所示情况,则无法映射到霍夫空间中,
    在这里插入图片描述
  • 此时,斜率 k k k 为无穷大,截距 b b b 无法取值。因此,上图中的直线无法映射到霍夫空间内。
  • 为了解决上述问题,可以考虑将笛卡儿坐标系映射到极坐标系上,如下图所示。
    在这里插入图片描述
  • 在笛卡儿坐标系内使用的是斜率 k k k 和截距 b b b,即用 ( k , b ) (k, b) (k,b)表示一条直线。在极坐标系内,采用极径 r r r(有时也用 ρ \rho ρ表示)和极角 θ θ θ来表示,即 ( r , θ ) (r, θ) (r,θ)来表示。
    • 极坐标系中的直线可表示为: x = r c o s θ , y = r s i n θ , r 2 = x 2 + y 2 x=rcosθ,y=rsinθ,r^2=x^2+y^2 x=rcosθ,y=rsinθ,r2=x2+y2 r = x c o s θ + y s i n θ r = xcos\theta + ysin\theta r=xcosθ+ysinθ
  • 上图的直线 A A A,可以使用极坐标的极径 r r r 和极角 θ θ θ来表示。其中, r r r 是直线 A A A 与图像原点 O O O 之间的距离,参数 θ θ θ是直线 A A A 的直线 B B B x x x 轴的角度。
  • 在这种表示方法中,图像中的直线有一个 ( 0 , π ) (0,π) (0,π)的角 θ θ θ,而 r r r 的最大值是图像对角线的长度。用这种表示方法,可以很方便地表示三个点所构成的直线。
  • 与笛卡儿空间和霍夫空间的映射关系类似:
    • 极坐标系内的一个点映射为霍夫坐标系(霍夫空间)内的一条线(曲线)。
    • 极坐标系内的一条线映射为霍夫坐标系内的一个点。
  • 一般来说,在极坐标系内的一条直线能够通过在霍夫坐标系内相交于一点的线的数量来评估。在霍夫坐标系内,经过一个点的线越多,说明其映射在极坐标系内的直线,是由越多的点所构成(穿过)的。因此,霍夫变换选择直线的基本思路是:选择由尽可能多条线汇成的点。通常情况下,设置一个阈值,当霍夫坐标系内交于某点的曲线达到了阈值,就认为在对应的极坐标系内存在(检测到)一条直线。
  • 上述内容是霍夫变换的原理,即使完全不理解上述原理,也不影响我们使用 OpenCV 提供的霍夫变换函数来进行霍夫变换。OpenCV 给我们提供了接口(参数、返回值),我们只需要掌握接口的正确使用方法,就可以正确地处理图像问题,无须掌握其内部工作原理。

霍夫直线变换(cv2.HoughLines())

  • OpenCV 提供了函数 cv2.HoughLines()用来实现霍夫直线变换,该函数要求所操作的源图像是一个二值图像,所以在进行霍夫变换之前要先将源图像进行二值化,或者进行 Canny 边缘检测。
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread('line.jpg')
# 灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 边缘检测
edges = cv2.Canny(gray,50,150,apertureSize = 3)
# BGR -> RGB
img_RGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
img_Show=img_RGB.copy()
# 霍夫直线变化
'''
lines=cv2.HoughLines(image, rho, theta, threshold)
参数:
    image 是输入图像,即源图像,必须是 8 位的单通道二值图像。
        如果是其他类型的图像,在进行霍夫变换之前,需要将其修改为指定格式。
    rho 为以像素为单位的距离 r 的精度。一般情况下,使用的精度是 1。
    theta 为角度𝜃的精度。一般情况下,使用的精度是π/180,表示要搜索所有可能的角度。
    threshold 是阈值。该值越小,判定出的直线就越多。
        识别直线时,要判定有多少个点位于该直线上。在判定直线是否存在时,对直线所穿过的点的数量进行评估,
        如果直线所穿过的点的数量小于阈值,则认为这些点恰好(偶然)在算法上构成直线,但是在源图像中该直线并不存在;
        如果大于阈值,则认为直线存在。所以,如果阈值较小,就会得到较多的直线;阈值较大,就会得到较少的直线。
返回值:
    lines 中的每个元素都是一对浮点数,表示检测到的直线的参数,即(r, θ),是numpy.ndarray 类型。

需要注意的是,使用函数 cv2.HoughLines()检测到的是图像中的直线而不是线段,因此检测到的直线是没有端点的。
所以,我们在进行霍夫直线变换时所绘制的直线都是穿过整幅图像的。
'''
lines = cv2.HoughLines(edges,1,np.pi/180,140)

# 绘制检测到的直线
for line in lines:
    # (r, θ)
    rho,theta = line[0] 
    # 通过(r, θ)获取到(x0,y0)
    a = np.cos(theta) 
    b = np.sin(theta)
    x0 = a*rho
    y0 = b*rho
    # 获得(x1,y1),(x2,y2)
    x1 = int(x0 + 1000*(-b))
    y1 = int(y0 + 1000*(a))
    x2 = int(x0 - 1000*(-b))
    y2 = int(y0 - 1000*(a))
    # 绘制直线
    cv2.line(img_RGB,(x1,y1),(x2,y2),(255,0,0),2)

# 显示图片
plt.figure(figsize=(20, 20))
plt.subplot(121)
plt.title("Origin")
plt.imshow(img_Show)
plt.axis('off')
plt.subplot(122)
plt.title("Result")
plt.imshow(img_RGB)
plt.axis('off')
plt.show()

在这里插入图片描述

概率霍夫变换(cv2.HoughLinesP())

  • 概率霍夫变换对基本霍夫变换算法进行了一些修正,是霍夫变换算法的优化。它没有考虑所有的点。相反,它只需要一个足以进行线检测的随机点子集即可。
  • 为了更好地判断直线(线段),概率霍夫变换算法还对选取直线的方法作了两点改进:
    • 所接受直线的最小长度。如果有超过阈值个数的像素点构成了一条直线,但是这条直线很短,那么就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原图中并不存在这条直线。
    • 接受直线时允许的最大像素点间距。如果有超过阈值个数的像素点构成了一条直线,但是这组像素点之间的距离都很远,就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原始图像中并不存在这条直线。
  • OpenCV 提供了函数 cv2.HoughLinesP()用来实现概率霍夫变换。
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread('line.jpg')
# 灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 边缘检测
edges = cv2.Canny(gray,50,150,apertureSize = 3)
# BGR -> RGB
img_RGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
img_Show=img_RGB.copy()
# 概率霍夫变换
'''
lines =cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap)
参数:
    image 是输入图像,即源图像,必须为 8 位的单通道二值图像。
        对于其他类型的图像,在进行霍夫变换之前,需要将其修改为这个指定的格式。
    rho 为以像素为单位的距离 r 的精度。一般情况下,使用的精度是 1。
    theta 是角度𝜃的精度。一般情况下,使用的精度是 np.pi/180,表示要搜索可能的角度。
    threshold 是阈值。该值越小,判定出的直线越多;值越大,判定出的直线就越少。
    minLineLength 用来控制“接受直线的最小长度”的值,默认值为 0。
    maxLineGap 用来控制接受共线线段之间的最小间隔,即在一条线中两点的最大间隔。
            如果两点间的间隔超过了参数 maxLineGap 的值,就认为这两点不在一条线上。默认值为 0。
返回值:
    lines 是由 numpy.ndarray 类型的元素构成的,其中每个元素都是一对浮点数,表示检测到的直线的参数,即(r, θ)。
'''
lines = cv2.HoughLinesP(edges,1,np.pi/180,1,minLineLength=100,maxLineGap=10)
for line in lines:
    x1,y1,x2,y2 = line[0]
    cv2.line(img_RGB,(x1,y1),(x2,y2),(255,0,0),2)

# 显示图片
plt.figure(figsize=(20, 20))
plt.subplot(121)
plt.title("Origin")
plt.imshow(img_Show)
plt.axis('off')
plt.subplot(122)
plt.title("Result")
plt.imshow(img_RGB)
plt.axis('off')
plt.show()

在这里插入图片描述

霍夫圆变换(cv2.HoughCircles())

  • 霍夫变换除了用来检测直线外,也能用来检测其他几何对象。实际上,只要是能够用一个参数方程表示的对象,都适合用霍夫变换来检测。
  • 用霍夫圆变换来检测图像中的圆,与使用霍夫直线变换检测直线的原理类似。在霍夫圆变换中,需要考虑圆半径和圆心(x 坐标、y 坐标)共 3 个参数。在 OpenCV 中,采用的策略是两轮筛选。第 1 轮筛选找出可能存在圆的位置(圆心);第 2 轮再根据第 1 轮的结果筛选出半径大小。
  • 与用来决定是否接受直线的两个参数“接受直线的最小长度(minLineLength)”和“接受直线时允许的最大像素点间距(MaxLineGap)”类似,霍夫圆变换也有几个用于决定是否接受圆的参数:圆心间的最小距离、圆的最小半径、圆的最大半径。
  • OpenCV 中,函数 cv2.HoughCircles()用来实现霍夫圆变换,该函数将 Canny 边缘检测和霍夫变换结合。
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图片
img = cv2.imread('circle.jpg')
# 灰度图
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# BGR -> RGB
img_RGB=cv2.cvtColor(imgo,cv2.COLOR_BGR2RGB)
img_Show=img_RGB.copy()
# 图像平滑:中值滤波 cv2.medianBlur(img, ksize)
img = cv2.medianBlur(img,5)

# 霍夫圆变换
'''
circles=cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxRadius)
参数:
    image:输入图像,即源图像,类型为 8 位的单通道灰度图像。
    method:检测方法。截止到 OpenCV 4.0.0-pre 版本,HOUGH_GRADIENT 是唯一可用的参数值。
            该参数代表的是霍夫圆检测中两轮检测所使用的方法。
    dp:累计器分辨率,它是一个分割比率,用来指定图像分辨率与圆心累加器分辨率的比例。
        例如,如果 dp=1,则输入图像和累加器具有相同的分辨率。
    minDist:圆心间的最小间距。该值被作为阈值使用,如果存在圆心间距离小于该值的多个圆,则仅有一个会被检测出来。
            因此,如果该值太小,则会有多个临近的圆被检测出来;如果该值太大,则可能会在检测时漏掉一些圆。
    param1:该参数是缺省的,在缺省时默认值为 100。它对应的是 Canny 边缘检测器的高阈值(低阈值是高阈值的二分之一)。
    param2:圆心位置必须收到的投票数。只有在第 1 轮筛选过程中,投票数超过该值的圆,才有资格进入第 2 轮的筛选。
            因此,该值越大,检测到的圆越少;该值越小,检测到的圆越多。
            这个参数是缺省的,在缺省时具有默认值 100。
    minRadius:圆半径的最小值,小于该值的圆不会被检测出来。
            该参数是缺省的,在缺省时具有默认值 0,此时该参数不起作用。
    maxRadius:圆半径的最大值,大于该值的圆不会被检测出来。
            该参数是缺省的,在缺省时具有默认值 0,此时该参数不起作用。
返回值:
    circles:由圆心坐标和半径构成的 numpy.ndarray。

需要特别注意,在调用函数 cv2.HoughLinesCircles()之前,要对源图像进行平滑操作,
以减少图像中的噪声,避免发生误判。该函数具有非常多的参数,在实际检测中可以根据需要设置不同的值。
'''
circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,300,param1=50,param2=30,minRadius=700,maxRadius=900)
# 将数值转化为整数
circles = np.uint16(np.around(circles))

# 绘制检测到的圆
for i in circles[0,:]:
    # 绘制圆 (i[0],i[1])为圆心坐标,i[2]为圆的半径
    cv2.circle(img_RGB,(i[0],i[1]),i[2],(255,0,0),12)
    # 绘制圆心
    cv2.circle(img_RGB,(i[0],i[1]),2,(255,0,0),12)

# 显示图片
plt.figure(figsize=(20, 20))
plt.subplot(121)
plt.title("Origin")
plt.imshow(img_Show)
plt.subplot(122)
plt.title("Result")
plt.imshow(img_RGB)
plt.show()

在这里插入图片描述

参考文献

[1] https://opencv.org/
[2] 李立宗. OpenCV轻松入门:面向Python. 北京: 电子工业出版社,2019

  • 更多精彩内容,可点击进入OpenCV-Python快速入门专栏或我的个人主页查看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/38242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

移动端测试理论

App测试基础 App功能测试及专项测试 前言: 对于APP项目的测试,一般是进行系统测试。 测试主要从业务功能和非业务功能两个方面考虑。业务功能测试 根据软件说明,设计文档或用户需求验证App的各个功能的实现。 专项测试 兼容性测试 兼容性测试的关注点…

阿里Java研发面经(已拿offer)

一、自我总结: 1)首先最重要的一点。对自己的要求高点。不要以简单的实习生来要求自己。你要想 你会的别人都会 你的核心竞争力是什么呢。所以楼主建议以Java高级工程师来要求自己。不会的就学嘛。人面对未知的事物 本能反应是恐惧与退缩。可当你尝试去…

Xxl-Job 初次体验

Xxl-Job 初次体验一、定时任务-前置知识二、演变机制三、xxl-Job 设计思想四、xxl-job 实战1. 调度中心部署2. 编写执行器简单使用一下2.1. 让执行器run起来!2.2. 在调度中心配置任务,调度一下!3. XxlJob 任务的生命周期4. 路由策略5. 父子任…

汇川PLC编程软件AutoShop的使用

文章目录一、数据类型二、系统参数.1、内存容量设置2、“掉电保持范围”设置3、系统设置三、符号表1、编辑符号表2、符号表的打印四、元件监控表1、新建元件监控表2、编辑元件监控表3、复制元件监控表4、快速监控表五、元件使用信息表六、交叉引用表七、软元件内存表1、新建和复…

windows的小米11真机appium微信爬虫

1、下载appium 仓库地址 2、下载python的包 pip install Appium-Python-Client -i https://pypi.tuna.tsinghua.edu.cn/simple 3、下载android-sdk 先下SDK Tools 国内一个镜像网站 参考这个教程 安装好后,运行这个SDK Manager.exe 然后install,同意协…

Alphalens使用方法细节判断

首先alphalens的数据格式: factor: MultiIndex(用stack()方法来转换) prices: DataFrame #转换成MultiIndex factor alpha_mom.stack() print (factor.tail()) datetime 2017-11-20 15:00:00 601857.XSHG 1…

小学生python游戏编程arcade----烟花粒子

小学生python游戏编程arcade----烟花粒子前言烟花粒子1、Vector向量类1.1 arcade中的向量类1.2 应用2、绘制粒子所有纹理图片2.1 给定直径和颜色的圆的纹理2.2 arcade.make_circle_texture函数原码2.3 make_soft_circle_texture 函数原码2.4 公共纹理代码3 效果图4 代码源码获取…

【读点论文】Densely Connected Convolutional Networks用残差连接大力出奇迹,进一步叠加特征图,以牺牲显存为代价

Densely Connected Convolutional Networks Abstract 如果卷积网络在靠近输入的层和靠近输出的层之间包含较短的连接,则卷积网络可以训练得更深入、更准确和有效。在本文中,接受了这种观察,并介绍了密集卷积网络(DenseNet),它以…

Linux - Linux下Java安装路径查找;配置Java环境变量

一、查看Java的安装路径 1、已经安装好了JDK,也配置了环境变量 1、执行 java -version java -version 出现了版本号,表示安装过了JDK,配置了环境变量 2、在配置过jdk的情况下,执行java -verbose指令,在打印出的文本…

java stream中的peek()用法

文章目录前言最终操作(terminal operation)peek() vs forEach()peek() 的典型用法:协助调试总结前言 最近看到一段代码: aeFormList.stream().peek(object -> saveInfomation(object, params)).collect(Collectors.toList())…

std::shared_ptr(基础、仿写、安全性)

目录 一、c参考手册 1、解释说明 2、代码示例 3、运行结果 二、对std::shared_ptr分析 1、shared_ptr基础 2、创建shared_ptr实例 3、访问所指对象 4、拷贝和赋值操作 5、检查引用计数 三、仿写std::shared_ptr代码 1、单一对象 2、数组对象 四、shared_ptr遇到问…

MyBatis 环境搭建

MyBatis 环境搭建步骤 1.创建一张表和表对应的实体类 2.创建一个 maven 项目,把项目添加到 git 仓库 创建maven项目 教程见:Maven[项目构建工具]_chen☆的博客-CSDN博客 添加到git仓库: 3.在文件 pom.xml 添加 mybiatis 相关依赖(导入 MyBa…

Java - 利用Nacos做一个动态开关配置功能

Java - 利用Nacos做一个动态开关配置功能前言一. Nacos配置类编写二. 测试三. 展望前言 我公司里有一个Config配置功能(我相信这是很普遍的一个功能)。简单来说就是: 将相关的键值对放到这个Config配置系统里面。代码里通过这个Config配置系…

博客项目(前台功能实现)

博客项目(前台接口实现) 文章目录博客项目(前台接口实现)1.前置知识1.1Controller1.1.1ResponseResult类1.1.2该类的方法1.2Service1.3ServiceImpl1.4Mapper1.5Vo的理解1.6可能会用到的相关插件1.7设置字面量1.8后端接口测试工具2.热门文章接口分析2.1热门文章接口位置2.2接口的…

Internet Download Manager2023最新版下载器功能介绍

说到下载器在国内就不得不提迅雷,迅雷真是伟大,几乎垄断了国内的下载市场,的确,有的时候用迅雷可以下载到很不错的资源,但在没有VIP的情况下,迅雷是不友好的,相信使用迅雷的各位都有被限速过的经…

三、Eureka

文章目录一、认识服务提供者和服务调用者二、Eureka 的工作流程三、服务调用出现的问题及解决方法四、搭建 eureka-server五、注册 user-service、order-service六、在 order-service 完成服务拉取(order 模块能访问 user 模块)七、配置远程服务调用八、…

分布式锁:不同实现方式实践测评

Hello读者朋友们,今天打算分享一篇测评实践类的文章,用优雅的代码与真实的数据来讲述在分布式场景下,不同方式实现的分布式锁,分别探究每一种方式的性能情况与最终的优劣分析。 开门见山,我们先看一张表格&#xff0c…

fiddler安卓模拟器与ios手机抓包

一.安卓模拟器(雷电模拟器)抓包 1.1fiddler基本配置 1.2导出Fiddler证书 Tools -> Options -> HTTPS -> Actions -> Export Root Certificate to Desktop 在桌面上看到导出的FiddlerRoot.cer证书文件 1.3下载和安装openssl openssl下载地址 git终端输入 open…

蜂鸟E203学习(一)--RISC的前世今生

第一章 CPU之前世今生 1.1、CPU众生相 1.1.1 处理器(cpu)和处理器内核(core)的区分 处理器严格意义上是soc,包含了内核和其他设备或者存储器. 1.1.2 不同CPU架构的诞生时间 CPU架构诞生时间Intel 80861978年ARM19…

Opencv之Mat常用类成员(一篇就够了)

1. 重要类成员 data:数据存储的起始地址 (uchar*类型);dims:矩阵维度。如 3 * 4 的矩阵为 2 维, 3 * 4 * 5 的为3维;channels():通道数量,矩阵中表示一个元素所需要的值的个数。例:…