高斯平滑、双边平滑 和 均值平滑、中值平滑 介绍的平滑处理可以看做是图像的“低通滤波”,它会滤除掉图像的“高频”部分,使图像看起来更平滑,而图像梯度则可以看做是对图像进行“高通滤波”,它会滤除图像中的低频部分,为的是凸显出图像的突变部分。
1.sobel
Sobel梯度会有x和y 2个方向的梯度,x方向梯度是该点右侧像素值的2倍加上右上、右下像素值的和减去左侧像素值的2倍加上左上、左下像素值的和,该点的梯度值和自身无关,只和其左右2侧的像素值有关,y方向的梯度则是和上下方向的像素值有关。
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
src:源图像;
ddepth:目标图像深度;
dx:x方向求导阶数;
dy:y方向求导阶数;
dst:目标图像;
ksize:kernel尺寸,说明文档上指出必须是1,3,5,7中的一个,但是实验可以得到应该是小于31的正奇数;如果是-1表示scharr滤波;
scale:缩放比例,默认为1;
delta:叠加值,默认为0;
borderType:边界填充类型;
2.Scharr
Scharr变换可以看做是使用了Scharr核的Sobel变换,是一种经过改进的Sobel变换,同样也要区分x和y方向分开计算梯度。
dst = cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])
src:源图像;
ddepth:目标图像深度;
dx:x方向求导阶数;
dy:y方向求导阶数;
dst:目标图像;
scale:缩放比例,默认为1;
delta:叠加值,默认为0;
borderType:边界填充类型;
注意Scharr()没有ksize参数,因为Scharr kernel的大小固定为3×3。
先设置dx=1,dy=0计算x方向的梯度,再设置dy=1,dx=0计算y方向的梯度,为了避免出现饱和运算,
dtype设置的是比源图像数据类型CV_8U更高的CV_16S,然后将结果用convertScaleAbs()转换回
CV_8U(np.unit8)类型,最后用addWeighted()将x和y方向的梯度图加权相加。
grad_x = cv2.Scharr(img_src,cv2.CV_16S,1,0)
grad_y = cv2.Scharr(img_src,cv2.CV_16S,0,1)
abs_grad_x = cv2.convertScaleAbs(grad_x)
abs_grad_y = cv2.convertScaleAbs(grad_y)
grad = cv2.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0)
3.Laplacian
Laplacian变换是对图像求二阶导数,下图是2种3×3尺寸的kernel,这里ksize是Laplacian()的入参名称:
Laplacian()变换不需要区分图像的x和y方向计算梯度,从上图的2种kernel也可以看到其x和y方向是对称的。
在Laplacian()变换中,ksize必须是小于31的正奇数,但是当ksize等于1时,这时kernel的尺寸大小并非是1,其实际尺寸仍然为3×3,这点从源码上可以看到当ksize=1时,实际为一个包含9个元素的3×3尺寸的kernel:
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
src:源图像;
ddepth:目标图像深度;
dst:目标图像;
ksize:kernel尺寸,小于31的正奇数;如果为1仍然是一个3×3的kernel;
scale:缩放比例,默认为1;
delta:叠加值,默认为0;
borderType:边界填充类型;
Laplacian变换中没有dx或dy参数,因为Laplacian是对图像求二阶导数。
从运行结果可以看到Laplacian()中ksize越大,梯度信息越丰富,这点和Sobel变换是一样的。另外在相同的ksize时,二阶Sobel变换和Laplacian变换对比看,Laplacian变换取得的梯度信息要更明显一些。
#Laplacian
grad_lap = cv2.Laplacian(img_src,cv2.CV_16S,ksize=1)
abs_grad_lap1 = cv2.convertScaleAbs(grad_lap)
grad_lap = cv2.Laplacian(img_src,cv2.CV_16S,ksize=3)
abs_grad_lap3 = cv2.convertScaleAbs(grad_lap)
grad_lap = cv2.Laplacian(img_src,cv2.CV_16S,ksize=5)
abs_grad_lap5 = cv2.convertScaleAbs(grad_lap)
#二阶Sobel
grad_x = cv2.Sobel(img_src, cv2.CV_16S, 2, 0, ksize=3)
grad_y = cv2.Sobel(img_src, cv2.CV_16S, 0, 2, ksize=3)
abs_grad_x = cv2.convertScaleAbs(grad_x)
abs_grad_y = cv2.convertScaleAbs(grad_y)
abs_grad_sobel = cv2.addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0)
图像梯度 反映的是图像像素值的变化过程,不管变化大小都考虑在内,所以Sobel,Laplacian变换得到的是一个多级灰度图。边沿检测也可以看做是图像梯度的一种延伸,不过边沿检测更注意图像的“边沿”部分,图像梯度变化较小的部分会被忽略,只有较大变化的部分保留下来。
canny边沿检测有低错误率、很好地定位边缘点、单一的边缘点响应等优点。canny边沿检测算法由以下几个步骤组成:
1)高斯滤波器平滑输入图像;
2)计算梯度幅值图像和角度方向;
3)对梯度幅值图像应用非最大值抑制;
4)用双阈值处理和连接分析检测和连接边沿。
第一种接口
edges=cv2.Canny(image, threshold1, threshold1[, edges[, apertureSize[, L2gradient]]])
image:8bit源图像,可以是单通道或多通道;
threshold1:迟滞阈值1;
threshold2:迟滞阈值2,和threshold1没有大小要求,函数内部会调整交换;
edges:目标图像,二值图像;
apertureSize:kernel尺寸,默认为3;
L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;
第2种接口形式如下:
edges=cv2.Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]])
dx:源图像的16bit(CV_16SC1 or CV_16SC3) x方向梯度图像;
dy:源图像的16bit(CV_16SC1 or CV_16SC3) y方向梯度图像;
threshold1:迟滞阈值1;
threshold2:迟滞阈值2;
edges:目标图像;
L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;
第2种接口形式和第1种实现边沿检测的效果是一样的,第2种形式需要先计算图像的x和y方向的梯度,所以计算梯度的kernel尺寸在第2种接口中就不需要了。
Ksize差异
img_edge3 = cv2.Canny(img_src,20,100,apertureSize=3)
img_edge5 = cv2.Canny(img_src,20,100,apertureSize=5)
img_edge7 = cv2.Canny(img_src,20,100,apertureSize=7)
相同的threshold值,ksize越大边沿细节越多,这点和Sobel(),Scharr(),Laplacian()计算图像梯度效果是一样的。
阈值宽度
img_edge3 = cv2.Canny(img_src,50,60,apertureSize=3)
img_edge5 = cv2.Canny(img_src,50,120,apertureSize=3)
img_edge7 = cv2.Canny(img_src,50,240,apertureSize=3)
相同的ksize时,threshold1和threshold2的差值越小,边沿细节越多。