6 滤波器
6.1 卷积
6.1.1 图片卷积
这里有一篇文章我们可以进行可视化的理解:卷积操作
图像卷积就是卷积核在图像上按行滑动遍历像素是不断的相乘求和的过程。
以右下角的3 * 3矩阵为例,矩阵数字 * 该矩阵数字对应的权值再求和得出结果4
(即 1 * 1 + 1 * 0 + 1 * 1 + 1 * 0 + 1 * 1 + 1 * 0 + 1 * 1 + 0 * 0 + 0 * 1 = 4)。
6.1.2 步长
步长就是卷积核再图像上移动的步幅(上面例子中的步幅即为1)。
为了充分扫描图片,步长一般设为1。
6.1.3 padding
由于卷积之后图片的长宽会变小,如果要保持图片的大小不变,我们需要在图片的周围填充0。
padding指的就是对原始图片填充0的圈数。
6.1.4 卷积核的大小
图像中,卷积核的大小一般为奇数,比如3 * 3, 5 * 5。
基于以下两个原因:
1.根据padding的计算公式,如果保持图片大小不变,采用偶数卷积核的话,将会出现填充x.5圈0的情况。
2.奇数维度的过滤器有中心,便于指出过滤器的文职,即OpenCV卷积中的锚点。
卷积核即滤波器。
6.1.5 卷积案例
- cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
– ddepth:卷积之后图片的位深,即卷积之后图片的数据类型,一般设为-1,表示和原图类型一致。
– kernel:卷积核大小,用元组或者ndarray表示,要求数据类型必须是float型。
– anchor:锚点,即卷积核的中心点,是可选参数,默认是(-1, -1)
– delta:可选参数,表示卷积之后额外加的一个值,相当于线性方程中的偏差,默认是0.
– borderType:边界类型,一般不设。
import cv2
import numpy as np
img = cv2.imread('./dog.jpeg')
#相当于原始图片中的每个点都被平均了一下,图像变模糊
kernel = np.ones((5, 5), np.float32) / 25
dst = cv2.filter2D(img, -1, kernel)
cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
6.2 方盒滤波与均值滤波
- cv2.boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]):方盒滤波
– normalize = True时,a = 1 / (W * H)
– normalize = False时,a = 1
– 一般情况下使用normalize = True,这时方盒滤波等价于均值滤波
- cv2.blur(src, ksize[, dst[, anchor[, borderType]]]):均值滤波
import cv2
import numpy as np
img = cv2.imread('./dog.jpeg')
#此处卷积核只要给出大小
dst = cv2.boxFilter(img, -1, (5, 5), normalize = True)
dst1 = cv2.blur(img, (5, 5))
cv2.imshow('img', np.hstack((img, dst, dst1)))
cv2.waitKey(0)
cv2.destroyAllWindows()
6.3 高斯滤波
高斯函数是符合高斯分布(正态分布)的数据的概率密度函数。
高斯滤波就是使用符合高斯分布的卷积核对图片进行卷积操作,所以高斯滤波的重点就是如何计算高斯分布的卷积核,即高斯模板。
卷积核的特点是越靠近中心,值越大,越远离中心,值越小。
我们需要对求出的高斯函数值进行归一化操作。
- cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])
– kernel:高斯核的大小
– sigmaX:X轴的标准差
– sigmaY:Y轴的标准差,默认为0
– 如果没有指定sigma值,会分别从ksize的宽度和高度中计算sigma - 选择不同的sigma值会得到不同的平滑效果,sigma越大,平滑效果越明显。
- 没有指定sigma时,ksize越大,平滑效果越明显。
- 平滑效果越明显,图片越模糊,细节越少。
import cv2
import numpy as np
img = cv2.imread('./dog.jpeg')
dst = cv2.GaussianBlur(img, (5, 5), sigmaX=1)
cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
6.4 中值滤波
原理非常简单,假设有一个数组[1, 5, 5, 6, 7 , 8, 9],去七中的中间值即中位数作为卷积后的结果值即可。
中值滤波对胡椒噪音(椒盐噪音)效果明显。
- cv2.medianBlur(src, ksize[, dst])
import cv2
import numpy as np
img = cv2.imread('./papper.png')
#注意这里的ksize就是一个数字
dst = cv2.medianBlur(img, 5)
cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
6.5 双边滤波
该滤波可以实现美颜的效果0.0。
对椒盐噪声几乎没效果。
双边滤波对于图像的边缘信息能够更好的保存,原理为一个与空间距离有关的高斯函数与一个灰度距离相关的高斯函数相乘。
双边滤波本质上是高斯滤波,两者的不同点在于:
– 双边滤波利用了位置信息有利于了像素信息来定义滤波窗口的权重;而高斯滤波只用了位置信息。
– 高斯滤波认为离中心点越近的点,其权重系数越大;
双边滤波认为在邻域内,灰度值越接近中心点灰度值的点权重更大,灰度值相差大得点权重越小。此权重的大小,有值域高斯函数确定。
- cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])
– sigmaColor:计算像素信息使用
– sigmaSpace:计算空间信息使用
import cv2
import numpy as np
img = cv2.imread('./lena.png')
#要处理的图像,直径:可以理解为卷积核的大小,后面俩参数需要根据效果去调试
dst = cv2.bilateralFilter(img, 7, 20, 50)
cv2.imshow('img', np.hstack((img, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
注意:滤波我们一般用来消除噪声,而算子我们一般是用来找边界的。
6.6 Sobel算子
边缘是像素值发生跃迁的位置,是图像的显著特征之一,在图像特征提取、对象检测、模式识别等方面都有重要的作用。
人眼识别图像边缘是看像素的灰度值快速变化的地方,比如一幅图中,一条线左边很亮,右边很暗,那人眼就很容易识别这条线作为边缘。
sobel算子对图像求一阶导数。一阶导数越大,说明像素在该方向的变化越大,边缘信号越强。
因为图像的灰度值都是离散的数字,sobel算子采用离散差分算子计算图像像素点亮度值的近似梯度。
图像是二维的,即沿着宽度、高度两个方向。
我们使用两个卷积核对图像进行处理:
- cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
import cv2
import numpy as np
img = cv2.imread('./chess.png')
#sobel要分开计算x,y轴的梯度
#x轴方向获取的是垂直边缘,计算完后是64位float型
dx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
#y轴获取的是水平边缘
dy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
#把x,y轴梯度合在一起
dst = cv2.add(dx, dy)
cv2.imshow('img', img)
cv2.imshow('new_img', np.hstack((dx, dy, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
6.7 Scharr算子
-
cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])
-
当内核大小为3时,以上Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。
-
为解决这一问题,OpenCV提供了Scharr函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确。
-
Scharr算子和Sobel算子很类似,只不过使用不同的kernel值,放大了像素变换的情况:
-
Scharr算子只支持3 * 3的kernel,所以参数列表没有kernel参数。
-
Scharr算子只能求x方向或y方向的边缘。
-
Sobel算子的ksize设为-1就是Scharr算子。
-
Scharr算子擅长寻找细小的边缘,一般用得较少。
import cv2
import numpy as np
img = cv2.imread('./lena.png')
#x轴方向获取的是垂直边缘,计算完后是64位float型
dx = cv2.Scharr(img, cv2.CV_64F, 1, 0)
#y轴获取的是水平边缘
dy = cv2.Scharr(img, cv2.CV_64F, 0, 1)
#把x,y轴梯度合在一起
dst = cv2.add(dx, dy)
cv2.imshow('dst', np.hstack((dx, dy, dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
6.8 Laplacian算子
Laplacian算子对图像进行二阶求导,边缘处的二阶导数=0(注意:二阶求导为0的位置也可能是无意义的位置,一般是噪声)。
以下提供Laplacian算子的推导过程(看看就好,不看也无所谓):
- cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
- 可以同时求两个方向的边缘
- 对噪音敏感,一般需要先进行去噪再调用Laplacian算子
import cv2
import numpy as np
img = cv2.imread('./chess.png')
dst = cv2.Laplacian(img, -1, ksize=3)
cv2.imshow('dst', np.hstack((img,dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
6.9 边缘检测Canny
Canny边缘检测是非常流行的边缘检测算法,它是一个多阶段的算法,由多个步骤构成:
1.图像降噪:由于边缘检测很容易受到噪声影响,所以首先使用5 * 5的高斯滤波去除噪声。
2.计算图像梯度大小和方向
3.非极大值抑制:消除边缘检测带来的杂散响应。
线性插值法:
简单方法:
4.阈值筛选:使用双阈值筛选来确定真实的和潜在的边缘。
- cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])
– threshold1:minval
– threshold2:maxval
import cv2
import numpy as np
#输入的图片无噪声
img = cv2.imread('./lean.png')
#大数值,边缘信息偏少
canny1 = cv2.Canny(img, 80, 150)
#小数值,边缘信息偏多
canny2 = cv2.Canny(img, 50, 100)
cv2.imshow('img', np.hstack((canny1, canny2)))
cv2.waitKey(0)
cv2.destroyAllWindows()
这一部分需要阈值处理的知识,顺序好像出了点问题…