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

article/2025/9/23 19:38:55

文章目录

  • 前言
  • canny边缘检测算法主要流程
  • 一、高斯模糊
  • 二、图像梯度计算
  • 三、非极大值抑制
  • 四、双阈值边界跟踪


前言

本文通过介绍canny边缘检测原理与代码解析,希望能让大家深入理解canny边缘检测


canny边缘检测算法主要流程

canny边缘检测主要分为4个部分,本文分别从每一个部分进行解析并附代码。

  1. 图像降噪
  2. 梯度计算
  3. 非极大值抑制
  4. 双阈值边界跟踪

一、高斯模糊

图像去噪是进行边缘检测的第一步,通过去噪可以去除图像中的一些噪点,从而使边缘检测时免受噪点干扰。
一般去噪卷积核中心点的值由周围点像素均值决定,以3×3卷积核的8邻域为例,卷积中心点坐标值由周围坐标与本身的均值决定。
然而这种均值决定中心点的方法本质是周围点均采用相同的权重,但是当卷积核变大时,离中心点最远的点与最近的点占有相同权重显然是不合适的,这就需要我们的高斯核出场啦。
高斯模糊就是将卷积核中不同位置的点按照高斯分布(正态分布)进行权重分配,二维高斯分布公式如下:
二维高斯分布
这边我们已σ=1为例生成一个3×3的高斯卷积核。如下图所示,首先将普通的卷积核按(x,y)坐标,σ=1,代入上述二维高斯分布公式,得到权重具有高斯分布特性的卷积核。其次,由于卷积核所有点的权重和为1,所以需要将卷积核每个点的权重值再除以所有点的权重合。最后就完成了高斯卷积核,可以对图像进行去噪卷积操作啦。高斯卷积核
代码如下:

import numpy as np
from scipy import ndimage
from scipy.ndimage.filters import convolve
def gaussian_kernel(self, size, sigma=1):size = int(size) // 2x, y = np.mgrid[-size:size+1, -size:size+1]normal = 1 / (2.0 * np.pi * sigma**2)g = np.exp(-((x**2 + y**2) / (2.0*sigma**2))) * normalreturn g

二、图像梯度计算

要进行边缘检测,就需要得到图像梯度信息,根据图像的梯度幅值和梯度方向来确定边缘,一般均采用sobel算子对图像进行梯度幅值与梯度方向计算。
sobel算子分为垂直方向和水平方向两个模板,模板如下:
sobel算子
梯度幅值G和梯度方向θ计算公式如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码如下:


def sobel_filters(self, img):Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32)Ix = ndimage.filters.convolve(img, Kx)Iy = ndimage.filters.convolve(img, Ky)G = np.hypot(Ix, Iy)theta = np.arctan2(Iy, Ix)return (G, theta)

三、非极大值抑制

在获取图像的梯度幅值和梯度方向后,需要通过获取的梯度幅值和梯度方向对图像边缘进行非极大值抑制操作,由于梯度方向与边缘方向是垂直的,所以非极大值抑制可以有效的剔除一大部分非边缘点。
如下图所示,在非极大值抑制中,梯度方向是一条无向直线,也就是正负两侧均为梯度方向(即下图红线),并将梯度方向分为四个部分:

  • 垂直梯度方向(0,22.5]∪(-22.5,0]∪(157.5,180]∪(-180,157.5]
  • 45°梯度方向 [22.5,67.5)∪[-157.5,-112.5)
  • 水平梯度方向 [67.5,112.5]∪[-112.5,-67.5]
  • 135°梯度方向 (112.5, 157.5]∪[-67.5, -22.5]

确定了梯度方向后,就需要通过这个梯度方向上交点q’和r’的值来确定是否将中心点抑制,但是实际上我们是得不到交点值的,以下图8邻域我们只能得到周围8个点,这时有两种办法,1)线性插值,通过p、q两点的线性插值求得q’,r’同理可得。2)取相近点作为极大值,即取q点取代q’点。下方代码是以方法2为基础的。
在这里插入图片描述
具体代码如下,

def non_max_suppression(self,img, D):M, N = img.shapeZ = np.zeros((M,N), dtype=np.int32)angle = D * 180. / np.piangle[angle < 0] += 180for i in range(1,M-1):for j in range(1,N-1):try:q = 255r = 255#angle 0if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):q = img[i, j+1]r = img[i, j-1]#angle 45elif (22.5 <= angle[i,j] < 67.5):q = img[i+1, j-1]r = img[i-1, j+1]#angle 90elif (67.5 <= angle[i,j] < 112.5):q = img[i+1, j]r = img[i-1, j]#angle 135elif (112.5 <= angle[i,j] < 157.5):q = img[i-1, j-1]r = img[i+1, j+1]if (img[i,j] >= q) and (img[i,j] >= r):Z[i,j] = img[i,j]else:Z[i,j] = 0except IndexError as e:passreturn Z

四、双阈值边界跟踪

双阈值边界跟踪分为两个步骤:1)通过选取强弱阈值,将梯度幅值低与弱阈值的点置为0,大于强阈值的保留并标记为255。2)对于梯度幅值大于弱阈值但又小于高阈值的点,通过判断它的8邻域是否存在大于强阈值的点,若存在,则保留并置为255,若不存在,则舍弃并置为0。
具体代码如下:
步骤1:

def threshold(self, img):highThreshold = img.max() * self.highThresholdlowThreshold = highThreshold * self.lowThresholdM, N = img.shaperes = np.zeros((M,N), dtype=np.int32)weak = np.int32(self.weak_pixel)strong = np.int32(self.strong_pixel)strong_i, strong_j = np.where(img >= highThreshold)zeros_i, zeros_j = np.where(img < lowThreshold)weak_i, weak_j = np.where((img <= highThreshold) & (img >= lowThreshold))res[strong_i, strong_j] = strongres[weak_i, weak_j] = weakreturn (res)

步骤2:

def hysteresis(self, img):M, N = img.shapeweak = self.weak_pixelstrong = self.strong_pixelfor i in range(1, M-1):for j in range(1, N-1):if (img[i,j] == weak):try:if ((img[i+1, j-1] == strong) or (img[i+1, j] == strong) or (img[i+1, j+1] == strong)or (img[i, j-1] == strong) or (img[i, j+1] == strong)or (img[i-1, j-1] == strong) or (img[i-1, j] == strong) or (img[i-1, j+1] == strong)):img[i, j] = strongelse:img[i, j] = 0except IndexError as e:passreturn img

总体代码如下:

from scipy import ndimage
from scipy.ndimage.filters import convolve
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import osdef rgb2gray(rgb):r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2]gray = 0.2989 * r + 0.5870 * g + 0.1140 * breturn graydef load_data(dir_name):imgs = []for filename in os.listdir(dir_name):if os.path.isfile(dir_name + '/' + filename):img = mpimg.imread(dir_name + '/' + filename)img = rgb2gray(img)imgs.append(img)return imgsdef visualize(imgs, format=None, gray=False):plt.figure(figsize=(10, 20))for i, img in enumerate(imgs):if img.shape[0] == 3:img = img.transpose(1,2,0)plt_idx = i+1plt.subplot(2, 2, plt_idx)plt.imshow(img, format)plt.show()class cannyEdgeDetector:def __init__(self, imgs, sigma=1, kernel_size=5, weak_pixel=75, strong_pixel=255, lowthreshold=0.05, highthreshold=0.15):self.imgs = imgsself.imgs_final = []self.img_smoothed = Noneself.gradientMat = Noneself.thetaMat = Noneself.nonMaxImg = Noneself.thresholdImg = Noneself.weak_pixel = weak_pixelself.strong_pixel = strong_pixelself.sigma = sigmaself.kernel_size = kernel_sizeself.lowThreshold = lowthresholdself.highThreshold = highthresholdreturn def gaussian_kernel(self, size, sigma=1):size = int(size) // 2x, y = np.mgrid[-size:size+1, -size:size+1]normal = 1 / (2.0 * np.pi * sigma**2)g = np.exp(-((x**2 + y**2) / (2.0*sigma**2))) * normalreturn gdef sobel_filters(self, img):Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32)Ix = ndimage.filters.convolve(img, Kx)Iy = ndimage.filters.convolve(img, Ky)G = np.hypot(Ix, Iy)G = G / G.max() * 255theta = np.arctan2(Iy, Ix)return (G, theta)def non_max_suppression(self, img, D):M, N = img.shapeZ = np.zeros((M,N), dtype=np.int32)angle = D * 180. / np.piangle[angle < 0] += 180for i in range(1,M-1):for j in range(1,N-1):try:q = 255r = 255#angle 0if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):q = img[i, j+1]r = img[i, j-1]#angle 45elif (22.5 <= angle[i,j] < 67.5):q = img[i+1, j-1]r = img[i-1, j+1]#angle 90elif (67.5 <= angle[i,j] < 112.5):q = img[i+1, j]r = img[i-1, j]#angle 135elif (112.5 <= angle[i,j] < 157.5):q = img[i-1, j-1]r = img[i+1, j+1]if (img[i,j] >= q) and (img[i,j] >= r):Z[i,j] = img[i,j]else:Z[i,j] = 0except IndexError as e:passreturn Zdef threshold(self, img):highThreshold = img.max() * self.highThresholdlowThreshold = highThreshold * self.lowThresholdM, N = img.shaperes = np.zeros((M,N), dtype=np.int32)weak = np.int32(self.weak_pixel)strong = np.int32(self.strong_pixel)strong_i, strong_j = np.where(img >= highThreshold)zeros_i, zeros_j = np.where(img < lowThreshold)weak_i, weak_j = np.where((img <= highThreshold) & (img >= lowThreshold))res[strong_i, strong_j] = strongres[weak_i, weak_j] = weakreturn (res)def hysteresis(self, img):M, N = img.shapeweak = self.weak_pixelstrong = self.strong_pixelfor i in range(1, M-1):for j in range(1, N-1):if (img[i,j] == weak):try:if ((img[i+1, j-1] == strong) or (img[i+1, j] == strong) or (img[i+1, j+1] == strong)or (img[i, j-1] == strong) or (img[i, j+1] == strong)or (img[i-1, j-1] == strong) or (img[i-1, j] == strong) or (img[i-1, j+1] == strong)):img[i, j] = strongelse:img[i, j] = 0except IndexError as e:passreturn imgdef detect(self):imgs_final = []for i, img in enumerate(self.imgs):self.img_smoothed = convolve(img, self.gaussian_kernel(self.kernel_size, self.sigma))self.gradientMat, self.thetaMat = self.sobel_filters(self.img_smoothed)self.nonMaxImg = self.non_max_suppression(self.gradientMat, self.thetaMat)self.thresholdImg = self.threshold(self.nonMaxImg)img_final = self.hysteresis(self.thresholdImg)self.imgs_final.append(img_final)return self.imgs_finalimgs = load_data(dir_name='faces_imgs')
visualize(imgs, 'gray')
detector = cannyEdgeDetector(imgs, sigma=1, kernel_size=5, lowthreshold=0.09, highthreshold=0.17, weak_pixel=100)
imgs_final = detector.detect()
visualize(imgs_final, 'gray')

在这里插入图片描述
效果还不错



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

相关文章

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; 区别在于左外连接是查询完左表后&…

sql的四种连接——左外连接、右外连接、内连接、全连接

一、内连接 满足条件的记录才会出现在结果集中。 二、 左外连接&#xff08;left outer join&#xff0c;outer可省略&#xff09; 左表全部出现在结果集中&#xff0c;若右表无对应记录&#xff0c;则相应字段为NULL 举例说明&#xff1a; 客户表&#xff1a; 订单表&#x…

数据库的内连接、外连接(左外连接、右外连接、全外连接)以及交叉连接

SQL数据库的连接&#xff1a;内连接、和外连接&#xff08;左外连接、右外连接、和全连接&#xff09; 本次实验在MySQL数据库中进行&#xff0c;数据库初始表如下&#xff1a; 一、内连接&#xff1a;结果仅包含符合连接条件的两表中的行。如下&#xff1a; 二、外连接&#…

oracle左外连接、右外连接、完全外连接以及(+)号用法

准备工作 oracle连接分为&#xff1a; 左外连接&#xff1a;左表不加限制&#xff0c;保留左表的数据&#xff0c;匹配右表&#xff0c;右表没有匹配到的行中的列显示为null。右外连接&#xff1a;右表不加限制&#xff0c;保留右表的数据。匹配左表&#xff0c;左表没有匹配到…

左连接,右链接

用两个表&#xff08;a_table、b_table&#xff09;&#xff0c;关联字段a_table.a_id和b_table.b_id来演示一下MySQL的内连接、外连接&#xff08; 左(外)连接、右(外)连接、全(外)连接&#xff09;。 MySQL版本&#xff1a;Server version: 5.6.31 MySQL Community Server (…

全外连接、左外连接和右外连接区别(含举例)

全外连接是左外连接和右外连接的结合。 左外连接和右外连接的区别如下&#xff1a; 1、数据集合上的区别 &#xff08;1&#xff09;左外连接&#xff1a;是A与B的交集&#xff0c;然后连接A的所有数据。 &#xff08;2&#xff09;右外连接&#xff1a;是A与B的交集&#xff…

连接查询之外连接(左外链接和右外连接)

内连接&#xff1a; 假设A表和B表进行连接查询&#xff0c;使用内连接的话&#xff0c;凡是A表和B表能够匹配上的记录被查询出来&#xff0c;这就是内连接。A、B两张表没有主副之分&#xff0c;两张表是平等的。 外连接&#xff1a; 假设A表和B表进行连接查询&#xff0c;使用…

左连接、右连接、内连接、外连接

一.外连接 外连接分三类&#xff1a;左外连接&#xff08;LEFT OUTER JOIN&#xff09;、右外连接&#xff08;RIGHT OUTER JOIN&#xff09;和全外连接&#xff08;FULL OUTER JOIN&#xff09;。 1.左外连接 左连接 left join 是left outer join的简写&#xff0c;left j…

左外连接和右外连接的含义及区别,举例说明

左连接的含义是限制表2中的数据必须满足连接条件&#xff0c;而不管表1中的数据是否满足连接条件&#xff0c;均输出表1的内容。 右连接的含义是限制表1中的数据必须满足连接条件&#xff0c;而不管表2中的数据是否满足连接条件&#xff0c;均输出表2的内容。 创建表格A1,B1,…