OpenCV-Python教程:均值平滑、中值平滑(blur,medianBlur)

原文链接:http://www.juzicode.com/opencv-python-blur-medianblur

返回Opencv-Python教程

图像在生成、传输或存储过程中可能因为外界干扰产生噪声,从而使图像在视觉上表现为出现一些孤立点或者像素值突然变化的点,图像平滑处理的目的就是为了消除图像中的这类噪声。

在讲平滑处理前,先来了解下在OpenCV中平滑处理用到的“滑动窗口”的概念,下面的这个例子中选择了一个ksize=3×3的滑动窗口(或称滤波器模板、kernel),如黄色部分所示。用这个ksize=3×3的窗口作用于原始图像上的每一个像素,如下图的绿色部分所示,被这个窗口覆盖的9个像素点都参与计算,这样在该像素点上就会得到一个新的像素值,当窗口沿着图像逐个像素进行计算,就会得到一幅新的图像。

上图中滤波器模板的不同就构成了滤波算法的差异,比如均值平滑算法中滑动窗口中各个像素点的系数均为1/(窗口高*窗口宽),高斯平滑中系数和中心点的距离满足高斯分布。

从上图也可以看到,当滑动窗口作用于图像边沿的时候,滑动窗口的某些像素并没有和图像重合,这时就需要对边沿做特殊处理,常用的方法有填0、填1、复制边沿等方式。

1、均值平滑blur()

均值平滑的滑动窗口所有系数为1/(窗口高*窗口宽),新生成的像素值就是窗口中心点以及周围所有像素值相加后的平均值。比如选择一个ksize=5×5的窗口,新图像的(x,y)点的像素值用numpy表示为 np.sum(i[x-2:i+3,y-2:y+3])/(5*5)。

blur()的接口形式:

dst=cv2.blur(src, ksize[, dst[, anchor[, borderType]]])
  • 参数含义:
  • src:源图像,通道数不限,数据类型必须为CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • ksize:kernel尺寸、窗口大小,二元组类型,元素值可以是偶数或奇数;
  • anchor:锚点,默认为(-1,-1),作用于滑动窗口的中心点;
  • borderType:边界处理类型;

下面这个例子是使用不同ksize进行均值平滑的对比:

import matplotlib.pyplot as plt 
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')

img = cv2.imread('..\\lena.jpg')
img_ret1 = cv2.blur(img,(3,3))
img_ret2 = cv2.blur(img,(5,5))
img_ret3 = cv2.blur(img,(11,11))

#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('VX:桔子code  原图')
ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[0,1].set_title('blur ksize=3')
ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
ax[1,0].set_title('blur ksize=5')
ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
ax[1,1].set_title('blur ksize=11') 
ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show() 

运行结果:

从运行效果可以看到,ksize越大,图像越模糊,清晰度越低。

通过观察前面经过处理后的图像能得到比较直观的感受,下面将从数值角度验证下定量的结果。下面这个例子中设置ksize=(5,5),提取新图像坐标x,y = 10,10处的像素值,并提取原图像的x,y=10,10为中心高宽为5的子图,计算子图的平均值:

import numpy as np 
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
 
img = cv2.imread('..\\lena.jpg')
img_ret1 = cv2.blur(img,(5,5))
x,y = 10,10
b,g,r = cv2.split(img[x-2:x+3,y-2:y+3])#分离出原图在x,y点的bgr通道
print('img b:\n',b )
print('img g:\n',g )
print('img r:\n',r )
print('img average(b,g,r):',np.sum(b)/25,np.sum(g)/25,np.sum(r)/25)#分别计算每个通道的平均值
print('img_ret1[x,y]',img_ret1[x,y])

 运行结果:

cv2.__version__: 4.5.2
img b:
 [[124 113 113 113 114]
 [117 111 111 110 108]
 [112 110 111 108 106]
 [114 112 111 110 106]
 [112 108 106 109 105]]
img g:
 [[142 131 133 133 136]
 [135 129 131 130 133]
 [132 130 133 130 131]
 [134 132 133 132 131]
 [134 130 131 134 130]]
img r:
 [[235 224 228 228 231]
 [228 222 226 225 227]
 [227 225 228 225 225]
 [229 227 228 227 225]
 [229 225 225 228 224]]
img average(b,g,r): 110.96 132.4 226.84
img_ret1[x,y] [111 132 227]

从运行结果可以看到新图像img_ret1像素点x,y=10,10的像素值为[111,132,227]等于原始图像的平均值:110.96,132.4,226.84取整后的结果。

2、中值平滑medianBlur()

中值平滑和均值平滑一样也用到了滑动窗口,但是它并不是计算滑动窗口中的某种加权和,而是使用原图像滑动窗口中所有像素值排序后的中值作为新图像的像素值。

medianBlur()的接口形式如下:

dst=cv2.medianBlur(src, ksize[, dst])
  • 参数含义:
  • src:源图像,通道数可以是1,3或4,当ksize为3或者5时,数据类型可以是CV_8U, CV_16U, CV_32F,当使用更大的ksize时,数据类型只能是CV_8U;
  • ksize:kernel尺寸、窗口大小,整数型,大于1的奇数值;

下面这个例子是使用不同ksize进行中值平滑的对比:

import matplotlib.pyplot as plt 
import cv2

print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')

img = cv2.imread('..\\lena.jpg')
img_ret1 = cv2.medianBlur(img,3)
img_ret2 = cv2.medianBlur(img,5)
img_ret3 = cv2.medianBlur(img,11)

#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('VX:桔子code  原图')
ax[0,0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib显示图像为rgb格式
ax[0,1].set_title('medianBlur ksize=3')
ax[0,1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
ax[1,0].set_title('medianBlur ksize=5')
ax[1,0].imshow(cv2.cvtColor(img_ret2,cv2.COLOR_BGR2RGB))
ax[1,1].set_title('medianBlur ksize=11') 
ax[1,1].imshow(cv2.cvtColor(img_ret3,cv2.COLOR_BGR2RGB))
ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show() 

运行结果:

下面仍然从数值角度看下中值平滑的处理,ksize=(5,5),提取新图像坐标x,y = 10,10处的像素值,并提取原图像的x,y=10,10为中心高宽为5的子图,再计算子图像素值展开成list后的中值:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
 
img = cv2.imread('..\\lena.jpg')
img_ret1 = cv2.medianBlur(img,5)

x,y = 10,10
b,g,r = cv2.split(img[x-2:x+3,y-2:y+3]) #分离出原图在x,y点的bgr通道
print('img b:\n',b )
print('img g:\n',g )
print('img r:\n',r )
list_b = list(b.flatten())     #展开得到一个list
list_b.sort()                  #list排序
list_g = list(g.flatten())
list_g.sort()
list_r = list(r.flatten())
list_r.sort()
print('list_b:',list_b )
print('list_g:',list_g )
print('list_r:',list_r )
print('list_b[12]:',list_b[12]) #在25个元素中提取中值
print('list_g[12]:',list_g[12])
print('list_r[12]:',list_r[12])
print('img_ret1[x,y]',img_ret1[x,y]) #新图像像素值

运行结果:

list_b: [105, 106, 106, 106, 108, 108, 108, 109, 110, 110, 110, 111, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 117, 124]
list_g: [129, 130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 132, 132, 132, 133, 133, 133, 133, 133, 134, 134, 134, 135, 136, 142]
list_r: [222, 224, 224, 225, 225, 225, 225, 225, 225, 225, 226, 227, 227, 227, 227, 228, 228, 228, 228, 228, 228, 229, 229, 231, 235]
list_b[12]: 111 #中值
list_g[12]: 132
list_r[12]: 227
img_ret1[x,y] [111 132 227]

从上面的运行结果可以看到新图像在坐标x,y = 10,10处的像素值为[111 132 227],等于该处滑动窗口内像素值的中值。

3、像素值对比

通过前面的介绍我们可以看到平滑处理后图像会变得更“模糊”,这是因为不管均值也好,中值也罢,都会降低图像变化的程度,下面我们从另外一个角度来验证下变化效果。

因为图像在水平和垂直方向都发生了“平滑”,为了方便观察下面我们仅以水平方向为例,以图像的水平方向坐标作为绘图的X轴,在上面这个lena图片中,X轴的取值范围就是0~511(图像宽度为512个像素),提取第10行的像素值作为绘图的Y轴,因为是uint8(CV_8U)类型的数据,Y轴分布在0~255。分别绘制原图、均值滤波和中值滤波后的像素值的曲线:

import numpy as np
import matplotlib.pyplot as plt 
import cv2

print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')

img = cv2.imread('..\\lena.jpg')
img_ret1 = cv2.blur(img,(15,15)) 
img_ret2 = cv2.medianBlur(img,15)
 
X = np.arange(img.shape[1])  
Y = img[:,:,0][10,:]         #仅提取b通道第10行
Y1 = img_ret1[:,:,0][10,:]   #提取均值平滑后图像b通道的第10行
Y2 = img_ret2[:,:,0][10,:]   #提取中值平滑后图像b通道的第10行
plt.plot(X,Y,'-g',label='raw' ) #绘图
plt.plot(X,Y1,'-r',label='blur')
plt.plot(X,Y2,'-b',label='medianBlur')
plt.legend(title='img type(juzicode.com)',fontsize='xx-large',loc='upper center')
plt.show()

运行结果:

从上面的对比可以看到绿色的原始图像,其像素值变化的非常“剧烈”,有很多波峰或波谷,但是经过平滑处理后的像素值(红色和蓝色)则显得平滑的多。

小结:平滑处理是图像滤波的一种,可以看做是低通滤波,它会消除图像的高频“信号”,让图像看起来更模糊、平滑,通过将变化前后的图像像素值绘制曲线可以更形象地观察到这种平滑效果。

 原文链接:http://www.juzicode.com/opencv-python-blur-medianblur

扩展阅读:

  1. OpenCV-Python教程

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注