DFT 的维基百科方程的实现

信息处理 傅里叶变换 Python 自由度
2021-12-25 11:23:56

我正在写一个简单的傅里叶变换实现,并查看了维基百科上的 DFT 方程以供参考,当我注意到我在做一些不同的事情时,经过思考后觉得维基百科的版本一定是错误的,因为它很容易想到一个信号表明,当傅立叶变换(使用该等式)将返回不正确的频谱:因为该等式仅将信号围绕复平面包裹一次(由于),任何周期性的信号偶数次(在包裹复平面时)将没有频谱,因为在 DFT 期间出现的通常峰值(在围绕单位圆时)将相互抵消(当它们出现偶数时)。n/N0<n<N1

为了检查这一点,我编写了一些生成以下图像的代码,这似乎证实了我的想法。 在此处输入图像描述

“时间使用方程”使用方程 向量时间(例如采样它可以在下面的函数中找到。

Xf=n=0N1xn(cos(2πftn)isin(2πftn))
ttnxnft

上面链接的维基百科方程在这里复制以供参考: 可以在函数中找到

Xf=n=0N1xn(cos(2πfnN)isin(2πfnN))
ft2

import numpy as np
import matplotlib.pyplot as plt 
plt.style.use('ggplot')

def ft(t, s, fs):
    freq_step = fs / len(s)
    freqs = np.arange(0, fs/2 + freq_step, freq_step)
    S = []
    for freq in freqs:
        real = np.sum(s * np.cos(2*np.pi*freq * t)) 
        compl = np.sum(- s * np.sin(2*np.pi*freq * t)) 
        tmpsum = (real**2 + compl**2) ** 0.5 
        S.append(tmpsum)
    return S, freqs

def ft2(s, fs):  # Using wikipedia equation
    nump=len(s)
    freq_step = fs / nump
    freqs = np.arange(0, fs/2 + freq_step, freq_step)
    S = []
    for i, freq in enumerate(freqs):
        real = np.sum(s * np.cos(2*np.pi*freq * i/nump))
        compl = np.sum(- s * np.sin(2*np.pi*freq * i/nump))
        tmpsum = (real**2 + compl**2) ** 0.5 
        S.append(tmpsum)
    return S, freqs


def main():
    f = 5 
    fs = 100 
    t = np.linspace(0, 2, 200)
    y = np.sin(2*np.pi*f*t) + np.cos(2*np.pi*f*2*t)

    fig = plt.figure()
    ax = fig.add_subplot(311)
    ax.set_title('Signal in time domain')
    ax.set_xlabel('t')
    ax.plot(t, y)

    S, freqs = ft(t, y, fs) 

    ax = fig.add_subplot(312)
    ax.set_xticks(np.arange(0, freqs[-1], 2)) 
    ax.set_title('Time using equation')
    ax.set_xlabel('frequency')
    ax.plot(freqs, S)

    S, freqs = ft2(y, fs) 
    ax = fig.add_subplot(313)
    ax.set_title('Using Wiki equation')
    ax.set_xlabel('frequency')
    ax.set_xticks(np.arange(0, freqs[-1], 2)) 
    ax.plot(freqs, S)

    plt.tight_layout()
    plt.show()

main()

显然,我似乎不太可能在如此高调的 wiki 页面上随机发现错误。但我看不出我所做的有什么错误?

3个回答

你有一个错误ft2你正在增加i,并freq在一起。这不是你希望你的求和工作的方式。我搞砸了修复它,但它变得一团糟。我决定从离散的角度重写它,而不是尝试使用连续的术语。在 DFT 中,采样率是无关紧要的。重要的是使用了多少样本 ( N)。然后,bin 编号 ( k) 对应于以每帧周期为单位的频率。我试图让您的代码尽可能完整,以便您可以轻松理解它。我还展开了 DFT 计算循环,希望能更好地揭示它们的性质。

希望这可以帮助。

赛德

将 numpy 导入为 np
将 matplotlib.pyplot 导入为 plt

定义 ft(t, s, fs):
    freq_step = fs / len(s)
    频率 = np.arange(0, fs/2, freq_step)
    S = []
    对于频率中的频率:
        实数 = np.sum(s * np.cos(2*np.pi*freq * t))
        compl = np.sum(- s * np.sin(2*np.pi*freq * t))
        tmpsum = (real**2 + compl**2) ** 0.5
        S.append(tmpsum)
    返回 S,频率

def ft3(s, N): # 更有效的维基百科方程形式

    S = []

    切片 = 0.0
    条子 = 2*np.pi/float(N)

    对于范围内的k(N / 2):

        sum_real = 0.0    
        sum_imag = 0.0
        角度 = 0.0
        对于范围内的n(N):
            sum_real += s[n] * np.cos(角度)
            sum_imag += -s[n] * np.sin(角度)
            角度 += 切片

        切片 += 条子
        tmpsum = (sum_real**2 + sum_imag**2) ** 0.5
        S.append(tmpsum)

    返回 S

def ft4(s, N): # 使用维基百科方程

    S = []

    对于范围内的k(N / 2):

        sum_real = 0.0    
        sum_imag = 0.0
        对于范围内的n(N):
            sum_real += s[n] * np.cos(2*np.pi*k*n/float(N))
            sum_imag += -s[n] * np.sin(2*np.pi*k*n/float(N))

        tmpsum = (sum_real**2 + sum_imag**2) ** 0.5
        S.append(tmpsum)

    返回 S

def ft5(s, N): # 统一加权和的根

    条子 = 2 * np.pi / float( N )

    root_real = np.zeros(N)
    root_imag = np.zeros( N )

    角度 = 0.0
    对于范围内的 r (N):
        root_real[r] = np.cos( 角度 )
        root_imag[r] = -np.sin( 角度 )
        角度 += 条子

    S = []

    对于范围内的k(N/2):

        sum_real = 0.0    
        sum_imag = 0.0
        r = 0

        对于范围内的n(N):
            sum_real += s[n] * root_real[r]
            sum_imag += s[n] * root_imag[r]
            r += k
            如果 r >= N : r -= N

        tmpsum = np.sqrt( sum_real*sum_real + sum_imag*sum_imag )
        S.append(tmpsum)

    返回 S

定义主():

    N = 200
    fs = 100.0

    time_step = 1.0 / fs
    t = np.arange(0, N * time_step, time_step)

    f = 5.0
    y = np.sin(2*np.pi*f*t) + np.cos(2*np.pi*f*2*t)

    无花果 = plt.figure()
    斧头 = fig.add_subplot(311)
    ax.set_title('时域信号')
    ax.set_xlabel('t')
    ax.plot(t, y)

    S, 频率 = ft(t, y, fs)

    斧头 = fig.add_subplot(312)
    ax.set_xticks(np.arange(0, freqs[-1], 2))
    ax.set_title('时间使用方程')
    ax.set_xlabel('频率')
    ax.plot(频率,S)

    S = ft3(y, N)
    斧头 = fig.add_subplot(313)
    ax.set_title('使用维基方程')
    ax.set_xlabel('频率')
    ax.set_xticks(np.arange(0, freqs[-1], 2))
    打印 len(S), len(freqs)
    ax.plot(频率,S)

    plt.tight_layout()
    plt.show()

主要的()

在此处输入图像描述

我不会看你的代码。维基百科页面看起来不错,但它数学家和电气工程师之间“格式战争”“符号战争”“风格战争”的一个很好的例子。其中一些,我认为数学家是对的。EE 不应该采用“ ”作为虚数单位。也就是说,这是 DFT 的更好表达,反之则为:j

DFT:

X[k]=n=0N1x[n]ej2πnk/N

iDFT:

x[n]=1Nk=0N1X[k]ej2πnk/N

因为做 DSP 的电气工程师喜欢使用作为“时间”中的样本序列,而作为“频率”中的离散样本序列。数学家可能更喜欢这个:x[n]X[k]

DFT:

Xk=n=0N1xnei2πnk/N

iDFT:

xn=1Nk=0N1Xkei2πnk/N

这与维基百科页面相同。

您可能需要更多地注意在指数中使用以及如何将其转换为术语相反。++sin()

我回到这一点并尝试派生有助于使事情更有意义的离散版本:

不知何故fktn=f(n,k,N)

fk=fsNktn=TNn

fs=NT

所以

fktn=fsNkTNn=NTNkTNn=knN

完毕!