In-depth understanding of Fourier transform (3)

Recap

The formula of Fourier transform:
F ^ ( f ) = ∫ f ( t ) e − i 2 π ftdt \hat{F}(f) = \int f(t) e^{-i2 \pi ft} dtF^(f)=f(t)ei 2 π f t dt
The formula of inverse Fourier transform:
f ( t ) = ∫ F ^ ( f ) ei 2 π ftdff(t) = \int \hat{F}(f) e^{i2 \ pi ft} dff(t)=F^(f)ei2πftdf

The problem we encountered before is that the amplitude of the reconstructed signal obtained directly by the formula of inverse Fourier transform is much larger than the original signal.

  • rebuild signal
    insert image description here
  • original signal
    insert image description here

Now I can tell you the reason: the Fourier transform done by a computer is essentially a discrete Fourier transform, so to reconstruct the signal, the inverse discrete Fourier transform should also be used.

Discrete Fourier Transform

The formula of Fourier transform F ^ ( f ) = ∫ f ( t ) e − i 2 π ftdt \hat{F}(f) = \int f(t) e^{-i2 \pi ft} dtF^(f)=f(t)e f ( t ) f(t)in i 2 π f t dtf ( t ) represents a continuous time series signal, which will be discretized in the computer, namely: sampling -> quantization -> encoding. T in the figure below is the sampling period, which is the interval time of each sampling point in the computer. After digitization,f ( t ) f(t)f ( t ) becomesx ( n ) x(n)x(n) x ( n ) = f ( n T ) x(n)=f(nT) x(n)=f ( n T ) .
insert image description here
Next, step by step, the formula of Fourier transform is also discretized:

  • The sign of the signal in the formula should be changed to a discrete form, and the integral sign should be changed to a summation sign
    X ^ ( f ) = ∑ nx ( n ) e − i 2 π fn \hat{X} (f) = \sum_ {n} x(n) e^{-i2 \pi fn}X^(f)=nx(n)ei 2 π f nSo
    , the original signal ande − i 2 π fne^{-i2 \pi fn}ei 2 π f n signals have become discrete and become an array that can be accessed with the following n.
  • There are two more questions:
    1. The original signal is infinite in time series, how to represent the original signal in the computer?
    2. Need to choose e − i 2 π fne^{-i2 \pi fn} of different frequenciesei 2 π f n signal, find out the corresponding optimal amplitude and initial phase, and these possible frequencies are infinitely many, how to gete − i 2 π fne^{-i2 \ pi fn}eWhat about the − i 2 π f n signal?
  • There are still ways:
    1. Assume that the frequency of the original signal to be decomposed is non-zero within a finite time series interval. This is easy to understand, a 3-minute song should already have enough information for frequency decomposition.
      X ^ ( f ) = ∑ n = 0 N − 1 x ( n ) e − i 2 π fn \hat{X} (f) = \sum_{n=0}^{N-1} x(n) e ^{-i2 \pi fn}X^(f)=n=0N1x(n)ei2πfn
    2. Only select a limited number of frequencies to get a limited number of e − i 2 π fne^{-i2 \pi fn}ei 2 π f n signals, for these finite signals, find the best amplitude and initial phase.
    3. The number of selected finite frequencies is preferably the same as the number of sampling points of the original signal within a finite time sequence interval. This is mainly for better inverse Fourier transform.
      X ^ ( k N ) = ∑ n = 0 N − 1 x ( n ) e − i 2 π k N n , k = 0 , 1 , 2 , . . . , N − 1 \hat{X} (\frac {k}{N}) = \sum_{n=0}^{N-1} x(n) e^{-i2 \pi \frac{k}{N}n},k=0,1,2 ,...,N-1X^(Nk)=n=0N1x(n)ei2πNkn,k=0,1,2,...,N1
    4. What is the actual meaning of k? Why k N \frac{k}{N}NkCan it replace the position of frequency f? In fact, there is a mapping relationship between k and f, and the function is expressed as:
      f = g ( k ) = k NT = k ⋅ sr N f = g(k) = \frac{k}{NT} =\frac{k \ cdot s_r}{N}f=g(k)=NTk=Nksr
      Where sr is the sampling frequency of the original signal. That is to say, the selected frequency range is [0, sr), within this range, the selected frequency is equally divided by the number of sampling points.

Inverse Discrete Fourier Transform

The formula of inverse discrete Fourier transform can be given directly:
x ( n ) = 1 N ∑ k = 0 N − 1 X ^ ( k N ) ei 2 π k N nx(n) = \frac{1}{N } \sum_{k=0}^{N-1} \hat{X} (\frac{k}{N}) e^{i2 \pi \frac{k}{N}n}x(n)=N1k=0N1X^(Nk)ei2πNkn

import matplotlib.pyplot as plt
import numpy as np


def create_signal(frequency, time: np.ndarray):
    sin0 = np.sin(2 * np.pi * (frequency * time))
    sin1 = np.sin(2 * np.pi * (2 * frequency * time))
    sin2 = np.sin(2 * np.pi * (3 * frequency * time))

    return sin0 + sin1 + sin2


def create_pure_tone(frequency, time: np.ndarray):
    angle = 2 * np.pi * frequency * time

    return np.cos(angle) - complex(0, 1) * np.sin(angle)


def calculate_mean(multi_signal):
    x_mean = np.mean([x.real for x in multi_signal])
    y_mean = np.mean([x.imag for x in multi_signal])

    return x_mean, y_mean


def calculate_sum(multi_signal):
    x_sum = np.sum([x.real for x in multi_signal])
    y_sum = np.sum([x.imag for x in multi_signal])

    return x_sum, y_sum


def plot_fourier_transform(signal,
                           pure_tone,
                           plot_mean=False,
                           plot_sum=False,
                           plot=True):
    multi_signal = signal * pure_tone
    x_series = np.array([x.real for x in multi_signal])
    y_series = np.array([x.imag for x in multi_signal])

    mean = calculate_mean(multi_signal)
    sum = calculate_sum(multi_signal)
    # print(f"mean:{mean}\nsum:{sum}")

    if plot:
        plt.plot(x_series, y_series)
        if plot_mean:
            plt.plot([mean[0]], [mean[1]],
                     marker='o',
                     markersize=10,
                     color="red")
        if plot_sum:
            plt.plot([sum[0]], [sum[1]],
                     marker='o',
                     markersize=10,
                     color="green")
        plt.show()

    return complex(mean[0], mean[1]), complex(sum[0], sum[1])


def plot_inverse_fourier_transform(cfs, pure_tones, time, plot=True):
    assert len(cfs) == len(pure_tones)
    N = len(cfs)
    signal = 0
    for i in range(N):
        signal += cfs[i] * np.conj(pure_tones[i])
    signal /= N

    if plot:
        plt.plot(time, signal)
        plt.show()

    return signal


if "__main__" == __name__:
    time = np.linspace(0, 10, 1000)
    N = time.shape[0]
    sr = 1000 // (10 - 0)

    signal = create_signal(frequency=1, time=time)

    pure_tones = []
    for k in np.linspace(0, N, N, endpoint=False):
        pure_tone = create_pure_tone(frequency=k * sr / N, time=time)
        pure_tones.append(pure_tone)

    cfs = []
    for pure_tone in pure_tones:
        mean, sum = plot_fourier_transform(signal,
                                           pure_tone,
                                           plot_mean=True,
                                           plot_sum=True,
                                           plot=False)
        cfs.append(sum)

    rebuild = plot_inverse_fourier_transform(cfs, pure_tones, time, plot=False)

    plt.plot(time, signal, label="signal")
    plt.plot(time, rebuild, label="rebuild")
    plt.fill_between(time, signal * rebuild, alpha=0.25)
    plt.legend()

    plt.show()

    # np.testing.assert_array_almost_equal(signal, rebuild)

insert image description here
The original signal and the reconstructed signal almost coincide, and the products are all positive!
You can use numpy's unit test to calculate the error point by point:

np.testing.assert_array_almost_equal(signal, rebuild)
Arrays are not almost equal to 6 decimals

Mismatched elements: 996 / 1000 (99.6%)
Max absolute difference: 0.00249961
Max relative difference: 1.05935375
 x: array([ 0.000000e+00,  3.758780e-01,  7.428670e-01,  1.092347e+00,
        1.416225e+00,  1.707181e+00,  1.958881e+00,  2.166163e+00,
        2.325182e+00,  2.433514e+00,  2.490210e+00,  2.495807e+00,...
 y: array([ 8.928912e-14-8.203696e-14j,  3.755021e-01-1.757345e-13j,
        7.421241e-01-8.970006e-14j,  1.091254e+00-9.888207e-14j,
        1.414809e+00-5.689618e-15j,  1.705474e+00-3.955452e-15j,...

The maximum relative error is 1.05935375%.

compare with numpy

The fft effect of numpy is excellent. Although the rebuild I wrote coincides with the ift waveform, the absolute error of ift is less than 14 digits after the decimal point.

    # numpy
    ft = np.fft.fft(signal)
    ift = np.fft.ifft(ft)
    plt.plot(time, rebuild, label="rebuild")
    plt.plot(time, ift, label="ift")
    plt.fill_between(time, rebuild * ift, alpha=0.25, label="product")
    plt.legend()

    plt.show()

    np.testing.assert_array_almost_equal(signal, ift, decimal=20)

insert image description here

Arrays are not almost equal to 20 decimals

Mismatched elements: 1000 / 1000 (100%)
Max absolute difference: 1.875756e-15
Max relative difference: 1.
 x: array([ 0.0000000000000000e+00,  3.7587797360061515e-01,
        7.4286697945743752e-01,  1.0923466914234694e+00,
        1.4162251787527200e+00,  1.7071811899304179e+00,...
 y: array([ 0.0000000000000000e+00-9.2967300524549044e-17j,
        3.7587797360061515e-01-7.7896022965262552e-17j,
        7.4286697945743785e-01+1.0284828544371295e-16j,...

The algorithm complexity of fft is O ( N ⋅ log 2 N ) O(N \cdot log_2 N)O ( Nlog2N ) , it can only be run when the number of sampling points is an integer power of 2.

symmetry

Remember to mention in the in-depth understanding of Fourier transform (1) : take the selected frequency as the abscissa, and the corresponding wave amplitude as the ordinate, and you will get something called a spectrum, as shown in the following figure:

if "__main__" == __name__:
    time = np.linspace(0, 10, 1000)
    sr = 1000 // (10 - 0)
    signal = create_signal(frequency=1, time=time)
    N = signal.size

    pure_tones = []
    frequencies = []
    for k in np.linspace(0, N, N, endpoint=False):
        pure_tone = create_pure_tone(frequency=k * sr / N, time=time)
        pure_tones.append(pure_tone)
        frequencies.append(k * sr / N)

    cfs = []
    for pure_tone in pure_tones:
        mean, sum = plot_fourier_transform(signal,
                                           pure_tone,
                                           plot_mean=True,
                                           plot_sum=True,
                                           plot=False)
        cfs.append(sum)
        print(len(cfs))

    magnitudes = np.abs(cfs)
    rebuild = plot_inverse_fourier_transform(cfs, pure_tones, time, plot=False)
    plt.plot(frequencies, magnitudes, label="spectrum")

    plt.legend()
    plt.show()

insert image description here
It is found that this picture is left-right symmetrical, and the center is sr 2 \frac{s_r}{2}2sr

The reason is: For a periodic signal, if you want to accurately measure this signal, you need to sample at least two times per cycle: once for the peak part and once for the trough part. The more samples per cycle, the more accurately the original signal can be reconstructed. However, if there are less than two samples per cycle, the frequency information of the original signal is completely lost and it is impossible to reconstruct the original signal.

Now our sampling rate for the original signal is sr s_rsr, then it can be seen that this sampling rate allows us to reconstruct the highest frequency contained in the original signal at most as sr 2 \frac{s_r}{2}2srsignal, higher frequency signals cannot be reconstructed.

Theorem: Given a sampling rate, we can reconstruct a periodic signal whose frequency is half of that sampling rate. Half of this sampling rate is called the Nyquist frequency, which is the center of the above figure sr 2 \frac{s_r}{2}2sr

The next section deals with the short-time Fourier transform.

Guess you like

Origin blog.csdn.net/m0_46324847/article/details/128229942