图像梯度 边缘检测
图像梯度,图像边界
使用到的函数有: cv2.Sobel(), cv2.Schar(), cv2.Laplacian()
梯度简单来说就是求导,OpenCV 提供了三种不同的梯度滤波器,或者说高通滤波器: Sobel,Scharr 和 Laplacian。
- Sobel, Scharr 其实就是求一阶或二阶导数。
- Scharr 是对 Sobel(使用小的卷积核求解求解梯度角度时)的优化。
- Laplacian 是求二阶导数。
Sobel算子 cv2.Sobel()
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
前四个是必须的参数:
- src参数是需要处理的图像;
- ddepth参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
- dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
其后是可选的参数:
- ksize是Sobel算子的大小,必须为1、3、5、7。
- scale是缩放导数的比例常数,默认情况下没有伸缩系数;
- delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
- borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
Sobel算子是高斯平滑与微分操作的结合体,所以它的抗噪声能力很好,可以设定求导的方向(xorder 或 yorder),还可以设定使用的卷积核的大小(ksize),如果 ksize=-1,会使用 3x3 的 Scharr 滤波器,它的的效果要比 3x3 的 Sobel 滤波器好(而且速度相同,所以在使用 3x3 滤波器时应该尽量使用 Scharr 滤波器)。
%matplotlib inline
from matplotlib import pyplot as plt
import cv2
import numpy as np
img = cv2.imread('edage.jpg', 0)
"""
在Sobel函数的第二个参数这里使用了cv2.CV_16S。
因为OpenCV文档中对Sobel算子的介绍中有这么一句:
“in the case of 8-bit input images it will result in truncated derivatives”。
即Sobel函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,
所以Sobel建立的图像位数不够,会有截断。因此要使用16位有符号的数据类型,即cv2.CV_16S
"""
x = cv2.Sobel(img,cv2.CV_16S,1,0)
y = cv2.Sobel(img,cv2.CV_16S,0,1)
"""
在经过处理后,别忘了用convertScaleAbs()函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口。
dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])
可选参数alpha是伸缩系数,beta是加到结果上的一个值。结果返回uint8类型的图片
"""
absX = cv2.convertScaleAbs(x) # 转回uint8
absY = cv2.convertScaleAbs(y)
"""
由于Sobel算子是在两个方向计算的,最后还需要用cv2.addWeighted(...)函数将其组合起来
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
其中alpha是第一幅图片中元素的权重,beta是第二个的权重,gamma是加到最后结果上的一个值。
"""
dst = cv2.addWeighted(absX,0.5,absY,0.5,0)
img_h1 = np.hstack([img, absX])
img_h2 = np.hstack([absY, dst])
img_all = np.vstack([img_h1, img_h2])
plt.figure(figsize=(20,10))
plt.imshow(img_all, cmap=plt.cm.gray)
plt.show()
Laplacian 算子
拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶Sobel导数,事实上,OpenCV在计算拉普拉斯算子时直接调用Sobel 算子。
Laplacian算子:图像中的边缘区域,像素值会发生“跳跃”,对这些像素求导,在其一阶导数在边缘位置为极值,这就是Sobel算子使用的原理——极值处就是边缘。
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
前两个是必须的参数:
- src参数是需要处理的图像;
- ddepth参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
其后是可选的参数:
- ksize是算子的大小,必须为1、3、5、7。默认为1。
- scale是缩放导数的比例常数,默认情况下没有伸缩系数;
- delta是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中;
- borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
拉普拉斯对噪声敏感,会产生双边效果。不能检测出边的方向。通常不直接用于边的检测,只起辅助的角色,检测一个像素是在边的亮的一边还是暗的一边利用零跨越,确定边的位置。
%matplotlib inline
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("edage.jpg", 0)
gray_lap = cv2.Laplacian(img, cv2.CV_16S, ksize=3)
dst = cv2.convertScaleAbs(gray_lap)
plt.imshow(dst, cmap=plt.cm.gray)
plt.show()
边缘检测
边缘检测的一般步骤:
- 滤波——消除噪声
- 增强——使边界轮廓更加明显
- 检测——选出边缘点
Canny边缘检测
图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘。
Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。
Canny算子检测原理是通过图像信号函数的极大值来判定图像的边缘像素点。边缘检测的算法主演是基于图像强度的一阶和二阶微分操作,但导数通常对噪声很敏感,边缘检测算法常常需要根据图像源的数据进行预处理操作,因此必须采用滤波器来改善与噪声有关的边缘检测的性能。在进行Canny算子边缘检测前,应当先对原始数据与高斯模板进行卷积操作,得到的图像与原图像相比有些模糊。通常使用高斯平滑滤波器卷积降噪。
edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])
必要参数:
- 第一个参数是需要处理的原图像,该图像必须为单通道的灰度图;
- 第二个参数是阈值1;
- 第三个参数是阈值2。 其中较大的阈值2用于检测图像中明显的边缘,但一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的。所以这时候用较小的第一个阈值用于将这些间断的边缘连接起来.
可选参数中apertureSize就是Sobel算子的大小。
而L2gradient参数是一个布尔值,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开放),否则使用L1范数(直接将两个方向导数的绝对值相加)
Canny边缘检测基本原理:
- 图象边缘检测必须满足两个条件:一能有效地抑制噪声;二必须尽量精确确定边缘的位置。
- 根据对信噪比与定位乘积进行测度,得到最优化逼近算子。这就是Canny边缘检测算子。
- 类似与Marr(LoG)边缘检测方法,也属于先平滑后求导数的方法。
Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:
- 好的检测 - 算法能够尽可能多地标识出图像中的实际边缘。
- 好的定位 - 标识出的边缘要尽可能与实际图像中的实际边缘尽可能接近。
- 最小响应 - 图像中的边缘只能标识一次,并且可能存在的图像雜訊不应标识为边缘。
canny 算法五步骤
- 高斯模糊
- 灰度转换
- 计算梯度
- 非最大信号抑制
- 高低阈值输出二值图像
%matplotlib inline
import cv2
import numpy as np
import matplotlib.pyplot as plt
def edge_detect(img):
#高斯模糊,降低噪声
blurred = cv2.GaussianBlur(img,(3,3),0)
#灰度图像
gray = cv2.cvtColor(blurred,cv2.COLOR_RGB2GRAY)
#图像梯度
xgrad = cv2.Sobel(gray,cv2.CV_16SC1,1,0)
ygrad = cv2.Sobel(gray,cv2.CV_16SC1,0,1)
#计算边缘
#50和150参数必须符合1:3或者1:2
edge_output = cv2.Canny(xgrad,ygrad,50,150)
dst = cv2.bitwise_and(img,img,mask=edge_output)
return edge_output, dst
img = cv2.imread('edage.jpg')
edge_output, canny_edge = edge_detect(img.copy())
plt.figure(figsize=(20, 8))
plt.subplot(131)
plt.imshow(img[:,:,::-1])
plt.subplot(132)
plt.imshow(canny_edge[:,:,::-1])
plt.subplot(133)
plt.imshow(edge_output, cmap=plt.cm.gray)
plt.tight_layout()
plt.show()