Canny 边缘检测算法

article/2025/9/23 19:05:46

目录

一、边缘检测的步骤

二、最优边缘定义

三、Canny边缘检测算法步骤

1.对图像进行灰度化:

2.对图像进行高斯滤波:

3. 计算梯度幅值和方向 

4.非极大值(Non-Maximum Suppression)抑制

5.用双阈值算法检测和连接边缘

代码实现(按复杂到简单依次实现):

方法一:

方法二:

方法三:


一、边缘检测的步骤

1)滤波: 边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,
因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。常见的滤波方法主要有高斯滤波、均值滤波等。
2)增强: 增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将图像灰度点邻域
强度值有显著变化的点凸显出来。在具体编程实现时,可通过计算梯度幅值来确定。一般用sobel算子
3)检测: 经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定的应用中,这些
点并不是我们要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际工程中,常用
的方法是通过阈值化方法来检测。细化边缘(找到真正的边缘)

二、最优边缘定义

Canny是目前最优秀的边缘检测算法,其目标为找到一个最优的边缘,其最优边缘的定义为:
1、好的检测:算法能够尽可能的标出图像中的实际边缘
2、好的定位:标识出的边缘要与实际图像中的边缘尽可能接近
3、最小响应:图像中的边缘只能标记一次

三、Canny边缘检测算法步骤

1. 对图像进行灰度化
2. 对图像进行高斯滤波
3. 计算梯度幅值和方向(如使用Prewitt,Sobel算子等)
4 对梯度幅值进行非极大值抑制
5 用双阈值算法检测和连接边缘


1.对图像进行灰度化:

方法1:Gray=(R+G+B)/3;
方法2:Gray=0.299R+0.587G+0.114B

2.对图像进行高斯滤波:

        为了尽可能减少噪声对边缘检测结果的影响,所以必须滤除噪声以防止由噪声引起的错误检测。为了平滑图像,使用高斯滤波器与图像进行卷积,该步骤将平滑图像,以减少边缘检测器上明显的噪声影响。大小为(2k+1)x(2k+1)的高斯滤波器核的生成方程式由下式给出:

下面是一个sigma = 1.4,尺寸为3x3的高斯卷积核的例子(需要注意归一化):

若图像中一个3x3的窗口为A,要滤波的像素点为e,则经过高斯滤波之后,像素点e的亮度值为:

其中*为卷积符号,sum表示矩阵中所有元素相加求和。

重要的是需要理解,高斯卷积核大小的选择将影响Canny检测器的性能。尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个比较不错的trade off。

3. 计算梯度幅值和方向 

        图像中的边缘可以指向各个方向,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。边缘检测的算子(如Roberts,Prewitt,Sobel等)返回水平Gx和垂直Gy方向的一阶导数值,由此便可以确定像素点的梯度G和方向theta 。

其中G为梯度强度(幅值), theta表示梯度方向,arctan为反正切函数。下面以Sobel算子为例讲述如何计算梯度强度和方向。

x和y方向的Sobel算子分别为:

其中Sx表示x方向的Sobel算子,用于检测y方向的边缘; Sy表示y方向的Sobel算子,用于检测x方向的边缘(边缘方向和梯度方向垂直)。在直角坐标系中,Sobel算子的方向如下图所示。

 若图像中一个3x3的窗口为A,要计算梯度的像素点为e,则和Sobel算子进行卷积之后,像素点e在x和y方向的梯度值分别为: 

 

其中*为卷积符号,sum表示矩阵中所有元素相加求和。根据公式(3-2)便可以计算出像素点e的梯度和方向。

4.非极大值(Non-Maximum Suppression)抑制

非极大值抑制是一种边缘稀疏技术,非极大值抑制的作用在于“瘦”边。对图像进行梯度计算后,仅仅基于梯度值提取的边缘仍然很模糊。对于标准3,对边缘有且应当只有一个准确的响应。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0,对梯度图像中每个像素进行非极大值抑制的算法是:

  • 将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
  • 如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。

通常为了更加精确的计算,在跨越梯度方向的两个相邻像素之间使用线性插值来得到要比较的像素梯度,现举例如下:

如图3-2所示,将梯度分为8个方向,分别为E、NE、N、NW、W、SW、S、SE,其中0代表045度,1代表4590度,2代表-90~ -45度,3代表-45~ 0度。像素点P的梯度方向为theta,则像素点P1和P2的梯度线性插值为:

因此非极大值抑制的伪代码描写如下:

需要注意的是,如何标志方向并不重要,重要的是梯度方向的计算要和梯度算子的选取保持一致。

非极大值抑制

5.用双阈值算法检测和连接边缘

要进行双阈值处理的原因:

        完成非极大值抑制后,会得到一个以梯度局部极小值构成的图像,在图像上显示的就是有许多离散的点点,因此要把真正是边缘的点连接起来,同时去除孤立的噪声点。

思想:

  • 人工给定两个阈值,一个是低阈值TL,一个高阈值TH,
  • 如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素,该位置的像素值置255;
  • 如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;
  • 如果边缘像素的梯度值小于低阈值,则会被抑制,该位置的像素值置0。

双阈值检测: 

大于高阈值为强边缘,小于低阈值不是边缘。介于中间是弱边缘。
阈值的选择取决于给定输入图像的内容。

算法步骤:

1、选取系数TH和TL,比率为2:1或3:1。(一般取TH=0.3或0.2,TL=0.1);
2、将小于低阈值的点抛弃,赋0;将大于高阈值的点立即标记(这些点为确定边缘点),赋1或255;
3、将小于高阈值,大于低阈值的点使用8连通区域确定(即:只有与TH像素连接时才会被接受,成为边缘点,赋1或255)具体的,当强边缘的8邻域内有弱边缘像素,则将如边缘像素变成强边缘,赋值1或255,或者反过来理解,只要弱边缘的8邻域内有强边缘,则如边缘变成强边缘,赋值1或255


代码实现(按复杂到简单依次实现):

方法一:

# -*- coding: utf-8 -*-
"""
@author: 绯雨千叶Canny 边缘检测算法
"""
import numpy as np
import matplotlib.pyplot as plt
import mathif __name__ == '__main__':pic_path = '../img/lrn.jpg'img = plt.imread(pic_path)if pic_path[-4:] == '.png':  # .png图片在这里的存储格式是0到1的浮点数,所以要扩展到255再计算img = img * 255  # 还是浮点数类型img = img.mean(axis=-1)  # 取均值就是灰度化了# 1、高斯平滑# sigma = 1.52  # 高斯平滑时的高斯核参数,标准差,可调sigma = 0.5  # 高斯平滑时的高斯核参数,标准差,可调dim = int(np.round(6 * sigma + 1))  # round是四舍五入函数,根据标准差求高斯核是几乘几的,也就是维度if dim % 2 == 0:  # 最好是奇数,不是的话加一dim += 1Gaussian_filter = np.zeros([dim, dim])  # 存储高斯核,这是数组不是列表了tmp = [i - dim // 2 for i in range(dim)]  # 生成一个序列n1 = 1 / (2 * math.pi * sigma ** 2)  # 计算高斯核n2 = -1 / (2 * sigma ** 2)for i in range(dim):for j in range(dim):Gaussian_filter[i, j] = n1 * math.exp(n2 * (tmp[i] ** 2 + tmp[j] ** 2))Gaussian_filter = Gaussian_filter / Gaussian_filter.sum()dx, dy = img.shapeimg_new = np.zeros(img.shape)  # 存储平滑之后的图像,zeros函数得到的是浮点型数据tmp = dim // 2img_pad = np.pad(img, ((tmp, tmp), (tmp, tmp)), 'constant')  # 边缘填补for i in range(dx):for j in range(dy):img_new[i, j] = np.sum(img_pad[i:i + dim, j:j + dim] * Gaussian_filter)plt.figure(1)plt.subplot(221)plt.imshow(img_new.astype(np.uint8), cmap='gray')  # 此时的img_new是255的浮点型数据,强制类型转换才可以,gray灰阶plt.axis('off')# 2、求梯度。以下两个是滤波求梯度用的sobel矩阵(检测图像中的水平、垂直和对角边缘)sobel_kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])sobel_kernel_y = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])img_tidu_x = np.zeros(img_new.shape)  # 存储梯度图像img_tidu_y = np.zeros([dx, dy])img_tidu = np.zeros(img_new.shape)img_pad = np.pad(img_new, ((1, 1), (1, 1)), 'constant')  # 边缘填补,根据上面矩阵结构所以写1for i in range(dx):for j in range(dy):img_tidu_x[i, j] = np.sum(img_pad[i:i + 3, j:j + 3] * sobel_kernel_x)  # x方向img_tidu_y[i, j] = np.sum(img_pad[i:i + 3, j:j + 3] * sobel_kernel_y)  # y方向img_tidu[i, j] = np.sqrt(img_tidu_x[i, j] ** 2 + img_tidu_y[i, j] ** 2)img_tidu_x[img_tidu_x == 0] = 0.00000001angle = img_tidu_y / img_tidu_xplt.subplot(222)plt.imshow(img_tidu.astype(np.uint8), cmap='gray')plt.axis('off')# 3、非极大值抑制img_yizhi = np.zeros(img_tidu.shape)for i in range(1, dx - 1):for j in range(1, dy - 1):flag = True  # 在8邻域内是否要抹去做个标记temp = img_tidu[i - 1:i + 2, j - 1:j + 2]  # 梯度幅值的8邻域矩阵if angle[i, j] <= -1:  # 使用线性插值法判断抑制与否num_1 = (temp[0, 1] - temp[0, 0]) / angle[i, j] + temp[0, 1]num_2 = (temp[2, 1] - temp[2, 2]) / angle[i, j] + temp[2, 1]if not (img_tidu[i, j] > num_1 and img_tidu[i, j] > num_2):flag = Falseelif angle[i, j] >= 1:num_1 = (temp[0, 2] - temp[0, 1]) / angle[i, j] + temp[0, 1]num_2 = (temp[2, 0] - temp[2, 1]) / angle[i, j] + temp[2, 1]if not (img_tidu[i, j] > num_1 and img_tidu[i, j] > num_2):flag = Falseelif angle[i, j] > 0:num_1 = (temp[0, 2] - temp[1, 2]) * angle[i, j] + temp[1, 2]num_2 = (temp[2, 0] - temp[1, 0]) * angle[i, j] + temp[1, 0]if not (img_tidu[i, j] > num_1 and img_tidu[i, j] > num_2):flag = Falseelif angle[i, j] < 0:num_1 = (temp[1, 0] - temp[0, 0]) * angle[i, j] + temp[1, 0]num_2 = (temp[1, 2] - temp[2, 2]) * angle[i, j] + temp[1, 2]if not (img_tidu[i, j] > num_1 and img_tidu[i, j] > num_2):flag = Falseif flag:img_yizhi[i, j] = img_tidu[i, j]plt.subplot(223)plt.imshow(img_yizhi.astype(np.uint8), cmap='gray')plt.axis('off')# 4、双阈值检测,连接边缘。遍历所有一定是边的点,查看8邻域是否存在有可能是边的点,进栈lower_boundary = img_tidu.mean() * 0.5high_boundary = lower_boundary * 3  # 这里我设置高阈值是低阈值的三倍zhan = []for i in range(1, img_yizhi.shape[0] - 1):  # 外圈不考虑了for j in range(1, img_yizhi.shape[1] - 1):if img_yizhi[i, j] >= high_boundary:  # 取,一定是边的点img_yizhi[i, j] = 255zhan.append([i, j])elif img_yizhi[i, j] <= lower_boundary:  # 舍img_yizhi[i, j] = 0while not len(zhan) == 0:temp_1, temp_2 = zhan.pop()  # 出栈a = img_yizhi[temp_1 - 1:temp_1 + 2, temp_2 - 1:temp_2 + 2]if (a[0, 0] < high_boundary) and (a[0, 0] > lower_boundary):img_yizhi[temp_1 - 1, temp_2 - 1] = 255  # 这个像素点标记为边缘zhan.append([temp_1 - 1, temp_2 - 1])  # 进栈if (a[0, 1] < high_boundary) and (a[0, 1] > lower_boundary):img_yizhi[temp_1 - 1, temp_2] = 255zhan.append([temp_1 - 1, temp_2])if (a[0, 2] < high_boundary) and (a[0, 2] > lower_boundary):img_yizhi[temp_1 - 1, temp_2 + 1] = 255zhan.append([temp_1 - 1, temp_2 + 1])if (a[1, 0] < high_boundary) and (a[1, 0] > lower_boundary):img_yizhi[temp_1, temp_2 - 1] = 255zhan.append([temp_1, temp_2 - 1])if (a[1, 2] < high_boundary) and (a[1, 2] > lower_boundary):img_yizhi[temp_1, temp_2 + 1] = 255zhan.append([temp_1, temp_2 + 1])if (a[2, 0] < high_boundary) and (a[2, 0] > lower_boundary):img_yizhi[temp_1 + 1, temp_2 - 1] = 255zhan.append([temp_1 + 1, temp_2 - 1])if (a[2, 1] < high_boundary) and (a[2, 1] > lower_boundary):img_yizhi[temp_1 + 1, temp_2] = 255zhan.append([temp_1 + 1, temp_2])if (a[2, 2] < high_boundary) and (a[2, 2] > lower_boundary):img_yizhi[temp_1 + 1, temp_2 + 1] = 255zhan.append([temp_1 + 1, temp_2 + 1])for i in range(img_yizhi.shape[0]):for j in range(img_yizhi.shape[1]):if img_yizhi[i, j] != 0 and img_yizhi[i, j] != 255:img_yizhi[i, j] = 0# 绘图plt.subplot(224)plt.imshow(img_yizhi.astype(np.uint8), cmap='gray')plt.axis('off')  # 关闭坐标刻度值plt.show()

效果展示:

方法二:

#!/usr/bin/env python
# encoding=gbk
"""
@author: 绯雨千叶Canny边缘检测:优化的程序
"""
import cv2def CannyThreshold(lowThreshold):  detected_edges = cv2.GaussianBlur(gray, (3, 3), 0)  # 高斯滤波detected_edges = cv2.Canny(detected_edges,lowThreshold,lowThreshold*ratio,apertureSize = kernel_size)  # 边缘检测dst = cv2.bitwise_and(img,img,mask = detected_edges)  # 用原始颜色添加到检测的边缘上cv2.imshow('canny demo', dst)lowThreshold = 0  
max_lowThreshold = 100  
ratio = 3  
kernel_size = 3  img = cv2.imread('../img/lrn.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  # 转换彩色图像为灰度图
cv2.namedWindow('canny demo')  #设置调节杠
'''
下面是第二个函数,cv2.createTrackbar()
共有5个参数,其实这五个参数看变量名就大概能知道是什么意思了
第一个参数,是这个trackbar对象的名字
第二个参数,是这个trackbar对象所在面板的名字
第三个参数,是这个trackbar的默认值,也是调节的对象
第四个参数,是这个trackbar上调节的范围(0~count)
第五个参数,是调节trackbar时调用的回调函数名
'''
cv2.createTrackbar('Min threshold','canny demo',lowThreshold, max_lowThreshold, CannyThreshold)  CannyThreshold(0)  # initialization  
if cv2.waitKey(0) == 27:  # 按esc退出cv2.destroyAllWindows()

效果展示:

方法三:

#!/usr/bin/env python
# encoding=gbk
"""
@author: 绯雨千叶Canny边缘检测:二次优化的程序
"""
import cv2'''
cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])   
必要参数:
第一个参数是需要处理的原图像,该图像必须为单通道的灰度图;
第二个参数是滞后阈值1;
第三个参数是滞后阈值2。
'''
img = cv2.imread("../img/lrn.jpg", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("canny", cv2.Canny(gray, 200, 300))
cv2.waitKey(0)

效果展示:

 


http://chatgpt.dhexx.cn/article/HqV0Z8o8.shtml

相关文章

Canny算法

Canny Canny分为5个步骤 1)、使用高斯滤波器&#xff0c;以平滑图像&#xff0c;滤除噪声。 高斯滤波器是一种平滑空间滤波器&#xff0c;用于模糊处理和降低噪声。我们的高斯滤波器通过以下公式得到。 我们运用该公式计算出高斯卷积核&#xff0c;如k越大&#xff0c;检测…

OpenCv之Canny

目录 一、自适应阈值 二、边缘检测Canny 一、自适应阈值 引入前提:在前面的部分我们使用是全局闻值&#xff0c;整幅图像采用同一个数作为闻值。当时这种方法并不适应与所有情况&#xff0c;尤其是当同一幅图像上的不同部分的具有不同亮度时。这种情况下我们需要采用自适应闻…

图像处理——Canny算子

首先感谢以下两位的渊博知识&#xff1a; &#xff08;1&#xff09;爱鱼 https://www.cnblogs.com/mightycode/p/6394810.html &#xff08;2&#xff09;mitutao https://www.cnblogs.com/love6tao/p/5152020.html 图像边缘信息主要集中在高频段&#xff0c;通常…

Canny算子与霍夫变换检测圆与直线

目录 引言 一、canny算子 二、canny算子代码 三、霍夫变换检测直线 四、霍夫变换检测直线代码 五、霍夫变换检测直线效果 六、霍夫变换检测圆 七、霍夫变换检测圆代码 八、霍夫变换检测圆效果 引言 canny算子是计算机视觉最常用的一种算子&#xff0c;是目前一种非常流行…

OpenCV——Canny边缘检测(cv2.Canny())

Canny边缘检测 Canny 边缘检测是一种使用多级边缘检测算法检测边缘的方法。1986 年&#xff0c;John F. Canny 发 表了著名的论文 A Computational Approach to Edge Detection&#xff0c;在该论文中详述了如何进行边缘 检测。 Canny()边缘检测步骤 Canny 边缘检测分为如下…

(十一)Canny 边缘检测算法

Canny边缘检测算法 一、边缘检测的步骤 1&#xff09;滤波&#xff1a; 边缘检测的算法主要是基于图像强度的一阶和二阶导数&#xff0c;但导数通常对噪声很敏感&#xff0c; 因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。常见的滤波方法主要有高斯滤波、均值滤波…

Canny边缘检测算法的实现

图像边缘信息主要集中在高频段&#xff0c;通常说图像锐化或检测边缘&#xff0c;实质就是高频滤波。我们知道微分运算是求信号的变化率&#xff0c;具有加强高频分量的作用。在空域运算中来说&#xff0c;对图像的锐化就是计算微分。由于数字图像的离散信号&#xff0c;微分运…

【canny边缘检测】canny边缘检测原理及代码详解

文章目录 前言canny边缘检测算法主要流程一、高斯模糊二、图像梯度计算三、非极大值抑制四、双阈值边界跟踪 前言 本文通过介绍canny边缘检测原理与代码解析&#xff0c;希望能让大家深入理解canny边缘检测 canny边缘检测算法主要流程 canny边缘检测主要分为4个部分&#xff…

Canny边缘检测原理

一. Canny基本思想 1. 边缘检测 解析&#xff1a;边缘是对象和背景之间的边界&#xff0c;还能表示重叠对象之间的边界。边缘检测是图像分割的一部分&#xff0c;图像分割的目的是识别出图像中的区域。边缘检测是定位边缘像素的过程&#xff0c;而边缘增强是增加边缘和背景之…

Canny边缘检测

边缘检测发展 Canny 边缘检测是一种从不同视觉对象中提取有用结构信息并显着减少要处理的数据量的技术。它已广泛应用于各种计算机视觉系统。 Canny 发现&#xff0c;在不同的视觉系统上应用边缘检测的要求是比较相似的。因此&#xff0c;可以在各种情况下实施满足这些要求的边…

Canny边缘检测算法(python 实现)

文章目录 最优边缘准则算法实现步骤1. 应用高斯滤波来平滑(模糊)图像&#xff0c;目的是去除噪声2. 计算梯度强度和方向3. 应用非最大抑制技术NMS来消除边误检4. 应用双阈值的方法来决定可能的&#xff08;潜在的&#xff09;边界5. 利用滞后技术来跟踪边界 opencv实现Canny边缘…

左外连接和右外连接是什么样的?

1左外连接 左表的记录都会出现在结果集中&#xff0c;并上右表与之对应的部分&#xff0c;如果右表没有&#xff0c;使用null填充。 举例。 select * from people p left join role r p.idr.id 左外连接结果&#xff1a; 2、右外连接 右表的记录都会出现在结果集中&#xf…

sql左外连接和右外连接的区别

一、数据集合不同 1、左外连接&#xff1a;是A和B的交集再并上A的所有数据。 2、右外连接&#xff1a;是A和B的交集再并上B的所有数据。 二、语法不同 1、左外连接&#xff1a;SELECT *FROM aLEFT OUTER JOIN bON a.ageId b.id。 2、右外连接&#xff1a;SELECT *FROM a…

MySQL自连接和内连接和外连接_左外连接+右外连接

自连接&#xff1a;将一张表看作两张表 练习&#xff1a;查询员工id&#xff0c;员工姓名及其管理者的id和姓名select emp.employee_id,emp.last_name,mgr.employee_id,mgr.last_name from employees emp,employees mgr where emp.manager_id mgr.employee_id;内连接 只是把左…

SQL--左外连接(LEFT JOIN)

LEFT JOIN 关键字从左表&#xff08;table1&#xff09;返回所有的行&#xff0c;即使右表&#xff08;table2&#xff09;中没有匹配。如果右表中没有匹配&#xff0c;则结果为 NULL。 SQL LEFT JOIN 语法: SELECT column_name(s) FROM table1 LEFT JOIN table2 ON table1.c…

内连接、左连接、左外连接、右连接的区别

一&#xff1a;测试例子 存在如下两张表&#xff1a;年级与班级&#xff0c;表内容如下 班级表 二&#xff1a;内连接与自然连接 1&#xff1a;自然连接 对于select * from gradeclass gc where gc.grade_id in (select id from grade)&#xff0c;其结果如下图 其查询是把符…

数据库之SQL(基本连接,内连接,左外连接,右外连接,全外连接,交叉连接,自连接)

之前的博客内容我们分享了数据表的查询与管理&#xff0c;但那只是针对数据库中的一个表格进行的查询管理&#xff0c;现在如果我们想要同时看到两个数据表中的数据的话&#xff0c;那要怎么办呢&#xff1f;采用多连接查询的方式。 SQL中有哪几种多连接的方式&#xff1f; 有七…

左连接、右连接、内连接、外连接简单说明

例如有两个表: test1: test2: 内连接&#xff1a;&#xff08;inner join on&#xff09;返回两个表的交集。 例如&#xff1a; select * from test1 a inner join test2 b on a.idb.id; 结果&#xff1a; 外连接&#xff1a;返回两个表的并集。&#xff08;在此就不做截图…

MySQL之左外连接右外连接

外连接&#xff1a; 假设A和B表进行连接&#xff0c;使用外连接的话&#xff0c; AB两张表中有一张表是主表&#xff0c;一张表是副表&#xff0c;主要查询主表中的数据&#xff0c;捎带着查询副表&#xff0c; 当副表中的数据没有和主表中的数据匹配上&#xff0c;副表自动模…

秒懂sql中的左外连接

左外连接&#xff1a; 1.语法&#xff1a; SELECT 查询字段 FROM 查询表格&#xff08;左表&#xff09; LEFT JOIN 右表 ON 查询条件 2.左外连接查询的就是左表&#xff0c;那干嘛加个右表&#xff0c;岂不是没有作用&#xff1a; 区别在于左外连接是查询完左表后&…