(九)图像梯度与Canny边缘检测

目录

 

1、图像梯度

1)Sobel算子核Scharr算子

2)Laplacian算子

2.Canny边缘检测

1)噪声去除

2)计算图像梯度

3)非极大值抑制

4)滞后阈值


1、图像梯度

目标:

  • 图像梯度,图像边界
  • 函数:cv2.Sobel(),cv3.Schar(),cv2.Laplacian()

原理:梯度简单来说就是求导。OpenCV提供了三种不同的梯度滤波器或者说高通滤波器(很明显高通滤波器就是来检测边界的,低通滤波器是用来滤除噪声的),Sobel,Scharr和Laplacian。

Sobel,Schar其实就是求一阶或者二阶导数。Schar是对Sobel(使用小的卷积核求解梯度角度时)的优化。Laplacian是求二阶导数。

1)Sobel算子核Scharr算子

Sobel算子是高斯与微分操作的结合体,所以它的抗噪声能力很好。你可以设定求导的方向(xorder或yorder)。还可以设定使用的卷积核的大小(ksize)。如果ksize=-1,(这里的ksize代表kernelsize),会使用3x3的Scharr滤波器,它的效果要比3x3的Sobel滤波器好(而且速度相同,所以在使用3x3的滤波器的时候应该尽量使用Scharr滤波器)。3x3的Scharr滤波器卷积核如下:

    

2)Laplacian算子

拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶Sobel导数,事实上,OpenCV在计算拉普拉斯算子时直接调用Sobel算子。

计算公式如下:

     

拉普拉斯滤波器使用的卷积核:

     

可以对三种滤波器效果做一下对比:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

path="D:\\openCV\\opencv\\sources\\samples\\data\\sudoku.png"
img=cv.imread(path)
#gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)

#cv2.CV_64F 输出图像的深度(数据类型),可以使用-1;与原图像保持一致 np.uint8
laplacian=cv.Laplacian(img,cv.CV_64F)

#参数 1,0 为只在x方向求一阶导数,最大可求二阶导数
sobelx=cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
#参数0,1 为只在y方向上求一阶导数,最大可求二阶导数
sobely=cv.Sobel(img,cv.CV_64F,0,1,ksize=5)

sobel=cv.Sobel(img,cv.CV_64F,1,1,ksize=5)

titles=["origianl","sobel","sobelx","sobely"]
images=[img,sobel,sobelx,sobely]
cv.imshow("laplacian",laplacian)


for i in range(4):
    plt.subplot(2,2,i+1),plt.imshow(images[i],"gray"),plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()



cv.waitKey()

Sobel过滤图:

    

拉普拉斯二阶过滤:

    

从这两个对比图可以看出二阶求导过滤效果还是不错的,也就是说在边缘检测中二阶求导要比一阶求导效果更明显。

注意:

在上面过滤时,可以通过参数-1来设定输出图像的深度(数据类型)与原图像保持一致,但是我们自爱代码中却使用了cv.CV_64F。这时为什么呢?假设一个从黑到白的边界的导数的正数,而一个从白到黑的边界的导数是负数。如果原图像的深度的np.uint8时,所有的负数值都会被截断变成0,换句话说就是把边界都丢失掉。

所以,如果这两种边界都想检测到,最好的 办法就是将输出的数据类型设置的更高,比如cv2.CV_16S,cv2,CV_64F等。取绝对值然后再把它转回到cv.CV_8U

2.Canny边缘检测

Canny边缘检测一般分为4步:

1)噪声去除

由于边缘检测很容易受到噪声影响,所以第一步是使用5x5的高斯滤波器去除噪声。

2)计算图像梯度

对平滑后的图像使用Sobel算子计算水平方向和竖直方向的一阶导数(图像梯度)(Gx和Gy)。根据得到的这两幅梯度图(Gx和Gy)找到边界的梯度和方向,公式如下:

    

梯度的方向一般总是与边界垂直。梯度方向被归为4类:垂直,水平和两个对角线。

3)非极大值抑制

在获得梯度的方向和大小之后,应该对整幅图像做一个扫描,去除那些非边界上的点。对每一个像素进行检查,看这个点的梯度是不是周围具有相同梯度方向的点中最大的。如下图所示:

    

4)滞后阈值

现在需要确定哪些边界才是真正的边界。这时我们需要设置两个阈值:maxValminVal当图像的灰度梯度高于maxVal时被认为是真正的边界,那些低于minVal的边界会被抛弃。如果介于两者之间的话,就要看这个点是否与某个被确定为真正的边界点相连,如果是就认为它也是边界点,如果不是就抛弃

如图所示:

     

说明:A高于阈值maxVal所以是真正的边界点,C虽然低于maxVal但是它高于minVal,而且它与A相连,所以C也被认为是真正的边界。B就不同了,它虽然介于minVal和maxVal之间,但是它是一条孤立的线(点),所以B会被抛弃

Canny()函数:

第一个参数是输入图像,第二、第三个参数是minVal和maxVal。第四个参数用来计算图像梯度的Sobel卷积核的大小,默认值为3.最后一个参数是L2gradient,它可以用来设定求梯度大小的方程。如果设为True,就会使用上面提到的方程,否则使用方程:

Edge_Gradient(G)=|Gx(平方)|+|Gy(平方)|代替,默认值为False。

示例代码:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

path="D:\\openCV\\opencv\\sources\\samples\\data\\messi5.jpg"
img=cv.imread(path)

#做边缘检测之前先转换为灰度图
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges=cv.Canny(gray,100,230)
cv.imshow("original",gray)
cv.imshow("canny",edges)

cv.waitKey()

效果图:

     

感觉边缘检测之后条纹太多了呢,可以考虑将maxVal设置小一点,我设置的是230比较高了,可以设置为200试一试。

猜你喜欢

转载自blog.csdn.net/weixin_38664232/article/details/86551234
今日推荐