Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

離散フーリエ変換

離散フーリエ変換

複素フーリエ級数

θ\thetaを角度とすると、円周に沿って値が定義された関数f(θ)f(\theta)(sinやcosなどのこと??)は周期T=2πT=2\piの周期関数であり、θ\theta2π2\piの任意の整数倍を足しても引いてもf(θ)f(\theta)は同じ値になる。

フーリエ係数の複素表示

f(t)=k=Ckeikωot,Ck=1TT/2T/2f(t)eikωot dtf(t)=\sum_{k=-\infty}^{\infty} C_k e^{i k \omega_o t}, \quad C_k=\frac{1}{T} \int_{-T / 2}^{T / 2} f(t) e^{-i k \omega_o t} \mathrm{~d} t

より、f(θ)f(\theta)の基本周波数はω0=2π/T=1\omega_0=2\pi/T = 1であるから、f(θ)f(\theta)のフーリエ級数は、

f(θ)=k=Ckeikθ,Ck=12πππf(θ)eikθdθf(\theta)=\sum_{k=-\infty}^{\infty} C_k e^{i k \theta}, \quad C_k=\frac{1}{2 \pi} \int_{-\pi}^\pi f(\theta) e^{-i k \theta} d \theta

と書くことができる。

離散フーリエ変換

円周上をNN分割し、NN個のサンプル点をとる

θl=2πNl,l=0,1,2,,N1\theta_l=\frac{2 \pi}{N} l, \quad l=0,1,2, \ldots, N-1

(1周期が2π2\piなのをNN分割したもののll倍がθl\theta_l

このサンプル点での f(θ)f(\theta) のサンプル値を fl=f(θl)f_l=f\left(\theta_l\right) とする。

前述のフーリエ係数は連続関数 f(θ)f(\theta) を無限個の係数 {Ck},k=0,±1,±2,±3,\left\{C_k\right\}, k=0, \pm 1, \pm 2, \pm 3, \ldots, で表すものだが、 もし NN 個のサンプル値 {fl}\left\{f_l\right\} のみが必要な場合は NN 個の係数のみで表される。

fl=k=0N1Fkei2πkl/N,Fk=1Nl=0N1flei2πkl/Nf_l=\sum_{k=0}^{N-1} F_k e^{i 2 \pi k l / N}, \quad F_k=\frac{1}{N} \sum_{l=0}^{N-1} f_l e^{-i 2 \pi k l / N}

係数 {Fk}\left\{F_k\right\} をデータ {fl}\left\{f_l\right\}離散フーリエ変換 と呼ぶ。

Source
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(4, 3))

# 軸を描画
ax.arrow(-1.2, 0, 2.4, 0, head_width=0.05, head_length=0.1, fc='black', ec='black', length_includes_head=True)
ax.arrow(0, -1.2, 0, 2.4, head_width=0.05, head_length=0.1, fc='black', ec='black', length_includes_head=True)

ax.text(1.3, 0, "Re", fontsize=14, color='black')
ax.text(0, 1.3, "Im", fontsize=14, color='black')

ax.set_aspect('equal', adjustable='datalim')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.set(xticks=[], yticks=[])


# 単位円
theta = np.linspace(0, 2*np.pi, 100)
x = np.cos(theta)
y = np.sin(theta)
plt.plot(x, y, 'k-', linewidth=1)  # 黒い実線で単位円を描画

# curve
t = 0.3*np.pi
theta = np.linspace(0, t, 100)
x = np.cos(theta) * 0.2
y = np.sin(theta) * 0.2
ax.plot(x, y, 'k--', linewidth=1)  # 黒い実線で単位円を描画

# text
m = len(x) // 2
ax.text(x[m], y[m], r"$\theta_l = 2\pi l / N$")

# line
x = np.cos(t)
y = np.sin(t)
ax.plot([0, x], [0, y], color="blue", linestyle='-.', linewidth=0.8)
ax.scatter([x], [y], color="blue")

plt.show()
<Figure size 400x300 with 1 Axes>

逆フーリエ変換

クロネッカーのデルタ関数の離散バージョン
1Nk=0N1ei2π(mn)k/N={1mn(modN)0m≢n(modN)\frac{1}{N} \sum_{k=0}^{N-1} e^{i 2 \pi(m-n) k / N} = \begin{cases}1 & m \equiv n(\bmod N) \\ 0 & m \not \equiv n(\bmod N) \end{cases}

ただし mn(modN)m \equiv n \quad (\bmod N)NN として 合同 であると読む)は mnm-nNN の倍数であることを表す。

これを使うことで、データ{fl}\{f_l\}から離散フーリエ変換FkF_kを定義すると

k=0N1Fkei2πkl/N=k=0N1(1Nm=0N1fmei2πkm/N)ei2πkl/N=m=0N1fm(1Nk=0N1ei2π(lm)k/N)\begin{aligned} \sum_{k=0}^{N-1} F_k e^{i 2 \pi k l / N} & =\sum_{k=0}^{N-1}\left(\frac{1}{N} \sum_{m=0}^{N-1} f_m e^{-i 2 \pi k m / N}\right) e^{i 2 \pi k l / N} \\ & =\sum_{m=0}^{N-1} f_m\left(\frac{1}{N} \sum_{k=0}^{N-1} e^{i 2 \pi(l-m) k / N}\right) \end{aligned}

となる。最後の項のカッコの中はlm (modN)l \equiv m ~ (\bmod N)のとき1、それ以外は0となる。0l<N,0m<N0 \leq l<N, 0 \leq m<N の範囲では lm(modN)l \equiv m(\bmod N) となるのはl=ml=mの場合のみなので、fmf_mを掛けて和m=0N1\sum^{N-1}_{m=0}をとるとflf_lになる。よって逆フーリエ変換の式fl=k=0N1Fkei2πkl/Nf_l=\sum_{k=0}^{N-1} F_k e^{i 2 \pi k l / N}が成立する。

周期的な添字に拡張する

取り扱いを便利にするため、以下ではfl,Fkf_l,F_kl,k=0,1,,N1l,k=0,1,\dots,N-1の値を周期的に拡張する。 例えばfN=f0,fN+1=f1,f_N=f_0, f_{N+1} = f_1, \dotsとする。

このように拡張すると、総和は任意の連続する NN 個の和に置き換えても同じになる。例えば k=0N1\sum_{k=0}^{N-1}k=1N,k=2N+1,k=3N+2,\sum_{k=1}^N, \sum_{k=2}^{N+1}, \sum_{k=3}^{N+2}, \ldots と書いても k=1N2,k=2N3,\sum_{k=-1}^{N-2}, \sum_{k=-2}^{N-3}, \ldots と書いても同じである。

周期関数のサンプリング定理

帯域制限

周期 2π2 \pi の連続関数 f(θ)f(\theta) がフーリエ級数に展開されるとき、そのフーリエ係数 CkC_k がある kk の範囲以外は 0 であるなら f(θ)f(\theta)帯域制限 されているという。

帯域制限された周期関数は、ある間隔より細かくサンプルすればフーリエ係数 CkC_k と離散フーリエ変換 FkF_k が等しくなる。

離散フーリエ変換Fk,k<N2F_k, |k| < \frac{N}{2}は次のように書くことができる。

Fk=1Nl=0N1f(2πlN)ei2πkl/N=1Nl=0N1(m=Cmei2πlm/N)ei2πkl/N=N/2<m<N/2Cm(1Nl=0N1ei2π(mk)l/N)\begin{aligned} F_k & =\frac{1}{N} \sum_{l=0}^{N-1} f\left(\frac{2 \pi l}{N}\right) e^{-i 2 \pi k l / N}\\ & =\frac{1}{N} \sum_{l=0}^{N-1}\left(\sum_{m=-\infty}^{\infty} C_m e^{i 2 \pi l m / N}\right) e^{-i 2 \pi k l / N} \\ & =\sum_{-N / 2<m<N / 2} C_m\left(\frac{1}{N} \sum_{l=0}^{N-1} e^{i 2 \pi(m-k) l / N}\right) \end{aligned}

N/2<m<N/2,N/2<k<N/2-N / 2<m<N / 2,-N / 2<k<N / 2 のとき mk (modN)m \equiv k ~(\bmod N) となるのは m=km=k の場合しかない。 ゆえに 上式は CkC_k に等しい。

よって、 帯域制限された周期関数は、ある間隔より細かくサンプルすればそのサンプル値の補間によって表現できる。

周期関数のサンプリング定理

ただし、 ϕN(θ)\phi_N(\theta) は次のように定義した補間関数である。

ϕN(θ)=1NN/2<k<N/2eikθ=1+20<k<N/2coskθN\phi_N(\theta)=\frac{1}{N} \sum_{-N / 2<k<N / 2} e^{i k \theta}=\frac{1+2 \sum_{0<k<N / 2} \cos k \theta}{N}

kN/2|k| \geq N / 2 では Ck=0C_k=0 であり、 k<|k|< N/2N / 2 では Ck=FkC_k=F_k であるから、 f(θ)f(\theta) は次のように書ける。

f(θ)=N/2<k<N/2Fkeikθ=N/2<k<N/2(1Nl=0N1flei2πkl/N)eikθ=l=0N1fl(1NN/2<k<N/2eik(θ2πl/N))=l=0N1fl(1NN/2<k<N/2eik(θθl))=l=0N1flϕN(θθl)\begin{aligned} f(\theta) &= \sum_{-N / 2<k<N / 2} F_k e^{i k \theta}\\ &= \sum_{-N / 2<k<N / 2}\left(\frac{1}{N} \sum_{l=0}^{N-1} f_l e^{-i 2 \pi k l / N}\right) e^{i k \theta} \\ &= \sum_{l=0}^{N-1} f_l\left(\frac{1}{N} \sum_{-N / 2<k<N / 2} e^{i k(\theta-2 \pi l / N)}\right)\\ &= \sum_{l=0}^{N-1} f_l\left(\frac{1}{N} \sum_{-N / 2<k<N / 2} e^{i k(\theta-\theta_l)}\right)\\ &= \sum_{l=0}^{N-1} f_l \phi_N\left(\theta-\theta_l\right) \end{aligned}

畳み込み和定理

証明
1Nl=0N1flglei2πkl/N=1Nl=0N1(1Nm=0N1fmglm)ei2πkl/N=1Nm=0N1fm(1Nl=0N1glmei2πkl/N)=1Nm=0N1fm(1Nl=mN1mglei2πk(l+m)/N)=1Nm=0N1fm(1Nl=0N1glei2πkl/N)ei2πkm/N=(1Nm=0N1fmei2πkm/N)(1Nl=0N1glei2πkl/N)=FkGk\begin{aligned} \frac{1}{N} \sum_{l=0}^{N-1} f_l * g_l e^{-i 2 \pi k l / N} & =\frac{1}{N} \sum_{l=0}^{N-1}\left(\frac{1}{N} \sum_{m=0}^{N-1} f_m g_{l-m}\right) e^{-i 2 \pi k l / N} \\ & =\frac{1}{N} \sum_{m=0}^{N-1} f_m\left(\frac{1}{N} \sum_{l=0}^{N-1} g_{l-m} e^{-i 2 \pi k l / N}\right) \\ & =\frac{1}{N} \sum_{m=0}^{N-1} f_m\left(\frac{1}{N} \sum_{l^{\prime}=-m}^{N-1-m} g_{l^{\prime}} e^{-i 2 \pi k\left(l^{\prime}+m\right) / N}\right) \\ & =\frac{1}{N} \sum_{m=0}^{N-1} f_m\left(\frac{1}{N} \sum_{l^{\prime}=0}^{N-1} g_{l^{\prime}} e^{-i 2 \pi k l^{\prime} / N}\right) e^{-i 2 \pi k m / N} \\ & =\left(\frac{1}{N} \sum_{m=0}^{N-1} f_m e^{-i 2 \pi k m / N}\right)\left(\frac{1}{N} \sum_{l^{\prime}=0}^{N-1} g_{l^{\prime}} e^{-i 2 \pi k l^{\prime} / N}\right) \\ & =F_k G_k \end{aligned}

パワースペクトル

(1/N)l=0N1fl2(1 / N) \sum_{l=0}^{N-1}\left|f_l\right|^2{fl}\left\{f_l\right\} の平均エネルギーを表しているとみなせる。パーセバルの式の第2式はこれが k=0N1Fk2\sum_{k=0}^{N-1}\left|F_k\right|^2 で表されることを意味している。したがってパワースペクトルを

Pk:=Fk2P_k := |F_k|^2

と定義するとパーセバルの式の第2式は

1Nl=0N1fl2=k=0N1Pk\frac{1}{N} \sum_{l=0}^{N-1}\left|f_l\right|^2=\sum_{k=0}^{N-1} P_k

と書き換えることができる。

データ{fk}\{f_k\}が実数のとき、Fk=FkF_{-k} = \overline{F_k}であるからPk=PkP_{-k} = P_kである。なのでPkP_kのグラフをk=,2,1,0,1,2,k = \dots, -2, -1, 0, 1, 2, \dotsに対してプロットするとk=0k=0に関して左右対称になる。

Source
import numpy as np
import matplotlib.pyplot as plt

# サンプルデータを生成する
N = 1000
d = 0.0001 # サンプリング周期
t = np.arange(0, N * d, d) # 時間

f = np.zeros_like(t)
components = [
    # (スペクトルF_k, 周期θ)
    (1.5, 50),
    (2, 100),
    (2.5, 200),
    (2, 300),
    (1, 500),
]
for F_k, frequency_k in components:
    f += F_k * np.sin(t * 2 * np.pi * frequency_k)

F = np.fft.fft(f) # フーリエ変換
freq = np.fft.fftfreq(N, d=d)  # 周波数スケール

# 振幅スペクトル Amplitude の取得
# 振幅スペクトルは信号をフーリエ変換した結果の絶対値をとったもの
Amp = np.abs(F)
Amp = Amp / (N / 2) # 正規化
Pow = Amp**2

# 結果をプロット
fig, axes = plt.subplots(nrows=2)
axes[0].plot(t, f)
axes[0].set(title=r'Data $\{ f_l \}$', xlabel=r"$l$", ylabel=r"$f_l$")

axes[1].stem(freq, Pow, 'r', markerfmt=" ", basefmt=" ")
# 左右対称なので正の値だけ N//2 で取り出してもいい
# axes[1].stem(freq[:N//2], Amp[:N//2], 'r', markerfmt=" ", basefmt=" ")
axes[1].set(title=f'Power $P_k$', xlabel=r'$k$', ylabel=r'$P_k$', xlim=(-550, 550))

fig.tight_layout()
fig.show()
<Figure size 640x480 with 2 Axes>
Source
import numpy as np
import matplotlib.pyplot as plt

# サンプルデータを生成する
N = 100
d = 0.001 # サンプリング周期
t = np.arange(0, N * d, d) # 時間

f = np.zeros_like(t)
for k in range(2 * N):  # 適当にNより大きいkにしてみる
    F_k = k
    frequency_k = k
    f += F_k * np.sin(t * 2 * np.pi * frequency_k)

F = np.fft.fft(f) # フーリエ変換
freq = np.fft.fftfreq(N, d=d)  # 周波数スケール

# 振幅スペクトル Amplitude の取得
# 振幅スペクトルは信号をフーリエ変換した結果の絶対値をとったもの
Amp = np.abs(F)
Amp = Amp / (N / 2) # 正規化
Pow = Amp**2

# 結果をプロット
fig, axes = plt.subplots(nrows=2)
axes[0].plot(t, f)
axes[0].set(title=r'Data $\{ f_l \}$', xlabel=r"$l$", ylabel=r"$f_l$")

axes[1].stem(freq, Pow, 'r', markerfmt=" ", basefmt=" ")
# 左右対称なので正の値だけ N//2 で取り出してもいい
# axes[1].stem(freq[:N//2], Amp[:N//2], 'r', markerfmt=" ", basefmt=" ")
axes[1].set(title=f'Power $P_k$', xlabel=r'$k$', ylabel=r'$P_k$')

fig.tight_layout()
fig.show()
<Figure size 640x480 with 2 Axes>

自己相関係数

周期 NN のデータ {fl}\{f_l\} の自己相関係数 {Rn}\{R_n\} を次のように定義する。

Rn=1Nl=0N1flflnR_n=\frac{1}{N} \sum_{l=0}^{N-1} f_l \overline{f_{l-n}}

ウィーナー・ヒンチンの定理

証明
1Nn=0N1Rnei2πkn/N=1Nn=0N1(1Nl=0N1flfln)ei2πkn/N=1Nl=0N1fl(1Nn=0N1flnei2πkn/N)=1Nl=0N1fl(1Nn=0N1flnei2πkn/N)=1Nl=0N1fl(1Nn=0N1fnei2πk(ln)/N)=(1Nl=0N1flei2πkl/N)(1Nn=0N1fnei2πkn/N)=FkFk=1Nn=0N1f2=Pk\begin{aligned} \frac{1}{N} \sum_{n=0}^{N-1} R_n e^{-i 2 \pi k n / N} & =\frac{1}{N} \sum_{n=0}^{N-1}\left(\frac{1}{N} \sum_{l=0}^{N-1} f_l \overline{f_{l-n}}\right) e^{-i 2 \pi k n / N} \\ & =\frac{1}{N} \sum_{l=0}^{N-1} f_l\left(\frac{1}{N} \sum_{n=0}^{N-1} \overline{f_{l-n}} e^{-i 2 \pi k n / N}\right) \\ & =\frac{1}{N} \sum_{l=0}^{N-1} \overline{f_l\left(\frac{1}{N} \sum_{n=0}^{N-1} f_{l-n} e^{i 2 \pi k n / N}\right)} \\ & =\frac{1}{N} \sum_{l=0}^{N-1} f_l\left(\frac{1}{N} \sum_{n^{\prime}=0}^{N-1} f_{n^{\prime}} e^{i 2 \pi k\left(l-n^{\prime}\right) / N}\right) \\ & = \left(\frac{1}{N} \sum_{l=0}^{N-1} f_l e^{-i 2 \pi k l / N}\right) \overline{\left(\frac{1}{N} \sum_{n^{\prime}=0}^{N-1} f_{n^{\prime}} e^{-i 2 \pi k n^{\prime} / N}\right)} \\ & =F_k \overline{F_k}=\left\lvert\, \frac{1}{N} \sum_{n^{\prime}=0}^{N-1} f^2=P_k\right. \end{aligned}

まとめ

flFk=1Nl=0N1flei2πkl/NRn=1Nl=0N1flflnPk={Fk21Nn=0N1Rnei2πkn/N\begin{array}{ccc} f_l & \longrightarrow & F_k=\frac{1}{N} \sum_{l=0}^{N-1} f_l e^{-i 2 \pi k l / N} \\ \downarrow & & \downarrow \\ R_n=\frac{1}{N} \sum_{l=0}^{N-1} f_l \overline{f_{l-n}} & \longrightarrow & P_k=\left\{\begin{array}{l} \left|F_k\right|^2 \\ \frac{1}{N} \sum_{n=0}^{N-1} R_n e^{-i 2 \pi k n / N} \end{array}\right. \end{array}

連続のフーリエ変換と同様に、ウィーナー・ヒンチンの定理を使うことでもパワースペクトルを得ることができる。

しかし離散の場合は高速フーリエ変換があるため、ウィーナー・ヒンチンの定理を使うことによる計算量削減などの効果は相対的に低い。実用上は高速フーリエ変換一択になる。