前面提到,我们可以用平均或加权平均来降低噪声,以增强图像。前面我们是对像素点的周边8领域进行的操作,要是我们想对周边更多领域进行操作呢?如果我们想要调整加权的权重值呢?

那么用前2篇文章中的方法显然是非常烦琐且不灵活的。我们注意到,对8领域进行平均或加权平均操作,实质上是对3×3的一个矩形区域进行操作。

空间滤波器是怎么来的-编程之家

如果相邻像素再扩大一圈,就是5×5的矩形区域:
空间滤波器是怎么来的-编程之家

我们前面说:图像就是矩阵,那么这个相邻像素构成的区域不也是矩阵吗?同样的,不管是平均操作还是加权平均,这个区域矩阵中的每一个像素点都需要乘以一个系数,那么这个系数是不是同样组成了一个矩阵呢,如果是3×3的区域,那么系数矩阵就是:

空间滤波器是怎么来的-编程之家

根据线性代数的矩阵数乘法则,我们可以把9放在矩阵外部,变成这个形式:

空间滤波器是怎么来的-编程之家

同样的,加权操作的系数矩阵是这样的:
空间滤波器是怎么来的-编程之家

那么,对图像的平均操作和加权平均操作进行降噪实质上就是用这个系数矩阵与图像中的任意一点的领域区域矩阵进行矩阵点乘,然后求点乘后矩阵的和。

写出公式就是:

g(x,y)=a=nnb=nnw(x,y)f(x+a,y+b)g(x,y)=∑a=−nn∑b=−nnw(x,y)⋅f(x+a,y+b)

w是大小为n×n的系数矩阵,f(x,y)是点(x,y)处的值,g(x,y)是进行平均操作或加权平均操作后点(x,y)处的值。

import cv2
import numpy as npsalt = cv2.imread("salt_lena.bmp", 0)
row, column = salt.shape
reduce = salt[:]
coefficient = np.array([[1, 2, 1],[2, 4, 2],[1, 2, 1]])
region_row, region_column = coefficient.shapefor x in range((region_row - 1) // 2, row - (region_row - 1) // 2):for y in range((region_column - 1)  // 2, column - (region_column - 1) // 2):reduce[x, y] = np.sum(coefficient * salt[x - (region_row - 1) // 2:x + (region_row - 1) // 2 + 1,y - (region_column - 1) // 2:y + (region_column - 1) // 2 + 1]) / np.sum(coefficient)cv2.imshow("reduce_filter", reduce.astype("uint8"))
cv2.waitKey()

空间滤波器是怎么来的-编程之家

效果是以前的做法是一样的。

这样的话,就把系数矩阵w抽象了出来,如果需要扩大领域操作的范围,就改变系数矩阵的大小n×n,如果需要调整权重,就改变系数矩阵中的相应位置的值。

这个抽象的系数矩阵w我们可以称它为模板、核、窗口、卷积核等等,都是等价的。而这种操作既然是降低噪声,就是过滤掉噪声,借用频率域的术语,又可以称之为滤波操作,那么这个系数矩阵w就称之为滤波器。由于是在图像的二维空间中进行操作,所以又称为空间滤波器。以后你看到模板卷积核窗口滤波器空间滤波器等等术语,都指的是这个东西。当然,卷积的真实含义与这里的操作有一点点的区别,但不必过于拘泥于术语的精确性。关于卷积以后还将探讨到。

事实上opencv已经给我们封装好了。我们用一个5×5的空间滤波器,使用opencv的filter2D函数。

salt = cv2.imread("salt_lena.bmp", 0)
filter = 1 / 25 * np.ones((5, 5))
reduce_filter = cv2.filter2D(salt, -1, filter)
cv2.imshow("reduce_filter", reduce_filter)
cv2.waitKey()

输出结果:
空间滤波器是怎么来的-编程之家

这里我们用了3×3和5×5的空间滤波器,大小都是奇数,那么可不可以使用4×4,8×8,甚至是5×4,8×7的尺寸呢,当然是可以的。但是,使用奇数尺寸的滤波器可以简化索引,并更为直观,因为需要操作的像素点是落在滤波器的中心位置上。