如何做外卖网站app,wordpress添加自定义字段,海拉尔网站建设 网站设计,巨好用企业网站源码Canny边缘检测 声明#xff1a;阅读本文需要了解线性代数里面的点乘#xff08;图像卷积的原理#xff09;#xff0c;高等数学里的二元函数的梯度#xff0c;极大值定义#xff0c;了解概率论里的二维高斯分布 1.canny边缘检测原理和简介 2.实现步骤 3.总结 一、 Canny边…Canny边缘检测 声明阅读本文需要了解线性代数里面的点乘图像卷积的原理高等数学里的二元函数的梯度极大值定义了解概率论里的二维高斯分布 1.canny边缘检测原理和简介 2.实现步骤 3.总结 一、 Canny边缘检测算法的发展历史 Canny算子是28岁的John Canny在1986年提出的该文章发表在PAMI顶级期刊(1986. A computational approach to edge detection. IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 8, 1986, pp. 679-698)。现在老大爷目前61岁在加州伯克利做machine learning,主页(http://www.cs.berkeley.edu/~jfc/)大爷就是大爷。 边缘检测是从图像中提取有用的结构信息的一种技术如果学过信息论就会知道一面充满花纹的墙要比一面白墙的信息量大很多没学过也没关系直观上也能理解充满花纹的图像要比单色图像信息更丰富。为什么要检测边缘?因为我们需要计算机自动的提取图像的底层纹理等或者高层时间地点人物等的信息边缘可以说是最直观、最容易发现的一种信息了。Canny提出了一个对于边缘检测算法的评价标准包括 1) 以低的错误率检测边缘也即意味着需要尽可能准确的捕获图像中尽可能多的边缘。 2) 检测到的边缘应精确定位在真实边缘的中心。 3) 图像中给定的边缘应只被标记一次并且在可能的情况下图像的噪声不应产生假的边缘。 简单来说就是检测算法要做到边缘要全位置要准抵抗噪声的能力要强。 接下来介绍最经典的canny边缘检测算法很多边缘检测算法都是在此基础上进行改进的学习它有利于一通百通。 二、实现步骤 step1高斯平滑滤波 没有哪张图片是没有噪声的。————鲁迅 滤波是为了去除噪声选用高斯滤波也是因为在众多噪声滤波器中高斯表现最好表现怎么定义的最好好到什么程度你也可以试试其他滤波器如均值滤波、中值滤波等等。一个大小为(2k1)x(2k1)的高斯滤波器核核一般都是奇数尺寸的的生成方程式由下式给出 ‘ 下面是一个sigma 1.4尺寸为3x3的高斯卷积核的例子注意矩阵求和值为1归一化 举个例子若图像中一个3x3的窗口为A要滤波的像素点为e则经过高斯滤波之后像素点e的亮度值为 其中*为卷积符号sum表示矩阵中所有元素相加求和简单说就是滤波后的每个像素值其原像素中心值及其相邻像素的加权求和。图像卷积是图像处理中非常重要且广泛使用的操作一定要理解好。 其中高斯卷积核的大小将影响Canny检测器的性能。尺寸越大去噪能力越强因此噪声越少但图片越模糊canny检测算法抗噪声能力越强但模糊的副作用也会导致定位精度不高一般情况下推荐尺寸5*53*3也行。 step2 计算梯度强度和方向 边缘的最重要的特征是灰度值剧烈变化如果把灰度值看成二元函数值那么灰度值的变化可以用二元函数的”导数“或者称为梯度来描述。由于图像是离散数据导数可以用差分值来表示差分在实际工程中就是灰度差说人话就是两个像素的差值。一个像素点有8邻域那么分上下左右斜对角因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。算子是以图像卷积的形式来计算梯度比如RobertsPrewittSobel等这里选用Sobel算子来计算二维图像在x轴和y轴的差分值这些数字的由来将下面两个模板与原图进行卷积得出x和y轴的差分值图最后计算该点的梯度G和方向θ 计算梯度的模和方向属于高等数学部分的内容如果不理解应该补习一下数学基本功图像处理经常会用到这个概念。 这部分我实现了下首先了解opencv的二维滤波函数dstcv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) dst 输出图片 src 输入图片 ddepth 输出图片的深度, 详见 combinations,如果填-1那么就跟与输入图片的格式相同。 kernel 单通道、浮点精度的卷积核。 以下是默认参数 anchor内核的基准点(anchor)其默认值为(-1,-1)表示位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点。举个例子就是在上面的step1中eH*A得到的e是放在原像素的3*3的哪一个位置一般来说都是放在中间位置设置成默认值就好。 delta 在储存目标图像前可选的添加到像素的值默认值为0。没用过 borderType像素向外逼近的方法默认值是BORDER_DEFAULT,即对全部边界进行计算。没用过 上代码 1 import cv22 import numpy as np3 import matplotlib.pyplot as plt4 5 6 imgcv2.imread(images/luxun.png,cv2.IMREAD_GRAYSCALE) # 读入图片7 sobel_x np.array([[-1, 0, 1],[-2,0,2],[-1, 0, 1]]) # sobel的x方向算子8 sobel_y np.array([[1, 2, 1],[0,0,0],[-1, -2, -1]]) # sobel的x方向算子9 sobel_xcv2.flip(sobel_x,-1) # cv2.filter2D()计算的是相关真正的卷积需要翻转在进行相关计算。
10 sobel_xcv2.flip(sobel_y,-1)
11 # cv2.flip()第二个参数等于0沿着x轴反转。大于0沿着y轴反转。小于零沿着x轴y轴同时反转
12
13 # 卷积 opencv是用滤波器函数实现的
14 img_xcv2.filter2D(img,-1, sobel_x)
15 img_ycv2.filter2D(img,-1, sobel_y)
16 # 画图 plt不支持中文但是可以通过以下方法设置修复
17 plt.rcParams[font.sans-serif][SimHei]
18 plt.rcParams[axes.unicode_minus] False
19
20 plt.subplot(221), plt.imshow(img_x, gray),plt.title(sobel_x)
21 plt.subplot(222), plt.imshow(img_y, gray),plt.title(sobel_y)
22 plt.subplot(223), plt.imshow(img_yimg_x, gray),plt.title(sobel)
23 plt.subplot(224), plt.imshow(img, gray),plt.title(原图)
24 plt.show() View Code 运行效果 需要注意一点在图像处理领域卷积运算的定义是先将核关于x轴和y轴反转然在做相关运算。然而工程实践中往往跳过反转用相关运算代替卷积比如opencv。如果你需要严格的卷积运算应该注意原函数的具体实现方式。sobel算子天生关于中心对称所以反转与否并不影响结果我在代码里用cv2.flip进行了反转操作。 在之后的实现中我发现用opencv自带的滤波函数算出来的梯度是归一化到0-255的引入其他的库也很麻烦因此自己写了个简单的二位卷积函数来实现梯度计算。所以上面的图适合看效果并不适合在程序中使用卷积函数的代码如下 1 def conv2d(src,kernel): # 输入必须为方形卷积核2 # 本函数仍然是相关运算没有反转。如果非要严格的卷积运算把下面一行代码的注释取消。3 #kernelcv2.flip(kernel,-1)4 [rows,cols] kernel.shape5 borderrows//2 # 向下取整 获得卷积核边长6 [rows,cols]src.shape7 dst np.zeros(src.shape) # 采用零填充再卷积卷积结果不会变小。8 # print(图像长,rows,宽,cols,核边界,border)9 # print(border,rows-border,border,cols-border)
10 temp[]
11 for i in range(border,rows-border):
12 for j in range(border,cols-border):
13 tempsrc[i-border:iborder1,j-border:jborder1] # 从图像获取与核匹配的图像
14 # 切片语法索引位置包括开头但不包括结尾 [start: end: step]
15 dst[i][j](kernel*temp).sum() # 计算卷积
16 return dst View Code 小技巧用plt显示二维矩阵鼠标移到某个像素就会显示坐标xy和灰度值浮点数也可以显示。这可以很方便的看某个数据像素点是否有问题。 梯度和幅值的计算效果如下 能看出来sobel算子计算的边缘很粗很亮比较明显但是不够精确我们的目标是精确到一个像素宽至于梯度相位就很难看出什么特征并且梯度相位实际上是为了下一步打基础的。下面附上代码 1 img_xconv2d(img,sobel_x) # 使用我自己的写的卷积计算梯度
2 img_yconv2d(img,sobel_y)
3 Gnp.sqrt(img_x*img_ximg_y*img_y) # 梯度幅值
4 thetanp.arctan(img_y,(img_x0.0000000000001))*180/np.pi # 化为角度分母极小值是为了避免除以0
5 # plt.imshow(theta, gray),plt.title(梯度相位)
6 plt.imshow(G, gray),plt.title(梯度幅值)
7 plt.show() View Code step3:非极大值抑制 sobel算子检测出来的边缘太粗了我们需要抑制那些梯度不够大的像素点只保留最大的梯度从而达到瘦边的目的。这些梯度不够大的像素点很可能是某一条边缘的过渡点。按照高数上二位函数的极大值的定义即对点x0y0的某个邻域内所有xy都有fxy≤(fx0y0则称f在x0y0具有一个极大值极大值为fx0y0。简单方案是判断一个像素点的8邻域与中心像素谁更大但这很容易筛选出噪声因此我们需要用梯度和梯度方向来辅助确定。 如下图所示中心像素C的梯度方向是蓝色直线那么只需比较中心点C与dTmp1和dTmp2的大小即可。由于这两个点的像素不知道假设像素变化是连续的就可以用g1、g2和g3、g4进行线性插值估计。设g1的幅值M(g1)g2的幅值M(g2),则M(dtmp1)w*M(g2)(1-w)*M(g1) 其wdistance(dtmp1,g2)/distance(g1,g2) 。也就是利用g1和g2到dTmp1的距离作为权重来估计dTmp1的值。w在程序中可以表示为tanθ来表示具体又分为四种情况下面右图讨论。 如下图经过非极大值抑制可以很明显的看出去除了很多点边缘也变得很细。在程序实现中要注意opencv的默认坐标系是从左到右为x轴从上到下是y轴原点位于左上方计算g1、g2、g3、g4的位置的时候一定要小心坑了我很久。经过非极大值抑制可以看出来图片的边缘明显变细很多看起来黑色的部分其实有值的只是因为值太小了看不清楚而这些黑色的部分可能是噪声或者其他原因造成的局部极大值下一步我们就要用双阈值来限定出强边缘和弱边缘尽可能的减少噪声的检出。代码附上 1 # step3:非极大值抑制2 anchornp.where(G!0) # 获取非零梯度的位置3 Masknp.zeros(img.shape)4 5 for i in range(len(anchor[0])):6 xanchor[0][i]7 yanchor[1][i]8 center_pointG[x,y]9 current_thetatheta[x,y]
10 dTmp10
11 dTmp20
12 W0
13 if current_theta0 and current_theta45:
14 # g1 第一种情况
15 # g4 C g2
16 # g3
17 g1 G[x 1, y - 1]
18 g2 G[x 1, y]
19 g3 G[x - 1, y 1]
20 g4 G[x - 1, y]
21 Wabs(np.tan(current_theta*np.pi/180)) # tan0-45范围为0-1
22 dTmp1 W*g1(1-W)*g2
23 dTmp2 W*g3(1-W)*g4
24
25 elif current_theta45 and current_theta90:
26 # g2 g1 第二种情况
27 # C
28 # g3 g4
29
30 g1 G[x 1, y - 1]
31 g2 G[x, y - 1]
32 g3 G[x - 1, y 1]
33 g4 G[x, y 1]
34 W abs(np.tan((current_theta-90) * np.pi / 180))
35 dTmp1 W*g1(1-W)*g2
36 dTmp2 W*g3(1-W)*g4
37
38 elif current_theta-90 and current_theta-45:
39 # g1 g2 第三种情况
40 # C
41 # g4 g3
42 g1 G[x - 1, y - 1]
43 g2 G[x, y - 1]
44 g3 G[x 1, y 1]
45 g4 G[x, y 1]
46 W abs(np.tan((current_theta-90) * np.pi / 180))
47 dTmp1 W*g1(1-W)*g2
48 dTmp2 W*g3(1-W)*g4
49
50 elif current_theta-45 and current_theta0:
51 # g3 第四种情况
52 # g4 C g2
53 # g1
54 g1 G[x 1, y 1]
55 g2 G[x 1, y]
56 g3 G[x - 1, y - 1]
57 g4 G[x - 1, y]
58 W abs(np.tan(current_theta * np.pi / 180))
59 dTmp1 W*g1(1-W)*g2
60 dTmp2 W*g3(1-W)*g4
61
62 if dTmp1center_point and dTmp2center_point: # 记录极大值结果
63 Mask[x,y]center_point
64 #Mask(Mask-Mask.min())/(Mask.max()-Mask.min())*256 #归一化
65 plt.imshow(Mask,gray),plt.title(Mask)
66 plt.show() View Code step4用双阈值算法检测和连接边缘 双阈值法非常简单我们假设两类边缘经过非极大值抑制之后的边缘点中梯度值超过T1的称为强边缘梯度值小于T1大于T2的称为弱边缘梯度小于T2的不是边缘。可以肯定的是强边缘必然是边缘点因此必须将T1设置的足够高以要求像素点的梯度值足够大变化足够剧烈而弱边缘可能是边缘也可能是噪声如何判断呢当弱边缘的周围8邻域有强边缘点存在时就将该弱边缘点变成强边缘点以此来实现对强边缘的补充。实际中人们发现T1:T22:1的比例效果比较好其中T1可以人为指定也可以设计算法来自适应的指定比如定义梯度直方图的前30%的分界线为T1我实现的是人为指定阈值。检查8邻域的方法叫边缘滞后跟踪连接边缘的办法还有区域生长法等等。强边缘、弱边缘、综合效果、和opencv的canny函数对比如下 三、总结 实现结果还是很打击的我检测到的边缘过于断续没有opencv实现的效果好。查了一下opencv的源码这里猜测两个可能的原因源码里梯度的方向被近似到四个角度之一 04590135但我用线性插值的的结果是梯度方向更精确而过于精确--过于严格--容易受到噪声干扰所以在非极大值抑制这之后我比opencv少了更多的点最终导致了边缘不够连续第二个原因可能是边缘连接算法效果不够好把图象放大来看我产生的边缘倾向于对角线上连接而opencv的边缘倾向于折线连接因此opencv的边缘更完整连续而我的边缘更细更容易断续。 限于时间暂时研究到这里希望各位多多指正感谢所有我参考过的博客和文档 1 import cv22 import numpy as np3 import matplotlib.pyplot as plt4 5 # 画图 plt不支持中文但是可以通过以下方法设置修复6 plt.rcParams[font.sans-serif][SimHei]7 plt.rcParams[axes.unicode_minus] False8 9 def conv2d(src,kernel): # 输入必须为方形卷积核10 # 本函数仍然是相关运算没有反转。如果非要严格的卷积运算把下面一行代码的注释取消。11 #kernelcv2.flip(kernel,-1)12 [rows,cols] kernel.shape13 borderrows//2 # 向下取整 获得卷积核边长14 [rows,cols]src.shape15 dst np.zeros(src.shape) # 采用零填充再卷积卷积结果不会变小。16 # print(图像长,rows,宽,cols,核边界,border)17 # print(border,rows-border,border,cols-border)18 temp[]19 for i in range(border,rows-border):20 for j in range(border,cols-border):21 tempsrc[i-border:iborder1,j-border:jborder1] # 从图像获取与核匹配的图像22 # 切片语法索引位置包括开头但不包括结尾 [start: end: step]23 dst[i][j](kernel*temp).sum() # 计算卷积24 return dst25 26 27 # step0:读入图片28 imgcv2.imread(images/luxun.png,cv2.IMREAD_GRAYSCALE) # 读入图片29 30 # step1:高斯滤波31 imgcv2.GaussianBlur(img,(5,5),0)32 33 # step2:计算梯度强度和方向34 sobel_x np.array([[-1, 0, 1],[-2,0,2],[-1, 0, 1]]) # sobel的x方向算子35 sobel_y np.array([[1, 2, 1],[0,0,0],[-1, -2, -1]]) # sobel的y方向算子36 37 # img_xcv2.filter2D(img,-1, sobel_x) # 这个滤波器会将卷积结果归一化到0-255无法计算梯度方向。38 # img_ycv2.filter2D(img,-1, sobel_y) # 而真正的图像卷积可能会出现负数因此只能自己写个卷积。39 img_xconv2d(img,sobel_x) # 使用我自己的写的卷积计算梯度40 img_yconv2d(img,sobel_y)41 Gnp.sqrt(img_x*img_ximg_y*img_y) # 梯度幅值42 thetanp.arctan(img_y,(img_x0.0000000000001))*180/np.pi # 化为角度分母极小值是为了避免除以043 44 # plt.imshow(theta, gray),plt.title(梯度相位)45 # plt.imshow(G, gray),plt.title(梯度幅值)46 # plt.show()47 # exit()48 49 # step3:非极大值抑制50 anchornp.where(G!0) # 获取非零梯度的位置51 Masknp.zeros(img.shape)52 for i in range(len(anchor[0])):53 xanchor[0][i] # 取出第i个非零梯度的x坐标54 yanchor[1][i]55 center_pointG[x,y]56 current_thetatheta[x,y]57 dTmp1058 dTmp2059 W060 if current_theta0 and current_theta45:61 # g1 第一种情况62 # g4 C g263 # g364 g1 G[x 1, y - 1]65 g2 G[x 1, y]66 g3 G[x - 1, y 1]67 g4 G[x - 1, y]68 Wabs(np.tan(current_theta*np.pi/180)) # tan0-45范围为0-169 dTmp1 W*g1(1-W)*g270 dTmp2 W*g3(1-W)*g471 72 elif current_theta45 and current_theta90:73 # g2 g1 第二种情况74 # C75 # g3 g476 g1 G[x 1, y - 1]77 g2 G[x, y - 1]78 g3 G[x - 1, y 1]79 g4 G[x, y 1]80 W abs(np.tan((current_theta-90) * np.pi / 180))81 dTmp1 W*g1(1-W)*g282 dTmp2 W*g3(1-W)*g483 elif current_theta-90 and current_theta-45:84 # g1 g2 第三种情况85 # C86 # g4 g387 g1 G[x - 1, y - 1]88 g2 G[x, y - 1]89 g3 G[x 1, y 1]90 g4 G[x, y 1]91 W abs(np.tan((current_theta-90) * np.pi / 180))92 dTmp1 W*g1(1-W)*g293 dTmp2 W*g3(1-W)*g494 elif current_theta-45 and current_theta0:95 # g3 第四种情况96 # g4 C g297 # g198 g1 G[x 1, y 1]99 g2 G[x 1, y]
100 g3 G[x - 1, y - 1]
101 g4 G[x - 1, y]
102 W abs(np.tan(current_theta * np.pi / 180))
103 dTmp1 W*g1(1-W)*g2
104 dTmp2 W*g3(1-W)*g4
105
106 if dTmp1center_point and dTmp2center_point: # 记录极大值结果
107 Mask[x,y]center_point
108 # plt.imshow(Mask,gray),plt.title(Mask)
109 # plt.show()
110 # exit()
111
112 # step4:双阈值选取
113 high_threshold100
114 low_thresholdhigh_threshold/2
115 strong_edgenp.zeros(G.shape) # 强边缘
116 weak_edgenp.zeros(G.shape) # 弱边缘
117
118 xNum [1, 1, 0, -1, -1, -1, 0, 1] # 8邻域偏移坐标
119 yNum [0, 1, 1, 1, 0, -1, -1, -1]
120 [rows, cols] G.shape
121 for i in range(rows):
122 for j in range(cols):
123 current_pointMask[i,j]
124 if current_point0:
125 if current_pointhigh_threshold: # 强边缘提取
126 strong_edge[i,j]255
127 elif current_pointhigh_threshold and current_pointlow_threshold: # 弱边缘提取
128 # step6:顺便进行边缘连接
129 change True
130 while change:
131 change False
132 for k in range(8):
133 xxixNum[k]
134 yyjyNum[k]
135 if Mask[xx,yy]high_threshold:
136 weak_edge[i, j] 255
137 break # 跳出八邻域循环
138 outputstrong_edgeweak_edge # 强弱边缘综合效果
139
140 img_edge cv2.Canny(img, 50, 100) # opencv实现效果
141
142 # 显示效果
143 plt.subplot(141), plt.imshow(strong_edge, gray),plt.title(strong_edge)
144 plt.subplot(142), plt.imshow(weak_edge, gray),plt.title(weak_edge)
145 plt.subplot(143), plt.imshow(output, gray),plt.title(result)
146 plt.subplot(144), plt.imshow(img_edge, gray),plt.title(opencv)
147 plt.show() 完整代码 参考文献 https://www.cnblogs.com/love6tao/p/5152020.html https://www.cnblogs.com/techyan1990/p/7291771.html https://www.cnblogs.com/centor/p/5937788.html https://blog.csdn.net/piaoxuezhong/article/details/62217443 转载于:https://www.cnblogs.com/mmmmc/p/10524640.html