高速フーリエ変換FFT概説

June2002
(本校)coskx

1.はじめに

 この概説は高速フーリエ変換FFTのアウトラインを解説する。高速フーリエ変換の解説書はそのアルゴリズムを記述したものがあるが,この概説は高速フーリエ変換アルゴリズム詳細は他に譲り,計算式は省き,高速フーリエ変換関数(内容には触れない)を用いて高速フーリエ変換によりデータがどのように変換されるかのみを述べる。具体的イメージを確立するために,マイクロフォンで捕らえ,ADコンバータで一定間隔でサンプリングされた音響データが「高速フーリエ変換」される場合を想定することにする。
 ここで議論する際,高速フーリエ変換のプログラムはC言語で書かれた小坂研究室で使用している表1.1の3つのファイルを用いることとする。読者は,マルチソースファイルでのプログラミングおよびコンパイルについて,またC言語の関数,関数の引数,複素数型をつくるための構造体についての知識があるものとする。

表1.1 使用するファイル

ファイル名

内容

common.h  複素数型の定義,Πの定義
fftcore.h fftcore.hのプロトタイプ宣言
fftcore.c 高速フーリエ変換に関連する関数ライブラリ
ここで3つのファイルをダウンロードしておくとよい。


2.高速フーリエ変換の例

 例えばDTMF(プッシュホンのピッポッパの音:Dual Tone Multi Frequency)音の例として,ボタン「0」および「1」を押したときの音を1024点サンプリングした波形を図2.1,2.3に示すが,これを見ただけでは,この音の性質はわからない。しかし図2.2,2.4のように高速フーリエ変換した「パワースペクトル」(詳細は8.パワースペクトルの計算)であらわすと,プッシュホンの1つのダイアルボタンを押した時の音は2つの周波数の音でできていることが分かり,どのボタンを押したときの音なのか知ることができる。

DTMFTIMEDOMAIN.GIF - 6,475BYTES

1ブロック=1024サンプルを表示
サンプリング周波数は11025Hz
図2.1 DTMFの音の波形(ボタン「0」を押した時の音


DTMFPOWERSPECTRUM.GIF - 2,864BYTES

横軸は周波数,縦軸は音のパワー
941Hzと1336Hzの音の合成であることがわかる
図2.2 プシュボタン「0]に対応するDTMFの波形のパワースペクトル

DTMFTIMEDOMAIN.GIF - 6,475BYTES

1ブロック=1024サンプルを表示
サンプリング周波数は11025Hz
図2.3 DTMFの音の波形(ボタン「1」を押した時の音


DTMFPOWERSPECTRUM.GIF - 2,864BYTES

横軸は周波数,縦軸は音のパワー
697Hzと1209Hzの音の合成であることがわかる
図2.4 プシュボタン「1]に対応するDTMFの波形のパワースペクトル


3.正弦波の高速フーリエ変換の例(1)

 ADコンバータで一定間隔でサンプリングされた音響データは,音の波形を表し,「時間領域データ」の1種である。本来「時間領域データ」は無限に続くデータ列であるが,高速フーリエ変換では「時間領域データ」は512個,1024個など2のべき乗個のサンプルブロックで扱われる。そして,変換後は同じサンプル個数の「周波数領域データ(スペクトル;spectrum)」になる。「周波数領域データ(スペクトル;spectrum)」とは,変換前の「時間領域データ」にどのような周波数の音が,どのくらいの大きさで入っていたかを表す概念である。
 実際のプログラムでは,「時間領域データ」は実数型変数の配列に格納されるが,「周波数領域データ(スペクトル;spectrum)」は複素数型配列に格納される。具体的なデータで高速フーリエ変換を行なってみよう。ここではデータブロック長は16とする。リスト3.1は16サンプルで構成された正弦波の高速フーリエ変換を行なうプログラムである。関数maketimedomain()は「16サンプルで構成された振幅1の1周期の正弦波」を作る関数である。表1.1にあげた3つのファイルとともにコンパイルと実行ができる。このプログラムを実行すると「16サンプルで構成された振幅1の1周期の正弦波」はファイル「timedomain.txt」として保存され,グラフにすると図3.1のようになる。プログラムの途中で実数の「時間領域データ」は,虚部が0.0の複素数の「時間領域データ」となり「comptimedomain.txt」として保存される。高速フーリエ変換後のデータはファイル「spectrum.txt」として保存され,グラフにすると図3.2のようになる。

リスト3.1 16サンプルで構成された正弦波の高速フーリエ変換

#include <stdio.h>
#include <math.h>
#include "common.h"
#include "fftcore.h"
#define SIZE 16

void maketimedomain(double x[],int size)
{
    int i;
    for (i=0;i<size;i++) {
        x[i]=sin((2*M_PI)*i/size);
    }
}

main()
{
    double timedomain[SIZE];  /*時間領域データ格納域*/
    dcomplex_t fftwork[SIZE]; /*複素数作業領域*/

    /*FFTの初期化*/
    initfft(SIZE);

    /*時間領域データの作成とファイル保存*/
    maketimedomain(timedomain,SIZE);
    savereal(timedomain,SIZE,"timedomain.txt");

    /*FFT作業の準備のため,時間領域データを複素数作業領域へ*/
    /*コピーとファイル保存*/
    real2cmp(timedomain,fftwork,SIZE);
    savecmp(fftwork,SIZE,"comptimedomain.txt");

    /*FFT(順方向フーリエ変換)*/
    /*作業領域fftworkの内容がスペクトルになる*/
    /*得られたスペクトルのファイルへの保存*/
    fft(1,fftwork,SIZE);
    savecmp(fftwork,SIZE,"spectrum.txt");

}

/**************     実行結果のはじまり    *************
ファイル「timedomain.txt」(時間領域データ)
   0    0.000000e+000
   1    3.826834e-001
   2    7.071068e-001
   3    9.238795e-001
   4    1.000000e+000
   5    9.238795e-001
   6    7.071068e-001
   7    3.826834e-001
   8    3.231085e-015
   9    -3.826834e-001
  10    -7.071068e-001
  11    -9.238795e-001
  12    -1.000000e+000
  13    -9.238795e-001
  14    -7.071068e-001
  15    -3.826834e-001

ファイル「comptimedomain.txt」(複素数化された時間領域データ)
(データは実部と虚部から成り立っているが,虚部は0.0である)
   0    0.000000e+000    0.000000e+000
   1    3.826834e-001    0.000000e+000
   2    7.071068e-001    0.000000e+000
   3    9.238795e-001    0.000000e+000
   4    1.000000e+000    0.000000e+000
   5    9.238795e-001    0.000000e+000
   6    7.071068e-001    0.000000e+000
   7    3.826834e-001    0.000000e+000
   8    3.231085e-015    0.000000e+000
   9    -3.826834e-001    0.000000e+000
  10    -7.071068e-001    0.000000e+000
  11    -9.238795e-001    0.000000e+000
  12    -1.000000e+000    0.000000e+000
  13    -9.238795e-001    0.000000e+000
  14    -7.071068e-001    0.000000e+000
  15    -3.826834e-001    0.000000e+000

ファイル「spectrum.txt」(複素数スペクトル)
(データは実部と虚部から成り立っている)
   0    1.984734e-016    0.000000e+000
   1    -6.175616e-016    -5.000000e-001
   2    2.093026e-016    -6.618772e-016
   3    3.878632e-016    -3.469447e-016
   4    2.019428e-016    -2.324529e-016
   5    2.220446e-016    -1.318390e-016
   6    1.945830e-016    -7.901015e-017
   7    7.764412e-016    0.000000e+000
   8    2.054123e-016    0.000000e+000
   9    1.734723e-016    1.110223e-016
  10    1.945830e-016    7.901015e-017
  11    4.156187e-016    3.469447e-016
  12    2.019428e-016    2.324529e-016
  13    2.220446e-016    4.649059e-016
  14    2.093026e-016    6.618772e-016
  15    -3.195466e-015    5.000000e-001

***************     実行結果のおわり    **************/

(データ番号16で0にもどるはず)
ファイル「timedomain.txt」
図3.1 16サンプルで構成された正弦波の時間領域波形

ファイル「spectrum.txt」
図3.2 16サンプルで構成された正弦波のスペクトル

 

4.正弦波の高速フーリエ変換の例(2)

リスト3.1の関数maketimedomain()をリスト4.1のように変更すると図4.1の時間領域データと図4.2のスペクトルができる。時間領域波形は,「16サンプルで構成された振幅2の3周期の正弦波」である。

リスト4.1 maketimedomain()の変更

void maketimedomain(double x[],int size)
{
    int i;
    for (i=0;i<size;i++) {
        x[i]=2.0*sin(3.0*(2*M_PI)*i/size);
    }
}

/**************     実行結果のはじまり    *************
ファイル「timedomain.txt」(時間領域データ)
    0    0.000000e+000
   1    1.847759e+000
   2    1.414214e+000
   3    -7.653669e-001
   4    -2.000000e+000
   5    -7.653669e-001
   6    1.414214e+000
   7    1.847759e+000
   8    1.849833e-014
   9    -1.847759e+000
  10    -1.414214e+000
  11    7.653669e-001
  12    2.000000e+000
  13    7.653669e-001
  14    -1.414214e+000
  15    -1.847759e+000

ファイル「spectrum.txt」(複素数スペクトル)
(データは実部と虚部から成り立っている)
   0    1.225535e-015    0.000000e+000
   1    1.229404e-015    2.775558e-016
   2    1.126707e-015    2.744484e-015
   3    -5.372134e-015    -1.000000e+000
   4    1.045123e-015    -2.983724e-015
   5    1.870642e-015    -1.110223e-015
   6    1.185585e-015    -9.192517e-016
   7    8.422557e-016    3.330669e-016
   8    1.308801e-015    0.000000e+000
   9    1.229404e-015    6.106227e-016
  10    1.185585e-015    9.192517e-016
  11    6.785818e-016    1.998401e-015
  12    1.045123e-015    2.983724e-015
  13    -1.056958e-014    1.000000e+000
  14    1.126707e-015    -2.744484e-015
  15    8.422557e-016    -1.887379e-015

***************     実行結果のおわり    **************/

(データ番号16で0にもどるはず)
ファイル「timedomain.txt」
図4.1 16サンプルで構成された時間領域波形

ファイル「spectrum.txt」
図4.2 16サンプルで構成されたスペクトル

 

5.正弦波の高速フーリエ変換の例(3)

リスト3.1の関数maketimedomain()をリスト5.1のように変更すると図5.1の時間領域データと図5.2のスペクトルができる。時間領域波形は,「16サンプルで構成された振幅2の4周期の余弦波」である。

リスト5.1 maketimedomain()の変更

void maketimedomain(double x[],int size)
{
    int i;
    for (i=0;i<size;i++) {
        x[i]=2.0*cos(4.0*(2*M_PI)*i/size);
    }
}

/**************     実行結果のはじまり    *************
ファイル「timedomain.txt」(時間領域データ)
   0    2.000000e+000
   1    3.231085e-015
   2    -2.000000e+000
   3    -9.249166e-015
   4    2.000000e+000
   5    1.659951e-014
   6    -2.000000e+000
   7    -2.217351e-014
   8    2.000000e+000
   9    2.952386e-014
  10    -2.000000e+000
  11    -3.332149e-014
  12    2.000000e+000
  13    4.067184e-014
  14    -2.000000e+000
  15    -4.802219e-014

ファイル「spectrum.txt」(複素数スペクトル)
(データは実部と虚部から成り立っている)
    0    -1.421254e-015    0.000000e+000
   1    -1.859266e-015    1.062160e-017
   2    -2.304348e-015    -1.373831e-016
   3    -4.027095e-015    2.564281e-017
   4    1.000000e+000    -1.267454e-014
   5    4.027095e-015    2.564281e-017
   6    2.304348e-015    -1.373831e-016
   7    1.859266e-015    1.062160e-017
   8    1.421254e-015    0.000000e+000
   9    1.859266e-015    -1.062160e-017
  10    2.304348e-015    1.373831e-016
  11    4.027095e-015    -2.564281e-017
  12    1.000000e+000    1.267454e-014
  13    -4.027095e-015    -2.564281e-017
  14    -2.304348e-015    1.373831e-016
  15    -1.859266e-015    -1.062160e-017

***************     実行結果のおわり    **************/

(データ番号16で+1.0にもどるはず。本来正弦波だが,
サンプル数が少ないので三角波のように見えている。)
ファイル「timedomain.txt」
図5.1 16サンプルで構成された時間領域波形

ファイル「spectrum.txt」
図5.2 16サンプルで構成されたスペクトル

6.正弦波の高速フーリエ変換の例のまとめ

3,4,5で検討した高速フーリエ変換の3つの例をまとめてみよう。各スペクトルを見ると表6.1のようになる。

表6.1 高速フーリエ変換の3つの例のまとめ

時間領域波形の特徴

生成式

算出されたスペクトルの特徴

振幅1,sin波1波

x[i]=sin((2*M_PI)*i/size); データ番号1虚部:-0.5
データ番号15虚部:0.5
他は0.0(微小値)
振幅2,sin波3波 x[i]=2.0*sin(3.0*(2*M_PI)*i/size); データ番号3虚部:-1.0
データ番号13虚部:1.0
他は0.0(微小値)
振幅2,cos波4波 x[i]=2.0*cos(4.0*(2*M_PI)*i/size); データ番号4実部:1.0
データ番号12実部:1.0
他は0.0(微小値)

ここでよく見ると,スペクトルの図はデータ番号8に関して対称になっていることに気づく。正確に言うと実部はデータ番号8に関して線対称,虚部はデータ番号8に関して点対称である。実は,スペクトルデータは全データの前半のみ,意味があり,後半は前半の鏡像となっている。例では,データ番号0から7までが意味を持つ。前半だけで表6.1を書き直すと表6.2となる。

表6.2 高速フーリエ変換の3つの例のまとめ

時間領域波形の特徴

生成式

算出されたスペクトルの特徴

振幅1,sin波1波

x[i]=sin((2*M_PI)*i/size); データ番号1虚部:-0.5
他は0.0(微小値)
振幅2,sin波3波 x[i]=2.0*sin(3.0*(2*M_PI)*i/size); データ番号3虚部:-1.0
他は0.0(微小値)
振幅2,cos波4波 x[i]=2.0*cos(4.0*(2*M_PI)*i/size); データ番号4実部:1.0
他は0.0(微小値)

表6.2より次のことがわかる。
(1)sin波のi波よりなる時間領域波形ではスペクトルのデータ番号iの虚部に値を持つ。
(2)cos波のi波よりなる時間領域波形ではスペクトルのデータ番号iの実部に値を持つ。
(3)sin波またはcos波の振幅値は0.5倍されてスペクトルの値になる。(逆にスペクトルの値を2倍するともとの正弦波の振幅となる)

さて,ここで,3つの例を,音の波形のサンプリングの例であると仮定してみよう。

仮定 時間領域のデータはサンプリング周波数1600Hzであったとする。(サンプリング周期は1/1600秒)

そうすると,実際の周波数の関係は表6.3のようになる。周波数の有効範囲は700Hzまでとなる。そして

スペクトルの第0番目の周波数は 0 [Hz](一定値,非振動成分)
スペクトルの第1番目の周波数は 100 [Hz]
スペクトルの第2番目の周波数は 200 [Hz]
スペクトルの第3番目の周波数は 300 [Hz]
           :
スペクトルの第7番目の周波数は 700 [Hz]

となる。

表6.3 サンプリング周波数1600Hzと仮定した場合の3つの例の音の周波数

時間領域波形の特徴

生成式

音の周期[秒]

周波数[Hz]

振幅1,sin波1波

x[i]=sin((2*M_PI)*i/size);

1/1600×16

100

振幅2,sin波3波 x[i]=2.0*sin(3.0*(2*M_PI)*i/size);

1/1600×16×1/3

300

振幅2,cos波4波 x[i]=2.0*cos(4.0*(2*M_PI)*i/size);

1/1600×16×1/4

400

一般化すると,サンプリング周波数fp[Hz]でサンプリングしたデータN個をデータブロックとして高速フーリエ変換した場合,

スペクトルの第0番目の周波数は 0 [Hz](一定値,非振動成分)
スペクトルの第1番目の周波数は fs / N [Hz]
スペクトルの第2番目の周波数は 2×fs / N [Hz]
スペクトルの第3番目の周波数は 3×fs / N [Hz]
           :
スペクトルの第i番目の周波数は i×fs / N [Hz]
           :
スペクトルの第(N/2-1)番目の周波数は (N/2-1)×fs / N [Hz]

となる。周波数の有効範囲は0[Hz]から (N/2-1)×fs / N [Hz]までである。
このことは,時間領域データの波形中にサンプリング周波数の1/2未満の周波数成分しかありえないことを示す。
これは「ナイキストのサンプリング定理」そのものである。

7.複合した正弦波の高速フーリエ変換の例

リスト3.1の関数maketimedomain()をリスト7.1のように変更すると2つの正弦波の合成波を高速フーリエ変換することになる。図7.1の時間領域データと図7.2のスペクトルができる。図7.1の時間領域データは,図3.1の時間領域データと図5.1の時間領域データを加え合わせてものであり,図7.2の周波数領域データは,図3.2の周波数領域データと図5.2の周波数領域データを加え合わせてものである。このように複数の正弦波が合成されてできた時間領域の波形は,スペクトルで見ると各周波数に分解して見ることができる。

リスト7.1 maketimedomain()の変更

void maketimedomain(double x[],int size)
{
    int i;
    for (i=0;i<size;i++) {
        x[i]=sin((2*M_PI)*i/size)+2.0*cos(4.0*(2*M_PI)*i/size);
    }
}

ファイル「timedomain.txt」
図7.1 16サンプルで構成された時間領域波形

ファイル「spectrum.txt」
図7.2 16サンプルで構成されたスペクトル


8.パワースペクトルの計算

リスト8.1のように計算すると,パワースペクトルが計算できる。パワースペクトルは,スペクトル(複素数)の各値の絶対値の2乗のことで,スペクトル(複素数)の各値の「実部の2乗」+「虚部の2乗」で計算される。ここで1ブロックのサンプルデータ数を64とし,7と同じ時間領域波形とする。またこれより,周波数領域のデータは前半のみを表示する。実行結果は図8.1,8.2,8.3に示される。スペクトルは複素数でできており,直感的につかみにくいが,パワースペクトルは実数であり,どの周波数の成分がどれだけのパワーを持っているか考察するのに適している。「2.高速フーリエ変換の例」で示したDTMF音の分析は1024サンプルを1ブロックとしてこのパワースペクトルを求めたものである。
(図2.2のパワースペクトルも前半のみを示したものである。)
(パワースペクトルは本来パワースペクトル密度と呼ばれるのが正しい)

リスト8.1 64サンプルで構成された波形の高速フーリエ変換とパワースペクトル

#include <stdio.h>
#include <math.h>
#include "common.h"
#include "fftcore.h"
#define SIZE 64

void maketimedomain(double x[],int size)
{
    int i;
    for (i=0;i<size;i++) {
        x[i]=sin((2*M_PI)*i/size)+2.0*cos(4.0*(2*M_PI)*i/size);
    }
}

main()
{
    double timedomain[SIZE];    /*時間領域データ格納域*/
    dcomplex_t fftwork[SIZE];   /*複素数作業領域*/
    double powerspectrum[SIZE]; /*パワースペクトル格納域*/

    /*FFTの初期化*/
    initfft(SIZE);

    /*時間領域データの作成とファイル保存*/
    maketimedomain(timedomain,SIZE);
    savereal(timedomain,SIZE,"timedomain.txt");

    /*FFT作業の準備のため,時間領域データを複素数作業領域へ*/
    /*コピー*/
    real2cmp(timedomain,fftwork,SIZE);

    /*FFT(順方向フーリエ変換)*/
    /*作業領域fftworkの内容がスペクトルになる*/
    /*得られたスペクトルのファイルへの保存*/
    fft(1,fftwork,SIZE);
    savecmp(fftwork,SIZE/2,"spectrum.txt");

    /*パワースペクトルの計算とファイルへの保存*/
    makepower(fftwork,powerspectrum,SIZE,-1);
    savereal(powerspectrum,SIZE/2,"powerspectrum.txt");
}

ファイル「timedomain.txt」
図8.1 64サンプルで構成された時間領域波形

ファイル「spectrum.txt」
図8.2 64サンプルで構成されたスペクトルの前半32個

ファイル「spectrum.txt」
図8.3 64サンプルで構成されたスペクトルの前半32個


 9.逆フーリエ変換

リスト9.1のプログラムの後半は逆フーリエ変換を示している。時間領域のデータ(comptimedomain.txt)が一度周波数領域に変換され,逆変換でふたたび時間領域のデータ(comptimedomain2.txt)になっている。この2つのデータがほとんど等しいことがわかる。

リスト3.1 16サンプルで構成された正弦波の高速フーリエ変換

#include <stdio.h>
#include <math.h>
#include "common.h"
#include "fftcore.h"
#define SIZE 16

void maketimedomain(double x[],int size)
{
    int i;
    for (i=0;i<size;i++) {
        x[i]=sin((2*M_PI)*i/size)+2.0*cos(4.0*(2*M_PI)*i/size);
    }
}

main()
{
    double timedomain[SIZE];  /*時間領域データ格納域*/
    dcomplex_t fftwork[SIZE]; /*複素数作業領域*/

    /*FFTの初期化*/
    initfft(SIZE);

    /*時間領域データの作成とファイル保存*/
    maketimedomain(timedomain,SIZE);
    savereal(timedomain,SIZE,"timedomain.txt");

    /*FFT作業の準備のため,時間領域データを複素数作業領域へ*/
    /*コピーとファイル保存*/
    real2cmp(timedomain,fftwork,SIZE);
    savecmp(fftwork,SIZE,"comptimedomain.txt");

    /*FFT(順方向フーリエ変換)*/
    /*作業領域fftworkの内容がスペクトルになる*/
    /*得られたスペクトルのファイルへの保存*/
    fft(1,fftwork,SIZE);
    savecmp(fftwork,SIZE,"spectrum.txt");

    /*ここからがフーリエ逆変換*/
    fft(-1,fftwork,SIZE);
    savecmp(fftwork,SIZE,"comptimedomain2.txt");
}

/**************     実行結果のはじまり    *************
ファイル「comptimedomain.txt」(複素数化された時間領域データ)
   0    2.000000e+000    0.000000e+000
   1    3.826834e-001    0.000000e+000
   2    -1.292893e+000    0.000000e+000
   3    9.238795e-001    0.000000e+000
   4    3.000000e+000    0.000000e+000
   5    9.238795e-001    0.000000e+000
   6    -1.292893e+000    0.000000e+000
   7    3.826834e-001    0.000000e+000
   8    2.000000e+000    0.000000e+000
   9    -3.826834e-001    0.000000e+000
  10    -2.707107e+000    0.000000e+000
  11    -9.238795e-001    0.000000e+000
  12    1.000000e+000    0.000000e+000
  13    -9.238795e-001    0.000000e+000
  14    -2.707107e+000    0.000000e+000
  15    -3.826834e-001    0.000000e+000

ファイル「comptimedomain2.txt」(逆変換で得られた,複素数化された時間領域データ)
   0    2.000000e+000    0.000000e+000
   1    3.826834e-001    1.110223e-016
   2    -1.292893e+000    1.110223e-016
   3    9.238795e-001    -8.326673e-017
   4    3.000000e+000    0.000000e+000
   5    9.238795e-001    8.950111e-017
   6    -1.292893e+000    -1.431604e-016
   7    3.826834e-001    4.789245e-017
   8    2.000000e+000    0.000000e+000
   9    -3.826834e-001    -1.110223e-016
  10    -2.707107e+000    -1.110223e-016
  11    -9.238795e-001    8.326673e-017
  12    1.000000e+000    0.000000e+000
  13    -9.238795e-001    -8.950111e-017
  14    -2.707107e+000    1.431604e-016
  15    -3.826834e-001    -4.789245e-017
***************     実行結果のおわり    **************/

10.まとめ

高速フーリエ変換について,その動作について解説した。高速フーリエ変換のアルゴリズムのプログラムへの実装は面倒で,難しい。この概説はアルゴリズムには触れず,FFTを関数としてとらえ,時間軸領域のデータと変換された周波数領域のデータ間の関係から,フーリエ変換で何ができるかに重点をおいた。