Canny 边缘检测算法-python实现(附代码)

这篇具有很好参考价值的文章主要介绍了Canny 边缘检测算法-python实现(附代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


摘要 : Canny 边缘检测算法由计算机科学家 John F. Canny 于 1986 年提出的。其不仅提供了算法,还带来了一套边缘检测的理论,分阶段的解释如何实现边缘检测。Canny 检测算法包含下面几个阶段:
  • 图像灰度化
  • 高斯模糊处理
  • 图像梯度、梯度幅值、梯度方向计算
  • NMS(非极大值抑制)
  • 双阈值的边界选取

1、调用opencv进行canny边缘检测

如果你只是想应用canny得到图片的边缘的话,那么就没有必要往下阅读canny的具体原理与实现了。因为python的opencv库中提供了很好的功能函数来实现这一功能。我们以一个示例来说明如何使用opencv进行canny边缘检测:

这是原图与使用opencv进行canny边缘检测的结果图:

Canny 边缘检测算法-python实现(附代码)

这是代码实现:

import cv2 #导入opencv库
 #读取图片
img = cv2.imread("images/2007\_000032.jpg")
#进行canny边缘检测
edge = cv2.Canny(img,50,150)
#保存结果
cv2.imwrite('test.jpg',edge)

这四行代码的关键在于 cv2.Canny 函数。我们对它的参数进行详细的解读。希望能对你有帮助。OpenCV-Python中Canny函数的原型为:

cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])

必要参数:

  • 第一个参数是需要处理的原图像,该图像必须为单通道的灰度图;
  • 第二个参数是阈值1;
  • 第三个参数是阈值2。

其中较大的阈值2用于检测图像中明显的边缘,但一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的。所以这时候用较小的第一个阈值用于将这些间断的边缘连接起来。

可选参数中apertureSize就是Sobel算子的大小。而L2gradient参数是一个布尔值,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开放),否则使用L1范数(直接将两个方向导数的绝对值相加)。

到这其实已经可以将canny边缘检测应用到你的项目中了。当然如果你想了解canny边缘检测的原理的话,请继续往下阅读。

2、图像灰度化

对于一张图片,当我们只关心其边界的时候,单通道的图片已经足够提供检测出边界的信息。所以我们可以将R、G、B的3通道图片乃至更高维的高光谱遥感图像进行灰度化。灰度化实际上是一种降维操作,它减少了冗余数据从而降低了计算开销。以下是对RGB图片灰度化的方法:

# 灰度化
def gray(self, img\_path):
	"""
	计算公式:
	Gray(i,j) = [R(i,j) + G(i,j) + B(i,j)] / 3
	or :
	Gray(i,j) = 0.299 \* R(i,j) + 0.587 \* G(i,j) + 0.114 \* B(i,j)
	"""
	
	# 读取图片
	img = plt.imread(img_path)
	# BGR 转换成 RGB 格式
	img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
	# 灰度化
	img_gray = np.dot(img_rgb[...,:3], [0.299, 0.587, 0.114])
	return img_gray

3、高斯模糊处理

高斯模糊实际上是对灰度化后的图像去噪。从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积。进行高斯滤波之前,需要先得到一个高斯滤波器(kernel)。如何得到一个高斯滤波器呢?其实就是将高斯函数离散化,将滤波器中对应的横纵坐标索引代入高斯函数,即可得到对应的值。不同尺寸的滤波器,得到的值也不同,下面是二维高斯函数与 (2k+1)x(2k+1) 滤波器的计算公式 :

Canny 边缘检测算法-python实现(附代码)

高斯滤波常用尺寸为 5x5,σ=1.4 的高斯滤波器。下面是 5x5 高斯滤波器的实现代码:

# 去除噪音 - 使用 5x5 的高斯滤波器
def smooth(self, img\_gray):
	# 生成高斯滤波器
	"""
	要生成一个 (2k+1)x(2k+1) 的高斯滤波器,滤波器的各个元素计算公式如下:
	H[i, j] = (1/(2\*pi\*sigma\*\*2))\*exp(-1/2\*sigma\*\*2((i-k-1)\*\*2 + (j-k-1)\*\*2))
	"""
	sigma1 = sigma2 = 1.4
	gau_sum = 0
	gaussian = np.zeros([5, 5])
	for i in range(5):
		for j in range(5):
			gaussian[i, j] = math.exp((-1/(2*sigma1*sigma2))*(np.square(i-3)+ np.square(j-3)))/(2*math.pi*sigma1*sigma2)
			gau_sum = gau_sum + gaussian[i, j]
	
	# 归一化处理
	gaussian = gaussian / gau_sum
	
	# 高斯滤波
	W, H = img_gray.shape
	new_gray = np.zeros([W-5, H-5])
	
	for i in range(W-5):
		for j in range(H-5):
			new_gray[i, j] = np.sum(img_gray[i:i+5, j:j+5] * gaussian)
	
	return new_gray

4、图像梯度、梯度幅值、梯度方向计算

这个步骤的重要性不言而喻。直观感受上来讲我们知道一个图像上处于边界附近位置的像素值变化较大。而处于物体内部位置的像素值大多相近。这样我们可以计算当前像素与其附近像素的像素值的差值判断该像素处于物体内部还是边界。这个差值我们称为图像梯度。梯度幅值、梯度方向由图像梯度计算而来。
具体而言,我们用一阶导数来计算梯度:

Canny 边缘检测算法-python实现(附代码)

对于上式,实际操作时就是用当前像素的下一个像素减去当前像素。此时 Δ x = 1 \Delta x=1 Δx=1;

梯度包含x方向的梯度与y方向的梯度。它们是两个向量。梯度幅值是这两个向量的向量和:
Canny 边缘检测算法-python实现(附代码)

既然梯度幅值是一个向量,那么我们需要计算它的方向:

Canny 边缘检测算法-python实现(附代码)

我们用如下代码实现:

# 计算梯度幅值
def gradients(self, new_gray):
	"""
	:type: image which after smooth
	:rtype:
		dx: gradient in the x direction
		dy: gradient in the y direction
		M: gradient magnitude
		theta: gradient direction
	"""
	W, H = new_gray.shape
	dx = np.zeros([W-1, H-1])
	dy = np.zeros([W-1, H-1])
	M = np.zeros([W-1, H-1])
	theta = np.zeros([W-1, H-1])
	
	for i in range(W-1):
		for j in range(H-1):
		dx[i, j] = new\_gray[i+1, j] - new\_gray[i, j]
		dy[i, j] = new\_gray[i, j+1] - new\_gray[i, j]
		# 图像梯度幅值作为图像强度值
		M[i, j] = np.sqrt(np.square(dx[i, j]) + np.square(dy[i, j]))
		# 计算 θ - artan(dx/dy)
		theta[i, j] = math.atan(dx[i, j] / (dy[i, j] + 0.000000001))
	return dx, dy, M, theta

在计算得到的梯度幅值中我们实际上已经得到了图像的边界(即函数返回值中的M)。如下:
Canny 边缘检测算法-python实现(附代码)

但是,很容易发现这个边缘存在两个问题:

  • 边缘较粗;
  • 很多边缘断断续续。

针对这两个问题便有了以下两个步骤NMS、双阈值边界选取。

5、NMS(非极大值抑制)

理想情况下,最终得到的边缘应该是很细的。因此,需要执行非极大值抑制以使边缘变细。原理很简单:遍历梯度矩阵上的所有点,并保留边缘方向上具有极大值的像素。就像下面这幅图一样。图中黑色和灰色表示边界。我们通过NMS找出其中的局部最大值(也就是图中的黑色)而把其他位置(也就是图中的灰色)的值取0。

Canny 边缘检测算法-python实现(附代码)

下面说说 NMS 的细节内容。NMS在八个领域:上,下,左,右,左上,左下,右上,右下上进行(当然,比较的时候不需要将该点与其它八个点比较。只需要将其与其梯度方向上的点比较即可。这个很好理解。因为我们只需要当前值在它所属的边缘上是局部最大值即可,而不需要它在其它边缘上也是局部最大)如下图所示,C 周围的 8 个点就是其附近的八个领域。

Canny 边缘检测算法-python实现(附代码)

NMS 是要找出局部最大值,因此,需要将当前的像素的梯度,与其他方向进行比较。如下图所示,g1,g2,g3,g4 分别是 C 八个领域中的 4 个点,蓝线是 C 的梯度方向。如果 C 是局部最大值的话,C 点的梯度幅值就要大于梯度方向直线与 g1g2,g4g3 两个交点的梯度幅值,即大于点 dTemp1 和 dTemp2 的梯度幅值。上面提到这种方法无法达到最好的效果,因为 dTemp1 和 dTemp2 不是整像素,而是亚像素。亚像素的意思就是在两个物理像素之间还有像素。那么,亚像素的梯度幅值怎么求?可以使用线性插值的方法,计算 dTemp1 在 g1,g2 之间的权重,就可以得到其梯度幅值。计算公式如下:

weight = |gx| / |gy| or |gy| / |gx|
dTemp1 = weight*g1 + (1-weight)*g2
dTemp2 = weight*g3 + (1-weight)*g4

计算时分两种情况(都是比较当前像素与dtemp1与dtemp2的大小,大于这两个值则保留,小于其中任意一个则将其值取0):

  • 下面两幅图是 y 方向梯度值比较大的情况,即梯度方向靠近 y 轴。所以,g2 和 g4 在 C 的上下位置,此时 weight = |gy| / |gx| 。左边的图是 x,y 方向梯度符号相同的情况,右边是 x,y 方向梯度符号相反的情况。
    Canny 边缘检测算法-python实现(附代码)

  • 下面两幅图是 x 方向梯度值比较大的情况,即梯度方向靠近 x 轴。所以,g2 和 g4 在 C 的左右位置,此时 weight = |gy| / |gx| 。左边的图是 x,y 方向梯度符号相同的情况,右边是 x,y 方向梯度符号相反的情况。

Canny 边缘检测算法-python实现(附代码)

代码实现如下:

def NMS(self, M, dx, dy):
	d = np.copy(M)
	W, H = M.shape
	NMS = np.copy(d)
	NMS[0, :] = NMS[W-1, :] = NMS[:, 0] = NMS[:, H-1] = 0
	for i in range(1, W-1):
		for j in range(1, H-1):
			# 如果当前梯度为0,该点就不是边缘点
			if M[i, j] == 0:
				NMS[i, j] = 0
			else:
				gradX = dx[i, j] # 当前点 x 方向导数
				gradY = dy[i, j] # 当前点 y 方向导数
				gradTemp = d[i, j] # 当前梯度点
				
				# 如果 y 方向梯度值比较大,说明导数方向趋向于 y 分量
				if np.abs(gradY) > np.abs(gradX):
					weight = np.abs(gradX) / np.abs(gradY) # 权重
					grad2 = d[i-1, j]
					grad4 = d[i+1, j]

					# 如果 x, y 方向导数符号一致
					# 像素点位置关系
					# g1  g2
					#     c
					#     g4  g3

					if gradX * gradY > 0:
						grad1 = d[i-1, j-1]
						grad3 = d[i+1, j+1]

					# 如果 x,y 方向导数符号相反
					# 像素点位置关系
					#     g2  g1
					#     c
					# g3  g4
					
					else:
						grad1 = d[i-1, j+1]
						grad3 = d[i+1, j-1]

				# 如果 x 方向梯度值比较大
				else:
					weight = np.abs(gradY) / np.abs(gradX)
					grad2 = d[i, j-1]
					grad4 = d[i, j+1]
					
					# 如果 x, y 方向导数符号一致
					# 像素点位置关系
					#      g3
					# g2 c g4
					# g1
					if gradX * gradY > 0:
						grad1 = d[i+1, j-1]
						grad3 = d[i-1, j+1]
					
					# 如果 x,y 方向导数符号相反
					# 像素点位置关系
					# g1
					# g2 c g4
					#      g3
					else:
						grad1 = d[i-1, j-1]
						grad3 = d[i+1, j+1]

				# 利用 grad1-grad4 对梯度进行插值
				gradTemp1 = weight \* grad1 + (1 - weight) \* grad2
				gradTemp2 = weight \* grad3 + (1 - weight) \* grad4
				
				# 当前像素的梯度是局部的最大值,可能是边缘点
				if gradTemp >= gradTemp1 and gradTemp >= gradTemp2:
					NMS[i, j] = gradTemp
				else:
					# 不可能是边缘点
					NMS[i, j] = 0

	return NMS

6、双阈值的边界选取

这个阶段决定哪些边缘是真正的边缘,哪些边缘不是真正的边缘。为此,需要设置两个阈值,minVal 和 maxVal。梯度大于 maxVal 的任何边缘肯定是真边缘,而 minVal 以下的边缘肯定是非边缘,因此被丢弃。位于这两个阈值之间的边缘会基于其连通性而分类为边缘或非边缘,如果它们连接到"可靠边缘"像素,则它们被视为边缘的一部分。否则,也会被丢弃。代码如下所示:

def double\_threshold(self, NMS):
	W, H = NMS.shape
	DT = np.zeros([W, H])
	
	# 定义高低阈值
	TL = 0.1 \* np.max(NMS)
	TH = 0.3 \* np.max(NMS)
	
	for i in range(1, W-1):
		for j in range(1, H-1):
			# 双阈值选取
			if (NMS[i, j] < TL):
				DT[i, j] = 0
			elif (NMS[i, j] > TH):
				DT[i, j] = 1
			# 连接
			elif (NMS[i-1, j-1:j+1] < TH).any() or (NMS[i+1, j-1:j+1].any() or (NMS[i, [j-1, j+1]] < TH).any()):
				DT[i, j] = 1	
	return DT

进行完所有的步骤后,结果如下图所示:

Canny 边缘检测算法-python实现(附代码)文章来源地址https://www.toymoban.com/news/detail-443956.html

到了这里,关于Canny 边缘检测算法-python实现(附代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • OpenCV——Canny边缘检测算法

    图像分割是将数字图像细分为多个子区域的过程,在计算机视觉/机器视觉领域被广泛应用。它的目的是简化或改变图像的表示形式,以便更容易理解和分析。常见的图像分割方法包括阈值处理、聚类法、边缘检测和区域生长等。解决图像分割问题通常需要结合领域知识,以提

    2024年04月17日
    浏览(30)
  • python Canny边缘检测

        参考文献 Canny边缘检测算法(python 实现)_Master_miao的博客-CSDN博客_python canny 使用Pytorch从头实现Canny边缘检测 

    2024年02月06日
    浏览(35)
  • 计算机视觉算法中的Canny边缘检测(Canny Edge Detection)

    在计算机视觉领域,边缘检测是一项重要的任务。边缘是图像中物体之间的边界,通过边缘检测可以帮助我们识别出图像中的物体。Canny边缘检测是一种经典且常用的边缘检测算法。本文将对Canny边缘检测算法进行介绍和分析。 Canny边缘检测算法由约翰·Canny在1986年提出,是一

    2024年02月08日
    浏览(32)
  • Python Opencv实践 - Canny边缘检测

     

    2024年02月11日
    浏览(34)
  • 【OpenCV实现图像梯度,Canny边缘检测】

    OpenCV中,可以使用各种函数实现图像梯度和Canny边缘检测,这些操作对于图像处理和分析非常重要。 图像梯度通常用于寻找图像中的边缘和轮廓。在OpenCV中,可以使用cv2.Sobel()函数计算图像的梯度,该函数可以计算图像在水平和垂直方向上的梯度。梯度的方向和大小可以帮助

    2024年02月07日
    浏览(37)
  • Python实现:高斯滤波 均值滤波 中值滤波 Canny(边缘检测)PCA主成分分析 直方图规定化 Mean_Shift

    左侧为原图右侧为高斯滤波后的图 左图为带有噪声的输入原图,右图为经过均值滤波后的图片 左图为带有噪声的输入原图,右图为经过中值滤波后的图 上图为输入原图,下图为经过Canny边缘检测后的图 上图为原图 中图为规定目标图 下图为原图经过规定化后的图 左图为原输

    2024年02月07日
    浏览(31)
  • python使用Canny算法和HoughCiecle算法实现圆的检测与定位

    任务是编写一个钱币定位系统,其不仅能够检测出输入图像中各个钱币的边缘,同时,还能给出各个钱币的圆心坐标与半径。 ① 使用高斯滤波器滤波; ② 计算图像的梯度图并获得梯度方向; ③ 对梯度图进行非极大化抑制; ④ 使用双阈值法获得最终的边缘图。 高斯滤波

    2024年02月01日
    浏览(20)
  • python数字图像处理基础(五)——Canny边缘检测、图像金字塔、图像分割

    梯度是什么? 梯度就是变化的最快的那个方向 edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]]) 第一个参数是需要处理的原图像,该图像必须为单通道的灰度图; 第二个参数是阈值1; 第三个参数是阈值2。 原理步骤 1)使用高斯滤波器,以平滑图像,滤除噪

    2024年01月18日
    浏览(35)
  • Canny算子边缘检测原理讲解及其完整C语言实现(不使用opencv)

    作者:队友调车我吹空调 日期:2023/05/17 版权:遵循CC 4.0 BY-SA版权协议 这里是后期的笔者,本文算是笔者的学习笔记,主要是在单片机中使用的,再者就是由于某些原因,笔者不想使用opencv,因此尝试跟着原理手搓了这份代码,笔者也尽力将代码写到最简和效率最优了。然而

    2024年04月27日
    浏览(34)
  • Python图像锐化及边缘检测(Roberts、Prewitt、Sobel、Lapllacian、Canny、LOG)

    目录 图像锐化概述 算法方法介绍  代码实现 效果展示 图像锐化 (image sharpening) 是补偿图像的轮廓,增强图像的边缘及灰度跳变的部分,使图像变得清晰,分为空间域处理和频域处理两类。图像锐化是为了突出图像上地物的边缘、轮廓,或某些线性目标要素的特征。这种滤波

    2023年04月17日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包