机器视觉学习-day18-霍夫变换

霍夫变换可以检测图像中直线、圆等其他形状。基本思想是将图像空间(原始空间)的点映射到参数空间(霍夫空间)中,通过参数空间中寻找最大值来实现检测。

代码运行步骤:图片输入→灰度化→Canny算法→霍夫变换→图片输出

1 霍夫直线变换

对于一条直线(不垂直于x轴),可以 y=kx+b 来表示,此时x和y是横纵坐标,k和b是固定参数,对上面的公式进行变换:b=-kx+y

此时,以k和b为横纵坐标,x和y为固定参数,前后如下所示。

从上图中可以看出,在直角坐标系下的一条直线,在变换后的空间中仅仅表示为一点,对于变换后的空间,称之为霍夫空间。

原始空间的直角坐标系下一条无限长的直线对应了霍夫空间中的一个点,霍夫空间的一条直线对应了原始空间的直角坐标中的一个点。

一个二值化的图像,前景可能是一些白色像素散点,每个点在霍夫空间中都是一条直线,当霍夫空间的两条直线相交时,表示原始空间中两个点之间的连线。相反的,在原始空间中有n条直线相交与一点,在霍夫空间中就是n个点共线。

利用上述现象,可以通过霍夫空间的现象,找到原始空间的特征。

  • 如果霍夫空间中有很多线相交于一点,表示可以找到原始空间中很多点构成的一条线。

  • 如果霍夫空间中有很多点共线,表示可以找到原始空间中很多线相交的点。

对于垂直与x轴的直线,由于斜率无穷大,导致无法使用上述的空间变换。解决的方法是在原始空间中,不再使用斜率和截距表示直线,而更换为极坐标系。

  • 极径 ρ

该点到原点的距离

  • 极角 θ

从正x轴到该点与极径的夹角,逆时针为正

使用极坐标系的目的是让霍夫空间可以表示原始空间,对于原始空间还是使用直角坐标系。

原始空间(笛卡尔坐标系)→ 原始空间(极坐标系)→ 霍夫空间(极坐标系)

投票规则:根据上图,霍夫空间在极坐标系下,原始空间的一个点可以在霍夫空间中产生一条三角函数曲线,多条三角函数的曲线可能相交于一点,因此可以设定一个阈值,来检测霍夫空间中三角函数曲线相交的次数,如果一个交点的三角函数相交次数超过阈值,这个交点代表的原始空间直线可能是寻找的目标直线。

观察虚拟仿真实验中霍夫变换节点的参数:

  • hough_method:选择HoughLines表示霍夫直线变换

  • rho:极径 ρ的密度,值越大表示在单位面积内累加器格子的数量越大,常见的范围是1到图像宽度或高度的一半

  • theta:极角 θ的密度,值越大表示在单位面积内累加器格子的数量越大,常见的范围是0到π,建议选择π/180,即1度

  • threshold:累加器阈值,值越大表示在原始空间中识别越少的线,常见的范围是50-200,具体的值取决于图像的复杂度和噪声水平

              
color 绘制直线使用的颜色

代码实践:

图片名称:1.jpg

import cv2  # 导入OpenCV库,用于计算机视觉任务
import numpy as np  # 导入NumPy库,用于数值计算

if __name__ == '__main__':  # Python的主程序入口
    # 1. 图片输入
    path = '1.jpg'
    image_np = cv2.imread(path)

    # 2. 灰度化
    image_np_gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)

    # 3. Canny算法
    image_edges = cv2.Canny(image_np_gray, 30, 70)  # 使用Canny算法检测图像边缘

    # 4. 霍夫变换
    drawing = np.zeros(image_np.shape, dtype=np.uint8)  # 创建一个与原始图像相同大小的黑色画布
    # 霍夫直线变换
    lines = cv2.HoughLines(
        image_edges,  # 边缘检测之后的图像
        1,  # ρ的精度(单位:像素)
        np.pi / 180,  # θ的精度(单位:弧度)
        80  # 累加器阈值(只有得票数超过此值的直线才会被检测到)
    )
    print(lines)  # 打印检测到的直线参数

    # 5. 图片输出
    for line in lines:  # 遍历所有检测到的直线
        print(line[0])  # 打印ρ和θ值
        rho, theta = line[0]  # 解包ρ(距离)和θ(角度)值

        # 求三角函数
        sin_theta = np.sin(theta)  # 计算θ的正弦值
        cos_theta = np.cos(theta)  # 计算θ的余弦值
        print(sin_theta, cos_theta)  # 打印三角函数值

        # 计算ρ的垂足的横纵坐标
        x0 = cos_theta * rho  # 计算垂足的x坐标
        y0 = sin_theta * rho  # 计算垂足的y坐标
        print(x0, y0)  # 打印垂足坐标

        # 只有一个点无法画线,因此需要给一个偏移量
        x1 = int(x0 + 1000 * (-sin_theta))  # 计算直线起点的x坐标
        y1 = int(y0 + 1000 * cos_theta)  # 计算直线起点的y坐标
        x2 = int(x0 - 1000 * (-sin_theta))  # 计算直线终点的x坐标
        y2 = int(y0 - 1000 * cos_theta)  # 计算直线终点的y坐标
        print((x1, y1))  # 打印起点坐标
        print((x2, y2))  # 打印终点坐标

        # 画线
        cv2.line(
            drawing,  # 目标图像
            (x1, y1),  # 起点坐标
            (x2, y2),  # 终点坐标
            (0, 0, 255),  # 颜色(红色)
            1,  # 线宽
            lineType=cv2.LINE_AA  # 抗锯齿线型
        )

    # 显示原始图像和检测到的直线
    cv2.imshow('image_np', image_np)  # 显示原始图像
    cv2.imshow('drawing', drawing)  # 显示带检测直线的图像
    cv2.waitKey(0)  # 等待按键,0表示无限等待
    cv2.imwrite('drawing.jpg', drawing)

运行结果:drawing.jpg

2 统计概率霍夫直线变换

前面的方法是标准霍夫变换,计算的点多,计算量大,得到的直线无法推出原图中直线的端点,因此OpenCV提供了统计概率霍夫直线变换。

统计概率霍夫直线变换是一种改进的霍夫变换,它获得直线之后,会检测原图中直线的点,找到两个端点的坐标,并通过坐标来计算直线的长度,与阈值对比,判断是否保留。

在虚拟仿真实验中,相关参数如下:

比之前的标准霍夫直线变换多了两个参数:

  • minLineLength 最短长度阈值,比此长度短的线,会被排除

  • maxLineGap 同一直线两点之间的最大距离

代码实践:

import cv2  # 导入OpenCV库,用于计算机视觉任务
import numpy as np  # 导入NumPy库,用于数值计算和数组操作

if __name__ == '__main__':  # Python的主程序入口,确保代码只在直接运行时执行
    # 1. 图片输入
    path = '1.jpg'  # 定义图片路径
    image_np = cv2.imread(path)  # 读取图片,返回一个NumPy数组表示的图像

    # 2. 灰度化
    image_np_gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)  # 将彩色图像转换为灰度图像
    # 灰度化可以减少计算量,同时保留图像的主要结构信息

    # 3. Canny算法
    image_edges = cv2.Canny(image_np_gray, 30, 70)  # 使用Canny算法检测图像边缘
    # Canny算法基于梯度计算,能够很好地检测图像中的边缘

    # 4. 霍夫变换
    drawing = np.zeros(image_np.shape, dtype=np.uint8)  # 创建一个与原始图像相同大小的黑色画布
    # 霍夫直线变换(概率霍夫变换)
    lines = cv2.HoughLinesP(
        image_edges,  # 边缘检测之后的图像
        1,  # ρ的精度(单位:像素)
        np.pi / 180,  # θ的精度(单位:弧度)
        80,  # 累加器阈值(只有得票数超过此值的直线才会被检测到)
        minLineLength=50,  # 最短直线长度(小于此长度的线段会被忽略)
        maxLineGap=10  # 最大线段间隙(小于此间隙的线段会被连接成一条直线)
    )
    print(lines)  # 打印检测到的直线端点坐标

    # 5. 图片输出
    for line in lines:  # 遍历所有检测到的直线
        x1, y1, x2, y2 = line[0]  # 解包直线起点和终点坐标

        # 画线
        cv2.line(
            drawing,  # 目标图像
            (x1, y1),  # 起点坐标
            (x2, y2),  # 终点坐标
            (0, 0, 255),  # 颜色(红色)
            1,  # 线宽
            lineType=cv2.LINE_AA  # 抗锯齿线型,使线条更平滑
        )

    # 显示原始图像和检测到的直线
    cv2.imshow('image_np', image_np)  # 显示原始图像
    cv2.imshow('drawing', drawing)  # 显示带检测直线的图像
    cv2.imwrite('drawing_2.jpg', drawing)  # 保存带检测直线的图像
    cv2.waitKey(0)  # 等待按键,0表示无限等待

"""
函数详细说明
cv2.imread(): 读取图像文件
参数: 文件路径
返回值: 图像数据的三维数组(BGR格式)
注意: 如果图像不存在,返回None

cv2.cvtColor(): 颜色空间转换
    参数1: 输入图像
    参数2: 转换类型(如COLOR_BGR2GRAY表示BGR转灰度)
    返回值: 转换后的图像

cv2.Canny(): Canny边缘检测算法
    参数1: 输入图像(灰度图)
    参数2: 低阈值
    参数3: 高阈值
    返回值: 边缘图像(二值图像,边缘为白色,背景为黑色)
    原理: 基于梯度计算,使用双阈值检测边缘

cv2.HoughLinesP(): 概率霍夫直线变换
    参数1: 输入图像(二值图像,通常是边缘检测结果)
    参数2: ρ的精度(单位:像素)
    参数3: θ的精度(单位:弧度)
    参数4: 累加器阈值(只有得票数超过此值的直线才会被检测到)
    参数5: minLineLength - 最短直线长度
    参数6: maxLineGap - 最大线段间隙
    返回值: 检测到的直线列表,每条直线表示为[x1, y1, x2, y2](起点和终点坐标)
    与标准霍夫变换的区别: 概率霍夫变换直接返回线段的端点,而不是ρ和θ参数

cv2.line(): 在图像上画直线
    参数1: 目标图像
    参数2: 起点坐标
    参数3: 终点坐标
    参数4: 颜色(B,G,R)
    参数5: 线宽
    参数6: 线型(如LINE_AA表示抗锯齿)

cv2.imshow(): 显示图像
    参数1: 窗口名称
    参数2: 要显示的图像

cv2.imwrite(): 保存图像到文件
    参数1: 文件路径
    参数2: 要保存的图像

cv2.waitKey(): 等待键盘输入
    参数: 等待时间(毫秒),0表示无限等待
    返回值: 按键的ASCII码
"""

代码运行结果:drawing_2.jpg

3 霍夫圆变换

3.1 传统方法的缺陷

霍夫圆变换与直线变换在原理上类似,但是表示方法有所不同,直线在霍夫空间用ρ和θ表示,圆在霍夫空间用x,y与r来表示,(x,y)表示在原始空间中圆坐标,r表示在原始空间中圆的半径。

原始空间中圆的方程为:

  (a,b)是圆心坐标,(x,y是圆上的点),r是圆的半径。

在霍夫空间中进行坐标转换:

此时,x和y是原始空间直角坐标系中某一点的坐标,abr是未知量。

每个原始空间的xy组合(圆上的点),在霍夫参数空间中都是一个圆锥,因此变成了求多个圆锥的交点,从二维问题上升到三维,数据量变大了很多,不会直接计算此问题,需要使用霍夫梯度法降维。

3.2 或负梯度法

霍夫梯度法利用了圆上点的梯度进行降维,降低了运算量,直接在直角坐标系中求解,霍夫空间主要用于累加器投票。

主要思想分为:先找圆心,再找半径。

霍夫梯度法的前提条件

圆上的边缘点的梯度方向(圆上点的切线的法线)会指向圆心。

1. 利用边缘点的梯度做一条经过该边缘的直线,所有的边缘点均如此此操作,最后在这些直接相交次数最多的地方(累加器投票机制)就是圆心。

2. 找到圆心之后,计算所有边缘点与圆心的距离,再次通过累加器投票机制找到票数最多的点就是半径。

需要注意的是,上面的步骤在真正的计算过程中是一起的,即只有一个霍夫空间辅助投票。

hough_method选HoughCircles表示霍夫圆变换

dp 累加器分辨率与图像分辨率的反比

建议从1开始调试,数值越大精度越低,但速度越快。

minDist 检测到的圆心之间的最小距离

如果检测到两个圆的圆心之间的距离小于minDist,则一个圆会被忽略,这个参数可以用于避免检测到重叠的圆。

param2 累加器阈值

用于检测圆心的最小投票数,此参数可以控制圆的数量和质量。

method统一使用霍夫梯度法 HOUGH_GRADIENT

color 线段颜色

import cv2  # 导入OpenCV库,用于计算机视觉任务
import numpy as np  # 导入NumPy库,用于数值计算和数组操作

if __name__ == '__main__':  # Python的主程序入口,确保代码只在直接运行时执行
    # 1. 图片输入
    path = '1.jpg'  # 定义图片路径
    image_np = cv2.imread(path)  # 读取图片,返回一个NumPy数组表示的图像

    # 2. 灰度化
    image_np_gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)  # 将彩色图像转换为灰度图像
    # 灰度化可以减少计算量,同时保留图像的主要结构信息

    # 3. Canny算法
    image_edges = cv2.Canny(image_np_gray, 30, 70)  # 使用Canny算法检测图像边缘
    # Canny算法基于梯度计算,能够很好地检测图像中的边缘

    # 4. 霍夫变换(圆检测)
    drawing = np.zeros(image_np.shape, dtype=np.uint8)  # 创建一个与原始图像相同大小的黑色画布
    # 霍夫圆变换
    circles = cv2.HoughCircles(
        image_edges,  # 边缘检测之后的图像
        cv2.HOUGH_GRADIENT,  # 检测方法:霍夫梯度法
        1,  # dp:累加器分辨率与图像分辨率的反比,1表示相同分辨率
        20,  # 最小圆心距离(圆心之间的最小距离,小于此距离的圆会被合并)
        param2=30  # 累加器投票阈值(只有得票数超过此值的圆才会被检测到)
    )
    print(circles)  # 打印检测到的圆参数(圆心坐标和半径)

    # 如果有数据
    if circles is not None:  # 检查是否检测到圆
        # 5. 图片输出
        for circle in circles[0]:  # 遍历所有检测到的圆
            print(circle)  # 打印圆的参数(x坐标, y坐标, 半径)

            # 画圆
            cv2.circle(
                drawing,  # 目标图像
                (int(circle[0]), int(circle[1])),  # 圆心坐标
                int(circle[2]),  # 半径
                (0, 0, 255),  # 颜色(红色)
                2,  # 线宽
                lineType=cv2.LINE_AA  # 抗锯齿线型,使线条更平滑
            )

            # 画圆心
            cv2.circle(
                drawing,  # 目标图像
                (int(circle[0]), int(circle[1])),  # 圆心坐标
                2,  # 半径很小,表示一个点
                (0, 0, 255),  # 颜色(红色)
                2,  # 线宽
                lineType=cv2.LINE_AA  # 抗锯齿线型
            )

    # 5. 图像输出
    cv2.imshow('image_np', image_np)  # 显示原始图像
    cv2.imshow('drawing', drawing)  # 显示带检测圆的图像
    cv2.imwrite('drawing_3.jpg', drawing)  # 保存带检测圆的图像
    cv2.waitKey(0)  # 等待按键,0表示无限等待

代码运行结果:drawing_3.jpg

梯度在霍夫圆检测中的应用
霍夫圆检测(Hough Circle Transform)特别依赖于梯度信息。其工作原理如下:

        边缘检测:首先使用Canny算法检测图像中的边缘,这本身就是基于梯度计算的。

        梯度方向计算:对于每个边缘点,计算其梯度方向。对于圆形物体,边缘点的梯度方向指向圆心。

        圆心投票:沿着每个边缘点的梯度方向画线,这些线会在圆心处相交。在累加器中,圆心位置的投票数会增加。

        圆心确定:累加器中投票数超过阈值的点被认为是圆心候选。

        半径确定:对于每个圆心候选,计算所有边缘点到圆心的距离,这些距离的直方图中出现最多的距离就是半径。

梯度方向的重要性
在霍夫圆检测中,梯度方向是关键信息:

对于圆形物体的边缘,梯度方向指向圆心

这大大减少了计算量,因为我们只需要沿着梯度方向搜索,而不是在所有可能的方向搜索

这使得霍夫圆检测比传统的霍夫直线检测更高效

实际例子:Canny边缘检测和霍夫圆检测中的梯度使用
在代码中:

cv2.Canny()函数内部使用梯度计算来检测边缘

cv2.HoughCircles()函数内部使用梯度方向信息来高效地检测圆

对于初学者来说,理解梯度是理解许多高级图像处理算法的基础。它提供了图像局部变化的信息,是边缘检测、圆检测等任务的基石。通过这个代码示例,你可以看到梯度计算如何应用于实际的图像处理任务中,特别是如何利用梯度方向信息来高效地检测圆形物体。

拓展:

什么是梯度?
在图像处理中,梯度表示图像强度变化的方向和幅度。它可以理解为图像中每个像素点的"坡度"或"变化率"。

梯度的数学表示
对于二维图像函数f(x,y),在点(x,y)处的梯度是一个向量:
∇f = [∂f/∂x, ∂f/∂y]

这个向量的:

方向:指向图像强度增加最快的方向

幅度:表示变化的强度,计算公式为:|∇f| = √((∂f/∂x)² + (∂f/∂y)²)

在图像处理中的实际计算
在实际图像处理中,我们使用离散近似来计算梯度。常用的方法是使用Sobel算子:

x方向梯度:Gx = ∂f/∂x ≈ f(x+1,y) - f(x-1,y)

y方向梯度:Gy = ∂f/∂y ≈ f(x,y+1) - f(x,y-1)

Sobel算子使用卷积核来实现这些计算:

x方向Sobel核: [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]

y方向Sobel核: [[-1, -2, -1], [0, 0, 0], [1, 2, 1]]

梯度在图像处理中的应用
边缘检测:边缘通常对应于梯度幅度较大的区域

Canny算法就是基于梯度计算来检测边缘的

在代码中,cv2.Canny()函数内部就使用了梯度计算

特征提取:梯度信息可以用于描述图像纹理和形状特征

图像增强:通过梯度信息可以增强图像的边缘和细节

霍夫变换:霍夫变换利用梯度方向信息来高效地检测直线

梯度方向的重要性
梯度方向指示了图像强度变化最快的方向。在边缘检测中:

垂直于边缘的方向是梯度方向

沿着边缘的方向是梯度方向的垂直方向

在霍夫变换中,我们不仅关心边缘的存在(梯度幅度),还关心边缘的方向(梯度方向)。这有助于我们更准确地检测直线。

实际例子:Canny边缘检测中的梯度使用
在代码中的cv2.Canny(image_np_gray, 30, 70)调用中,Canny算法内部执行以下步骤:

使用高斯滤波器平滑图像,减少噪声

使用Sobel算子计算x和y方向的梯度

计算梯度幅度和方向

应用非极大值抑制:只保留梯度方向上幅度最大的像素

使用双阈值(30和70)检测和连接边缘:

高于高阈值(70)的像素被认为是强边缘

介于低阈值(30)和高阈值之间的像素被认为是弱边缘

强边缘直接保留,弱边缘只有在连接到强边缘时才保留

对于初学者来说,理解梯度是理解许多高级图像处理算法的基础。它提供了图像局部变化的信息,是边缘检测、角点检测等任务的基石。通过这个代码示例,你可以看到梯度计算如何应用于实际的图像处理任务中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值