Pythonで離散時間フーリエ変換(DTFT, Discrete-Time Fourier Transform)と離散時間フーリエ逆変換を実装してみます。
アルゴリズム
離散時間フーリエ変換 (DTFT)
離散時間データを周波数データへ変換するDTFTの式は(1)で計算できます。
$$X[\omega] = \sum_{n=-\infty}^\infty x[n] \exp(-j \omega n) \tag{1}$$
(1)式の左辺の\(\omega\)を指定することで、その\(\omega\)におけるDTFTの値を計算することが出来ます。DTFTの結果は連続時間になるので、\(\omega\)は実数で指定できます。
離散時間データは次のようなものです。(1)式は\(-\infty\)から\(\infty\)まで総和を計算していますが、離散データは有限なので、例えば以下の図のようなデータであれば、総和は”0″から”4″までを利用します。

横軸はデータをサンプリング時間\(T_0\)で取得した値です。つまり \(n=0,1,2,3,4\)は時間ではなくデータのカウント数です。これを正規化時間と呼びます。実際のデータの時間は\(nT_0[sec]\)です。
それから、変換を行った\(X[\omega]\)の横軸も正規化時間をもとに計算しているので、正規化角周波数\(\omega\)[rad/sample]になります。正規化角周波数\(\omega\)は1サンプリング時間\(T_0\)毎に何\(rad\)進むかを表す値です。正規化されていない角周波数\(\Omega[rad]\)への変換は
$$
\Omega = \omega / T_0
$$
で計算します。
そして、DTFTは\([-\pi, \pi]\)で周期的な結果となります。
離散時間フーリエ逆変換 (IDTFT)
周波数データを離散時間データへ変換するIDTFTは(2)式で計算できます。
$$
x[n] = \frac{1}{2\pi} \int_{-\pi}^{\pi} X[\omega] \exp(j \omega n) d\omega \tag{2}
$$
(2)式の左辺の\(n\)を指定して、右辺の積分計算を行います。
積分計算はコンピュータで計算するには厄介ですが、数値積分のシンプソンの公式を使って行います。
アルゴリズム実装
使用するモジュール
複素数の計算が含まれているため、”cmath”モジュールを使用します。
import numpy as np import cmath import matplotlib.pyplot as plt # 結果表示用
離散時間フーリエ変換 (DTFT)
$$X[\omega] = \sum_{n=-\infty}^\infty x[n] \exp(-j \omega n)$$
# 離散時間フーリエ変換関数
def DTFT(omega, xn):
_Xw = np.zeros(len(omega), dtype = np.complex)
for i, w in enumerate(omega):
for n, xn_i in enumerate(xn):
_Xw[i] += xn_i * cmath.exp(-1j * w * n)
return _Xw
この関数の戻り値は複素数型です。
離散時間フーリエ逆変換(IDTFT)
$$
x[n] = \frac{1}{2\pi} \int_{-\pi}^{\pi} X[\omega] \exp(j \omega n) d\omega
$$
# nを指定して、シンプソンの公式で逆離散時間フーリエ変換を計算
def InvertWithSimpsonRule(Xomega,omega_array,n):
lim_n = int(len(Xomega)/2)
h = abs(omega_array[1] - omega_array[0])
sum = np.zeros(1, dtype=complex)
for i in range(1, lim_n):
f1 = (Xomega[2*i - 2] * (cmath.exp((1j) * omega_array[2*i - 2] * n )) )
f2 = (Xomega[2*i - 1] * (cmath.exp((1j) * omega_array[2*i - 1] * n )) )
f3 = (Xomega[2*i - 0] * (cmath.exp((1j) * omega_array[2*i - 0] * n )) )
sum[0] += h*(f1 + 4*f2 + f3)/3
return sum[0] / (2 * cmath.pi)
# nを変化させながら離散時間フーリエ逆変換計算
def InverseDTFT(omega_array, Xw_array, n_array):
xn = np.zeros(len(n_array), dtype=np.complex)
for n, n_atom in enumerate(n_array):
xn[n] = InvertWithSimpsonRule(Xw_array, omega_array, n)
return xn
この関数の戻り値は複素数型です。
周波数データ表示関数
離散時間フーリエ変換のデータを表示する関数も作成しておきます。この関数の引数’normalize’と’samplingTime’で、表示する離散時間フーリエ変換の横軸データを正規化角周波数[rad]から周波数[Hz]へ変換します。
import math
from matplotlib.ticker import (MultipleLocator, AutoLocator)
# 離散時間フーリエ変換データ表示関数
def showDTFTData(omega, Xw, normalize=True, SamplingTime = 1):
fig = plt.figure(figsize=(12,5))
ax = fig.add_subplot(111)
if normalize:
plt.plot(omega,Xw)
plt.xlabel('Normalized angular frequency[rad]')
plt.ylabel('X($\omega$)')
ax.xaxis.set_major_locator(MultipleLocator(np.pi))
else:
x_data_hz = omega/SamplingTime/(2*np.pi)
plt.plot(x_data_hz ,Xw)
plt.xlabel('Frequency[Hz]')
plt.ylabel('abs(X($\omega$))')
xtick = x_data_hz[-1]/10
if xtick >= 1:
ax.xaxis.set_major_locator(MultipleLocator(xtick))
else:
ax.xaxis.set_major_locator(AutoLocator())
plt.minorticks_on()
plt.grid(b=True, which='minor', color='#999999', linestyle='-', alpha=0.2)
plt.grid(which='major', color='#666666', linestyle='-')
plt.show()
離散時間データ表示関数
合わせて離散時間データ表示関数も定義します。
# 離散データ表示用関数
def showDiscreteData(x,y):
markerline, stemline, baseline = plt.stem(x, y)
plt.setp(markerline, marker='o', markeredgewidth=2, markersize=8, color='black', markerfacecolor='white', zorder=3)
plt.setp(stemline, color='black', zorder=2, linewidth=1.0)
plt.setp(baseline, color='black', zorder=1)
plt.grid(True)
plt.xlabel('n')
plt.ylabel('y [n]')
plt.show()
実装を行った関数で計算
用いる離散時間データ
# 離散時間データの定義 xd = np.array([0,0,1,1,1,0,0,0]) n = np.arrange(start = 0, stop = len(xd)) # 離散時間データの表示 showDiscreteData(n, xd)

データを直接’np.array’で作成しているので、正規化時間データになっているように考えます。
離散時間フーリエ変換計算
# DEFTの計算に用いる周波数 omega の範囲を指定 omega = np.linspace(-3*3.14, 3*3.14, 2020) # 離散時間フーリエ変換の計算 Xw = DTFT(omega, xd) # フーリエ変換結果の表示 showDTFTData(omega, Xw.real)

\(\pi\)毎にグリッドを表示しています。これで、\([-\pi, \pi]\)毎に周期的な結果となっていることが分かります。横軸は正規化角周波数となっていることに注意してください。
そして、DTFTの結果は複素数なので、showDTFTData関数の引数へは、’.real’で実部のみを抽出したデータを渡しています。今回は実部を表示していますが、この表示方法は複素数なので、実部、虚部、絶対値の3種類が考えられます。
離散時間フーリエ逆変換の計算
離散時間フーリエ逆変換は区間\([-\pi, \pi]\)の積分計算なので、もう一度範囲を\([-\pi, \pi]\)で離散時間フーリエ変換を行ったデータを用います。
# DEFTの計算に用いる周波数 omega の範囲を指定 omega = np.linspace(-1*3.14, 1*3.14, 2020) # 離散時間フーリエ変換の計算 Xw = DTFT(omega, xd) # 離散時間フーリエ逆変換の計算 i_xd = InverseDTFT(omega, Xw, n) # 逆フーリエ変換結果の表示 showDiscreteData(n, i_xd.real)

InverseDTFT関数の戻り値は複素数型なので、showDiscreteData関数の引数には’.real’で実数部を与えています。これは、もともとのデータに虚数が含まれていないからですね。
試しにshowDiscreteData()の第二引数に”i_xd.imag”で虚数部を与えてグラフを表示すると、以下の図のようにほぼゼロです。

離散時間逆フーリエ変換の結果は、元のデータと比べて若干ずれは生じていますが、信号を再生できていると考えられる結果だと思います。
離散時間フーリエ逆変換の実装でシンプソンの公式を用いたので、数値計算誤差が生じた結果です。
でもしかし、離散時間フーリエ変換と離散時間フーリエ逆変換のアルゴリズムの確認をこれで出来ました。今回でアルゴリズムの確認が出来たので、次回はいろいろな信号に離散時間フーリエ変換を試してみます。


コメント