平滑图像
目标
在本文将学习如何使用OpenCV函数将各种线性过滤器应用于平滑图像,例如:
● blur()
● GaussianBlur()
● medianBlur()
● bilateralFilter()
理论
注意
下面的解释来自 Richard Szeliski 的《计算机视觉:算法和应用》一书和 LearningOpenCV
● 平滑,也称为模糊,是一种简单且常用的图像处理操作。
● 要进行平滑处理的原因有很多。本文将重点介绍用于减少噪声的平滑处理。
● 为了执行平滑操作,我们将对图像应用滤波器。最常见的滤波器类型是线性的,其中输出像素的值(如 g(i,j))被确定为输入像素值(如 f(i+k,j+l))的加权和:

h(k,l) 称为核,它只不过是滤波器的系数。
它有助于将滤波器可视化为在图像上滑动的系数窗口。
● 滤波器有很多种,这里我们将提到最常用的:
归一化盒子滤波器
● 这个滤波器是最简单的!每个输出像素是其内核邻域的平均值(所有邻域的权重相等)
● 内核如下:

高斯滤波器
● 可能是最有用的滤波器(虽然不是最快的)。高斯滤波是通过将输入阵列中的每个点与高斯核卷积,然后将它们相加来生成输出阵列来完成的。
● 为了让画面更清晰,请回顾一维高斯核是什么样子。

假设图像是1维,可以注意到位于中间的像素将具有最大的权重。邻域的权重随着它们与中心像素之间的空间距离的增加而减小。
注意
请记住,二维高斯分布可以表示为:

其中μ表示均值,σ2表示方差(基于每个变量x和y)
中值滤波器
中值滤波器遍历信号(在本例中为图像)的每个元素,并将每个像素替换为其相邻像素的中值(相邻像素位于被评估像素周围的正方形邻域中)。
双边滤波器
● 到目前为止,我们已经解释了一些主要目标是平滑输入图像的过滤器。然而,有时滤波器不仅能消除噪声,还能平滑边缘。为了避免这种情况(至少在一定程度上),我们可以使用双边滤波器。
● 与高斯滤波器类似,双边滤波器也会考虑相邻像素,会为每个像素分配权重。这些权重有两个部分,第一部分与高斯滤波器使用的加权方式相同。第二部分考虑相邻像素和被评估像素之间的强度差异。
代码
此程序做了什么?
● 加载图像
● 应用4种不同类型的滤波器(理论部分提到的),并按顺序显示过滤后的图像
import sysimport cv2 as cvimport numpy as np# 全局变量DELAY_CAPTION = 1500DELAY_BLUR = 100MAX_KERNEL_LENGTH = 31src = Nonedst = Nonewindow_name = 'Smoothing Demo'def main(argv):cv.namedWindow(window_name, cv.WINDOW_AUTOSIZE)# 加载源图像imageName = argv[0] if len(argv) > 0 else 'horizontal.png'global srcsrc = cv.imread(imageName)if src is None:print ('打开图片发生错误')print ('用法: smoothing.py [图片名称 -- 默认 horizontal.png] \n')return -1if display_caption('Original Image') != 0:return 0global dstdst = np.copy(src)if display_dst(DELAY_CAPTION) != 0:return 0# Applying Homogeneous blurif display_caption('Homogeneous Blur') != 0:return 0for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.blur(src, (i, i))if display_dst(DELAY_BLUR) != 0:return 0# Applying Gaussian blurif display_caption('Gaussian Blur') != 0:return 0for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.GaussianBlur(src, (i, i), 0)if display_dst(DELAY_BLUR) != 0:return 0# Applying Median blurif display_caption('Median Blur') != 0:return 0for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.medianBlur(src, i)if display_dst(DELAY_BLUR) != 0:return 0# Applying Bilateral Filterif display_caption('Bilateral Blur') != 0:return 0for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.bilateralFilter(src, i, i * 2, i / 2)if display_dst(DELAY_BLUR) != 0:return 0# Donedisplay_caption('Done!')return 0def display_caption(caption):global dstdst = np.zeros(src.shape, src.dtype)rows, cols, _ch = src.shapecv.putText(dst, caption,(int(cols / 4), int(rows / 2)),cv.FONT_HERSHEY_COMPLEX, 1, (255, 255, 255))return display_dst(DELAY_CAPTION)def display_dst(delay):cv.imshow(window_name, dst)c = cv.waitKey(delay)if c >= 0 : return -1return 0if __name__ == "__main__":main(sys.argv[1:])
代码介绍
下面仅介绍图像平滑过程用到的 OpenCV 函数。
归一化盒子滤波器:
对于此过滤器,OpenCV 提供了函数 blur() 来执行平滑。指定了 4 个参数(针对C++版函数):
○ src: 源图像
○ dst: 目标图像
○ Size( w, h ): 定义要使用的内核的大小(宽度为w像素,高度为h像素)
○ Point(-1, -1): 指示锚定点(评估的像素)相对于邻域的位置。如果存在负值,则内核的中心被视为锚定点。
for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.blur(src, (i, i))if display_dst(DELAY_BLUR) != 0:return 0
高斯滤波器:
通过函数 GaussianBlur() 执行:这里我们介绍以下参数(针对C++版本函数):
○ src: 源图像
○ dst: 目标图像
○ Size(w, h): 要使用的内核的大小(要考虑的邻域)。w 和 h 必须是奇数和正数,否则大小将使用 σx 和 σy 参数计算。
○ σx: x 的标准差。0 意味着 σx 是用核大小计算的。
○ σy: y 的标准差。0 意味着 σy 是用核大小计算的。
for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.GaussianBlur(src, (i, i), 0)if display_dst(DELAY_BLUR) != 0:return 0
中值滤波器:
这个滤波器由 medianBlur() 函数实现,使用以下参数:
○ src: 源图像
○ dst: 目标图像,必须与 src 类型相同
○ i: 内核的大小(只有一个,因为使用方窗)。必须是奇数。
for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.medianBlur(src, i)if display_dst(DELAY_BLUR) != 0:return 0
双边滤波器
由 OpenCV 函数 bilateralFilter() 提供,我们使用5个参数(针对C++版本):
○ src:源图像
○ dst:图像
○ d:每个像素邻域的直径。
○ σColor:颜色空间中的标准偏差。
○ σSpace:坐标空间中的标准偏差(以像素为单位)
# 记住,双边滤波有点慢,所以随着数值增大,它需要很长时间for i in range(1, MAX_KERNEL_LENGTH, 2):dst = cv.bilateralFilter(src, i, i * 2, i / 2)if display_dst(DELAY_BLUR) != 0:return 0
官方文档:
https://docs.opencv.org/4.5.5/dc/dd3/tutorial_gausian_median_blur_bilateral_filter.html




