Python 大白从零开始 OpenCV 学习课-7. 空间域图像滤波

Posted Python小白进阶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 大白从零开始 OpenCV 学习课-7. 空间域图像滤波相关的知识,希望对你有一定的参考价值。

Python 大白从零开始 OpenCV 学习课-7. 空间域图像滤波

本系列面向小白,从零开始实战解说 OpenCV 项目实战。
图像滤波是在尽可能保留图像细节特征的条件下对目标图像的噪声进行抑制,是常用的图像预处理操作。
空间域图像增强的方法很多,各有不同的特点和作用。本节介绍空间域滤波的平滑(低通滤波)和锐化(高通滤波)方法。常用的平滑算法有高斯平滑、均值平滑、中值平滑、双边滤波、导向滤波等;常用的锐化算法有钝化掩蔽、拉普拉斯算子、Sobel梯度算子和 Scharr 梯度算子。
本文提供上述各种算法的完整例程和运行结果,并通过一个应用案例示范综合使用多种图像增强方法。



1. 图像的相关与卷积运算

滤波通常是指对图像中特定频率的分量进行过滤或抑制。图像滤波是在尽可能保留图像细节特征的条件下对目标图像的噪声进行抑制,是常用的图像预处理操作。

数据采集都会带有一定的噪声,图像的噪声可以理解为灰度值的随机变化。对图像在空间域存在的随机噪声,可以通过平滑技术进行抑制或去除,称为空间域图像滤波。

频率域滤波是通过傅里叶变换方法实现的,而空间域滤波则是通过相关与卷积运算实现。常用的平滑处理算法有基于二维离散卷积的高斯平滑、均值平滑,基于统计方法的中值平滑,保留边缘信息的双边滤波、导向滤波等。

空间滤波器是由邻域和定义的操作构成的,滤波器规定了滤波时采用的邻域形状及该区域内像素值的处理方法。滤波器也被称为 “核”、“模板”、“窗口”、“掩模”、“算子”,一般在信号处理中称为 “滤波器”,在数学领域称为 “核”。线性滤波器就是指基于线性核的滤波,也就是卷积运算。


1.1 相关与卷积运算

滤波器核是指像素周围某一大小的矩形邻域,也称为模板、滑动窗口。

**相关运算(Correlation operation)**是利用模板对图像进行邻域操作:将滤波器模板的中心移动到待处理的像素点,对模板区域的各点加权相乘后求和。

大小为 m*n 的核(模板) w 与图像 f(x,y) 的相关运算 ( w ⋄ f ) ( x , y ) (w \\diamond f)(x,y) (wf)(x,y) 的数学描述为:

( w ⋄ f ) ( x , y ) = ∑ s = − a a ∑ t = − b b w ( s , t ) ∗ f ( x + s , y + t ) (w \\diamond f)(x,y) = \\sum_s=-a^a \\sum_t=-b^b w(s,t) * f(x+s,y+t) (wf)(x,y)=s=aat=bbw(s,t)f(x+s,y+t)
相关运算的计算步骤如下:

(1)将模板在图像中逐点移动,模板中心移动到被处理的像素点上;
(2)将模板区域中的各点的系数(权值)与图像的像素值相乘,对乘积求和,即加权求和;
(3)将加权求和结果赋值给模板中心的像素。

注意, “相关运算” 中的 “相关” 不是 “有关的”,而是一种特定的数学运算方式。

**卷积运算(Convolution operation)**也是利用模板对图像进行邻域操作,只是把相关运算的模板旋转了 180度。

大小为 m*n 的核(模板) w 与图像 f(x,y) 的卷积运算 ( w ★ f ) ( x , y ) (w \\bigstar f)(x,y) (wf)(x,y) 的数学描述为:
( w ★ f ) ( x , y ) = ∑ s = − a a ∑ t = − b b w ( s , t ) ∗ f ( x − s , y − t ) (w \\bigstar f)(x,y) = \\sum_s=-a^a \\sum_t=-b^b w(s,t) * f(x-s,y-t) (wf)(x,y)=s=aat=bbw(s,t)f(xs,yt)

卷积运算符合交换律、结合律和分配律,即:
f ★ g = g ★ f f ★ ( g ★ h ) = ( f ★ g ) ★ h f ★ ( g + h ) = ( f ★ g ) + ( f ★ h ) f \\bigstar g = g \\bigstar f \\\\ f \\bigstar (g \\bigstar h) = (f \\bigstar g) \\bigstar h \\\\ f \\bigstar (g + h) = (f \\bigstar g) + (f \\bigstar h) fg=gff(gh)=(fg)hf(g+h)=(fg)+(fh)


(本图片来自 “小黑鸭” 《OpenCV学习+常用函数记录②:图像卷积与滤波》,特此致谢。)


1.2 图像的边界扩充

相关和卷积运算都要对图像的边界点要进行特殊处理,就需要将边界进行适当扩充。

函数说明:

OpenCV 中提供了函数 cv.copyMakeBorder 进行边界扩充方式,也可以为图像设置边框。

cv.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value]]) → dst

参数说明:

  • src:进行边界扩充的图像
  • top, bottom, left, right:上侧、下侧、左侧、右侧边界扩充的的宽度(像素数)
  • value:当 borderType 为 BORDER_CONSTANT 时,以常量(value)填充扩充的边界,默认值为 (0,0,0)
  • borderType 边界扩充的类型
    • cv2.BORDER_REPLICATE:复制,复制最边缘像素进行填充(aa | abcdefg | gg),中值滤波采用复制法
    • cv2.BORDER_REFLECT:对称法,以图像边缘为轴进行对称填充(cba| abcdefg | gfe)
    • cv2.BORDER_REFLECTT_101:倒映法,以图像最边缘像素为轴进行对称填充(dcb| abcdefg | fed),函数 filter2D, blur, GaussianBlur, bilateralFilter 中默认的边界处理方法
    • cv2.BORDER_WRAP:用另一侧元素来填充这一侧的扩充边界(efg| abcdefg | ab)
    • cv2.BORDER_CONSTANT:以常数(value)作为像素值进行扩充(vv | abcdefg | vv)

例程 1.65:图像的边界扩充

    # 1.65 图像的边界扩充
    img = cv2.imread("../images/imgRose1.jpg")  # 读取彩色图像(BGR)

    top = bottom = left = right = 50
    imgReplicate = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REPLICATE)
    imgReflect = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT)
    imgReflect101 = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT_101)
    imgWrap = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_WRAP)
    imgConstant = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(200,200,200))

    plt.figure(figsize=(9, 6))
    plt.subplot(231), plt.axis([-50,562,-50,562]), plt.title('ORIGINAL'), plt.axis('off')
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.subplot(232), plt.axis('off'), plt.title('REPLICATE')
    plt.imshow(cv2.cvtColor(imgReplicate, cv2.COLOR_BGR2RGB))
    plt.subplot(233), plt.axis('off'), plt.title('REFLECT')
    plt.imshow(cv2.cvtColor(imgReflect, cv2.COLOR_BGR2RGB))
    plt.subplot(234), plt.axis('off'), plt.title('REFLECT_101')
    plt.imshow(cv2.cvtColor(imgReflect101, cv2.COLOR_BGR2RGB))
    plt.subplot(235), plt.axis('off'), plt.title('WRAP')
    plt.imshow(cv2.cvtColor(imgWrap, cv2.COLOR_BGR2RGB))
    plt.subplot(236), plt.axis('off'), plt.title('CONSTANT')
    plt.imshow(cv2.cvtColor(imgConstant, cv2.COLOR_BGR2RGB))
    plt.show()


1.3 Scipy 实现二维离散卷积(sp.convolve2d)

Scipy 中提供了函数 sp.convolve2d 实现二维离散卷积的计算。

对于二维离散卷积的运算,Python的科学计算包Scipy提供了函数实现该功能:

    convolve2d(in1, in2, mode="full", boundary="fill", fillvalue=0) → dst

参数说明:

  • in1:进行卷积运算的图像,二维数组——只能处理单通道图像,如灰度图像
  • in2:卷积操作的模板(卷积核),二维数组
  • mode:卷积类型,‘full’、‘valid’、‘same’,默认值为 ‘full’
  • boundary:边界扩充方式,‘fill’、‘wrap’、‘symm’,默认值为 ‘fill’
    • ‘fill’:以常数(fillvalue)作为像素值进行扩充(vv | abcdefg | vv)
    • ‘symm’:对称法,以图像边缘为轴进行对称填充(cba| abcdefg | gfe)
    • ‘wrap’:用另一侧元素来填充这一侧的扩充边界(efg| abcdefg | ab)
  • fillvalue:当 boundary=‘fill’ 时,以以常数(fillvalue)作为像素值进行扩充

例程 1.66:scipy.signal 实现图像的二维卷积

    # 1.66 scipy.signal 实现图像的二维卷积
    img = cv2.imread("../images/imgLena.tif", flags=0)  # # flags=0 读取为灰度图像
    kernel = np.array([[-3-3j,0-10j,+3-3j], [-10+0j,0+0j,+10+0j], [-3+3j,0+10j,+3+3j]])  # Gx + j*Gy

    # scipy.signal 实现卷积运算
    from scipy import signal
    convFull = signal.convolve2d(img, kernel, boundary='symm', mode='full')  # full 卷积
    convValid = signal.convolve2d(img, kernel, boundary='symm', mode='valid')  # valid 卷积
    convSame = signal.convolve2d(img, kernel, boundary='symm', mode='same')  # same 卷积
    print(img.shape, convFull.shape, convValid.shape, convSame.shape)  # 输出图像大小有区别

    plt.figure(figsize=(9, 6))
    plt.subplot(131), plt.axis('off'), plt.title('Original'), plt.axis('off')
    plt.imshow(img, cmap='gray', vmin=0, vmax=255)
    plt.subplot(132), plt.axis('off'), plt.title('Convolve (full)')
    plt.imshow(np.absolute(convFull), cmap='gray', vmin=0, vmax=255)
    plt.subplot(133), plt.axis('off'), plt.title('Convolve (same)')
    plt.imshow(np.absolute(convSame), cmap='gray', vmin=0, vmax=255)
    plt.tight_layout()
    plt.show()

注意事项:

  1. signal.convolve2d 只能对二维矩阵进行卷积操作,因此只能处理灰度图像。如果需要处理彩色图像,可以分别对每一通道进行卷积操作来实现。

  2. signal.convolve2d 选择不同卷积类型 ‘full’、‘valid’、‘same’ 时,图像卷积效果的差别并不明显,但图像尺寸大小有区别,这与不同类型时采用不同的边界处理方式有关。

img.shape: (512, 512)
convFull.shape: (514, 514)
convValid.shape: (510, 510)
convSame.shape: (512, 512)


1.4 cv2 实现二维离散卷积(flip 和 filter2D)

使用 OpenCV 中的 cv.flip 和 cv.filter2D 函数也可以实现图像的卷积运算。

函数 cv.flip 实现围绕轴线翻转二维阵列,将图像沿轴线进行轴对称变换,可以将图像沿水平方向、垂直方向、或水平/垂直方向同时进行翻转。例程 1.38 介绍了图像的翻转(镜像)的使用方法。

函数 cv.filter2D 对图像与核(模板)进行相关计算,与函数 cv.flip 共同实现卷积运算。

函数说明:

cv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) → dst

参数说明:

  • src:卷积处理的输入图像,可以是灰度图像,也可以是多通道的彩色图像
  • dst:卷积处理的输出图像,大小和类型与 src 相同
  • ddepth:目标图像每个通道的深度(数据类型),ddepth=-1 表示与输入图像的数据类型相同
  • kernel:卷积操作的模板(卷积核),二维实型数组
  • anchor:卷积核的锚点位置,默认值 (-1, -1) 表示以卷积核的中心为锚点
  • delta:输出图像的偏移量,可选项,默认值为 0
  • borderType:边界扩充的类型

cv.filter2D 可以处理灰度图像,也可以直接处理彩色图像,不需要对每一色彩通道分别操作。


例程 1.67:cv2 实现图像的二维卷积

    # 1.67:cv2 实现图像的二维卷积
    img = cv2.imread("../images/imgGaia.tif", flags=0)  # # flags=0 读取为灰度图像

    kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])  # Gx + j*Gy

    kFlip = cv2.flip(kernel, -1)  # 将卷积核旋转180度
    # 使用函数filter2D算出same卷积
    imgConv1 = cv2.filter2D(img, -1, kFlip,
               anchor=(0,0), borderType=cv2.BORDER_CONSTANT)
    imgConv2 = cv2.filter2D(img, -1, kFlip,
               anchor=(0,0), borderType=cv2.BORDER_REFLECT)

    plt.figure(figsize=(9, 6))
    plt.subplot(131), plt.axis('off'), plt.title('Original'), plt.axis('off')
    plt.imshow(img, cmap='gray', vmin=0, vmax=255)
    plt.subplot(132), plt.axis('off'), plt.title('cv2.filter2D (BORDER_CONSTANT)')
    plt.imshow(np.absolute(imgConv1), cmap='gray', vmin=0, vmax=255)
    plt.subplot(133), plt.axis('off'), plt.title('cv2.filter2D (BORDER_REFLECT)')
    plt.imshow(np.absolute(imgConv2), cmap='gray', vmin=0, vmax=255)
    plt.tight_layout()
    plt.show()



1.5 可分离卷积核

如果卷积核 w 可以被分解为两个或多个较小尺寸卷积核 w1、w2…,即: w = w 1 ★ w 2 w = w1 \\bigstar w2 w=w1w2,则成为可分离卷积核。

秩为 1 的矩阵可以分解为一个列向量与一个行向量的乘积,因此秩为 1 的卷积核是可分离卷积核。

可分离卷积核 w 与图像 f 的卷积(same 卷积),等于先用 f 与 w1 卷积,再用 w2 对结果进行卷积:
w ★ f = ( w 1 ★ w 2 ) ★ f = w 2 ★ ( w 1 ★ f ) = ( w 1 ★ f ) ★ w 2 w \\bigstar f = (w_1 \\bigstar w_2)\\bigstar f = w_2 \\bigstar (w_1 \\bigstar f) = (w_1 \\bigstar f)\\bigstar w_2

以上是关于Python 大白从零开始 OpenCV 学习课-7. 空间域图像滤波的主要内容,如果未能解决你的问题,请参考以下文章

Python 大白从零开始 OpenCV 学习课-1.安装与环境配置

Python 大白从零开始 OpenCV 学习课-5. 图像的几何变换

Python 大白从零开始 OpenCV 学习课-3.图像的创建与修改

Python 大白从零开始 OpenCV 学习课-4.图像的叠加与混合

Python 大白从零开始 OpenCV 学习课-6. 灰度变换与直方图处理

Python 大白从零开始 OpenCV 项目实战 图像读取与显示