Szybkie metody obliczania DFT i ich zastosowania

Autorzy: Krzysztof Duda, Tomasz P. Zieliński

Pliki: rozdzial_05.zip

Przykład 5.1

W przykładzie 5.1 (plik fft_dit.m) jest zamieszczona implementacja w języku MATLAB algorytmu FFT z podziałem w czasie.
% Przykład 5.1 - Algorytm FFT z podziałem w czasie (DIT)
 function Xw=fft_dit(xt);
 N=length(xt);
 %% uzupełnienie zerami do długości 2^v
 v=ceil(log2(N)); %liczba bitów
 xt=[xt zeros(1,2^v-N)];
 N=length(xt);
 %% odwrócenie kolejności bitów indeksowanie od 0
 xo= zeros(size(xt)); %inicjalizacja zmiennej
 b = 2.^(v-1:-1:0); %wagi binarne
 for k=0:N-1;
	 ind=k;
	 ko=zeros(1,v);
	 for k1=0:v-1
		 if ind-b(k1+1)>=0
			 ko(k1+1)=1; ind=ind-b(k1+1);
		 end
	 end
	 ind_o=sum(fliplr(ko).*b);
	 xo(ind_o+1)=xt(k+1);
 end
 %% algorytm FFT
 X=zeros(2,N); %pamieć dla danych i wyników
 X(1,:)=xo; %dane
 WN=exp(-j*2*pi/N);
 for k=0:v-1 %petla po etapach
	 M=2^k; %liczba motylków w bloku
	 for k1=0:N/2/M-1; %pętla po blokach
		 for k2=0:M-1; %pętla wewnątrz bloku
		 %ustalenie indeksów
		 p=k1*N/2^(v-k-1)+k2;
		 q=p+M;
		 r=2^(v-k-1)*k2;
		 %obliczenia motylkowe
		 X(2,p+1)=X(1,p+1)+WN^r*X(1,q+1);
		 X(2,q+1)=X(1,p+1)-WN^r*X(1,q+1);
		 end
	 end
	 X(1,:)=X(2,:); %obliczenia z podstawianiem
 end
 Xw=X(1,:);
Przykład (plik przyklad_5_1.m) ilustrujący użycie funkcji fft_dit()
clear all, close all, clc

N=32;         %liczba próbek sygnału

n = 0:N-1;
x=cos(5.5*(2*pi/N)*n)+0.1*randn(1,N);    %przykładowy sygnał testowy
X1=fft(x);      k1=0:N-1;
X2=fft_dit(x);  k2=0:length(X2)-1;

if length(X2)==N
    blad = max(abs(X1-X2))
end

figure
    plot(n, x,'.-b')        
    xlabel('n')    
    ylabel('x_n')    
    title('Sygnal testowy')    
    box on, axis tight
    
figure, hold on
subplot(3,1,1), hold on
    plot(k1, abs(X1),'.-b')
    plot(k2, abs(X2),'o-r')
    xlabel('k')    
    title('Modul')
    legend('fft','fft_dit')
    box on, axis tight
subplot(3,1,2), hold on
    plot(k1, real(X1),'.-b')
    plot(k2, real(X2),'o-r')
    xlabel('k')    
    title('Czesc rzeczywista')
    legend('fft','fft_dit')
    box on, axis tight
subplot(3,1,3), hold on    
    plot(k1, imag(X1),'.-b')
    plot(k2, imag(X2),'o-b')
    xlabel('k')
    title('Czesc urojona')
    legend('fft','fft_dit')
    box on, axis tight

Przykład 5.2

Wprzykładzie 5.2 (plik przyklad_5_2.m) przedstawiono implementacja w języku MATLAB metody obliczania widma DFT sygnału rzeczywistego o długości 2N, z użyciem jednego N punktowego DFT.
close all, clear all, clc
N = 4;           %liczba probek
x = rand(1,2*N); %sygnał testowy o długości 2N
WN=exp(-j*2*pi*(0:(2*N)/2-1)/(2*N));
x1=x(1:2:end);   % N próbek o indeksach parzystych   
x2=x(2:2:end);   % N próbek o indeksach nieparzystych   
x12=x1+j*x2;     % 'sztuczny' sygnal zespolony   
X12=fft(x12);    % FFT N-punktowe
X0 =X12(1); X12(1)=[];
X1 = [real(X0) (X12+fliplr(conj(X12)))/2    ]; %DFT próbek o indeksach parzystych   
X2 = [imag(X0) (X12-fliplr(conj(X12)))/(2*j)]; %DFT próbek o indeksach nieparzystych   
X  = [X1+WN.*X2]; %podział w częstotliwości
X  = [X X1(1)-X2(1) conj(X(end:-1:2))];
%% porównanie z Matlabem
Xf=fft(x); [X(:)-Xf(:)]

Przykład 5.3

W przykładzie 5.3 (plik przyklad_5_3.m) przedstawiono implementację algorytmu mSDFT w języku MATLAB.
close all, clear all, clc
x = rand(1,100); %sygnał testowy
M = 30;          %długość DFT
k = 3;           %prążek DFT, który ma być wyznaczany
W = exp(-j*2*pi/M); % twiddle factor
%% Wartosci początkowe
xmr = 1;            
X0M = 0;    
xn_M= 0;
fifo= zeros(1,M);    
for n=1:length(x);        
%% bufor FIFO
    fifo= [fifo(2:M) x(n)];      
%% mSDFT 
    X1M = X0M+xmr*( x(n)-xn_M );
    X0M = X1M;
    if mod(n,M)       
        xmr=xmr*W^(k);     %ciąg modulujący
        X1M = X1M*conj(xmr);%korekcja fazy        
    else
        xmr=1;        
    end                                  
%% nowe xn_M    
    xn_M  = fifo(1);      
end
%% błąd amplitudy i fazy w odniesieniu do blokowego DFT
Xk   = fft(fifo); Xk=Xk(k+1);   %blokowe DFT
Aerr = [abs(X1M)-abs(Xk)]       %bład amplitudy 
Perr = [angle(X1M)-angle(Xk)]   %bład fazy

Przykład 5.4

W przykładzie 5.4 (plik przyklad_5_4.m) przedstawiono implementację algorytmu szybkiego splotu dwóch sygnałów z wykorzystaniem FFT.
% Przykład 5.4
  close all, clear all, clc
  
  M = 512;         % długość filtra
  N = 10*M;        % długość sygnału
  x=randn(1,N);    % sygnał 1
  h=0.99.^(0:M-1); % sygnał 2, filtr

% Splot liniowy - funkcja Matlaba
  y1 = conv(x,h);

% Szybki splot kołowy całego sygnału za pomocą FFT
  hz = [ h zeros(1,N-M)];               % uzupełniamy zerami drugi sygnał
                                        % do długości pierwszego, dłuższego
  y2 = ifft( fft(x) .* fft( hz ) );     % szybki splot kołowy
  blad2 = max(abs(y1(M:N) - y2(M:N)))   % błąd, końcowe N-M+1 próbki są poprawne

% Szybki splot liniowy całego sygnału za pomocą FFT
  xz = [ x zeros(1,M-1)];               % uzupełniamy dłuższy sygnał M-1 zerami
  hzz = [ h zeros(1,N-M) zeros(1,M-1)]; % uzupełniamy krótszy sygnał N-1 zerami
  y3 = ifft( fft(xz) .* fft( hzz ) );   % szybki splot liniowy
  blad3 = max(abs( y1- y3))             % błąd, wszystkie próbki są poprawne

Przykład 5.5

W przykładzie 5.5 (plik przyklad_5_5.m), kontynuacji przykładu 5.4, przedstawiono implementację różnych wersji algorytmu sekcjonalnego szybkiego splotu overlap-add.
% Przykład 5.5
  close all, clear all, clc
  
  Nh = 512; Nx = 10*Nh;             % długość filtra, długość sygnału
  Nb = Nh;                          % długość bloku próbek sygnału
  x=randn(1,Nx); h=0.99.^(0:Nh-1);  % sygnał 1 = losowy, sygnał 2 = odp. impulsowa filtra
  y1 = conv(x,h);                   % wynik splotu funkcją Matlaba jako odniesienie

% Metoda czwarta: sekcjonowany szybki splot overlap-add #1 (dodawanie sygnałów na wyjściu)
  Lb = ceil(Nx/Nb);                      % liczba bloków
  hzz = [h zeros(1,Nb-Nh) zeros(1,Nh)];  % wypelnianie h zerami
  Hz = fft(hzz);                         % FFT odpowiedzi impulsowej
  y4 = zeros(1,Nh-1);                    % przygotowanie wektora y4
  for k = 1:Lb
      bx = x((k-1)*Nb+1:k*Nb);           % wycinanie bloku Nb próbek sygnału x[n]
      bxz = [bx zeros(1,Nh)];            % uzupełnianie zerami do długości Nb+Nh-1
      Bxz = fft(bxz);                    % FFT fragmentu sygnału
      by = real( ifft( Bxz.*Hz ) );      % iloczyn widm i IFFT
      y4(end-(Nh-2):end) = y4(end-(Nh-2):end)+ by(1:Nh-1);  % dodawanie koniec + początek
      y4 = [ y4 by(Nh:end-1)];           % wstawianie próbek na koniec wynikowego wektora
  end;
  blad4 = max(abs(y1-y4))                % błąd

% Metoda piąta: sekcjonowany szybki splot overlap-add #2 (dodawanie widm na wejściu)
  Lb = ceil(Nx/Nb);                      % liczba bloków próbek
  hzz = [h zeros(1,Nb-Nh) zeros(1,Nb)];  % wypełnianie h zerami Nb+Nb
  Hz = fft(hzz);                         % FFT odpowiedzi impulsowej
  J = (-1).^(0:2*Nb-1);                  % J = macierz korekcji opóźnienia w widmie X2
  X2 = zeros(1,2*Nb); y5 = [];           % przygotowanie wektora y5
  for k = 1:Lb
      x1 = x((k-1)*Nb+1:k*Nb);           % wycinanie bloku Nb próbek sygnału x[n]
      xx = [x1 zeros(1,Nb)];             % uzupełnianie zerami do długości Nb+Nh
      X1 = fft(xx);                      % FFT
      X = X1 + J.*X2; X2 = X1;           % dodawanie widm X1 i X2 (z korekcją opóźnienia)
      yy = real( ifft( X.*Hz ) );        % iloczyn widm, następnie IFFT
      y5 = [ y5 yy(1:Nb)];               % wstawianie próbek na koniec wynikowego wektora
  end;
  blad5 = max(abs(y1(1:length(y5))-y5))  % błąd

% Metoda szósta: sekcjonowany szybki splot overlap-add #3 z dodatkowym podziałem h
% na bloki (fragmenty) o równej długości
  Nb = 32;             % arbitralnie przyjęta długość bloku próbek x[n] i h[n]
  Lb = ceil(Nx/Nb);    % liczba bloków sygnału
  Lh = ceil(Nh/Nb);    % liczba bloków odpowiedzi impulsowej filtra
  for k = 1:Lh         % obliczenie macierzy H (widma fragmentów odpowiedzi impulsowej) 
      H(k,1:2*Nb) = fft( [h((k-1)*Nb+1:k*Nb) zeros(1,Nb)] ); % uzupełnianie zerami, FFT
  end
  bx = zeros(1,2*Nb); X = zeros(Lh,2*Nb); y6 = [];
  for k = 0 : Lb-1
      bx = [ bx(Nb+1:2*Nb) x(k*Nb+1:k*Nb+Nb) ]; % pobierz kolejny blok próbek x[n]
      X = [ fft(bx); X(1:Lh-1,:) ];      %  oblicz jego FFT, zmodyfikuj macierz X
                                         % (w wierszach widma fragmentów sygnału x)
      XH = X.*H;                         % iloczyn elementów macierzy widm X i H
      by = real( ifft( sum(XH) ) );      % sumowanie, potem IFFT 
      y6 = [ y6 by(Nb+1:2*Nb) ];         % wstawianie próbek na koniec wynikowego wektora
  end
  blad6 = max(abs(y1(1:length(y6))-y6))  % błąd

% Metoda siódma: sekcjonowany szybki splot overlap-add #4 z dodatkowym podziałem h
% na bloki (fragmenty) o nierównej długości - małe opóźnienie sygnału na wyjściu

  N = 32;                  % rozmiar pierwszego, najkrótszego fragmentu h
  max_index = Nh/(4*N);    % długość najdłuższego fragmentu h (wyrażona w wielokrotności 4N)
  H = zeros((log2(max_index)+1)*2, Nh/2);  % inicjalizacja macierzy H widm FFT fragmentów h
  Lb = size(H,1);          % liczba fragmentów (bloków) h        
  y7 = zeros(1,Nx+2*Nh-1);

% Podział odpowiedzi impulsowej filtra
  i=1; k=1;
  while i<=Lb              
     H(k  ,1:i*N) = h(2*N*i+1 : 2*N*i+N*i);   % bloki wag filtra są wpisywane do macierzy
     H(k+1,1:i*N) = h(3*N*i+1 : 3*N*i+N*i);   % o wymiarze: Nh/2 x log2(max_index)
     i=i*2; k=k+2;              
  end

% FFT fragmentów odp. impulsowej filtra (w jednym obiegu pętli liczymy dwa widma)   
  i=1;
  for k=1:2:size(H,1) 
      H(k,  1:2*i*N) = fft(H(k,   1:2*i*N));  % kazdy odcinek jest wypelniony zerami do 
      H(k+1,1:2*i*N) = fft(H(k+1, 1:2*i*N));  % dl 2*dlugość odcinka
      i=i*2;
  end;

% Główna pętla  
  x = [ x zeros(1,Nh-1) ];   % uzupełnienie sygnału zerami
  for n = 1 : Nx+Nh-1        % powtarzamy do końca sygnału
      i = 1;
    % Pierwszy fragment odpowiedzi impulsowej filtra - metoda bezposrednia
      if n<2*N
         splot = h(n:-1:1)*(x(1:n))';           % splot w dziedzinie czasu
      else
         splot = h(2*N:-1:1)*(x(n-2*N+1:n))';   % splot w dziedzinie czasu
      end
      y7(n) = y7(n)+splot;
    % Kolejne fragmenty odpowiedzi impulsowej filtra - metoda blokowa
      k = 1;
      while(i <= max_index)
        if mod(n,i*N)==0                   % czy mamy odp. liczbę probek (32, 64, 128, ...)
           bx=[x(n-i*N+1:n),zeros(1,i*N)]; % wycinamy fragment sygnału
           BX = fft(bx);				   % FFT 
           XH1 = BX.*H(k,   1:2*(i*N));    % w jednym przebiegu pętli
           XH2 = BX.*H(k+1, 1:2*(i*N));    % liczymy dwa sploty
           i1 = n+i*N;                     % gdzie wstawic wynik?
           i2 = n+2*i*N;                   % to samo dla drugiego odcinka
           Ny = 2*i*N;                     % długość odcinka sygnalu
           y7(i1+1:i1+Ny) = y7(i1+1:i1+Ny) + real(ifft(XH1));
           y7(i2+1:i2+Ny) = y7(i2+1:i2+Ny) + real(ifft(XH2));
           k=k+2;
        else
           break % jeżeli liczba próbek nie dzieli się przez N,
                 % to nie dzieli się też przez 2*N, 4*N itd.
        end
        i=i*2;
      end
  end    
  y7=y7(1:length(y7)-Nh);
  blad7 = max(abs(y1-y7))

Przykład 5.6

W przykładzie 5.6 (plik przyklad_5_6.m) przedstawiono implementację szybkiego algorytmu wyznaczania korelacji wzajemnej (lub własnej kiedy y[n] = x[n]).
% Przykład 5.6
  clear all; close all;

% Inicjowanie parametrów
  N = 256;       % liczba próbek obu sygnałów
  M = 64;        % liczba przesunięć w jedną stronę
  L = 1000;      % liczba prążków funkcji wzajemnej gęstości widmowej mocy
  m = N-M: N+M;  % indeksy współczynników liczonych "szybko"

% Generowanie (synteza) sygnałów
  fx = 100; fpr = 1000; dt=1/fpr; t=0:dt:(N-1)*dt; tR=-(N-1)*dt:dt:(N-1)*dt;
  x1 = rand(1,N); x2 = randn(1,N);
% x1 = sin(2*pi*fx*t); x2=x1;
  subplot(211); plot(x1); grid; axis tight; title('Sygnały analizowane');
  subplot(212); plot(t,x2); grid; axis tight; xlabel('czas [sek]'); pause

% Obliczenie funkcji korelacji wzajemnej sygnałów procedurą Matlaba
% R1 = xcorr(x1,x2,'biased');           % tak
  R1 = conv(x1, conj(x2(end:-1:1)))/N;  % lub tak

% Obliczenia "ręczne" funkcji korelacji wzajemnej bezpośrednio z definicji
  for k=0:M
      R21(k+1)= sum( x1(1+k:N).*x2(1:N-k) ) / N;
      R22(k+1)= sum( x1(1:N-k).*x2(1+k:N) ) / N;
  end
  R2 = [ R22(M+1:-1:2) R21(1:M+1) ];
  subplot(211); plot(tR(m),R1(m)); grid; axis tight; title('Funkcja korelacji - Matlab');
  subplot(212); plot(tR(m),R2); grid; axis tight; title('Funkcja korelacji - "ręcznie"'); pause
  max(abs( R1(m)-R2)), pause
  
% Obliczenia "szybkie" 2*M+1 środkowych współczynników funkcji korelacji wzajemnej
% z FFT
  x1 = [ x1 zeros(1,M) ];                  % dodaj M zer na końcu sygnału
  x2 = [ x2(M+1:end) zeros(1,M) x2(1:M) ]; % dodaj M zer "wewnątrz" sygnału
  X1 = fft(x1);                            % oblicz FFT (N+M)-punktowe
  X2 = fft(x2);                            % oblicz FFT (N+M)-punktowe
  X = X1.*conj(X2);                        % oblicz X1(k)*conj(X2(k))
  R = ifft(X);                             % oblicz odwrotne FFT (N+M)-punktowe
  R3 = real(R(1:2*M+1))/N;                 % pobierz poprawne wartości, przeskaluj
  subplot(111); plot(tR(m),R1(m),'r',tR(m),R3,'b'); grid; axis tight;
  title('Metoda "szybka" - Matlab (red), My (blue)'); xlabel('czas [sek]'); pause
  max(abs( R1(m)-R3)), pause

% Funkcja wzajemnej gęstości widmowej mocy metodą Blackmana-Tukeya
  w = hanning(2*M+1); w=w';                      % wybierz okno widmowe, np. Hanninga
  Rw = R3 .* w;                                  % wymnóż funkcję autokorelacji z oknem
  s = [ Rw(M:2*M+1) zeros(1,L-2*M+1) Rw(1:M+1)]; % symetria dane wejściowe dla FFT
  S = abs(fft(s));                               % transformacja Fouriera
  df = 1/(L*dt); f=0:df:(L-1)*df; k=1:L/2+1;
  subplot(111); plot(f(k),S(k),'b'); grid; title('Funkcja gęstości widmowej mocy');
  xlabel('f [Hz]'); pause
  

Przykład 5.7

W przykładzie 5.7 (plik przyklad_5_7.m) przedstawiono implementację szybkiego obliczania DTFT (Discrete-Time Fourier Transform) z wykorzystaniem FFT.
% Przykład 5.7
  clear all; close all;

% Inicjowanie wartości parametrów
  N = 256;                        % liczba próbek sygnału
  M = 32;                         % liczba prążków widma
  fpr = 128;                      % częstotliwość próbkowania
  fd = 10; fg = 20;               % częstotliwości dolna i górna
  df=(fg-fd)/(M-1); f=fd:df:fg;   % interesujące nas częstotliwości
  x = rand(1, N);                 % sygnał analizowany, szum z przedziału [0, 1]
  NM1 = N+M-1;
  A = exp( -j*2*pi * fd/fpr );                                   % punkt startowy
  W = exp( -j*2*pi * ((fg-fd)/(2*(M-1))/fpr) );                  % krok
  y1 = zeros(1,NM1); k=0:N-1; y1(k+1)= ((A*W.^k).^k) .* x(k+1);  % inicjowanie y1
  k = [ 0 : M-1, -(N-1):1:-1]; y2 = W.^(-k.^2);                  % inicjowanie y2
  %k = [ 0:-1:-(N-1), M-1:-1:1]; k = [ k(1) fliplr(k(2:end))]; y2 = W.^(-k.^2);

% inicjowanie y2
  Y1 = fft(y1);       % # algorytm szybkiego splotu kołowego
  Y2 = fft(y2);       % # sygnałów y1 i y2
  Y = Y1.*Y2;         % #
  y = ifft(Y)/(N/2);  % #
  k=0:M-1; XcztN(k+1) = y(k+1) .* (W.^(k.^2)); % korekcja fazowa wyniku splotu
  n = 0:N-1; Xref=x*exp(-j*2*pi*n(:)*f/fpr);   % obliczenia na podstawie definicji DTFT (2.14)
  Xref = Xref/(N/2);                           % skalowanie
  blad = max(abs( XcztN - Xref )),             % błąd w stosunku do obliczeń z definicji
  subplot(2,1,1);
         plot(f,real(XcztN),'r.-'); grid on; hold on;
         plot(f,real(Xref),'bo-');
  subplot(2,1,2);
         plot(f,imag(XcztN),'r.-'); grid on; hold on;
         plot(f,imag(Xref),'bo-'); pause