OpenCV 图像平滑处理 -《Opencv轻松入门-面向python》7

OpenCV 图像平滑处理 -《Opencv轻松入门-面向python》7

2 个月前 · 来自专栏 《OpenCV轻松入门面向python》

图像平滑处理

图像平滑处理:尽量保留图像原有信息的基础上,过滤掉图像内部的噪声,这一操作称为图像平滑处理。

图像平滑处理的原理:将噪声所在的像素点的像素值处理为周围邻近像素值的近似值。取近似值的方法有如下:

  • 均值滤波
  • 方框滤波
  • 高斯滤波
  • 中值滤波
  • 双边滤波
  • 2D 卷积
图像平滑 smoothing images == 图像模糊 blurring images == 图像滤波 images filtering

均值滤波

概念:用当前像素点周围的N×N个像素值的均值来代替当前像素值。使用该方法遍历图像中的每一个像素点,即完成了整张图像的均值滤波。

基本原理

例如对如下图中的黑色像素点处进行平滑处理,取5*5的区域进行平滑滤波,那么处理的结果是[(197+25+106+156+159)+(149+40+107+5+71)+(163+198+226+223+156)+(222+37+68+193+157)+(42+72+250+41+75)]/25=126,也就是用计算出的126来替代226,这就是滤波后的结果。

一幅图像中的像素值

函数语法

dst = cv2.blur( src, ksize, anchor, borderType )
ksize是滤波核的大小。滤波核大小是指在均值处理过程中,其邻域图像的高度和宽度。
例如(5*5)的大小的滤波核,表示以 5×5 大小的邻域均值作为图像均值滤波处理的结果。
anchor 是锚点,其默认值表示当前计算均值的点位于核的中心点位置。
borderType 是边界样式,一般情况下不需要考虑该值的取值,直接采用默认值即可。
卷积核
简化形式:
dst = cv2.blur(src,ksize)
###


程序示例

import numpy as np
import cv2
lena_noise = cv2.imread("lena_noise.jpg")
# 图像模糊 cv2.blur()
result5 = cv2.blur(lena_noise,(5,5))
result30 = cv2.blur(lena_noise,(20,20))
print("lena_noise.shape",lena_noise.shape) # (291, 293, 3)
cv2.imshow("original",lena_noise)
cv2.imshow('result5',result5)
cv2.imshow('result30',result30)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.blur模糊(平滑)

方框滤波

  • 均值滤波中,滤波结果的像素值是任意一个点的邻域平均值
  • 方框滤波中,可以自由选择滤波结果是邻域像素值之和的平均值,也可以选择是邻域像素值之和。(也就是说,方框滤波可以选择是否对均值滤波后的结果进行归一化)

基本原理

例如以5*5的领域为例,在进行方框滤波时候,如果计算的是领域像素值的均值,则滤波关系如下图所示:

方框滤波(计算领域像素值均值)

进行方框滤波时,如果计算的是邻域像素值之和,则滤波关系如下图所示:

方框滤波(计算领域像素值之和)

函数语法

dst = cv2.boxFilter( src, ddepth, ksize, anchor, normalize, borderType )
ddepth 是处理结果图像的图像深度,一般使用-1 表示与原始图像使用相同的图像深度
ksize 是滤波核的大小,例如,滤波核的值可以为(3,3)
anchor 是锚点,其默认值表示当前计算均值的点位于核的中心点位置。
normalize 表示在滤波时是否进行归一化,默认值是1,表示进行归一化处理。
    当参数 normalize=1 时,表示要进行归一化处理,要用邻域像素值的和除以面积。
    当参数 normalize=0 时,表示不需要进行归一化处理,直接使用邻域像素值的和。

函数 cv2.boxFilter()的常用简化形式为:

dst = cv2.boxFilter( src, ddepth, ksize )


import numpy as np
import cv2
lena_noise = cv2.imread("lena_noise.jpg")
# 图像模糊 cv2.blur()
result5 = cv2.blur(lena_noise,(5,5))
result30 = cv2.blur(lena_noise,(20,20))
print("lena_noise.shape",lena_noise.shape) # (291, 293, 3)
cv2.imshow("original",lena_noise)
cv2.imshow('result5',result5)
cv2.imshow('result30',result30)
# 图像方框滤波 cv2.boxFilter()
res1 = cv2.boxFilter(lena_noise,ddepth=-1,ksize=(5,5))
res2 = cv2.boxFilter(lena_noise,-1,(10,10))
# 不进行归一化, normalize=0
res3 = cv2.boxFilter(lena_noise,-1,(3,3),normalize=0)
cv2.imshow("cv2.boxFilter5",res1)
cv2.imshow("cv2.boxFilter10",res2)
cv2.imshow("cv2.boxFilter10",res3)
# 也就是说,当normalize=1时, cv2.blur() 等价于 cv2.boxFilter()
print("res2 == result5",np.all(res1==result5)) # True
### res2 == result5 True
cv2.waitKey()
cv2.destroyAllWindows()


cv2.blur模糊(平滑)
cv2.boxFilter模糊(平滑)
cv2.boxFilter(不进行归一化,设置normalize=0)

上图中,因为设置了normalize=0,因此大部分像素值在(3,3)这个区域内的和大于255,因此大部分显示为白色。


高斯滤波

均值滤波和方框滤波, 邻域内每个像素的权重是相等的。

高斯滤波: 中心点的权重值加大, 远离中心点的权重值减少。

基本原理

在高斯滤波中,卷积核中的值不再都是 1。

高斯滤波卷积核
高斯卷积示例图
实际计算中使用的卷积核

如上图所示,黑色像素点的像素值为226,卷积高斯卷积核计算后,(40×0.05+107×0.1+5×0.05)+(198×0.1+226×0.4+223×0.1)+(37×0.05+68×0.1+193×0.05)=164,因此使用164代替原始的226。

在实际使用中,高斯滤波使用的可能是不同大小的卷积核。如3×3、5×5、 7×7 大小的卷积核。在高斯滤波中,核的宽度和高度可以不相同,但是它们都必须是奇数。
注意:实际使用时高斯卷积核往往需要进行归一化。 严格来讲,使用没有进行归一化处理的卷积核进行滤波,得到的结果往往是错误的。

函数语法

dst = cv2.GaussianBlur( src, ksize, sigmaX, sigmaY, borderType )
src是需要处理的原始图像
ksize是滤波核的大小
sigmaX 是卷积核在水平方向上(X 轴方向)的标准差,其控制的是权重比例。
sigmaY 是卷积核在垂直方向上(Y 轴方向)的标准差。
如果 sigmaX 和 sigmaY 都是 0,则通过 ksize.width 和 ksize.height 计算得到。
      sigmaX = 0.3×[(ksize.width-1)×0.5-1] + 0.8
      sigmaY = 0.3×[(ksize.height-1)×0.5-1] + 0.8
borderType 是边界样式,该值决定了以何种方式处理边界。一般情况下,使用默认值即可。
官方文档建议显式地指定 ksize、 sigmaX 和 sigmaY 三个参数的值

函数 cv2.GaussianBlur()的常用的简化形式为:

dst = cv2.GaussianBlur( src, ksize, 0, 0 )


import os
import cv2
o = cv2.imread("lena_noise.jpg")
# 高斯滤波
# dst = cv2.GaussianBlur( src, ksize, sigmaX, sigmaY, borderType )
# dst = cv2.GaussianBlur( src, ksize, 0, 0 )
r = cv2.GaussianBlur(o,ksize=(9,9),sigmaX=0,sigmaY=0)
cv2.imshow("original",o)
cv2.imshow("result",r)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.GaussianBlur(高斯模糊)

中值滤波

中值滤波与前面介绍的滤波方式不同,不是采用加权求均值的方式计算滤波结果。

中值滤波是使用邻域内所有像素值的 中间值 来替代当前像素点的像素值。

基本原理

图像中的像素值

中值滤波会取当前像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将这些像素值从小到大排序,取中间位置的像素值作为该位置的值。

例如上图中3×3区域内的像素值,从小到大后的排序为[66,78,90,91, 93, 94,95,97,101]。中间位置是93,因此用93来代替原始的78。

中值滤波后的结果

函数语法

dst = cv2.medianBlur( src, ksize)
ksize 是滤波核的大小。滤波核大小是指在滤波处理过程中其邻域图像的高度和宽度。
需要注意,核大小必须是比 1 大的奇数,比如 3、 5、 7 等。


import os
import cv2
o = cv2.imread("lena_noise.jpg")
# 中值滤波
# dst = cv2.medianBlur(src,ksize)
#  ksize 是滤波核的大小。滤波核大小是指在滤波处理过程中其邻域图像的高度和宽度。
# 需要注意,核大小必须是比 1 大的奇数,比如 3、 5、 7 等.
r = cv2.medianBlur(o,3)
cv2.imshow("original",o)
cv2.imshow("result",r)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.medianBlur(中值滤波处理)

中值滤波优点:

  1. 由于没有进行均值处理,中值滤波 不存在 均值滤波等滤波方式带来的 细节模糊 问题。
  2. 在中值滤波处理中,噪声成分很难被选上,所以可以在几乎 不影响原有图像 的情况下 去除全部噪声。

中值滤波缺点:

中值滤波需要的 运算量较大,因为要进行像素值排序操作


双边滤波

双边滤波综合考虑了 空间信息 色彩信息 ,在滤波过程中能够有效地 保护图像内的边缘信息

基本原理

之前的滤波方式(如均值滤波,方框滤波,高斯滤波,中值滤波)基本都 只考虑了空间的权重信息 ,这种情况计算起来比较方便,但在边缘信息的处理上存在问题。

图像的边缘
高斯滤波后的边缘会导致模糊(也就是说边缘信息丢失了)

经过高斯滤波处理后,边缘信息变得模糊,均值滤波处理也会造成类似的问题。因为其单纯地考虑空间信息,造成了边界模糊和部分信息的丢失。

双边滤波在计算某一个像素点的新值时,不仅考虑距离信息(距离越远,权重越小),还考虑色彩信息(色彩差别越大,权重越小)。 双边滤波综合考虑距离和色彩的权重结果,既可以有效地去除噪声,又能够较好地保护边缘信息。

在双边滤波中,当处在边缘时,与当前点色彩相近的像素点(颜色距离很近) 会被给予较大的权重值;而与当前色彩差别较大的像素点(颜色距离很远) 会被给予较小的权重值(极端情况下权重可能为 0,直接忽略该点),这样就保护了边缘信息。

例如,在下图中:

  • 图(a)是原始图像,左侧区域是白色(像素值为 255),右侧区域是黑色(像素值为 0)。
  • 图(b)是进行均值滤波的可能结果。在进行均值滤波时,仅仅考虑空间信息,此时左右两侧的像素的处理结果是综合考虑周边元素像素值,并对它们取均值得到的。
  • 图(c)是进行双边滤波的可能结果。在进行 双边滤波 时,不仅考虑 空间信息 ,还考虑 色彩差别信息
原图与滤波后的结果

对于双边滤波而言, 在计算左侧白色区域边缘点的滤波结果时:

  • 对于白色的点,给予的权重较大。
  • 对于黑色的点,由于 色彩差异较大,颜色距离很远 (注意,不是像素点之间的物理距离,而是 颜色值的距离 。像素点的值分别是 0 和 255,差别很大,所以说它们颜色距离很远),因此将它们的权重设置很低,甚至设置为 0。

这样,在计算左侧白色边缘滤波结果时,得到的仍然是白色。因此双边滤波后,左侧边缘得到保留。

同样的, 在计算右侧黑色区域边缘点的滤波结果时:

  • 对于黑色的点,给予的权重较大。
  • 对于白色的点,由于色彩差异较大, 颜色距离很远 ,因此可以将它们的权重设置为 0。

这样,在计算右侧黑色边缘滤波结果时,得到的仍然是黑色。因此双边滤波后,左侧边缘得到保留。

函数语法

dst = cv2.bilateralFilter( src, d, sigmaColor, sigmaSpace, borderType )
src 是需要处理的图像,即原始图像。
d 是在滤波时选取的空间距离参数,这里表示以当前像素点为中心点的直径。 在实时应用中,推荐 d=5。
sigmaColor 是滤波处理时选取的颜色差值范围, 该值决定了周围哪些像素点能够参与到滤波中来。
sigmaColor值越大, 就说明周围有越多的像素点可以参与到运算中。 
sigmaSpace 是坐标空间中的 sigma 值。它的值越大,说明有越多的点能够参与到滤波计算中来。
当 d>0 时,sigmaSpace值失效,采用d的值 
borderType 是边界样式,采用默认值即可。


import os
import cv2
o = cv2.imread("lena_noise.jpg")
# 双边滤波
# dst = cv2.bilateralFilter( src, d, sigmaColor, sigmaSpace, borderType )
r = cv2.bilateralFilter(o,d=25,sigmaColor=200,sigmaSpace=200)
r2 = cv2.bilateralFilter(o,d=25,sigmaColor=80,sigmaSpace=200)
cv2.imshow("original",o)
cv2.imshow("result",r)
cv2.imshow("result",r2)
cv2.waitKey()
cv2.destroyAllWindows()
cv2.bilateralFilter(双边滤波来平滑图像,对应r)
cv2.bilateralFilter(双边滤波来平滑图像,对应r2)

从上图可知,双边滤波去除噪声的效果并不好,它的主要优点在于 对于边缘信息的处理上。

import os
import cv2
o = cv2.imread("black_white.png")
g =cv2.GaussianBlur(o,ksize=(9,9),sigmaX=0,sigmaY=0)
b = cv2.bilateralFilter(o,d=5,sigmaColor=200,sigmaSpace=100)
cv2.imshow("original",o)
cv2.imshow("GaussianBlur",g)
cv2.imshow("bilateralFilter",b)
cv2.waitKey()
cv2.destroyAllWindows()
不同的滤波方式来处理边缘信息(最右面是双边滤波)

从图中可以看到,经过高斯滤波的边缘被模糊虚化了,而双边滤波的边缘得到了较好的保留。


2D卷积

OpenCV 提供了多种滤波方式来实现平滑图像的效果,例如上述提及的均值滤波、方框滤波、高斯滤波、中值滤波,双边滤波等。大多数滤波方式所使用的卷积核都具有一定的灵活性,能够方便地设置卷积核的大小和数值。

但是,我们有时希望使用特定的卷积核实现卷积操作,例如使用如下卷积核进行卷积操作。

指定特定的卷积核

前面介绍过的滤波函数都无法将卷积核确定为上述形式,这时要使用 OpenCV 的自定义卷积函数。

OpenCV 允许自定义卷积核实现卷积操作, 使用自定义卷积核实现卷积操作 的函数是 cv2.filter2D()。

dst = cv2.filter2D( src, ddepth, kernel, anchor, delta, borderType )
ddepth 是处理结果图像的图像深度,一般使用-1 表示与原始图像使用相同的图像深度。
kernel 是卷积核,是一个单通道的数组。这个kernel就是自己自定义的!
anchor 是锚点,其默认值表示当前计算均值的点位于核的中心点位置。一般使用默认值即可。
delta 是修正值,它是可选项。使用默认值即可。
borderType 是边界样式,该值决定了以何种情况处理边界,通常使用默认值即可。

因此,函数 cv2.filter2D()的常用简化形式为:

dst = cv2.filter2D( src, ddepth, kernel )
kernel 是卷积核,是一个单通道的数组。这个kernel就是自己自定义的!


import os
import numpy as np
import cv2
o = cv2.imread("lena.jpg")
# 2D卷积,自定义二维卷积
# dst = cv2.filter2D(src,ddepth,kernel,anchor,delta,borderType)
# dst = cv2.filter2D(src,ddepth,kernel)
kernel = np.ones((9,9),dtype=np.float32)/81
r = cv2.filter2D(o,-1,kernel)
print("kernel:",kernel)
cv2.imshow('original',o)
cv2.imshow('Gaussian',r)
cv2.waitKey()
cv2.destroyAllWindows()
自定义的卷积核kernel
# 输出结果
kernel: [[0.01234568 0.01234568 0.01234568 0.01234568 0.01234568 0.01234568
  0.01234568 0.01234568 0.01234568]
 [0.01234568 0.01234568 0.01234568 0.01234568 0.01234568 0.01234568
  0.01234568 0.01234568 0.01234568]
 [0.01234568 0.01234568 0.01234568 0.01234568 0.01234568 0.01234568
  0.01234568 0.01234568 0.01234568]
 [0.01234568 0.01234568 0.01234568 0.01234568 0.01234568 0.01234568
  0.01234568 0.01234568 0.01234568]
 [0.01234568 0.01234568 0.01234568 0.01234568 0.01234568 0.01234568
  0.01234568 0.01234568 0.01234568]
 [0.01234568 0.01234568 0.01234568 0.01234568 0.01234568 0.01234568
  0.01234568 0.01234568 0.01234568]
 [0.01234568 0.01234568 0.01234568 0.01234568 0.01234568 0.01234568