Преобразование Фурье с помощью Matlab

Мой вопрос похож, но более общий, чем этот пост, и я думаю, что там есть ошибка в отношении нормализации, так или иначе, с новейшей версией Matlab (2015). Я не решался опубликовать это на CodeReview SE, если вы считаете, что это будет более уместно, дайте мне знать в комментариях.

Я хотел бы проверить следующий код преобразования Фурье с использованием Matlab fft, потому что я нашел противоречивые источники информации в Интернете, в том числе в самой справке Matlab, и мне не удалось проверить теорему Парсеваля с некоторыми такими "рецептами" (включая ответы от команды MathWorks, см. ниже), особенно те, которые извлекают односторонние спектры для реальных входных данных.

Например, удвоение амплитуды, которое обычно можно найти в Интернете для учета симметричных спектров реальных значений сигналов при извлечении положительных частот, только кажется неправильным (теорема Парсеваля не выполняется), и вместо этого представляется необходимым использовать квадратный корень из два коэффициента в Matlab (я не знаю почему). Некоторые люди также, кажется, нормализуют коэффициенты ДПФ, например, Y = fft(X)/L, но я думаю, что это сбивает с толку и не должно поощряться; амплитуда определяется как модуль комплексного коэффициента DFT, деленный на длину сигнала, сами коэффициенты не должны делиться. После проверки я намереваюсь опубликовать этот код в GistHub.

function [frq,amp,phi] = fourier_transform( time, vals )
% FOURIER_TRANSFORM computes the Fast Fourier Transform of a given time-series.
% 
% [freq,amp,phi] = fourier_transform(time,vals)
%
% Inputs:
%
%   time  - Nx1 column vector of equally-spaced timepoints (if not, input will be resampled).
%   vals  - NxM matrix of real- or complex-valued observations at previous timepoints.
%
% Outputs:
%
%   frq   - column vector of frequencies in Hz
%   amp   - corresponding matrix of amplitudes for each frequency (row) and signal (column)
%   phi   - corresponding unwrapped phase for each frequency (row) and signal (column)
%
% Note:
%   To compute the power from the output amplitude, you need to multiply by the number of timepoints:
%       power = numel(time) * amp.^2;
%
% References:
%   https://en.wikipedia.org/wiki/Discrete_Fourier_transform

    % make sure input time-series is uniformly sampled
    assert( iscolumn(time), 'Input time should be a column vector.' );
    assert( ismatrix(vals) && size(vals,1) == numel(time), 'Input values should be a matrix with same number of rows than of timepoints.' );

    if std(diff(time)) > 1e-6
        warning('Input time-course does not appear to be uniformly sampled. Resampling before continuing.');
        [vals,time] = resample(vals,time);
    end

    % sampling information
    nt = numel(time);
    dt = time(2)-time(1);
    fs = 1/dt;
    df = fs/nt;

    % complex spectrum coefficients
    coef = fft(vals);

    % real input
    if isreal(vals)

        % extract one-sided spectrum (spectrum is symmetric)
        nfft = floor( nt/2 + 1 ); % eg 8 -> 5, and 7 -> 4
        coef = coef( 1:nfft, : );
        frq  = (0:nfft-1)*df;

        % correct amplitude values to account for previous extraction
        fac = sqrt(2);
        amp = fac*abs(coef)/nt;
        amp(1,:) = amp(1,:)/fac; % .. except for the DC component
        if mod(nt,2) == 0
            amp(end,:) = amp(end,:)/fac; % .. and for the Nyquist frequency (only if nt is even)
        end

    % complex input
    else

        % shift the spectrum to center frequencies around 0
        coef = fftshift( coef );
        frq  = fftshift( (0:nt-1)*df );
        amp  = abs(coef)/nt;

    end

    % make sure frq is a column vector and compute phases
    frq = frq(:);
    phi = unwrap(angle(coef));

end

Пример использования

>> fs=1e3; t=transpose(0:1/fs:10); nt=numel(t); X=rand(nt,1);
>> [frq,amp,phi] = fourier_transform( t, X ); 
>> sum( abs(X).^2 ) - nt*sum( amp.^2 ) % Parseval theorem

ans =

  -2.7285e-11

Неправильный пример 1

Из собственного примера Matlab и размещенного на SO:

>> Fs = 1000;                    % Sampling frequency
T = 1/Fs;                     % Sample time
L = 1000;                     % Length of signal
t = (0:L-1)*T;                % Time vector
x = 0.7*sin(2*pi*50*t) + sin(2*pi*120*t);
y = x + 2*randn(size(t));     % Sinusoids plus noise
NFFT = 2^nextpow2(L); % Next power of 2 from length of y
Y = fft(y,NFFT)/L;
f = Fs/2*linspace(0,1,NFFT/2+1);
>> sum(abs(y).^2) - NFFT*sum(abs(Y).^2) % Parseval theorem

ans =

 -220.4804

Проблема и решение:

Происходит нормализация по линии Y = fft(y,NFFT)/L Это должно быть вместо:

>> Y = fft(y,NFFT);
Ya = abs(Y)/NFFT; % correctly normalised amplitudes
sum(abs(y).^2) - NFFT*sum(Ya.^2) % Parseval theorem

Неправильный пример 2

От команды MathWorks собственный разъясняющий пост:

>> Fs = 1000;                    % Sampling frequency
T = 1/Fs;                     % Sample time
L = 1000;                     % Length of signal
t = (0:L-1)*T;                % Time vector
% Sum of a 50 Hz sinusoid and a 120 Hz sinusoid
x = 0.7*sin(2*pi*50*t) + sin(2*pi*120*t);
y = x + 2*randn(size(t));     % Sinusoids plus noise

NFFT = 2^nextpow2(L); % Next power of 2 from length of y
Y = fft(y,NFFT)/L;
f = Fs/2*linspace(0,1,NFFT/2+1);

NumUniquePts = ceil((NFFT+1)/2);
Y=Y(1:NumUniquePts);
MX=2*abs(Y);
MX(1)=MX(1)/2; % DC component
if ~rem(NFFT,2)  % when NFFT is even, Y(1+Y/2) is the Nyquist frequency component of x, and needs to make sure it is unique.
    MX(length(MX))=MX(length(MX))/2;
end

>> sum( abs(y).^2 ) - NFFT*sum( MX.^2 )

ans =

  -5.3812e+03

Проблема и решение:

Снова нормализация. Заменить Y = fft(y,NFFT)/L; Y = fft(y,NFFT) и предположительно MX=2*abs(Y); по MX=2*abs(Y)/NFFT; , Но здесь возникает проблема удвоения амплитуды; поправочный коэффициент, кажется, sqrt(2) а не 2.

Неправильный пример 3

Нашел в качестве ответа на MatlabCentral:

>> Fs = 1000;                    % Sampling frequency
T = 1/Fs;                     % Sample time
L = 1000;                     % Length of signal
t = (0:L-1)*T;                % Time vector
x = 0.7*sin(2*pi*Fs/8*t) + sin(2*pi*Fs/4*t); 
NFFT = 2^nextpow2(L); % Next power of 2 from length of y
Y = fft(x,NFFT)/L;
f = Fs/2*linspace(0,1,NFFT/2+1);
>> sum( abs(x).^2 ) - NFFT*sum( abs(Y).^2 )

ans =

  -36.1891

Проблема и решение:

Как и в первом примере, проблема нормализации. Напишите вместо этого:

Y  = fft(x,NFFT);
Ya = abs(Y)/NFFT;
sum( abs(x).^2 ) - NFFT*sum( abs(Ya).^2 )

Ответ 1

TL; DR (резюме)

Очень трудно найти в Интернете примеры использования fft с Matlab, которые правильно нормализуют значения амплитуды/мощности (например, как можно проверить с помощью теоремы Парсеваля). Это очень важно, если вы хотите сравнить спектры между сигналами различной длины. Существует также дополнительная проблема с сигналами real-, потому что в этом случае спектр часто вычисляется только для положительной частоты, и, следовательно, значения амплитуды или мощности необходимо масштабировать для учета частотного свертывания. После поста и ответов ниже, вот суть, которая, я думаю, корректно и последовательно масштабирует коэффициенты для real- и комплексных входных данных.

Взять домой сообщения являются:

  • НЕ нормализуйте коэффициенты DFT напрямую (например, не пишите Y = fft(x)/L);
  • Если вы используете n- fft(x,nfft) преобразование (например fft(x,nfft)), тогда нормализатором будет nfft а не numel(x);
  • Если вы извлекаете односторонний спектр, вам нужно отрегулировать значения амплитуды/мощности в зависимости от того, какие соответствуют сопряженным парам коэффициенты DFT;
  • Если вы извлекаете односторонний спектр, вы должны рассчитывать амплитуду и мощность отдельно (т.е. не рассчитывать мощность по амплитудам).

Амплитуда, мощность и односторонние спектры

Как определено и объяснено в Википедии:

  • Коэффициенты ДПФ являются сложными и не нормированы, в то время как формула для обратного ДПФ несет в себе коэффициент 1/N перед суммой. В некотором смысле это естественно, так как движение в направлении времени к частоте можно рассматривать как проекцию на основу (ортогональных) волн с разными частотами, тогда как движение в направлении частоты к времени можно рассматривать как взвешенную суперпозицию. волн.
  • В этой суперпозиции общая величина должна быть равна исходной точке времени (т.е. Она является инверсией), а амплитуда каждой волны в этом взвешенном среднем значении представляет собой просто величину соответствующего коэффициента DFT, деленную на количество волн |Xk|/N |Xk|/N Аналогично, мощность каждой волны равна |Xk|^2/N Matlab тоже использует эту нормализацию (ну, FFTW).
  • Для real- -значных входов коэффициенты DFT являются сопряженными парами для соответствующих положительных/отрицательных частот, кроме компонента постоянного тока (средний член, частота 0), и для частоты Найквиста, когда число моментов времени является четным. На практике большинство приложений избегают этой избыточности, извлекая коэффициенты DFT только для положительных частот. Это приводит к усложнению дискретных значений амплитуды и мощности.
  • Амплитуды, соответствующие парным коэффициентам ДПФ (все, кроме первого и частоты Найквиста, когда она существует), можно просто удвоить, а затем отрицательную частоту отбросить. То же самое для власти.
  • Аналогично для мощности, но обратите внимание, что в этом случае неправильно вычислять дискретные значения мощности, используя скорректированные значения амплитуды. На самом деле N * amp_adjusted[k]^2 = N * (2*|Xk|/N)^2 не равно 2*|Xk|^2/N (отсюда корень квадратный из двух в OP). Поэтому необходимо независимо рассчитывать значения амплитуды и мощности из коэффициентов ДПФ (еще одна веская причина не масштабировать их).

Преобразование N-точек

Во многих примерах онлайн используется явное преобразование N-точек: Y = fft(x,NFFT) где NFFT обычно является степенью 2, что делает вычисления более эффективными с FFTW.

Эффективная разница в этом случае (при условии, что NFFT >= N) состоит в том, что x дополняется 0 в его конце, пока он не достигнет длины временных точек NFFT. Это означает, что количество частот в разложении изменяется, и, следовательно, нормализация должна выполняться относительно NFFT волны NFFT, а не исходных N моментов времени.

Следовательно, почти все примеры, найденные в Интернете, неверны в том, как они нормализуют коэффициенты. Это не должно быть Y = fft(x,NFFT)/N, а Y = fft(x,NFFT)/NFFT - еще одна веская причина Y = fft(x,NFFT)/NFFT от привычки нормализовать сложные коэффициенты.

Обратите внимание, что это не имеет никакого значения по сравнению с равенством Парсеваля, потому что все добавленные члены во временной области равны нулю, и поэтому их вклад в теперь большую сумму также равен нулю. В частотной области, тем не менее, добавленные дискретные частоты будут иметь отклик на исходный сигнал в целом, что дает интуитивное понимание того, почему полученные коэффициенты могут сильно отличаться между дополненными и не дополненными преобразованиями.

Код

Следовательно, код в OP неправильный, и вместо этого, по-видимому, необходимо выводить как амплитуду, так и мощность, поскольку нет общего коэффициента нормализации, который мог бы соответствовать сложным и реальным случаям с четным или нечетным числом временных точек. Вы можете найти суть здесь.

Ответ 2

Я нашел этот онлайн пример fft (https://habr.com/post/112068/) весьма полезным. Проверь это:

%% Parameters
Tm=5;% Length of signal (s)
Fd=512;% Sampling frequency (Hz)
Ak=0.5;% Constant component (Unit)
A1=1;% The amplitude of the first sinusoid (Unit)
A2=0.7;% Amplitude of the second sinusoid (Unit)
F1=13;% Frequency of the first sinusoid (Hz)
F2=42;% Frequency of the second sinusoid (Hz)
Phi1=0;% Initial phase of the first sinusoid (Degrees)
Phi2=37;% The initial phase of the second sinusoid (Degrees)
An=3*A1;% Noise Dispersion (Unit)
FftL=1024;% Number of Fourier Spectrum Lines
%% Generating work arrays
T=0:1/Fd:Tm;% Time Arrays
Noise=An*randn(1,length(T));% An array of random noise with a length equal to the array of time
Signal=Ak+A1*sind((F1*360).*T+Phi1)+A2*sind((F2*360).*T+Phi2);% Signal array (a mixture of 2x sinusoids and a constant component)
%% Spectral representation of the signal
FftS=abs(fft(Signal,FftL));% The amplitudes of the Fourier transform of the signal
FftS=2*FftS./FftL;% Spectrum normalization by amplitude
FftS(1)=FftS(1)/2;% The normalization of the constant component in the spectrum
FftSh=abs(fft(Signal+Noise,FftL));% The amplitudes of the Fourier transform of the signal + noise mixture
FftSh=2*FftSh./FftL;% Spectrum normalization by amplitude
FftSh(1)=FftSh(1)/2;% The normalization of the constant component in the spectrum
%% Plotting
subplot(2,1,1);
plot(T,Signal);
title('Signal');
xlabel('Time (s)');
ylabel('Amplitude (Unit)');
subplot(2,1,2);
plot(T,Signal+Noise);
title('Signal+Noise');
xlabel('Time (s)');
ylabel('Amplitude (Unit)');

F=0:Fd/FftL:Fd/2-1/FftL;% The frequency array of the calculated Fourier spectrum
figure();
subplot(2,1,1);
plot(F,FftS(1:length(F)));%  Plotting of the spectrum of the Fourier signal
title('Signal spectrum');
xlabel('Frequency (Hz)');
ylabel('Amplitude (Unit)');
subplot(2,1,2);
plot(F,FftSh(1:length(F)));% Plotting of the Fourier signal spectrum
title('Signal spectrum');
xlabel('Frequency (Hz)');
ylabel('Amplitude (Unit)');