滤波(blur)操作是一种基于邻域的图像平滑方法。
当图像噪声只是图像的一小部分时,用某一像素点的邻域进行变换得到的新的像素点可以减小噪声的影响,从而很好的平滑噪声。
均值滤波是对中心点的邻域求算术平均和,中值滤波是对中心点的邻域求中值。
本文主要说的高斯滤波,高斯滤波可以看作对均值滤波的改进,
以33的邻域为例,均值滤波是对这九个数求平均,而高斯滤波是对这个九个数求加权平均,其中心思想是邻域中每个点离中心点的距离不一样,不应该像均值滤波一样每个点的权重一样,而是离中心点越近,权值越大。而每个点的权重就是高斯分布(也就是正态分布)。
正态分布如下:
其中u为均值,为方差
x看作像素点距离中心点的距离,则取0,
基于正态分布的思想,33的邻域权重矩阵(高斯卷积核kernel)即为
,然后将权重矩阵归一化,最终的权重矩阵为
,其中sum=f(0)+4f(1)+4f(2);
#假使sigma = 1,卷积核ksize=3
def f(x):
return np.power(np.e,-x*x/2) #因为最后需要归一化,所以可以不管正态分布前面的参数
f0 = f(0)
f1 = f(1)
f2 = f(2)
sum=f0+4*(f1+f2)
kernel = np.array(
[[f2/sum,f1/sum,f2/sum],
[f1/sum,f0/sum,f1/sum],
[f2/sum,f1/sum,f2/sum],
]
)
print(kernel)
用这种方法计算的卷积核如下
但如果用这个卷积核与图片做卷积得到的结果和opencv的
GaussianBlur()方法得到的结果完全不同;
opencv的计算方式显然并不是这种。
首先看看opencv的GaussianBlur()方法:
cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) → dst
其中src,dst分别是输入和输出图片,borderType为边界填充方式。
ksize为卷积核大小,即邻域大小,比如ksize为(3,3),则对以中心点为中心点3 * 3的邻域做操作;
而在高斯分布中,需要设定的参数为和,因为对称性,所以设定为0,而与想象的不同,opencv的高斯模糊函数输入了两个(sigma)参数,sigmaX,sigmaY,显然opencv实现的高斯滤波和上文写的方法不太相同。
仔细阅读官方文档GaussianBlur后,可以看到
描述的很清楚,sigmaX是X轴的高斯核的,sigmaY是Y轴的高斯核的;
再阅读官方文档getGaussianKernel函数
可知:
这个函数可以根据ksize和sigma求出对应的高斯核,计算方式就是上文提到的计算方式,而返回值是一个一维高斯核。
其中需要注意的是,如果sigma为非正数(负数或0)的话,就会根据ksize来自动计算sigma,计算公式为sigma = 0.3*((ksize-1)*0.5-1)+0.8
由上述公式可以计算得出,
当ksize=3时,sigma=0.8
当ksize=5时,sigma为1.1.
再继续往下阅读,两个这样产生的一维高斯核可以传递给sepFilter2D函数,
继续阅读官方文档的sepFilter2D
cv2.sepFilter2D(src, ddepth, kernelX, kernelY[, dst[, anchor[, delta[, borderType]]]]) → dst
cv2.sepFilter2D函数传入两个一维kernel,然后对图像的每一行以kernelX为卷积核做卷积,对结果的每一列以kernelY为卷积核做卷积,最后归一化得到新的高斯滤波后的图像。
综上:opencv实现的高斯滤波,是对传入的sigmaX,sigmaY分别产生两个一维卷积核,然后分别再行和列上做卷积,其中sigmaX和sigmaY如果没有传入参数,则由ksize计算得到。
进行验证:
image_ori = cv2.imread('car.png')
image_gray = cv2.cvtColor(image_ori,cv2.COLOR_BGR2GRAY)
image1 = cv2.GaussianBlur(image_gray,(3,3),0.8,0.8)
image2 = cv2.sepFilter2D(image_gray,-1,cv2.getGaussianKernel(3,0.8),cv2.getGaussianKernel(3,0.8))
print(image1==image2)
验证结果: