Fourier_01_图像傅里叶变换的公式推导(一)
posted on 13 Feb 2018 under category 2018_02
f(x, y) 是时域函数,x、y表示图像点的空间位置;F(u, v) 是频域函数,u、v表示频域点的图像表示时的空间位置;M、N 和m、n 分别表示图像的尺寸。对于图像的傅里叶变换,就是一个二维离散傅里叶变换,它利用一个域平面上的每一点来生成另一个域上某个点的信息。而对于f和F并不需要符合一个连续的公式,只需要对每个离散的f(x, y) 和F(u, v) 变换的值进行累加即可。
图像傅里叶在原理上的核心公式只有这一部分,接下来在下将在这一章中的剩余介绍matlab中傅里叶变换几个函数的使用。
傅里叶变换中使用到的函数主要是:fft、ifft、fft2、ifft2、fftshift、ifftshift。
在进行傅里叶变换时先介绍fftshift和ifftshift。
细心的朋友可能已经注意到了,上一节傅里叶变换的公式中采用的累加区域是[0, M-1]、[0, N-1]、[0, m-1]和[0, n-1],并非采用1为起点来构造函数,这是因为在傅里叶变换的过程中是把一个有限的区域视作一个圆周,每相邻两点间的夹角经过一周会构成一个完整的圆。(具体的可视化过程我会在后面的章节或者文章中反复提出,这里只是使用它的结论)采用0作为基点表明0处是一个周期的中心部分,然而对于离散的线段而言,中心的位置有可能在一个离散点上,也有可能在两个点的中间,无取值处。所以,把一个这样的周期段位置中心化就用到了shift的计算(shift是傅里叶变换的一个性质,会在Fourier_02探究性质的过程中提到),而对应这两种中心不同的方式,以及shift操作的还原情况我们采用了以下代码来验证:
clear all
clc
I_1D_odd = [1, 2, 3, 4, 5];
I_1D_even = [6, 5, 4, 3, 2, 1];
I_2D_odd = magic(5);
I_2D_even = magic(6);
I_2D_eo = rand(5, 6);
I_2D_oe = rand(6, 5);
I_1D_odd_shift = fftshift(I_1D_odd);
I_1D_odd_si =ifftshift(I_1D_odd_shift);
I_1D_odd_ss = fftshift(I_1D_odd_shift);
I_1D_even_shift = fftshift(I_1D_even);
I_1D_even_si =ifftshift(I_1D_even_shift);
I_1D_even_ss = fftshift(I_1D_even_shift);
I_2D_odd_shift = fftshift(I_2D_odd);
I_2D_odd_si =ifftshift(I_2D_odd_shift);
I_2D_even_shift = fftshift(I_2D_even);
I_2D_even_si =ifftshift(I_2D_even_shift);
I_2D_eo_shift = fftshift(I_2D_eo);
I_2D_eo_si =ifftshift(I_2D_eo_shift);
I_2D_oe_shift = fftshift(I_2D_oe);
I_2D_oe_si =ifftshift(I_2D_oe_shift);
得到的结果如表格示意图:
对这张图进行标记可以更明显的看出shift的操作方式:
我们可以得到如下结论:
(1)fftshift将行(或者列)边界部分转换到正中心(无论奇偶)。
(2)ifftshift能将fftshift的矩阵还原到原矩阵,只使用fftshift会如角标_ss的部分无法完整还原到原矩阵。
这两个结论在MATLAB的help文档中都有很好的描述,在学习编程和这些函数操作的时候,在下非常鼓励大家仔细的阅读文档,读懂文档可以省去很多如上罗列的粗糙的验证过程,在文档说明不明确的时候再设计小实验验证,补全自己不完整的思考。在下在这里搬运出来fftshift和ifftshift的文档如下:
现在开始演示图像傅里叶变换函数的使用:
我们在这里可以设计一段程序来做实验,验证在傅里叶变换中经过正变换和逆变换之后仍然能还原成原来的信息,在此在下设计了一个一维波形wave和二维图像lena对来进行正反变换,代码如下:
clear all
clc
%%%---设计一维函数wave---%%%
N = 256;
for x = 1:1:N
func(x) = 0.5*(sin(x*2*pi/N))^2*exp(-abs(x-N/2)/(N/4));
end
figure;
subplot(231);plot(func);
title('wave');axis([1 N min(func) max(func)])
%%%---将一维函数进行傅里叶变换---%%%
func_fft = fftshift(fft(func));
subplot(232);plot(real(func_fft));%% 显示实部
title('wave-fft');axis([1 N min(real(func_fft)) max(real(func_fft))])
%%%---将频谱进行傅里叶逆变换---%%%
func_ifft = ifft(ifftshift(func_fft));%% 变换时还是变换复数谱
subplot(233);plot(real(func_ifft));
title('wave-ifft');axis([1 N min(func_ifft) max(func_ifft)])
%%%---读取lena图像---%%%
I = imread('lena.jpg');
I = rgb2gray(I);
I = im2double(I);%% 将图像数据转换成double(0, 1)的范围内
subplot(234);imshow(I);title('lena')
%%%---将lena进行傅里叶变换---%%%
I_fft = fftshift(fft2(I));
subplot(235);imshow(log(abs(I_fft)+1), [ ] );title('lena-fft')%% 对数化频谱
%%%---将二维频谱进行傅里叶逆变换---%%%
I_ifft = ifft2(ifftshift(I_fft));
subplot(236);imshow(real(I_ifft), [ ]);title('lena-ifft')
%%%---计算正反变换之后的差值---%%%
sub_1=func-func_ifft;
sub_2=I-I_ifft;
在这个实验中,我们先预测一个结果:在理想条件下,经过正反变换的信息能完全还原成初始信息。通过以上程序进行计算,我们得到如下结果:
图中fft表示频谱,ifft表示经过正反变换的结果图,计算得到的sub_1和sub_2的值为:sub_1_max = 5.5511 × 10^(-17)、sub_1_min = -5.5511 × 10^(-17)、 sub_2_max = 6.1062 × 10^(-16) 、sub_2_min = -6.6613 × 10 ^(-16) 。
人眼可观察的结果达到了还原的效果,但是计算并没有达到100%复原原始信息。差值的量级在10-16及以下,造成这个结果的原因是double这个数据类型的计算误差,详细请参考IEEE 754标准,因此,我们可以认为之前预测的结果成立:“在理想条件下,经过正反变换的信息能完全还原成初始信息。”
另外,要说明的是,设计代码的过程中,有一个“互补”的式子:fftshift(fft(function))和ifft(ifftshift(function))。在这个变换对中在下理解的是:fftshift主要作用仅仅是让可视化过程中基频移到正中央。
在这段代码的操作过程中,在下有一些“冗余动作”,这些动作对于有一定MATLAB图像处理基础的朋友来说并不陌生,如果这些“冗余动作”动作不理解的,有一些重要部分在下会在后面慢慢补充说明,也欢迎发邮件与在下交流这些问题,当然最希望的还是能第一时间能通过help文档解决这些小麻烦。
对于单纯的操作来说,这一部分也确是只需要调用函数来完成,因此,我们还是有必要尽早进入下一个部分:如何用MATLAB编写傅里叶正反变换的函数。
使用matlab来编写傅里叶变换的函数,主要目的是了解傅里叶变换的参量,实现算法流程的规划。那么回到二维离散傅里叶正反变换的式子:
在正变换的式子里面,已知空间域图像的边界长度为:m和n,频域的边界长度为M和N,自变量是图像在空间位置上某一点的能量强度:f(x,y),最终得到因变量:F(u,v)。反变换与此类似自变量和因变量位置互换。明确了参量之后,可以着手开始编写程序,并与MATLAB提供的fft函数结果进行比较,有代码如下:
clear all
clc
%%%---读取lena图像---%%%
I = imread('lena.jpg');
I = rgb2gray(I);
I = im2double(I);%% 将图像数据转换成double(0, 1)的范围内
J = I(76:175, 76:175);
figure;imshow(J)
[m, n] = size(J);
N = m*2;
M = n*2;
J_fft = zeros(M, N);
for u = 1:1:M
for v = 1:1:N
for x = 1:1:m
for y = 1:1:n
J_fft(u, v) = J_fft(u, v)+J(x, y)*exp(-1i*2*pi*(x*u/M+y*v/N));
end
end
end
end
figure;imshow(fftshift(log(abs(J_fft)+1)), [ ])
J_ifft = zeros(m, n);
for x = 1:1:m
for y = 1:1:n
for u = 1:1:M
for v = 1:1:N
J_ifft(x, y) = J_ifft(x, y)+J_fft(u, v)... %% 频谱尺度大于空域?
*exp(1i*2*pi*(x*u/(M)+y*v/(N)));%% 必须是频谱的尺度?
end
end
J_ifft(x, y) = 1/(M*N)*J_ifft(x, y);
end
end
figure;imshow(real(J_ifft), [ ])
sub = J - J_ifft;
figure;
subplot(131);
imshow(J, [ ]);title('lena-100');
subplot(132);
imshow(fftshift(log(abs(J_fft)+1)), [ ]);title('lena-100-fft');
subplot(133);
imshow(real(J_ifft), [ ]);title('lena-100-ifft');
得到结果如图:
在刚刚编写这段程序的过程中,在下发现了自己有一个不严密的地方:exp(function)里面除数的选择,这个部分有一些物理意义需要理清,可能还会更改这一页日志的内容。
不过,函数通过扩展频谱尺寸的做法(将空域100X100扩大到频域200X200)实现了时频域图片尺度不同时的完全转换(sub结果在计算误差范围内)达到了自编函数的目的。并且由于自编的算法是完全的傅里叶变换而不是fft(快速傅里叶变换),计算量比较大(也是这个原因在下将图像裁剪到100X100,否则原尺寸来算比现在计算量会大十倍)。
Waiting For Next Chapter ...