离散傅里叶变换实现

1 篇文章 0 订阅

Introduction

The discrete Fourier transform (DFT) is a basic yet very versatile algorithm for digital signal processing (DSP). This article will walk through the steps to implement the algorithm from scratch. It also provides the final resulting code in multiple programming languages.

The DFT overall is a function that maps a vector of \(n\) complex numbers to another vector of \(n\) complex numbers. Using 0-based indexing, let \(x(t)\) denote the \(t^\text{th}\) element of the input vector and let \(X(k)\) denote the \(k^\text{th}\) element of the output vector. Then the basic DFT is given by the following formula:

\( X(k) = \displaystyle\sum_{t=0}^{n-1} x(t) e^{-2 π i t k / n} \).

The interpretation is that the vector \(x\) represents the signal level at various points in time, and the vector \(X\) represents the signal level at various frequencies. What the formula says is that the signal level at frequency \(k\) is equal to the sum of {the signal level at each time \(t\) multiplied by a complex exponential}.

Coding walkthrough

With a basic knowledge of summation notation, complex numbers, and computer programming, it is straightforward to convert the description above into computer code. The purpose of this article is to guide you through the translation process step by step. We will use the Java programming language to illustrate.

Skeleton structure

The first part of the description says that the DFT takes an input vector of \(n\) complex numbers and calculates an output vector of \(n\) complex numbers. Since Java does not have a native complex number type, we will manually emulate a complex number with a pair of real numbers. A vector is a sequence of numbers, which can be represented by an array. Instead of returning the output arrays, we will have them passed in by reference as arguments. Let’s write a skeleton method:

void dft(double[] inreal , double[] inimag,
         double[] outreal, double[] outimag) {
    // Assume all 4 arrays have the same length
    int n = inreal.length;
    // Incomplete: Method body
}

Next, write the outer loop to assign a value to each output element:

void dft(double[] inreal , double[] inimag,
         double[] outreal, double[] outimag) {
    int n = inreal.length;
    for (int k = 0; k < n; k++) {  // For each output element
        outreal[k] = ?;  // Incomplete
        outimag[k] = ?;  // Incomplete
    }
}

Summation

Summation notation, while it might look intimidating, is actually easy to understand. The general form of a finite sum just means this:

\( \displaystyle\sum_{j=a}^{b} f(j) = f(a) + f(a+1) + \cdots + f(b-1) + f(b) \).

See how we substituted the values that \(j\) takes on? In imperative-style code, it looks like this:

double sum = 0;
for (int j = a; j <= b; j++) {
    sum += f(j);
}
// The value of 'sum' now has the desired answer

Complex arithmetic

The addition of complex numbers is easy:

\( (a+bi) + (c+di) = (a+c) + (b+d)i \).

The multiplication of complex numbers is slightly harder, using the distributive law and the identity \(i^2 = -1\):

\( (a+bi)(c+di) = ac + ad\,i + bc\,i - bd = (ac-bd) + (ad+bc)i \).

Euler’s formula tells us that \( e^{xi} = \cos x + i \sin x \), for any real number \(x\). Furthermore, cosine is an even function so \( \cos(-x) = \cos x \), and sine is an odd function so \( \sin(-x) = -(\sin x) \). By substitution:

\( e^{-2 π i t k / n} = e^{(-2 π t k / n) i} = \cos \left( -2 π \frac{t k}{n} \right) + i \sin \left( -2 π \frac{t k}{n} \right) = \cos \left( 2 π \frac{t k}{n} \right) - i \sin \left( 2 π \frac{t k}{n} \right) \).

Let \(\text{Re}(x)\) be the real part of \(x\) and let \(\text{Im}(x)\) be the imaginary part of \(x\). By definition, \(x = \text{Re}(x) + i \, \text{Im}(x)\). Therefore:

\( x(t) \, e^{-2 π i t k / n} = \left[ \text{Re}(x(t)) + i \, \text{Im}(x(t)) \right] \left[ \cos \left( 2 π \frac{t k}{n}\right) - i \sin \left( 2 π \frac{t k}{n} \right) \right] \).

Expand the complex multiplication to give:

\( = \left[ \text{Re}(x(t)) \cos \left( 2 π \frac{t k}{n} \right) + \text{Im}(x(t)) \sin \left( 2 π \frac{t k}{n} \right) \right] + i \left[ -\text{Re}(x(t)) \sin \left( 2 π \frac{t k}{n} \right) + \text{Im}(x(t)) \cos \left( 2 π \frac{t k}{n} \right) \right] \).

So each term in the summation has this code for the real and imaginary parts:

double angle = 2 * Math.PI * t * k / n;
double real =  inreal[t] * Math.cos(angle) + inimag[t] * Math.sin(angle);
double imag = -inreal[t] * Math.sin(angle) + inimag[t] * Math.cos(angle);

Putting it all together

Merge the code for each term of the sum into the code for the summation, and we’re done:

static void dft(double[] inreal , double[] inimag,
                double[] outreal, double[] outimag) {
    int n = inreal.length;
    for (int k = 0; k < n; k++) {  // For each output element
        double sumreal = 0;
        double sumimag = 0;
        for (int t = 0; t < n; t++) {  // For each input element
            double angle = 2 * Math.PI * t * k / n;
            sumreal +=  inreal[t] * Math.cos(angle) + inimag[t] * Math.sin(angle);
            sumimag += -inreal[t] * Math.sin(angle) + inimag[t] * Math.cos(angle);
        }
        outreal[k] = sumreal;
        outimag[k] = sumimag;
    }
}

Source code 

The end product of this DFT tutorial is available in multiple languages for your convenience, and the code is placed in the public domain:

Notes:

  • Python, C, C++, C#, and MATLAB have built-in support for complex numbers. This feature makes our job easier and the resulting DFT implementation much simpler.

  • Each implementation respects the naming convention, formatting style, and code idioms of its own language – they don’t necessarily imitate the tutorial’s Java implementation as closely as possible.

  • The flavor of DFT described here does not include normalization/scaling for the sake of simplicity. There are numerous choices in how to scale the DFT – such as scaling only the forward transform by \(1/n\), scaling both the forward and inverse transforms by \(1/\sqrt{n}\), scaling the precomputed trigonometric tables, etc. – with no clear winner.

  • The code on this page is a correct but naive DFT algorithm with a slow \(Θ(n^2)\) running time. A much faster algorithm with \(Θ(n \log n)\) run time is what gets used in the real world. See my page Free small FFT in multiple languages for an implementation of such.

More info

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在MATLAB中,可以使用`fft`函数来实现离散傅里叶变换DFT),其中`fft`函数的输入为一个向量或矩阵,输出为相应的DFT结果。 具体实现步骤如下: 1. 首先,将输入信号x转换为长度为N的向量,其中N为DFT的长度。如果x的长度小于N,则可以使用MATLAB中的`padarray`函数在后面补零,使其长度达到N。 2. 对转换后的向量x应用`fft`函数,得到DFT结果X。 3. 由于DFT的输出结果是复数,因此可以使用`abs`函数计算X的幅值谱,使用`angle`函数计算X的相位谱。 下面是一个实现示例: ```matlab % 定义输入信号x x = [1 2 3 4]; % 定义DFT的长度N N = 8; % 在x后面补零,使其长度达到N x_padded = padarray(x, [0 N-length(x)], 0, 'post'); % 计算x_padded的DFT结果X X = fft(x_padded); % 计算X的幅值谱和相位谱 X_abs = abs(X); X_phase = angle(X); ``` 该代码会输出X的幅值谱和相位谱。注意,由于DFT的对称性,只需要输出前一半结果即可。 ### 回答2: 在MATLAB中使用离散傅里叶变换Discrete Fourier Transform, DFT)进行频域分析,可以按照以下步骤实现: 1. 首先,需要定义输入信号。可以使用MATLAB提供的函数来生成或加载信号。例如,可以使用`sin`或`cos`函数生成一个正弦波信号,或使用`audioread`函数加载一个音频文件。 2. 接下来,使用MATLAB提供的`fft`函数计算离散傅里叶变换。`fft`函数接受一个向量作为输入,并返回变换后的频谱。可以使用快速傅里叶变换(Fast Fourier Transform, FFT)算法来进行计算,这样可以提高计算速度。 3. 可以选择对变换后的频谱进行幅度谱或相位谱分析,或者对频谱进行滤波、谱估计等操作,以实现不同的信号处理目标。 4. 最后,可以使用`ifft`函数进行逆变换,将频域信号重新转换为时域信号。逆变换的结果将与输入信号相同。 需要注意的是,MATLAB中的`fft`函数默认使用基2的FFT算法,如果输入信号的长度不是2的幂次方,会进行零填充。如果需要指定变换点数,可以使用`fft`函数的参数进行设置。此外,还可以使用`fftshift`函数将频谱重新排序,使得频谱的零频率位于中心位置。 总之,MATLAB提供了强大的信号处理工具箱,通过使用离散傅里叶变换和相关函数,可以实现信号的频域分析和处理。 ### 回答3: 离散傅里叶变换(DFT)是一种将时域信号转换为频域信号的数学工具。它在数字信号处理和频谱分析中具有广泛的应用。MATLAB是一种流行的科学计算软件,可以用于编写和执行数学和工程计算。 要在MATLAB中实现离散傅里叶变换,可以使用MATLAB内置的函数fft。以下是一个简单的示例代码: ```matlab % 输入信号 x = [1, 2, 3, 4]; % 计算离散傅里叶变换 X = fft(x); % 输出变换结果 disp(X); ``` 在这个示例中,我们定义了一个输入信号x,它是一个包含4个元素的向量。然后,我们使用fft函数对输入信号进行离散傅里叶变换,将结果存储在变量X中。最后,我们使用disp函数输出变换结果。 要注意的是,离散傅里叶变换的结果是一个复数向量,其中包含了信号的频域表示。实部表示信号幅度,虚部表示信号相位。 通过使用MATLAB的fft函数,我们可以方便地实现离散傅里叶变换,并获得信号在频域的表示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值