Loading [MathJax]/extensions/tex2jax.js

Pythonで離散時間フーリエ変換を実装

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 # 結果表示用
import numpy as np import cmath import matplotlib.pyplot as plt # 結果表示用
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
# 離散時間フーリエ変換関数 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
# 離散時間フーリエ変換関数
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
# 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
# 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()
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()
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()
# 離散データ表示用関数 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()
# 離散データ表示用関数
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)
# 離散時間データの定義 xd = np.array([0,0,1,1,1,0,0,0]) n = np.arrange(start = 0, stop = len(xd)) # 離散時間データの表示 showDiscreteData(n, xd)
# 離散時間データの定義
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)
# DEFTの計算に用いる周波数 omega の範囲を指定 omega = np.linspace(-3*3.14, 3*3.14, 2020) # 離散時間フーリエ変換の計算 Xw = DTFT(omega, xd) # フーリエ変換結果の表示 showDTFTData(omega, Xw.real)
# 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)
# 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)
# 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”で虚数部を与えてグラフを表示すると、以下の図のようにほぼゼロです。

離散時間逆フーリエ変換の結果は、元のデータと比べて若干ずれは生じていますが、信号を再生できていると考えられる結果だと思います。

離散時間フーリエ逆変換の実装でシンプソンの公式を用いたので、数値計算誤差が生じた結果です。

でもしかし、離散時間フーリエ変換と離散時間フーリエ逆変換のアルゴリズムの確認をこれで出来ました。今回でアルゴリズムの確認が出来たので、次回はいろいろな信号に離散時間フーリエ変換を試してみます。

コメント

タイトルとURLをコピーしました