这几天在进行其它运算时突然发觉自己对于卷积的概念和运算有一些陌生,重新复习一下。
目录
参考:
Convolution
Kernel (image processing)
图像卷积操作(convolution
),或称为核操作(kernel
),是进行图像处理的一种常用手段,
图像卷积操作的目的是利用像素点和其邻域像素之前的空间关系,通过加权求和的操作,实现模糊(blurring
),锐化(sharpening
),边缘检测(edge detection
)等功能。
图像卷积的计算过程就是卷积核按步长对图像局部像素块进行加权求和的过程。
卷积核实质上是一个固定大小的权重数组,该数组中的锚点通常位于中心。
通常情况下,选取卷积核大小为1x1,3x3,5x5,7x7
等
取奇数大小的目的是为了设置卷积核中心为锚点,方便卷积核和图像的对齐处理
设置卷积核大小对称的目的是为了在空间域中充分利用像素点和其领域像素间的关系。当然这不是必须的,如果需要针对某一轴进行处理,可以设置1x3
或3x1
大小。
二维离散卷积公式如下:
h [ x , y ] = f [ x , y ] ∗ g [ x , y ] = ∑ n 1 = − ∞ ∞ ∑ n 2 = − ∞ ∞ f ( n 1 , n 2 ) ⋅ g ( x − n 1 , y − n 2 ) h[x,y]=f[x,y]\ast g[x,y]=\sum_{n_{1}=-\infty}^{\infty}\sum_{n_{2}=-\infty}^{\infty}f(n_1, n_2)\cdot g(x-n_{1},y-n_{2}) h[x,y]=f[x,y]∗g[x,y]=n1=−∞∑∞n2=−∞∑∞f(n1,n2)⋅g(x−n1,y−n2)
图像卷积通常使用这个公式,其中 g [ x , y ] g[x,y] g[x,y]为卷积核,符号 ∗ \ast ∗表示卷积操作
以一维离散卷积公式为例:
y ( t ) = ( f ∗ g ) ( t ) = ∑ τ = − ∞ ∞ f ( τ ) g ( t − τ ) d τ y(t)=(f\ast g)(t)=\sum_{\tau =-\infty}^{\infty}f(\tau )g(t-\tau )d\tau y(t)=(f∗g)(t)=τ=−∞∑∞f(τ)g(t−τ)dτ
其图形化公式如下:
1
,表示函数 g ( t − τ ) g(t-\tau) g(t−τ)向左移动一步函数 f = [ 1 , 2 , 3 , 4 ] f=[1, 2, 3, 4] f=[1,2,3,4],函数 g = [ 1 , 3 , 2 ] g=[1, 3, 2] g=[1,3,2]
将函数 g g g逆转: g ( τ ) ⇒ g ( − τ ) g(\tau)\Rightarrow g(-\tau) g(τ)⇒g(−τ),值变为 [ 2 , 3 , 1 ] [2, 3, 1] [2,3,1]
计算过程如下:
h ( 0 ) = [ 1 ] ⋅ [ 1 ] = 1 ⋅ 1 = 1 h(0)=[1]\cdot [1]=1\cdot 1=1 h(0)=[1]⋅[1]=1⋅1=1
h ( 1 ) = [ 1 , 2 ] ⋅ [ 3 , 1 ] = 1 ⋅ 3 + 2 ⋅ 1 = 3 + 2 = 5 h(1)=[1,2]\cdot [3,1]=1\cdot 3+2\cdot 1=3+2=5 h(1)=[1,2]⋅[3,1]=1⋅3+2⋅1=3+2=5
h ( 2 ) = [ 1 , 2 , 3 ] ⋅ [ 2 , 3 , 1 ] = 1 ⋅ 2 + 2 ⋅ 3 + 3 ⋅ 1 = 2 + 6 + 3 = 11 h(2)=[1,2,3]\cdot [2,3,1]=1\cdot 2 +2\cdot 3+3\cdot 1=2+6+3=11 h(2)=[1,2,3]⋅[2,3,1]=1⋅2+2⋅3+3⋅1=2+6+3=11
h ( 3 ) = [ 2 , 3 , 4 ] ⋅ [ 2 , 3 , 1 ] = 2 ⋅ 2 + 3 ⋅ 3 + 4 ⋅ 1 = 4 + 9 + 4 = 17 h(3)=[2,3,4]\cdot [2,3,1]=2\cdot 2+3\cdot 3+4\cdot 1=4+9+4=17 h(3)=[2,3,4]⋅[2,3,1]=2⋅2+3⋅3+4⋅1=4+9+4=17
h ( 4 ) = [ 3 , 4 ] ⋅ [ 2 , 3 ] = 3 ⋅ 2 + 4 ⋅ 3 = 6 + 12 = 18 h(4)=[3,4]\cdot [2,3]=3\cdot 2+4\cdot 3=6+12=18 h(4)=[3,4]⋅[2,3]=3⋅2+4⋅3=6+12=18
h ( 5 ) = [ 4 ] ⋅ [ 2 ] = 4 ⋅ 2 = 8 h(5)=[4]\cdot [2]=4\cdot 2=8 h(5)=[4]⋅[2]=4⋅2=8
h ( x ) = [ 1 , 5 , 11 , 17 , 18 , 8 ] h(x)=[1, 5, 11, 17, 18, 8] h(x)=[1,5,11,17,18,8]
以此类推可知二维离散卷积的计算过程,先对角翻转卷积核,在逐步向两个正方向移动,计算重叠面积
flip the mask (horizontally and vertically) only once
(水平和垂直翻转掩模一次)slide the mask onto the image
(在图像上滑动掩模)multiply the corresponding elements and then add them
(将相应的元素相乘,然后求和)repeat this procedure until all values of the image has been calculated
(重复这一过程,直到所有图像值均已被计算)多说一句,关于信号与系统中的LTI
(linear time-invariant systems
,线性时不变系统)和LSI
(linear shift invariant system
,线性位移不变系统)的不变性一直没太理解,图形化理解就是信号(函数)可以随着时间/空间移动而不改变它的原先的形状,就像卷积核一样。
参考:
在定义卷积时为什么要对其中一个函数进行翻转?
如何通俗易懂地解释卷积?
在LTI
和LSI
中,信号在时间和空间中移动不改变其特性,不断有信号随时间移动和系统产生响应,某一时刻的输出(即卷积输出)不仅包括当前信号的响应,还有之前信号的残留,所以是累加的,转换卷积核是为了计算这一过程。
参考:卷积核翻转方法
在计算之前需要对卷积核进行 18 0 ∘ 180^{^{\circ}} 180∘翻转
[ 1 2 3 4 5 6 7 8 9 ] 18 0 ∘ ⇒ [ 9 8 7 6 5 4 3 3 1 ] \begin{bmatrix} 1& 2& 3\\ 4& 5& 6\\ 7& 8& 9 \end{bmatrix}\frac{180^{^{\circ}}}{\Rightarrow} \begin{bmatrix} 9& 8& 7\\ 6& 5& 4\\ 3& 3& 1 \end{bmatrix} ⎣⎡147258369⎦⎤⇒180∘⎣⎡963853741⎦⎤
可以先通过水平翻转,再进行垂直翻转,就能实现 18 0 ∘ 180^{^{\circ}} 180∘翻转
[ 1 2 3 4 5 6 7 8 9 ] ⇒ [ 3 2 1 6 5 4 9 8 7 ] ⇒ [ 9 8 7 6 5 4 3 2 1 ] \begin{bmatrix} 1& 2& 3\\ 4& 5& 6\\ 7& 8& 9 \end{bmatrix}\Rightarrow \begin{bmatrix} 3& 2& 1\\ 6& 5& 4\\ 9& 8& 7 \end{bmatrix}\Rightarrow \begin{bmatrix} 9& 8& 7\\ 6& 5& 4\\ 3& 2& 1 \end{bmatrix} ⎣⎡147258369⎦⎤⇒⎣⎡369258147⎦⎤⇒⎣⎡963852741⎦⎤
由一维离散卷积公式可知,设函数 f f f大小为 A A A,函数 g g g大小为 B B B,那么卷积结果大小为 A + B − 1 A+B-1 A+B−1
对于二维离散卷积公式,设函数 f f f大小为 [ A , B ] [A,B] [A,B],函数 g g g大小为 [ C , D ] [C,D] [C,D],那么结果卷积大小为 [ ( A + C − 1 ) , ( B + D − 1 ) ] [(A+C-1), (B+D-1)] [(A+C−1),(B+D−1)]
对于图像卷积而言,输出图像与输入图像大小一致,因此需要舍弃卷积核锚点(图像中心)不与图像像素点重叠时的计算
对图像卷积而言,当卷积核在图像边界计算时,会发生超出图像边界的情况,有几种方式来填充:
extend
):扩展最近邻的像素点,即扩展图像边界wrap
):假设图像是首尾相接的,即使用图像相反方向的像素值mirror
):超出边界多少个像素,就向图像内部读取对应位置的像素0
参考:Convolution Theorem
公式如下:
f ∗ g = D F T ( f ) ⋅ D F T ( g ) f\ast g=DFT(f)\cdot DFT(g) f∗g=DFT(f)⋅DFT(g)
D F T ( f ) ∗ D F T ( g ) = f ⋅ g DFT(f)\ast DFT(g)=f\cdot g DFT(f)∗DFT(g)=f⋅g
在进行卷积操作时,需要注意两点
卷积核的大小和值可以根据要求定义,但通常会将整个卷积核进行归一化操作,其目的是为了保证修改后结果图像的平均元素值和原始图像平均元素值一样。
因为卷积操作满足齐次性,所以可以卷积计算完成后再除以整个卷积核的值。
图像数值类型通常为uint8
,在进行卷积操作时很容易造成数值溢出,所以在进行操作之前可以先转换成更高精度的数值类型
参考:
Signal processing (scipy.signal) Convolution
Multi-dimensional image processing (scipy.ndimage)
Python
有多个包(Numpy/Scipy/OpenCV
)提供了卷积操作
Numpy
numpy.convolve
Numpy
包提供了方法convolve
,用于计算一维线性卷积
它提供了3
种模式:
full
:默认方式,计算所有重叠的面积same
:返回和最大数组一样长度的数组。从卷积核锚点位置开始计算valid
:仅计算完全重叠的面积实现结果如下:
import numpy as np
a = np.array([1, 2, 3, 4])
v = np.array([1, 3, 2])
full = np.convolve(a, v)
same = np.convolve(a, v, 'same')
valid = np.convolve(a, v, 'valid')
[ 1 5 11 17 18 8]
[ 5 11 17 18]
[11 17]
Scipy.signal
scipy.signal
包提供了好几种方法来计算卷积:
convolve(in1, in2[, mode, method])
correlate(in1, in2[, mode, method])
fftconvolve(in1, in2[, mode])
convolve2d(in1, in2[, mode, boundary, fillvalue])
correlate2d(in1, in2[, mode, boundary, …])
sepfir2d(input, hrow, hcol)
choose_conv_method(in1, in2[, mode, measure])
signal.convolve2d()
参考:
scipy.signal.convolve2d
convolve2d(in1, in2, mode='full', boundary='fill', fillvalue=0)
in1
和in2
的维数大小相同
参数mode
决定了返回卷积结果的大小:
full
:输出全离散线性卷积(默认)valid
:输出不依赖于零填充的卷积结果same
:输出大小和in1
一样,卷积核锚点对齐图像处理的结果它额外提供了边界填充模式boundary
和填充值fillvalue
有3
种填充模式:
fill
:默认方式,使用fillvalue
的值进行填充wrap
:环形填充symm
:镜像模式signal.convolve()
参考:scipy.signal.convolve
convolve(in1, in2, mode='full', method='auto')
convolve
用于计算N维数组的卷积,除了可以设定模式外,还可以选择计算方法:
direct
:直接按照卷积的定义进行加权求和计算fft
:使用快速傅里叶变换(fft
)进行卷积操作auto
:默认方式,估计计算时间,选择直接计算还是快速傅里叶方法Signal.ndimage
signal.ndiamge
包提供了多个卷积函数:
convolve(input, weights[, output, mode, …])
convolve1d(input, weights[, axis, output, …])
correlate(input, weights[, output, mode, …])
correlate1d(input, weights[, axis, output, …])
OpenCV
参考:
Making your own linear filters!
filter2D()
BorderTypes
Depth combinations
OpenCV
提供了一个2
维滤波器filter2D
:
def filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, borderType=None)
src:输入图像
dst:相同大小和通道的结果图像
ddepth:结果图像深度,看Depth combinations
kernel:卷积核,一个单通道浮点矩阵
anchor:卷积核锚点位置,默认值(-1,-1),表示在核中心
delta:额外值
borderType:边界填充像素方式,参考BorderTypes
比如实现一个Sobel
算子,计算x
轴梯度
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
def realize_sobel():
gray = cv.imread("lena.jpg", 0)
ddepth = cv.CV_16S
kernel = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float)
res_x = cv.filter2D(gray, ddepth, kernel)
abs_res_x = cv.convertScaleAbs(res_x)
sobel_x = cv.Sobel(gray, ddepth, 1, 0, ksize=3)
abs_sobel_x = cv.convertScaleAbs(sobel_x)
plt.figure(figsize=(10, 5)) # 设置窗口大小
plt.suptitle('sobel') # 图片名称
plt.subplot(1, 2, 1)
plt.title('filter2D_x')
plt.imshow(abs_res_x, cmap='gray'), plt.axis('off')
plt.subplot(1, 2, 2)
plt.title('sobel_x')
plt.imshow(abs_sobel_x, cmap='gray'), plt.axis('off')
plt.savefig('./sobel_x.png') # 保存图像
plt.show()