图像中的傅立叶变换(三)

在之前的文章中,我们介绍了傅立叶变换的本质和很多基本性质,现在,该聊聊代码实现的问题了。

为了方便起见,本文采用的编程语言是 Python3,矩阵处理用 numpy,图像处理则使用 OpenCV3。

离散傅立叶变换

首先,回忆一下离散傅立叶变换的公式:

(1)F(u,v)=1MNx=0M1y=0N1f(x,y)ej2πux/Mej2πvy/N(2)=1MNy=0N1{x=0M1f(x,y)ej2πux/M}ej2πvy/N

从上式可以得到一个很有用的性质:可分性。即我们可以先计算 x=0M1f(x,y)ej2πux/M,得到 F(u,y),再计算 1MNy=0N1F(u,y)ej2πvy/N 得到 F(u,v)

根据这种可分性,我们可以将二维的计算分为两个一维进行。

现在,考虑如何计算 F(u,y)=x=0M1f(x,y)ej2πux/M,这个式子中的 y 可以当作是常数,所以这其实是关于 x 的一维运算。

根据这个式子,可以得到:

F(0,y)=x=0M1f(x,y)ej2π0xF(1,y)=x=0M1f(x,y)ej2πx/MF(M1,y)=x=0M1f(x,y)ej2π(M1)x/M

我们完全可以用矩阵相乘的形式来表示这些式子:

[F(0,y)F(1,y)F(M1,y)]=[1111WM1WMM11WMM1WM(M1)(M1)]×[f(0,y)f(1,y)f(M1,y)]

(式子中的 WM 表示 ej2π/M

当然,由于图片是二维的,所以 f(x,y) 对应的向量实际上应该是:

[f(0,y1)f(0,y2)f(0,yN)f(1,y1)f(1,y2)f(1,yN)f(M1,y1)f(M1,y2)f(M1,yN)]

同理,得到的 F(u,y) 也是一个二维矩阵。

现在,我们还是先考虑怎么实现这个一维的计算。

首先,需要先把 WM 这个矩阵表示出来。注意到,这个矩阵实际上可以由 [WM0WM1WMM1] × [WM0WM1WMM1] 得到。借助 numpy 强大的矩阵处理能力,可以很方便的计算出这个矩阵。示例如下:

def dftmtx(M):
n = np.asmatrix(np.arange(M))
return np.exp((-2j * np.pi / M) * n.transpose() * n)

np.asmatrix 是把 M 维的向量变成 1 × M 的矩阵的格式,因为只有矩阵才有 transpose() 操作。np.exp 会把 exp 函数作用到矩阵的每个元素中。

得到这个矩阵后,最关键的一步其实就做完了,我们可以用这个矩阵计算出 F(u,y)

# input表示输入图像,M是图像的高
M = input.shape[0]
F = dftmtx(M) * input

得到 F(u,y) 后,剩下的是要对 y 这一维进行同样的操作:F(u,v)=1MNy=0N1F(u,y)ej2πvy/N。同样地,我们需要计算一个 WN 的矩阵。幸运的是,这个矩阵的计算方法和之前的 WM 一模一样,这样一来,我们已经可以得到完整的计算方法了:

# 傅立叶变换函数
def dft2d(input):
M, N = input.shape[0], input.shape[1]
return dftmtx(M) * input * dftmtx(N) / (M * N)

接下来我们把频谱图打印出来。傅立叶频谱图是实部和虚部的平方和,需要注意的是,由于数值显示的问题,我们需要将频谱图用 log 函数增强后,再标定到 [0, 255] 之间才能看清。代码如下:

# 将像素值标定到[0,255]之间
def scale_intensity(image):
min = image.min()
max = image.max()
image = (image - min) / (max - min) * 255.0
return image
# 计算频谱图
def spectrogram(image):
dft = dft2d(image)
spec = np.sqrt(np.power(np.real(dft), 2) + np.power(np.imag(dft), 2))
spec = np.log(0.5 + spec) * 10
spec = scale_intensity(spec)
return spec
image = cv2.imread("your_file.png", cv2.IMREAD_GRAYSCALE)
spec = spectrogram(image)
cv2.imwrite("spec.png", spec)

结果展示:

左图是原图,右图是频谱图。如果仔细看的话,可以发现频谱图四个角有一些白色的点。这是因为图片中低频成分居多,而频谱图四个角代表的就是低频分量(至于为什么四个角是低频,我也没搞懂)。

实践中,人们习惯于把低频都聚集到图片中心,这样方便后续的操作。根据平移性质:

F(uM2,vN2)=f(x,y)(1)x+y

要把频谱图的低频部分平移到中心,需要将整个频谱图平移 (M/2,N/2) 个单位,也就是需要对原图乘以 (1)x+y。代码如下:

def shift_image(image):
M, N = image.shape[0], image.shape[1]
for x in range(M):
for y in range(N):
image[x, y] *= np.power(-1, x + y)
return image
image = cv2.imread("your_file.png", cv2.IMREAD_GRAYSCALE)
shift_image(image)
spec = spectrogram(image)
cv2.imwrite("shift_spec.png", spec)

结果展示:

离散傅立叶反变换

讲完傅立叶变换后,反变换基本也得到了,唯一的区别是,这一次我们需要计算一个傅立叶反变换的矩阵。这个矩阵和之前计算的矩阵 WM 的区别只在于符号,这里就直接给出代码了:

def idftmtx(M):
n = np.asmatrix(np.arange(M))
# 下面的符号是正的
return np.exp((2j * np.pi / M) * n.transpose() * n)

反变换的代码如下:

def idft2d(input):
M, N = input.shape[0], input.shape[1]
return idftmtx(M) * input * idftmtx(N)

把之前得到的傅立叶变换的结果,输入 idft2d 函数后,再取实部既可以得到原图:

image = cv2.imread("your_file.png", cv2.IMREAD_GRAYSCALE)
dft = dft2d(image)
idft = idft2d(dft)
cv2.imwrite("idft.png", np.real(idft))

结果如下:

这个反变换的结果和原图是略有差别的,因为傅立叶变换时舍弃了很多高频成分。不过,由于图片中高频成分本身就比较少,所以这点差别可以忽略不计。

posted @   大白话AI  阅读(580)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示