チーム18E3
課題名
サンプル波形をフーリエ変換するシミュレーター
研究者名
Takumi Horinouchi
概要
①選択肢から波形を選択すると,4周期分の波形が表示される.
②Wave⇔Spectrumボタンを押すと,高速フーリエ変換によって得られた周波数スペクトルが表示される.
※スペクトル特性について考察しやすいように,奇関数と偶関数の成分ごとに色分けしてある.
※スペクトルを表示中にWave⇔Spectrumを押すと,元の波形が再び表示される.
③IFFTボタンを押すと,逆フーリエ変換によって得られた波形を表示する.
④AverageFilterボタンを押すと,4タップの平均化フィルタが適用される. (複数回可能)
※横についている選択欄で回数を設定して,その数だけフィルタをかけることができる.
※ここで,Wave⇔Spectrumボタンを押すと,平均化した波形のスペクトルを表示できる.
⑤Clearボタンを押すと,それまでの表示内容・データをリセットできる.
⑥Closeボタンを押すと,実行を終了してウィンドウを閉じる.
<参考>
ソースコード
FourierTransformer.java
package csahw;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class FourierTransformer extends JFrame implements ActionListener{
//フレームサイズ private static final int WIDTH = 1600; private static final int HEIGHT = 1200;
//各軸の幅 private static final int X_AXIS_LENGTH = 1000; private static final int Y_AXIS_LENGTH = 800;
//パネル private JPanel p;
//コンテナ private Container cp;
//描画 private Graphics g = null;
//表示する波の選択肢 private JComboBox<String> cmb;
//アベレージフィルタをかける回数の選択肢 private JComboBox<String> avecmb;
//波形とスペクトルのどちらを描画しているか ture:波形/false:スペクトル private boolean isWave;
//点の2次元平面上の座標を表すクラス private class Vertex{ //座標 int x, y;
//コンストラクタ Vertex(int x, int y){ this.x = x; this.y = y; } }
//原点 Vertex origin = new Vertex(100, 300 + Y_AXIS_LENGTH/2);
//サンプリングのポイント数 private final static int M = 32;
//各軸方向の拡大率 private final double WIDTH_RATE = (double)X_AXIS_LENGTH / (3.0 * (double)M); private final int HEIGHT_RATE = 800;
//コンストラクタ public FourierTransformer(){ //フレームの大きさを設定 setSize(WIDTH, HEIGHT); //タイトルを設定 setTitle("Fourier Transformer");
p = new JPanel(); p.setLayout(null); cp = getContentPane(); cp.add(p);
//波形の選択肢 String[] wave = {"Select", "SquareWave", "TriangleWave", "SawtoothWave", "SinWave", "CosWave", "GaussianFunction", "DirectCurrent"}; cmb = new JComboBox<String>(wave);
//波形の選択肢欄 cmb.setBounds(20, 10, 200, 50); cmb.setSelectedIndex(0); cmb.setActionCommand("cmb"); p.add(cmb); cmb.addActionListener(this);
//波形・スペクトル表示ボタン JButton transformButton = new JButton("Wave⇔Spectrum"); transformButton.setBounds(270, 10, 200, 50); transformButton.setActionCommand("waveandspectrum"); p.add(transformButton); transformButton.addActionListener(this);
//逆フーリエ変換ボタン JButton inversetransformButton = new JButton("IFFT"); inversetransformButton.setBounds(520, 10, 200, 50); inversetransformButton.setActionCommand("ifft"); p.add(inversetransformButton); inversetransformButton.addActionListener(this);
//平均化ボタン JButton averageButton = new JButton("AverageFilter"); averageButton.setBounds(770, 10, 200, 50); averageButton.setActionCommand("averageFilter"); p.add(averageButton); averageButton.addActionListener(this);
//何回平均化をかけるかの選択肢 String[] times = {"1", "5", "10", "50", "100"}; avecmb = new JComboBox<String>(times); avecmb.setBounds(970, 10, 100, 50); avecmb.setSelectedIndex(0); p.add(avecmb);
//クリアボタン JButton clearButton = new JButton("Clear"); clearButton.setBounds(1120, 10, 200, 50); clearButton.setActionCommand("clear"); p.add(clearButton); clearButton.addActionListener(this);
//終了ボタン JButton closeButton = new JButton("Close"); closeButton.setBounds(1370, 10, 200, 50); closeButton.setActionCommand("close"); p.add(closeButton); closeButton.addActionListener(this);
setVisible(true); setDefaultCloseOperation(EXIT_ON_CLOSE); }
//各軸を描画 void paintAxis(Graphics g){ g.drawLine(this.origin.x - 20, this.origin.y, this.origin.x + (int)(X_AXIS_LENGTH * 1.33), this.origin.y); g.drawLine(this.origin.x, this.origin.y - Y_AXIS_LENGTH / 2, this.origin.x, this.origin.y + Y_AXIS_LENGTH / 2); }
//入力となる波形の描画 void drawWave(Vertex past, double[] data){ for(int i = 1; i < 4 * M; i++){ int x0 = past.x + (int)((i - 1) * WIDTH_RATE); int y0 = past.y - (int)(data[i-1] * (double)Y_AXIS_LENGTH / 2.0); int x1 = past.x + (int)(i * WIDTH_RATE); int y1 = past.y - (int)(data[i] * (double)Y_AXIS_LENGTH / 2.0); g.fillOval(x0 - 5, y0 - 5, 10, 10); g.drawLine(x0, y0, x1, y1); } int x0 = past.x + (int)((double)(4 * M - 1) * WIDTH_RATE); int y0 = past.y - (int)(data[4 * M - 1] * (double)Y_AXIS_LENGTH / 2.0); int x1 = past.x + (int)((double)(4 * M - 1) * WIDTH_RATE); int y1 = past.y - (int)(data[4 * M - 1] * (double)Y_AXIS_LENGTH / 2.0); g.fillOval(x0 - 5, y0 - 5, 10, 10); g.drawLine(x0, y0, x1, y1); }
//スペクトルの描画 void drawSpectrum(Graphics g, Vertex origin, double[] data){ //スペクトルの幅 int width = 5;
//スペクトルの座標決定 for(int i = 0; i < M; i++){ int height = (int)(data[i] * HEIGHT_RATE); int x = origin.x + 4 * width * i; int y = origin.y - height;
//偶関数に含まれるスペクトルを赤 奇関数に含まれるスペクトルを青 想定されない値があった場合は黒となる switch(i % 2){ case 0: g.setColor(Color.RED); break; case 1: g.setColor(Color.BLUE); break; default: g.setColor(Color.BLACK); break; }
//スペクトルの描画 g.fillRect(x, y, width, height); } }
//描画したグラフのクリア void clear(Graphics g){ g.clearRect(50, 150, WIDTH, HEIGHT - 50); }
public void actionPerformed(ActionEvent ae){ //アクションコマンド String ac = ae.getActionCommand(); //変換ボタンが押された時 if(ac.equals("waveandspectrum")){ //フーリエ変換を行い、各数値を更新 FFT.fft();
//波形を描画中ならスペクトルを描画 if(isWave == true){ isWave = false; this.g = this.getGraphics();
//描画領域をクリア clear(g); //スペクトル用の原点と軸を描画 Vertex spectrumOrigin = new Vertex(this.origin.x, this.origin.y + Y_AXIS_LENGTH / 2 - 20); g.drawLine(spectrumOrigin.x - 20, spectrumOrigin.y, spectrumOrigin.x + X_AXIS_LENGTH, spectrumOrigin.y); g.drawLine(spectrumOrigin.x, spectrumOrigin.y - Y_AXIS_LENGTH + 20, spectrumOrigin.x, spectrumOrigin.y + 20);
//スペクトルを描画 g.setColor(Color.RED); drawSpectrum(g, spectrumOrigin, FFT.dataZ); } //スペクトルを描画中なら波形を描画 else{ isWave = true; this.g = this.getGraphics();
//描画領域をクリアして軸を描画 clear(g); paintAxis(g);
g.setColor(Color.RED);
//波形を描画 drawWave(origin, FFT.data); } this.g.dispose(); }
//逆フーリエ変換ボタンが押された時 else if(ac.equals("ifft")){ isWave = true; this.g = this.getGraphics();
//描画領域をクリアして軸を描画 clear(g); paintAxis(g);
g.setColor(Color.RED);
//逆フーリエ変換 FFT.ifft();
//波形を描画 drawWave(origin, FFT.dataInv);
this.g.dispose(); }
//平均化ボタンが押された時 else if(ac.equals("averageFilter")){ isWave = true; this.g = this.getGraphics();
//描画領域をクリアして軸を描画 clear(g); paintAxis(g);
g.setColor(Color.RED);
//選択肢から回数を取得 String avetimesString = (String)avecmb.getSelectedItem(); int avetimes = Integer.parseInt(avetimesString);
//アベレージフィルタを適用 for(int i = 0; i < avetimes; i++){ FFT.averageFilter(); }
//選択肢で選択された波形を描画 drawWave(origin, FFT.dataAve);
this.g.dispose(); }
//クリアボタンが押された時 else if(ac.equals("clear")){ isWave = false; this.g = this.getGraphics();
//各データを初期化 FFT.initData();
//選択肢を初期化 cmb.setSelectedIndex(0); avecmb.setSelectedIndex(0);
//描画領域をクリア clear(g); }
//終了ボタンが押された時 else if(ac.equals("close")){ System.exit(0); }
//選択肢で波形の種類が選択された時 else if(ac.equals("cmb") && !cmb.getSelectedItem().toString().equals("Select")){ String wave = cmb.getSelectedItem().toString();
//各データを初期化 FFT.initData();
//選択肢で選択された波形を4周期分AD変換 WaveADConverter.waveADC(wave, M, FFT.data, 4);
//フーリエ変換を実行 FFT.fft();
isWave = true; this.g = this.getGraphics();
//描画領域をクリアして軸を描画 clear(g); paintAxis(g);
g.setColor(Color.RED);
//選択肢で選択された波形を描画 drawWave(origin, FFT.data);
this.g.dispose(); } }
public static void main(String[] args){ new FourierTransformer(); }
}
WaveADConverter.java
package csahw;
public class WaveADConverter{
//AD変換 static void waveADC(String wave, int m, double[] data, int T){ //方形波 if(wave.equals("SquareWave")){ data[0] = 0.0; for(int j = 0; j < T; j++){ int i = 0; while(i < m / 2){ data[i + m * j] = 1.0; i++; } while(i < m){ data[i + m * j] = -1.0; i++; } } }
//三角波 else if(wave.equals("TriangleWave")){ data[0] = 0.0;
for(int j = 0; j < T; j++){ int i = 0; while(i < m / 4){ data[i + m * j] = (double)i * 4.0 / (double)m; i++; } while(i < m * 3 / 4){ data[i + m * j] = 2.0 - (double)i * 4.0 / (double)m; i++; } while(i < m){ data[i + m * j] = (double)i * 4.0 / (double)m - 4.0; i++; } } }
//鋸形波 else if(wave.equals("SawtoothWave")){ data[0] = 0.0;
for(int j = 0; j < T; j++){ int i = 0; while(i < m / 2){ data[i + m * j] = (double)i * 2.0 / ((double)m - 2.0); i++; } while(i < m){ data[i + m * j] = (double)i * 2.0 / (double)m - 2.0; i++; } } } //sin波 else if(wave.equals("SinWave")){ for(int j = 0; j < T; j++){ int i = 0; while(i < m){ data[i + m * j] = Math.sin(2 * Math.PI * i / m); i++; } } } //cos波 else if(wave.equals("CosWave")){ for(int j = 0; j < T; j++){ int i = 0; while(i < m){ data[i + m * j] = Math.cos(2 * Math.PI * i / m); i++; } } }
//ガウシアンフィルタ else if(wave.equals("GaussianFunction")){ for(int j = 0; j < T; j++){ int i = 0; while(i < m){ if(i == m / 2){ data[i + m * j] = 1.0; i++; } else{ data[i + m * j] = 0.0; i++; } } } }
//直流波 else if(wave.equals("DirectCurrent")){ for(int j = 0; j < T; j++){ int i = 0; while(i < m){ data[i + m * j] = 0.5; i++; } } } }
}
FFT.java
package csahw;
import static java.lang.Math.*;
public final class FFT{
//FFTのポイント数 static final int M_POINTS = 128; //周期数 static final int T = 4;
//入力データ static double[] data = new double[M_POINTS];
//出力データ(実部・虚部・絶対値) static double[] dataRe = new double[data.length]; static double[] dataIm = new double[data.length]; static double[] dataZ = new double[data.length]; //逆フーリエ変換の結果 static double[] dataInv = new double[data.length]; //アベレージフィルタの結果 static double[] dataAve = new double[data.length];
//データの初期化 static void initData(){ for(int i = 0; i < data.length; i++){ data[i] = 0.0; dataRe[i] = 0.0; dataIm[i] = 0.0; dataZ[i] = 0.0; dataInv[i] = 0.0; dataAve[i] = 0.0; } }
//高速フーリエ変換 static void fft(){ for(int i = 0; i < data.length; i++){ //周期iについてのフーリエ変換 dataRe[i] = 0.0; dataIm[i] = 0.0; for(int j = 0; j < data.length; j++){ double theta = 2 * PI * i * j / data.length; dataRe[i] += data[j] * cos(theta) - data[j] * sin(theta); dataIm[i] += data[j] * sin(theta) + data[j] * cos(theta); } dataRe[i] /= data.length; dataIm[i] /= data.length; }
//絶対値を計算 for(int i = 0; i < data.length; i++){ dataZ[i] = sqrt(dataRe[i] * dataRe[i] + dataIm[i] * dataIm[i]); } } //逆フーリエ変換 static void ifft(){ for(int i = 0; i < data.length; i++){ dataInv[i] = 0.0; } for(int i = 0; i < data.length; i++){ for(int j = 0; j < data.length; j++){ double theta = 2 * PI * i * j / data.length; dataInv[j] += dataRe[i] * cos(theta) + dataIm[i] * sin(theta); } } }
//4タップ アベレージフィルタ static void averageFilter(){ for(int i = 0; i < data.length - 3; i++){ dataAve[i] = 0.25 * data[i] + 0.25 * data[i + 1] + 0.25 * data[i + 2] + 0.25 * data[i + 3]; } for(int j = 0; j < data.length; j++){ data[j] = dataAve[j]; } }
}
実行結果
矩形波
矩形波
矩形波のスペクトル
逆フーリエ変換
平均化フィルタを5回かける
そのスペクトル
三角波
三角波
三角波のスペクトル
逆フーリエ変換
平均値フィルタを1回かける
そのスペクトル
鋸形波
鋸形波
鋸形波のスペクトル
逆フーリエ変換
平均値フィルタを5回かける
そのスペクトル
sin波
sin波
sin波のスペクトル
逆フーリエ変換
平均値フィルタを10回かける
そのスペクトル
cos波
cos波
cos波のスペクトル
逆フーリエ変換
平均値フィルタを10回かける
そのスペクトル
ガウス関数
ガウス関数
ガウス関数のスペクトル
逆フーリエ変換
平均値フィルタを1回かける
そのスペクトル
直流
直流
直流のスペクトル
逆フーリエ変換
平均値フィルタを50回かける
そのスペクトル
考察
参考としたチーム17F4の考察で,理想的な周波数スペクトルではなく,余計なスペクトルが混ざっていたとあった.
これは,方形波や三角波のスペクトルを見れば,一目瞭然であろう.
そこで,波形を生成する箇所の見直しを行い,これを修正した.
また,平均化を施す際に平均値に収束せずに1周期ごとにそれぞれ変化していた.
これは,入力する波形を1周期のみ作成し,それを3回描画するということをしていたためである.
そのような入力をとれば,あのように周期的な変化をして当り前であろう.
今回,参考にしたチームが解決できていない問題を解消し,個人的に不便に思った機能を改良した.
これ以上改良を加えるなら,ハイパスフィルタ・ローパスフィルタやバンドパスフィルタ等の機能を追加したり,
入力データをWaveGeneなどで生成した波形と指定できるようになれば,
フーリエ変換を行うアプリケーションとしての利便性が高まるであろう.
- 最終更新:2018-11-26 17:00:08