离散傅里叶变换(DFT)与相关性计算
本文主要介绍通过离散傅里叶变换的方法计算两个时间序列的相关性,不仅给出数学公式的推导,同时给出对应的
Matlab
程序的验证。文中涉及到相关,卷积等基本概念,本文假设读者已经具备相关的数学基础。
更改说明:由于CSDN的MD语法在解析部分公式时出现错误,所以讲部分公式改为图片。
补充:关于循环卷积与频域的相乘关系
基本定义
首先,我们给出离散傅里叶变换,卷积与相关的定义:
离散傅里叶变换
长度为N的离散时间序列
x
(
n
)
x(n)
x(n)的离散傅里叶变换的定义为:
X
(
k
)
=
∑
n
=
0
N
−
1
x
(
n
)
e
−
j
2
π
n
k
/
N
X(k) = \sum_{n=0}^{N-1}x(n)e^{-j2\pi nk/N}
X(k)=n=0∑N−1x(n)e−j2πnk/N
对应的Matlab
函数为fft(x)
卷积
长度为N的离散时间序列
x
(
n
)
x(n)
x(n)与
y
(
n
)
y(n)
y(n)的卷积定义为:
z
(
n
)
=
∑
m
=
0
N
−
1
x
(
m
)
y
(
n
−
m
)
z(n) = \sum^{N-1}_{m=0} x(m)y(n-m)
z(n)=m=0∑N−1x(m)y(n−m)
得到的卷积
z
(
n
)
z(n)
z(n)的长度为
2
N
−
1
2N-1
2N−1,n的取值范围是0到2N-2,卷积对应的Matlab
函数为conv(x, y)
。举个简单的例子:
x = [1, 2, 3]
y = [1, -1]
z = conv(x, y) % [1, 1, 1, -3]
相关
长度为N的离散时间序列
x
(
n
)
x(n)
x(n)的相关定义为:
z
(
n
)
=
∑
m
=
0
N
−
1
x
(
m
)
y
(
m
−
n
)
z(n) = \sum^{N-1}_{m=0} x(m)y(m-n)
z(n)=m=0∑N−1x(m)y(m−n)
同样,得到的相关
z
(
n
)
z(n)
z(n)的长度为
2
N
−
1
2N-1
2N−1,卷积对应的Matlab
函数为xcorr(x, y)
, 注意n的范围是
−
(
N
−
1
)
-(N-1)
−(N−1)到
(
N
−
1
)
(N-1)
(N−1)。举个简单的例子:
x = [1, 2, 3]
y = [1, -1, 1]
z = xcorr(x, y) % [1, 1, 2, -1, 3],对应的n是[-2, -1, 0, 1, 2]
注意
xcorr
中要求两个时间序列长度相等,如果不相等,会自动将较短的时间序列补0。
时域的卷积对应频域的相乘
设 z ( n ) z(n) z(n)是时间序列 x ( n ) x(n) x(n)与 y ( n ) y(n) y(n)的卷积,长度为 2 N − 1 2N-1 2N−1,则对应的离散傅里叶变换为:
Z
(
k
)
=
∑
n
=
0
2
N
−
2
z
(
n
)
e
−
j
2
π
n
k
/
2
N
−
1
=
∑
n
=
0
2
N
−
2
∑
m
=
0
N
−
1
x
(
m
)
y
(
n
−
m
)
e
−
j
2
π
n
k
/
2
N
−
1
\begin{aligned} Z(k) & = \sum^{2N-2}_{n=0} z(n)e^{-j2\pi nk/{2N-1}} \\ & = \sum^{2N-2}_{n=0} \sum^{N-1}_{m=0} x(m)y(n-m)e^{-j2\pi nk/{2N-1}} \end{aligned}
Z(k)=n=0∑2N−2z(n)e−j2πnk/2N−1=n=0∑2N−2m=0∑N−1x(m)y(n−m)e−j2πnk/2N−1
交换求和顺序得
Z
(
k
)
=
∑
m
=
0
N
−
1
x
(
m
)
∑
n
=
0
2
N
−
2
y
(
n
−
m
)
e
−
j
2
π
n
k
/
2
N
−
1
=
∑
m
=
0
N
−
1
x
(
m
)
e
−
j
2
π
m
k
/
2
N
−
1
∑
n
=
0
2
N
−
2
y
(
n
−
m
)
e
−
j
2
π
(
n
−
m
)
k
/
2
N
−
1
=
∑
m
=
0
N
−
1
x
(
m
)
e
−
j
2
π
m
k
/
2
N
−
1
∑
n
=
0
N
−
1
y
(
n
)
e
−
j
2
π
n
k
/
2
N
−
1
\begin{aligned} Z(k) & = \sum^{N-1}_{m=0} x(m) \sum^{2N-2}_{n=0} y(n-m)e^{-j2\pi nk/{2N-1}} \\ & = \sum^{N-1}_{m=0} x(m)e^{-j2\pi mk/{2N-1}} \sum^{2N-2}_{n=0} y(n-m)e^{-j2\pi (n-m)k/{2N-1}} \\ & = \sum^{N-1}_{m=0} x(m)e^{-j2\pi mk/{2N-1}} \sum^{N-1}_{n=0} y(n)e^{-j2\pi nk/{2N-1}} \end{aligned}
Z(k)=m=0∑N−1x(m)n=0∑2N−2y(n−m)e−j2πnk/2N−1=m=0∑N−1x(m)e−j2πmk/2N−1n=0∑2N−2y(n−m)e−j2π(n−m)k/2N−1=m=0∑N−1x(m)e−j2πmk/2N−1n=0∑N−1y(n)e−j2πnk/2N−1
注意等式(7)中第二项的变量替换。由于
Z
(
k
)
Z(k)
Z(k)序列的长度为
2
N
−
1
2N-1
2N−1,而
X
(
k
)
X(k)
X(k)与
Y
(
k
)
Y(k)
Y(k)的长度都为
N
N
N,为了长度上一致,我们将
x
(
n
)
x(n)
x(n)与
y
(
n
)
y(n)
y(n)分别补0,使得最终的序列长度为
2
N
−
1
2N -1
2N−1,分别记作
x
p
(
n
)
x_p(n)
xp(n)与
y
p
(
n
)
y_p(n)
yp(n),以
x
p
(
n
)
x_p(n)
xp(n)为例对应的数学描述为:
x
p
(
n
)
=
{
x
(
n
)
n
≤
N
−
1
0
n
>
N
−
1
x_p(n) = \left\{ \begin{array}{rl} x(n) & n \leq N-1\\ 0 & n>N-1 \end{array} \right.
xp(n)={x(n)0n≤N−1n>N−1
则其对应的傅里叶变换为:
X
p
(
k
)
=
∑
n
=
0
N
−
1
x
p
(
n
)
e
−
j
2
π
n
k
/
2
N
−
1
X_p(k) = \sum_{n=0}^{N-1}x_p(n)e^{-j2\pi nk/2N-1}
Xp(k)=n=0∑N−1xp(n)e−j2πnk/2N−1
联系等式(8)可以得到
Z
(
k
)
=
X
p
(
k
)
Y
p
(
k
)
Z(k) = X_p(k)Y_p(k)
Z(k)=Xp(k)Yp(k)
我们使用一个简单的Matlab程序进行验证。
N = 20;
x = randn(1, N);
y = randn(1, N);
z = conv(x, y);
xp = [x, zeros(1, N-1)]; %补0
yp = [y, zeros(1, N-1)]; %补0
zk = fft(z);
xpk = fft(xp);
ypk = fft(yp);
zke = xpk.*ypk;%频域相乘
real_err = max(abs(real(zk)-real(zke)));
imag_err = max(abs(imag(zk) - imag(zke)));
fprintf('实补误差 %e\n虚部误差 %e\n',real_err, imag_err);
%% 程序输出结果
% 实部误差 1.421085e-14
% 虚部误差 8.881784e-15
其他的文献补0的个数都选择N而非N-1,背后的道理是一样的。选择N的好处是 X p ( k ) X_p(k) Xp(k)与 X ( k ) X(k) X(k)有一个一致的对应关系,即 X p ( 2 k ) = X ( k ) X_p(2k) = X(k) Xp(2k)=X(k)
上面的定理可以简单描述为时域的卷积对应频域的乘积,一个典型应用加速卷积的计算。我们知道,如果直接套用卷积的定义,那么时间复杂度是 O ( n 2 ) O(n^2) O(n2),而快速傅里叶变换的时间复杂度是 O ( n log n ) O(n\log n) O(nlogn),一个直接的思路就是利用傅里叶变换计算卷积,即首先根据 X p ( k ) X_p(k) Xp(k)与 Y p ( k ) Y_p(k) Yp(k)得到时间 Z ( k ) Z(k) Z(k),然后进行傅里叶反变换得到卷积 z ( n ) z(n) z(n)。
循环卷积与相乘的关系
在图像处理中,图像与卷积核的运算通常需要保持图像大小不变,这种情况下时域的卷积与频域的乘积的对应关系是否仍然保持呢?
Matlab
对应的卷积函数为imfilter(u, v,'circular', 'conv')
,给出简单的Matlab函数进行验证。
a=[1,2,3,4,5,6,7];
b = [1,2,3];
d = imfilter(a,b,'circular','conv');
% 结果 d => [ 11 8 12 16 20 24 21]
了解了循环卷积的运算过程,我们进行公式的推导。首先,对循环卷积进行表示。对于长度为N的序列
x
(
n
)
x(n)
x(n)其中,n=0, 1, 2, …N-1。以及长度为M的序列
h
(
n
)
h(n)
h(n),这里对于
h
(
n
)
h(n)
h(n),如果长度为奇数2M+1,则对应的n的取值范围为-M, -(M-1), … 0, …, (M-1), M。如果长度为偶数2M,则对应的n的范围表示为-M, …M-1。则循环卷积y(n)可以表示为:
y
(
n
)
=
∑
m
=
0
N
−
1
x
(
m
)
h
)
(
n
−
m
)
,
n
=
0
,
1
,
2
,
.
.
.
N
−
1
y(n) = \sum_{m=0}^{N-1}x(m)h)(n-m), n=0,1,2,...N-1
y(n)=m=0∑N−1x(m)h)(n−m),n=0,1,2,...N−1
根据上述公式,进行形同推导,则可以得到:
Y
(
k
)
=
∑
m
=
0
N
−
1
x
(
m
)
∑
n
=
0
N
−
1
h
(
n
−
m
)
e
−
j
2
π
n
k
/
N
=
∑
m
=
0
N
−
1
x
(
m
)
e
−
j
2
π
m
k
/
N
∑
n
=
0
N
−
1
h
(
n
−
m
)
e
−
j
2
π
(
n
−
m
)
k
/
N
\begin{aligned} Y(k) & = \sum^{N-1}_{m=0} x(m) \sum^{N-1}_{n=0} h(n-m)e^{-j2\pi nk/N} \\ & = \sum^{N-1}_{m=0} x(m)e^{-j2\pi mk/N} \sum^{N-1}_{n=0} h(n-m)e^{-j2\pi (n-m)k/N} \\ \end{aligned}
Y(k)=m=0∑N−1x(m)n=0∑N−1h(n−m)e−j2πnk/N=m=0∑N−1x(m)e−j2πmk/Nn=0∑N−1h(n−m)e−j2π(n−m)k/N
定义
h
p
(
n
)
h_p(n)
hp(n)为将
h
(
n
)
h(n)
h(n)补0到长度为N,然后将向左循环平移M位。即:
h
p
(
n
)
=
{
h
(
n
)
0
≤
n
≤
M
−
1
x
(
n
−
m
+
N
)
n
≥
n
−
M
0
o
t
h
e
r
w
i
s
e
h_p(n) = \left\{ \begin{array}{rl} h(n) & 0\leq n \leq M-1\\ x(n-m+N) & n \geq n-M\\ 0 & otherwise \end{array} \right.
hp(n)=⎩⎨⎧h(n)x(n−m+N)00≤n≤M−1n≥n−Motherwise
则
Y
(
k
)
=
X
(
k
)
H
p
(
k
)
Y(k)=X(k)H_p(k)
Y(k)=X(k)Hp(k),我们通过一个Matlab
程序进行验证。
clc
clear
x=[1,2,3,4,5,6,7];
h = [1,2,-1];
y = imfilter(x, h, 'circular', 'conv'); %-3 6 8 10 12 14 9
hp = [h, zeros(1, length(x)-length(h))]; %补0
m = floor(length(h)/2);
hp = circshift(hp, -m); %向左循环平移
Yk = fft(x).*fft(hp);
ye = ifft(Yk); % -3.0000 6.0000 8.0000 10.0000 12.0000 14.0000 9.0000
% 计算结果可知,y与ye的结果一致(忽略舍入误差)
循环平移的傅里叶变换
循环平移的本质是将序列 x ( n ) x(n) x(n)看做周期为N的周期序列,也就是N到2N-1的取值与0到N-1的取值有一一对应的关系。这里我们给出证明:
将序列
x
(
n
)
x(n)
x(n)向右做循环平移m位,得到y(n),则:
y
(
n
)
=
{
x
(
n
−
m
)
n
≥
m
x
(
n
−
m
+
N
)
n
<
m
y(n) = \left\{ \begin{array}{rl} x(n-m) & n \geq m\\ x(n-m+N) & n < m \end{array} \right.
y(n)={x(n−m)x(n−m+N)n≥mn<m
则对应的傅里叶变换为:
Y
(
k
)
=
∑
n
=
0
N
−
1
y
(
n
)
e
−
j
2
π
n
k
/
N
=
∑
n
=
m
N
−
1
x
(
n
−
m
)
e
−
j
2
π
n
k
/
N
+
∑
n
=
0
m
−
1
x
(
n
−
m
+
N
)
e
−
j
2
π
n
k
/
N
=
∑
n
=
m
N
−
1
x
(
n
−
m
)
e
−
j
2
π
(
n
−
m
)
k
/
N
e
−
j
2
π
m
k
/
N
+
∑
n
=
0
m
−
1
x
(
n
−
m
+
N
)
e
−
j
2
π
(
n
−
m
+
N
)
k
/
N
e
−
j
2
π
(
m
−
N
)
k
/
N
=
∑
n
=
0
N
−
1
x
(
n
)
e
−
j
2
π
n
k
/
N
e
−
j
2
π
m
k
/
N
=
X
(
k
)
e
−
j
2
π
m
k
/
N
\begin{aligned} Y(k) &= \sum_{n=0}^{N-1} y(n)e^{-j2\pi nk/N} \\ &= \sum_{n=m}^{N-1} x(n-m)e^{-j2\pi nk/N} + \sum_{n=0}^{m-1} x(n-m+N)e^{-j2\pi nk/N} \\ &= \sum_{n=m}^{N-1} x(n-m)e^{-j2\pi (n-m)k/N}e^{-j2\pi mk/N} + \sum_{n=0}^{m-1} x(n-m+N)e^{-j2\pi (n-m+N)k/N}e^{-j2\pi (m-N)k/N} \\ &= \sum_{n=0}^{N-1}x(n)e^{-j2\pi nk/N}e^{-j2\pi mk/N} \\ &= X(k)e^{-j2\pi mk/N} \end{aligned}
Y(k)=n=0∑N−1y(n)e−j2πnk/N=n=m∑N−1x(n−m)e−j2πnk/N+n=0∑m−1x(n−m+N)e−j2πnk/N=n=m∑N−1x(n−m)e−j2π(n−m)k/Ne−j2πmk/N+n=0∑m−1x(n−m+N)e−j2π(n−m+N)k/Ne−j2π(m−N)k/N=n=0∑N−1x(n)e−j2πnk/Ne−j2πmk/N=X(k)e−j2πmk/N
相关与卷积
熟悉卷积的读者应该知道卷积的计算过程分为两步:翻转和平移,与相关的计算相比,只是多了一步翻转操作。如果直接序列进行翻转,是否能用卷积代替相关操作呢?答案是是显然的,我们用一个Matlab程序做证明。
x = [1,2,1];
y = [1,2,3];
z1 = xcorr(x, y); %相关
z2 = conv(x, flip(y)); %先翻转,然后卷积
z1 % => [3, 8, 8, 4, 1]
z2 % => [3, 8, 8, 4, 1]
这里不做数学的证明,有兴趣的读者可以自己证明。证明的过程中注意相关计算结果的第一项对应的n是 − ( N − 1 ) -(N-1) −(N−1),而卷积操作的第一项对应的n是0。
通过傅里叶变换计算相关
如果直接利用相关的公式,时间复杂度是
O
(
n
2
)
O(n^2)
O(n2),而快速傅里叶变换的时间复杂度是
O
(
n
log
n
)
O(n\log n)
O(nlogn),那么能否使用fft
方法来计算相关,从而提高运算速度呢?上一节我们已经知道相关与翻转后做卷积是等价的,那么一个直接的思路是:
- 将序列 y ( n ) y(n) y(n)翻转,得到 u ( n ) u(n) u(n),然后补0,得到 u p ( n ) u_p(n) up(n),进行傅里叶变换得到 U p ( k ) U_p(k) Up(k)
- 将序列 x ( n ) x(n) x(n)补0,得到 x p ( n ) x_p(n) xp(n),进行傅里叶变换得到 X p ( k ) X_p(k) Xp(k)
- 计算 Z ( k ) = X ( p ) U p ( k ) Z(k) = X_(p)U_p(k) Z(k)=X(p)Up(k),通过傅里叶反变换得到 z ( n ) z(n) z(n)
上述计算的缺点是多了一个中间变量
u
(
n
)
u(n)
u(n),那么能否用
Y
p
(
k
)
Y_p(k)
Yp(k) 来计算
U
p
(
k
)
U_p(k)
Up(k)呢?我们进行一个简单的推导。
U
p
(
k
)
=
∑
n
=
0
N
−
1
u
(
n
)
e
−
j
2
π
n
k
/
2
N
−
1
=
∑
n
=
0
N
−
1
y
(
N
−
1
−
n
)
e
−
j
2
π
n
k
/
2
N
−
1
=
∑
n
=
0
N
−
1
y
(
N
−
1
−
n
)
e
−
j
2
π
(
n
+
1
−
N
)
k
/
2
N
−
1
e
−
j
2
π
(
N
−
1
)
k
/
2
N
−
1
=
{
∑
n
=
0
N
−
1
y
(
N
−
1
−
n
)
e
−
j
2
π
(
N
−
1
−
n
)
k
/
2
N
−
1
}
∗
e
−
j
2
π
(
N
−
1
)
k
/
2
N
−
1
=
Y
p
∗
(
k
)
e
−
j
2
π
(
N
−
1
)
k
/
2
N
−
1
\begin{aligned} U_p(k) & = \sum_{n=0}^{N-1}u(n)e^{-j2\pi nk/2N-1} \\ & = \sum_{n=0}^{N-1}y(N-1-n)e^{-j2\pi nk/2N-1} \\ & = \sum_{n=0}^{N-1}y(N-1-n)e^{-j2\pi (n+1-N)k/2N-1}e^{-j2\pi (N-1)k/2N-1} \\ & = \left\{\sum_{n=0}^{N-1}y(N-1-n)e^{-j2\pi (N-1-n)k/2N-1}\right\}^*e^{-j2\pi (N-1)k/2N-1} \\ & = Y^*_p(k)e^{-j2\pi (N-1)k/2N-1} \end{aligned}
Up(k)=n=0∑N−1u(n)e−j2πnk/2N−1=n=0∑N−1y(N−1−n)e−j2πnk/2N−1=n=0∑N−1y(N−1−n)e−j2π(n+1−N)k/2N−1e−j2π(N−1)k/2N−1={n=0∑N−1y(N−1−n)e−j2π(N−1−n)k/2N−1}∗e−j2π(N−1)k/2N−1=Yp∗(k)e−j2π(N−1)k/2N−1
其中
∗
*
∗表示共轭运算,对应的matlab
函数为conj(X)
。
也就是说如果令 Z 1 ( k ) = X p ( k ) Y p ∗ ( k ) Z_1(k) = X_p(k)Y_p^*(k) Z1(k)=Xp(k)Yp∗(k),对应的傅里叶反变换为 z 1 ( n ) z_1(n) z1(n),则 Z ( k ) = Z 1 ( k ) e − j 2 π ( N − 1 ) k / 2 N − 1 Z(k) = Z_1(k)e^{-j2\pi (N-1)k/2N-1} Z(k)=Z1(k)e−j2π(N−1)k/2N−1,根据循环平移的傅里叶变换易知 z ( n ) = z 1 ( n − ( N − 1 ) ) z(n) = z1(n-(N-1)) z(n)=z1(n−(N−1))
因此,通过傅里叶变换计算相关运算的思路为:
- 将 x ( n ) x(n) x(n)与 y ( n ) y(n) y(n)分别补N-1个0,得到 x p ( n ) x_p(n) xp(n)与 y p ( n ) y_p(n) yp(n),进行快速傅里叶变换得到 X p ( k ) X_p(k) Xp(k)与 Y p ( k ) Y_p(k) Yp(k);
- 计算 Z 1 ( k ) = X ( k ) Y ∗ ( k ) Z_1(k) = X(k)Y^*(k) Z1(k)=X(k)Y∗(k),并进行傅里叶反变换得到 z 1 ( n ) z_1(n) z1(n)
- 进行循环平移得到 z ( n ) z(n) z(n)
同之前讨论过的一样,补0的个数只要大于等于N-1不影响计算结果,很多资料中都补N个0.
最后我们通过一个Matlab
程序对比一下4种计算相关性的方法。
% 通过4种方式计算相关
%% 数据生成
N = 30;
x = randn(1, N);
y = [randn(1, 4), x(1:N-4)];
%y = randn(1, N);
%% 方法1, 通过Matlab自带函数xcorr进行计算
z1 = xcorr(x, y);
%% 方法2,通过翻转,求卷积
z2 = conv(x, flip(y));
%% 方法3,通过翻转,傅里叶变换计算
u = flip(y);
xp = [x, zeros(1, N-1)];
up = [u, zeros(1, N-1)];
zk = fft(xp).*fft(up);
z3 = ifft(zk);
%% 方法4,傅里叶变换方法
xp = [x, zeros(1, N-1)];
yp = [y, zeros(1, N-1)];
z1k = fft(xp).*conj(fft(yp));
z1n = ifft(z1k);
z4 = circshift(z1n, N-1); %需要平移操作
%% 计算结果的可视化
lags = -(N-1):N-1;
scalediff = 5;
plot(lags, z1);
hold on;
plot(lags, z2 + scalediff*1);
plot(lags, z3 + scalediff*2);
plot(lags, z4 + scalediff*3);
plot([-4, -4], ylim, 'r:','LineWidth', 1.5); % 平移量
hold off
legend('方法一','方法二','方法三', '方法四');
结果: