opencv:Canny边缘检测算法思想及实现

Canny边缘检测算法背景

求边缘幅度的算法:
一阶导数:sobel、Roberts、prewitt等算子
二阶导数:Laplacian、Canny算子
Canny算子效果比其他的都要好,但是实现起来有点麻烦

Canny边缘检测算法的优势:

Canny是目前最优秀的边缘检测算法,其目标为找到一个最优的边缘,其最优边缘的定义为:

1、好的检测:算法能够尽可能的标出图像中的实际边缘
2、好的定位:标识出的边缘要与实际图像中的边缘尽可能接近
3、最小响应:图像中的边缘只能标记一次

Canny边缘检测算法的实现方法:

1.对图像进行灰度化:
方法1:Gray=(R+G+B)/3;
方法2:Gray=0.299R+0.587G+0.114B;(这种参数考虑到了人眼的生理特点)
2.对图像进行高斯滤波: 根据待滤波的像素点及其邻域点的灰度值按照一定的参数规则进行加权平均。这样 可以有效滤去理想图像中叠加的高频噪声
3. 检测图像中的水平、垂直和对角边缘(如Prewitt,Sobel算子等)。
4. 对梯度幅值进行非极大值抑制
5. 用双阈值算法检测和连接边缘

高斯平滑

高斯平滑水平和垂直方向呈现高斯分布,更突出了 中心点在像素平滑后的权重,相比于均值滤波而言, 有着更好的平滑效果。
在这里插入图片描述
高斯平滑水平和垂直方向呈现高斯分布,更突出了中心点在像素平滑后的权重,相比于均值滤波 而言,有着更好的平滑效果。
重要的是需要理解,高斯卷积核大小的选择将影响Canny检测器的性能: 尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个 比较不错的trade off。

非极大值抑制

非极大值抑制,简称为NMS算法,英文为Non-Maximum Suppression。 其思想是搜素局部最大值,抑制极大值。 NMS算法在不同应用中的具体实现不太一样,但思想是一样的。

为什么要用非极大值抑制?
以目标检测为例:目标检测的过程中在同一目标的位置上会产生大量的候选框,这些候选框相互之间可 能会有重叠,此时我们需要利用非极大值抑制找到最佳的目标边界框,消除冗余的边界框。
在这里插入图片描述
对于重叠的候选框,若大于规定阈值(某一提前设定的置信度),则删除;低于阈值则保留。 对于无重叠的候选框,都保留。

实现方法:
1.将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
2.如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则 该像素点将被抑制(灰度值置为0)。
在这里插入图片描述
例如这个图,点C是我们最大值,但是我们要知道是不是局部最大值,就看我们事先求出的该点梯度线,梯度线周围临近像素点取交叉点dTmp1与dTmp2,若dTmp1与dTmp2的灰度值都比c点大,该点将被舍弃,只有该点的灰度值大于dTmp1与dTmp2的灰度值才能被保留。

用双阈值算法检测(滞后阈值)

完成非极大值抑制后,会得到一个二值图像,非边缘的点灰度值均为0,可能为边缘的局部灰度极大值点可设置其灰度为128。 这样一个检测结果还是包含了很多由噪声及其他原因造成的假边缘。因此还需要进一步的 处理。 用双阈值算法检测(滞后阈值)
• 如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;
• 如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;
• 如果边缘像素的梯度值小于低阈值,则会被抑制。
双阈值检测: 大于高阈值为强边缘,小于低阈值不是边缘。介于中间是弱边缘。 阈值的选择取决于给定输入图像的内容。

抑制孤立低阈值点

到目前为止,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取 出来的。 然而,对于弱边缘像素,将会有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜 色变化引起的。 为了获得准确的结果,应该抑制由后者引起的弱边缘:
• 通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声响应未连接。
• 为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素, 则该弱边缘点就可以保留为真实的边缘。

Canny边缘检测算法的代码手动实现:

import numpy as np
import matplotlib.pyplot as plt
import mathif __name__ == '__main__':pic_path = 'lenna.png' 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.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.figure(2)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.figure(3)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.figure(4)plt.imshow(img_yizhi.astype(np.uint8), cmap='gray')plt.axis('off')  # 关闭坐标刻度值plt.show()

Canny边缘检测算法的代码实现:

import cv2
import numpy as np'''
cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])   
必要参数:
第一个参数是需要处理的原图像,该图像必须为单通道的灰度图;
第二个参数是滞后阈值1;
第三个参数是滞后阈值2。
'''img = cv2.imread("lenna.png", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("canny", cv2.Canny(gray, 200, 300))
cv2.waitKey()
cv2.destroyAllWindows()

输出结果:

在这里插入图片描述

额外内容:

可动态调整Canny边缘检测阈值的算法

上图的双阈值我们设置的是[200,300]
实际上Canny边缘检测算法中阈值的影响非常大,为了方便理解,老师编写了一个可动态调整阈值的算法。

import cv2
import numpy as np def CannyThreshold(lowThreshold):  detected_edges = cv2.GaussianBlur(gray,(3,3),0) #高斯滤波 detected_edges = cv2.Canny(detected_edges,lowThreshold,lowThreshold*ratio,apertureSize = kernel_size)  #边缘检测# just add some colours to edges from original image.  dst = cv2.bitwise_and(img,img,mask = detected_edges)  #用原始颜色添加到检测的边缘上cv2.imshow('canny demo',dst)  lowThreshold = 0  
max_lowThreshold = 200
ratio = 3  
kernel_size = 3  img = cv2.imread('lenna.png')  
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:  #wait for ESC key to exit cv2cv2.destroyAllWindows()  

实验结果:

0阈值时:
在这里插入图片描述
100阈值时:
在这里插入图片描述
200阈值时:
在这里插入图片描述
阈值越大,能够保留了边缘越少

Sobel,Laplace,Canny边缘检测的效果对比:

import cv2  
import numpy as np  
from matplotlib import pyplot as plt  img = cv2.imread("lenna.png",1)  img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)  '''
Sobel算子
Sobel算子函数原型如下:
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) 
前四个是必须的参数:
第一个参数是需要处理的图像;
第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
其后是可选的参数:
dst是目标图像;
ksize是Sobel算子的大小,必须为1、3、5、7。
scale是缩放导数的比例常数,默认情况下没有伸缩系数;
delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
'''img_sobel_x = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3)  # 对x求导
img_sobel_y = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3)  # 对y求导# Laplace 算子  
img_laplace = cv2.Laplacian(img_gray, cv2.CV_64F, ksize=3)  # Canny 算子  
img_canny = cv2.Canny(img_gray, 100 , 150)  plt.subplot(231), plt.imshow(img_gray, "gray"), plt.title("Original")  
plt.subplot(232), plt.imshow(img_sobel_x, "gray"), plt.title("Sobel_x")  
plt.subplot(233), plt.imshow(img_sobel_y, "gray"), plt.title("Sobel_y")  
plt.subplot(234), plt.imshow(img_laplace,  "gray"), plt.title("Laplace")  
plt.subplot(235), plt.imshow(img_canny, "gray"), plt.title("Canny")  
plt.show()  

实现结果:

在这里插入图片描述
从实验结果可以发现,在边缘检测的效果上,Canny > Laplace > Sobel

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

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

相关文章

opencv:畸变矫正:透视变换算法的思想与实现

畸变矫正 注意&#xff1a;虽然能够成功矫正但是也会损失了部分图像&#xff01; 透视变换(Perspective Transformation) 概念&#xff1a; 透视变换是将图片投影到一个新的视平面(Viewing Plane)&#xff0c;也称作投影映射(Projective Mapping)。 我们常说的仿射变换是透视…

数据多重共线性_多重共线性对您的数据科学项目的影响比您所知道的要多

数据多重共线性Multicollinearity is likely far down on a mental list of things to check for, if it is on a list at all. This does, however, appear almost always in real-life datasets, and it’s important to be aware of how to address it.多重共线性可能根本不…

K-Means聚类算法思想及实现

K-Means聚类概念&#xff1a; K-Means聚类是最常用的聚类算法&#xff0c;最初起源于信号处理&#xff0c;其目标是将数据点划分为K个类簇&#xff0c; 找到每个簇的中心并使其度量最小化。 该算法的最大优点是简单、便于理解&#xff0c;运算速度较快&#xff0c;缺点是只能应…

(2.1)DDL增强功能-数据类型、同义词、分区表

1.数据类型 &#xff08;1&#xff09;常用数据类型  1.整数类型 int 存储范围是-2,147,483,648到2,147,483,647之间的整数&#xff0c;主键列常设置此类型。 &#xff08;每个数值占用 4字节&#xff09; smallint 存储范围是-32,768 到 32,767 之间的整数&#xff0c;用…

充分利用昂贵的分析

By Noor Malik努尔马利克(Noor Malik) Let’s say you write a query in Deephaven which performs a lengthy and expensive analysis, resulting in a live table. For example, in a previous project, I wrote a query which pulled data from an RSS feed to create a li…

层次聚类和密度聚类思想及实现

层次聚类 层次聚类的概念&#xff1a; 层次聚类是一种很直观的算法。顾名思义就是要一层一层地进行聚类。 层次法&#xff08;Hierarchicalmethods&#xff09;先计算样本之间的距离。每次将距离最近的点合并到同一个类。然后&#xff0c;再 计算类与类之间的距离&#xff0…

通配符 或 怎么浓_浓咖啡的咖啡渣新鲜度

通配符 或 怎么浓How long could you wait to brew espresso after grinding? Ask a barista, any barista, and I suspect their answer is immediately or within a few minutes. The common knowledge on coffee grounds freshness is that after 30 minutes or so, coffee…

《netty入门与实战》笔记-02:服务端启动流程

为什么80%的码农都做不了架构师&#xff1f;>>> 1.服务端启动流程 这一小节&#xff0c;我们来学习一下如何使用 Netty 来启动一个服务端应用程序&#xff0c;以下是服务端启动的一个非常精简的 Demo: NettyServer.java public class NettyServer {public static v…

谱聚类思想及实现

&#xff08;这个我也没有怎么懂&#xff0c;为了防止以后能用上&#xff0c;还是记录下来&#xff09; 谱聚类 注意&#xff1a;谱聚类核心聚类算法还是K-means 算法进行聚类~ 谱聚类的实现过程&#xff1a; 1.根据数据构造一个 图结构&#xff08;Graph&#xff09; &…

Tengine HTTPS原理解析、实践与调试【转】

本文邀请阿里云CDN HTTPS技术专家金九&#xff0c;分享Tengine的一些HTTPS实践经验。内容主要有四个方面&#xff1a;HTTPS趋势、HTTPS基础、HTTPS实践、HTTPS调试。 一、HTTPS趋势 这一章节主要介绍近几年和未来HTTPS的趋势&#xff0c;包括两大浏览器chrome和firefox对HTTPS的…

opencv:SIFT——尺度不变特征变换

SIFT概念&#xff1a; Sift&#xff08;尺度不变特征变换&#xff09;&#xff0c;全称是Scale Invariant Feature Transform Sift提取图像的局部特征&#xff0c;在尺度空间寻找极值点&#xff0c;并提取出其位置、尺度、方向信息。 Sfit的应用范围包括 物体辨别、机器人地图…

pca(主成分分析技术)_主成分分析技巧

pca(主成分分析技术)介绍 (Introduction) Principal Component Analysis (PCA) is an unsupervised technique for dimensionality reduction.主成分分析(PCA)是一种无监督的降维技术。 What is dimensionality reduction?什么是降维&#xff1f; Let us start with an exam…

npm link run npm script

npm link & run npm script https://blog.csdn.net/juhaotian/article/details/78672390 npm link命令可以将一个任意位置的npm包链接到全局执行环境&#xff0c;从而在任意位置使用命令行都可以直接运行该npm包。 app-cmd.cmd #!/usr/bin/env nodeecho "666" &a…

一文详解java中对JVM的深度解析、调优工具、垃圾回收

2019独角兽企业重金招聘Python工程师标准>>> jvm监控分析工具一般分为两类&#xff0c;一种是jdk自带的工具&#xff0c;一种是第三方的分析工具。jdk自带工具一般在jdk bin目录下面&#xff0c;以exe的形式直接点击就可以使用&#xff0c;其中包含分析工具已经很强…

借用继承_博物馆正在数字化,并在此过程中从数据中借用

借用继承Data visualization is a great way to celebrate our favorite pieces of art as well as reveal connections and ideas that were previously invisible. More importantly, it’s a fun way to connect things we love — visualizing data and kicking up our fee…

高斯噪声,椒盐噪声的思想及多种噪声的实现

图像噪声&#xff1a; 概念&#xff1a; • 图像噪声是图像在获取或是传输过程中受到随机信号干扰&#xff0c;妨碍人们对图像理解及分析处理 的信号。 • 很多时候将图像噪声看做多维随机过程&#xff0c;因而描述噪声的方法完全可以借用随机过程的描述&#xff0c; 也就是使…

如何识别媒体偏见_描述性语言理解,以识别文本中的潜在偏见

如何识别媒体偏见TGumGum can do to bring change by utilizing our Natural Language Processing technology to shed light on potential bias that websites may have in their content. The ideas and techniques shared in this blog are a result of the GumGum Hackatho…

分享 : 警惕MySQL运维陷阱:基于MyCat的伪分布式架构

分布式数据库已经进入了全面快速发展阶段。这种发展是与时俱进的&#xff0c;与人的需求分不开&#xff0c;因为现在信息时代的高速发展&#xff0c;导致数据量和交易量越来越大。这种现象首先导致的就是存储瓶颈&#xff0c;因为MySQL数据库实质上还是一个单机版本的数据库&am…

数据不平衡处理_如何处理多类不平衡数据说不可以

数据不平衡处理重点 (Top highlight)One of the common problems in Machine Learning is handling the imbalanced data, in which there is a highly disproportionate in the target classes.机器学习中的常见问题之一是处理不平衡的数据&#xff0c;其中目标类别的比例非常…

最小二乘法以及RANSAC(随机采样一致性)思想及实现

线性回归–最小二乘法&#xff08;Least Square Method&#xff09; 线性回归&#xff1a; 什么是线性回归&#xff1f; 举个例子&#xff0c;某商品的利润在售价为2元、5元、10元时分别为4元、10元、20元&#xff0c; 我们很容易得出商品的利润与售价的关系符合直线&#xf…