✏️
【復習】Unity で DFT(離散フーリエ変換)を実装してみる
はじめに
フーリエ変換の復習に、DFT(離散フーリエ変換)をUnityで実装してみました。
フーリエ変換とは
連続な関数
フーリエ変換は以下のような数式で表現されます。
離散フーリエ変換は積分を利用したものになっていますが、
Unity(コンピューター)では積分を直接計算することができません。
Unityで実装する際は離散データを利用する 離散フーリエ変換 を使います。
離散フーリエ変換(DFT)
関数
N個のデータ
DFT(離散フーリエ変換)を利用すると、これらのデータ点から周波数スペクトル
DFTは以下のような式になります。
オイラーの公式
UnityでDFTを実装してみる
今回は、Sin波から作った信号データにDFTを適用して周波数スペクトル強度を求めるC#スクリプトを書いてみました。
using UnityEngine;
[RequireComponent(typeof(LineRenderer))]
public class FourierTransform : MonoBehaviour
{
[SerializeField] private int dataNum = 1024; // 信号のデータ数
[SerializeField] private int spectrumNum = 32; // 周波数スペクトル数
[SerializeField] private float sineFreq = 4f; // Sinの周波数(Hz)
private LineRenderer lineRenderer;
/// <summary>
/// スペクトル強度の計算
/// </summary>
private void ComputeSpectrum()
{
dataNum = Mathf.Max(2, dataNum);
spectrumNum = Mathf.Max(2, spectrumNum);
// 信号データ 初期化
var x = new float[dataNum]; // サンプリング点 x
var data = new float[dataNum]; // 信号のサンプリングして得られるデータ f(x)
for (int i = 0; i < dataNum; i++)
{
x[i] = (float) i / dataNum; // 区間[0, 1] を N分割したものを、サンプリング点とする
data[i] = Mathf.Sin(x[i] * Mathf.PI * 2f * sineFreq); // Sine波をデータ点とする
}
// 周波数スペクトルを計算
var spectrum = new float[spectrumNum];
for (int i = 0; i < spectrumNum; i++)
{
float t = i * 2f * Mathf.PI / spectrumNum; // 区間 [0, 2π] を分割する点でのスペクトル強度を計算
spectrum[i] = DFT(t, x, data);
}
// LineRendererに周波数スペクトルを反映
lineRenderer.positionCount = spectrumNum;
for (int i = 0; i < spectrumNum; i++)
{
float t = (float) i / (spectrumNum - 1);
// タテ軸(y)を周波数スペクトルとしてプロット
float y = spectrum[i] / dataNum; // 見やすいようにdataNumで割っておく
lineRenderer.SetPosition(i, new Vector3(t, y, 0f));
}
}
/// <summary>
/// 離散フーリエ変換を利用して周波数スペクトルを求める
/// </summary>
/// <param name="t">周波数スペクトルのサンプリング位置</param>
/// <param name="x">信号データの全サンプリング点</param>
/// <param name="data">全信号データ</param>
/// <returns></returns>
float DFT(float t, float[] x, float[] data)
{
float re = 0f;
float im = 0f;
for (int i = 0; i < dataNum; i++)
{
re += data[i] * Mathf.Cos(-i * 2f * Mathf.PI * t * x[i] / dataNum);
im += data[i] * Mathf.Sin(-i * 2f * Mathf.PI * t * x[i] / dataNum);
}
// 周波数スペクトル強度を計算
return Mathf.Sqrt(re * re + im * im);
}
private void Start()
{
lineRenderer = GetComponent<LineRenderer>();
ComputeSpectrum();
}
/// <summary>
/// インスペクターの値が変更された時に呼ばれる
/// </summary>
private void OnValidate()
{
ComputeSpectrum();
}
}
結果
周波数1Hzの場合
周波数2Hzの場合
周波数4Hzの場合
Discussion