二维卷积定理的验证(下,cv2.filter2D())
前言 : 内容承接了二维卷积定理的验证(上)。主要解决该博文中最后提到的问题,也即
cv2.filter2D()
如何应用傅里叶变换来替代进行图像的卷积计算。
0. 基本问题
二维卷积定理表达式如下
f
(
x
,
y
)
∗
g
(
x
,
y
)
=
F
−
1
[
F
(
f
(
x
,
y
)
)
⋅
F
(
g
(
x
,
y
)
)
]
f(x,y)*g(x,y) = F^{-1}[F(f(x,y))·F(g(x,y))]
f(x,y)∗g(x,y)=F−1[F(f(x,y))⋅F(g(x,y))]
如博文二维卷积定理的验证(上)中所述,在验证过程中发现,padding 的不同影响着最终结果的不同。
而等式左侧卷积操作和右侧傅里叶变换,都需要预先对图像进行 padding 操作,也即边界处理。
因此,在前述博文验证基础上,衍生出一个问题,即“是否存在着某种边界处理方式,使得卷积图像结果矩阵尺寸没有变化,且所有值都可以与傅里叶方式计算下等价。”
也即,cv2.filter2D()
是如何进行边界处理的。
1. 思路与流程
问题以及验证流程简化下,可以描述成以下:
-
先使用
cv2.filter2D( img, -1, kenel)
函数计算出一个结果。注意 kenel 要大于11*11,只有尺寸大时,该函数才是使用傅里叶变换来实现卷积操作。
-
使用
scipy.signal.convolve2d()
函数完成正常卷积操作。注意:这里涉及到了边界处理和卷积类型选择
-
使用
np.fft.fft2()
和np.fft.ifft2()
函数完成傅里叶变换操作。注意:这里涉及到了边界处理
最终目的是,通过调节(2)中卷积时的边界处理和类型选择,以及,(3)傅里叶变换时的边界处理,使得(2)(3)结果与(1)中结果完全一致。
这样,(1)(2)结果相同,也就验证了卷积定理;(1)(3)结果相同,可以弄清cv2.filter2D()
中傅里叶变换代替卷积过程。
2. 代码实践
按照上述思路,进行参数调试,因为比较繁琐,不呈现调试过程,只呈现最终代码。
完整代码
import cv2
import numpy as np
from scipy import signal
# ---------- 原始图像 f(x) 和 卷积核 g(x) -------------- #
img = cv2.imread("fp.jpg")[0:101,0:101]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kenel = np.ones((17,17))/17**2
# ---------------- cv2.filter2D() ---------------------#
# 数据类型
gray = gray.astype(np.float64)
Conv_img = cv2.filter2D(gray, -1, kenel)
np.savetxt('Conv_img64.txt', Conv_img, fmt='%0.8f')
# ------------------ signal.convolve2d() --------------#
# 边界类型选取
#img_pad = cv2.copyMakeBorder(gray, 8,8,8,8, cv2.BORDER_REFLECT)
img_pad = cv2.copyMakeBorder(gray, 8,8,8,8, cv2.BORDER_REFLECT_101)
#img_pad = cv2.copyMakeBorder(gray, 8,8,8,8, cv2.BORDER_REPLICATE)
#img_pad = cv2.copyMakeBorder(gray, 8,8,8,8, cv2.BORDER_CONSTANT, 0)
# 数据类型
img_pad = img_pad.astype(np.float64)
kenel = kenel.astype(np.float64)
Conv = signal.convolve2d(img_pad, kenel, mode='valid')
np.savetxt('Conv64.txt', Conv, fmt='%0.8f')
# ---------------- fft() & ifft() ---------------------#
# padding 边界选择
img_pad = cv2.copyMakeBorder(gray, 8,8,8,8, cv2.BORDER_REFLECT_101) #cv2.BORDER_REPLICATE
kenel_pad = cv2.copyMakeBorder(kenel, 50,50,50,50, cv2.BORDER_CONSTANT, 0)
# 数据类型
img_pad = img_pad.astype(np.float64)
kenel_pad = kenel_pad.astype(np.float64)
# F(f(x))
img_fft = np.fft.fftshift(np.fft.fft2(img_pad))
# F(g(x))
kenel_fft = np.fft.fftshift(np.fft.fft2(kenel_pad))
# ifft( F(f(x))·F(g(x)) )
FFT = np.fft.ifftshift(np.fft.ifft2(np.fft.fftshift(img_fft*kenel_fft)))
np.savetxt('FFT64.txt', np.abs(FFT)[8:-8, 8:-8], fmt='%0.8f')
细节说明
- 三种方法下,数据类型要保持一致!!!不然结果的小数点后会有些许差异。
- 傅里叶变换的结果图像尺寸会变大,需要剪切掉一部分,才能与前两者方法完全相等!!!
这两点,上述代码都有体现。
最终结果
- cv2.filter2D() 结果
- 卷积结果
- 傅里叶变换结果
3. 结论与分析
-
从上述结果中可以看出,三者结果是一致的,此条件下:
-
卷积,图片 padding 使用
cv2.BORDER_REFLECT_101
边界。使用了 ‘same’ 卷积。说明:上述代码中虽然使用了 ‘valid’。但因为预先进行了padding,所以实际上是 ’same‘ 操作。
-
傅里叶变换,图片 padding 使用
cv2.BORDER_REFLECT_101
边界,核 padding 使用补零操作。结果尺寸为 ( i m g _ H + k e n e l _ H − 1 ) × ( i m g _ W + k e n e l _ W − 1 ) (img\_H + kenel\_H-1)\times (img\_W + kenel\_W-1) (img_H+kenel_H−1)×(img_W+kenel_W−1) ,因此需要剪裁。
-
-
三种函数计算前,数据的精度要保持一致!!!这样最终结果才会完全相同。