Zaawansowane algorytmy analizy częstotliwościowej sygnałów

Autorzy: Tomasz P. Zieliński

Pliki: rozdzial_20.zip

Przykład 20.1

W przykładzie 20.1 (plik przyklad_20_1.m) przedstawiono w języku MATLAB dekompozycję EVD macierzy funkcji autokorelacji sygnału. Jest w nim generowana suma K, od 1 do 4, nietłumionych lub tłumionych sygnałów sinusoidalnych, zaszumonych. Należy zmieniać liczbę składowych, wartości ich pulsacji i tłumień, poziom szumu i obserwować wynik dekompozycji.
% Przykład 20.1
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

clear all; close all;

%% Parametry sygnałów
N = 500;                        % liczba próbek sygnału
K = 2;                          % liczba składowych rzeczywistych
SNR = 160;                      % stosunek sygnał/szum [decybele], od 20 do 160
Om = [12.345 77.89 111.22 145.67 ]*(2*pi)/N, % pulsacje - numery prążków DFT
d = [0.001 0.01 0.10 0.20 ],    % tłumienia
                                % WAŻNE: dla fPisarenko i fESPRIT wymagane d=0
%d = [ 0 0 0 0 ];               % dla metod fPisarenko i fESPRIT
A = [4.3 3.3 2.3 1.3 ];         % amplitudy
p = [-pi/11 -pi/7 -pi/5 -pi/3]; % fazy

%% Generacja sygnału testowego
n = 0:N-1; x = zeros(1,N);
for k=1:K
x = x + A(k)*cos(Om(k)*n+p(k)).*exp(-n*d(k));
end
y = awgn( x, SNR);              % dodanie szumu
plot(y); title('Analizowany sygnał'); xlabel('n'); grid; pause

%% Dekompozycja EVD
L = 2*K+20;                     % wymiar macierzy autokorelacji
ryy=xcorr(y,'unbiased');        % do wyboru: ‘unbiased', ‘biased', 'coeff', 'none'
ryy = ryy(N:end);               % z powodu symetrii, bierzemy tylko połowę
Ryy = toeplitz( ryy(1:L+1) );   % zbudowanie macierzy Rxx
[U, D ] = eig( Ryy );           % EVD( Rxx )
sigma = diag( D );              % wartości własne
[sigma, indx] = sort( sigma, 'descend' );
stem( sigma ), title('Warości własne'); grid, pause
subplot(411); plot(U(:,indx(1))), title('Wektor własny 1'); grid,
subplot(412); plot(U(:,indx(2))), title('Wektor własny 2'); grid,
subplot(413); plot(U(:,indx(3))), title('Wektor własny 3'); grid,
subplot(414); plot(U(:,indx(4))), title('Wektor własny 4'); grid,
pause

Przykład 20.2

W przykładzie 20.2 (plik fHilbert.m)zastosowano funkcję środowiska MATLAB hilbert() i pokazano, jak można wykorzystać transformację Hilberta do estymacji pulsacji i tłumienia pojedynczej składowej sinusoidalnej (20.1). Obliczane są w nim chwilowe wartości tych wielkości dla każdej próbki sygnału, a następnie są one uśredniane. Ponieważ na początku i na końcu okna obserwacji występują wyraźne oscylacje w obu estymatach (pulsacji i tłumienia), dlatego odrzucane są obliczone N/4 próbki początkowe i końcowe obu estymat. W ostatniej linii programu użyta jest zmienna temp, która służy do estymacji wartości tłumienia metodą regresji liniowej (aproksymacja linią prostą punktów pomiarowych). Sygnał, który będzie analizowany, można wygenerować w następujący sposób:
  N=1000; fpr=1000;
  dt=1/fpr; t=dt*(0:N-1);
  dA = 0.5; fA=2; A = 10*(1+dA*sin(2*pi*fA*t));
  df=200; fn = 250; fm=4; fi=2*pi*(fn*t-df/(2*pi*fm)*cos(2*pi*fm*t));
  x= A.*cos(fi);
Jest to sygnał z równoczesną modulacją amplitudy i częstotliwości. Na początku przyjmujemy dA = 0, df=0, a potem dA = 0.5, df=200. W drugim przypadku usuwamy znak % przed instrukcjami plot().
function [Ome, de]=fHilbert(x,W)
% Przykład 20.2
% x = A*cos(Om*n+p).*exp(-d*n)
% W - parametr wyboru okna: 0-Hanning, 1-Blackman
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

N = length(x);                    % długość sygnału
if(W==1) win = hanning(N)'; end   % wybór okna
if(W==2) win = blackman(N)'; end  %
Xh = hilbert(win.*x)./win;        % transformacja Hilberta w dziedzinie częstotliwości
Om = diff( unwrap( angle(Xh) ) ); % plot(Om), pause % pulsacja chwilowa - zmiany
A = abs(Xh);                      % plot(A), pause % amplituda chwilowa - zmiany
d = -diff( log(A) );              % plot(d), pause % tłumienie chwilowe - zmiany
ind = round( N/2-N/4 : N/2+N/4 ); % obcięcie oscylacji na brzegach
Ome = mean(Om(ind));              % uśredniona wartość pulsacji
de = mean(d(ind));                % uśredniona wartość tłumienia
% temp = polyfit(ind,log(abs(Xh(ind))),1); de=-temp(1); % regresja liniowa

Przykład 20.3

W przykładzie 20.3 (plik fProny.m), napisanym w języku MATLAB, zaimplementowano opisaną powyżej metodę, opartą na równaniach (20.41), (20.44), (20.49c). Zastosowana wewnętrzna funkcja MATLABa toeplitz() jest w nim wykorzystana do zbudowania macierzy X, takiej jak w (20.48) – jako parametry wejściowe otrzymuje ona pierwszą kolumnę (x(2:N-1)) oraz pierwszy wiersz (x(2:-1:1)) macierzy X. Zwróćmy uwagę, że MATLAB indeksuje elementy wektorów i macierzy od 1, a nie od 0. Do funkcji możemy przekazać do analizy sygnał, generowany w przykładzie 20.1.
function [Ome, de]=fProny(x,K)
% Przykład 20.3
% Metoda Prony'ego
% x - analizowany sygnał - suma tłumionych sinusoid rzeczywistych
% K - założona liczba składowych
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

% DLA JEDNEJ SKŁADOWEJ)
  if(K==1)
     N = length(x); X = toeplitz(x(2:N-1), x(2:-1:1)); x = x(3:N).';  % (20.48)
     a = X\x; % lub a = pinv(X)*x lub inv(X'*X)*X'*x;;                % (20.49c)
     p = log( roots( ([1; -a]) ));                                    % (20.43)
     Ome = abs(imag(p(1))); de = -real(p(1));                         % (20.44)
  end  
      
% DLA KILKU SKŁADOWYCH
  if(K>1)
     N = length(x);
     P = 2*K;                                                         % rząd predykcji
     FirstCol = x(P:N-1); FirstRow = x(P:-1:1);                       %
     X = toeplitz( FirstCol, FirstRow ); x = x(P+1:N).';              % (X.48)(X.57)
     a = X\x;       % lub inv(X'*X)*X'*y; lub a = pinv(X)*y;          % (X.49c)(X.58)
     a1 = [1; -a];  % rozwiązanie Prony’ego
     p = roots( a1 );                                                 %(X.43)(X.59)
     p = log(p) ;                                                     % (X.44)
     Om = imag(p); [Om indx] = sort( Om, 'descend' ); Ome = Om(1:K);  % X.44)
     de = -real(p(indx(1:K)));
  end

Przykład 20.4

W przykładzie 20.4 (plik fSTMB.m), zawierającym implementację algorytmu Steiglitza–McBride’a w języku MATLAB, algorytm ten jest kodowany z uwzględnieniem wzorów (20.56)–(20.63) dotyczących sygnału, który może się składać z większej liczby tłumionych sinusoid (K >= 1).Wprzypadku przyjęcia zerowej liczby iteracji, wyliczane jest jedynie rozwiązanie równania (20.57), czyli wynik metody Prony’ego. Ta część programu stanowi uogólnienie kodu zaprezentowanego w przykładzie 20.3. Do funkcji możemy przekazać do analizy sygnał generowany w przykładzie 20.1.
function [Ome, de]=fSTMB(x,K,NI)
% Przykład 20.4
% x - analizowany sygnał
% K - liczba tłumionych sinusoid A*cos(Om*n+p).*exp(-d*n) w równaniu (20.3)
% NI - liczba iteracji, dla NI=0 otrzymujemy metodę Prony'ego
% Ome - estymata pulsacji (rd)
% de - estymata tłumienia
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

N = length(x);                             % liczba próbek sygnału
P = 2*K;                                   % rząd predykcji
FirstCol = x(P:N-1); FirstRow = x(P:-1:1); % pierwsza kolumna i rząd macierzy X (20.57)
X = toeplitz( FirstCol, FirstRow ); y = x(P+1:N).';   % macierz X w (20.57), (20.48)
a = X\y; % lub inv(X'*X)*X'*y; lub a = pinv(X)*y;     % rozwiązanie (20.58), (20.49c)
a1 = [1; -a];                              % inicjalizacja rozwiązaniem metodą Prony'ego
%a1 = [1; zeros(P,1) ];                    % prostsza inicjalizacja zerami
delta = zeros(1,N); delta(1)=1;            % impuls jednostkowy
while ( NI )
   v = filter(1, a1, x);                              % (20.51)
   u = filter(1, a1, delta);                          % (20.52)
   V = toeplitz( v, [ v(1) zeros(1,P) ] );            % (20.54) (20.63)
   U = toeplitz( u, [ u(1) zeros(1,P-1) ] );          % (20.54) (20.63)
   VU = [ V(1:N,:) U(1:N,:) ];                        % (20.54) (20.63)
   A = VU(:,2:end); d = VU(:,1);                      % (20.54) (20.63)
   c = A\d; % c = inv(A'*A)*A'*d; % c = pinv(A)*d;    % (20.55)
   a1 = [ 1; -c(1:P) ];                               % iteracyjna aktualizacja
   NI = NI-1;
end
p = roots( a1 );                           % (20.43)(20.59)
p = log(p);                                % (20.44)
Om = imag(p); [Om indx] = sort( Om, 'descend' ); Ome = Om(1:K); % (20.44)
de = -real(p(indx(1:K)));                  % (20.44)

Przykład 20.5

W przykładzie 20.5 (plik flpCOR.m) przedstawiono kod w języku MATLAB, pokazujący jak metody korelacyjne implementuje się programowo. Do funkcji możemy przekazać do analizy sygnał generowany w przykładzie 20.1.
function [Ome, de] = flpCOR(x,K,nr)
% Przykład 20.5
% x - analizowany sygnał
% K - liczba tłumionych sinusoid A*cos(Om*n+p).*exp(-d*n) w równaniu (20.3)
% nr - numer metody
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

N = length(x);            % liczba próbek sygnału
L = 2*K;                  % rząd predykcji, podwojona liczba składowych rzeczywistych
M = N-L;                  % maksymalna liczba równań
r = xcorr(x,'unbiased');  % funkcja korelacji: ‘unbiased', ‘biased', 'coeff', 'none'
r = r(N:end);             % tylko przesunięcia nieujemne w funkcji korelacji
if(nr == 1) % ŚREDNIO DOBRE, GRORSZE NIŻ NR=2
   R1 = toeplitz( r(1:L) );              % macierz LxL
   r1 = r(2:L+1).'; % pause              % macierz Lx1
   a = R1\r1;
end
if(nr == 2) % ZAWSZE BARDZO DOBRE
   FirstCol = r(L:L+L-1); FirstRow = r(L:-1:1);
   R2 = toeplitz( FirstCol, FirstRow );  % macierz LxL
   r2 = r(L+1:L+L).'; % pause            % macierz Lx1
   a = R2\r2;
end
if(nr == 3) % DOBRE TYLKO BEZ TŁUMIEŃ
   FirstCol = r(L:M+L-1); FirstRow = r(L:-1:1);
   R3 = toeplitz( FirstCol, FirstRow );  % macierz MxL
   r3 = r(L+1:L+M).'; % pause            % macierz Mx1
   a = R3\r3;
end
a1 = [1 -a.'];
p = roots( a1 ); p = log(p) ;            % (20.43)(20.44)(20.59)
Om = imag(p); [Om indx] = sort( Om, 'descend' ); Ome = Om(1:K);   % (20.44)
de = -real(p(indx(1:K)));

Przykład 20.6

W przykładzie 20.6 (plik fKT.m) jest przedstawiony kod w języku MATLAB funkcji fKT(), w której zaimplementowano algorytm Kumaresana–Tuftsa. Jest to zmodyfikowana wersja procedury lpsvd.m, pochodzącej z biblioteki matNMR [4]. W przeciwieństwie do oryginału działa ona na sygnałach o wartościach rzeczywistych, a nie na zespolonych. Do funkcji możemy przekazać do analizy sygnał, generowany w przykładzie 20.1.
function [Ome, de]=fKT(x,K)
% Przykład 20.6
% x - sygnał analizowany
% K - założona liczba składowych (tłumione sinusoidy o~wartości rzeczywistej)
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl (na podstawie lpsvd.m [4])

M = 2*K;             % liczba składowych zespolonych
N = length(x);       % liczba próbek
L = floor(N*3/4);                      % przyjęty rząd liniowej predykcji L = 3/4*N
Y = hankel( x(2:N-L+1), x(N-L+1:N) );  % macierz próbek, związana z predykcją "do tyłu"
y = x(1:N-L)';       % wektor próbek, związana z predykcją "do tyłu"
[U,S,V] = svd(Y,0);  % dekompozycja względem wartości szczególnych
S = diag(S);
szum = mean(S(M+1:min([N-L,L])));            % przesunięcie - średnia energia szumu
b = V(:,1:M) * (diag(1./(S(1:M)-szum)) * (U(:,1:M)'*y)); % współczynniki predykcji "do tyłu"
z = roots([-b(length(b):-1:1);1]); p=log(z); % pierwiastki wielomianu (20.73), ln()
p = p( find(real(p)<0) );                    % wewnątrz okręgu jednostkowego
Om = imag(p); [Om indx] = sort( Om, 'descend' ); Ome = Om(1:K); % pulsacja kątowa
de = -real(p(indx(1:K)));                    % współczynnik tłumienia

Przykład 20.7

W przykładzie 20.7 (plik fMatPen.m) zaprezentowano funkcję fMatPen() [36] napisaną w języku MATLAB. Kod w języku Fortran można znaleźć w [29]. Do funkcji możemy przekazać do analizy sygnał, generowany w przykładzie 20.1.
function [Ome, de] = fMatPen(x,K)
% Przykład 20.7
% x - analizowany sygnał - suma tłumionych sinusoid rzeczywistych
% K - założona liczba składowych
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

M = 2*K;                        % liczba składowych zespolonych
N = length(x);                  % liczba próbek sygnału
L = floor(N/3);                 % przyjęty rząd liniowej predykcji L = N/3
X = hankel(x(1:N-L),x(N-L:N));  % X0=X(:,1:L), X1=X(:,2:L+1)
[U,S,V] = svd(X(:,2:L+1), 0); S = diag(S);                            % SVD( X1 )
p = log( eig( diag(1./S(1:M)) * ((U(:,1:M)'*X(:,1:L))*V(:,1:M)) ) );  % ln(EVD( Z ))
Om = imag(p); [Om indx] = sort( Om, 'descend' ); Ome = Om(1:K);       % pulsacja
de = real(p(indx(1:K)));        % tłumienie

Przykład 20.8

W przykładzie 20.8 (plik fPisarenko.m) jest zaimplementowana w języku MATLAB jedynie metoda Pisarenki, ponieważ bezpośrednio można w niej wyliczyć poszukiwane wartości parametrów sygnału. W innych metodach znajduje się je metodą poszukiwania maksimów odpowiedniej funkcji kosztu, co jest bardziej kłopotliwe. Nie jest to duża strata, gdyż brakujące algorytmy mają gorszą skuteczność, niż opisane metody Kumaresana–Tuftsa, TLS i Matrix Pencil. Do funkcji możemy przekazać do analizy sygnał generowany w przykładzie 20.1, z d = [0, 0, 0, 0].
function [Ome]=fPisarenko(y,K)
% Przykład 20.8
% y - analizowana suma tłumionych sinusoid o wartościach rzeczywistych w szumie
% K - założona liczba składowych
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

N = length(y);                   % liczba próbek sygnału
L = 2*K;                         % rząd predykcji, liczba składowych zespolonych
ryy=xcorr(y,'unbiased');         % do wyboru: 'unbiased', 'biased', 'coeff', 'none'
ryy = ryy(N:end);                % z powodu symetrii, bierzemy tylko połowę wyniku
Ryy = toeplitz( ryy(1:L+1) );    % zbudowanie macierzy Ryy
[V,D] = eig(Ryy);                % dekompozycja EVD tej macierzy
[Dmin indx] = min(diag(D));      % znalezienie najmniejszej wartości własnej
a = V(:,indx).';                 % znalezienie skojarzonego z nią wektora własnego
z = roots(a); Om = imag(log(z)); % znalezienie pierwiastków wielomianu
Om = sort( Om, 'descend' ); Ome = Om(1:K); % pulsacja (odrzucenie wartości ujemnych)

Przykład 20.9

W przykładzie 20.9 (plik fESPRIT.m) jest przedstawiony kod w języku MATLAB algorytmu ESPRIT. Do funkcji możemy przekazać do analizy sygnał generowany w przykładzie 20.1, z d = [0, 0, 0, 0].
function [Ome]=fESPRIT(x,K)
% Przykład 20.9
% x - analizowany sygnał - suma tłumionych sinusoid o~wartościach rzeczywistych
% K - założona liczba składowych
% Autor: Tomasz ZIELIŃSKI, tzielin@agh.edu.pl

N = length(x);                     % licza próbek sygnału
L = 2*K;                           % rząd predykcji, liczba składowych zespolonych
L1 = L+1;
r=xcorr(x,'unbiased');             % do wyboru: 'unbiased', 'biased', 'coeff', 'none'
r = r(N:end);                      % funkcja autokorelacji ryy
R0 = toeplitz( r(1:L+1) );         % macierz autokorelacji Ry0y0
R1 = [ R0(:,2:L1) r(L1+1:-1:2)'];  % macierz autokorelacji Ry0y1
[V,D] = eig(R0);                   % EVD( Ry0y0 )
sigma = min(diag(D));              % minimalna wartość własna
I = eye(L1); J = [zeros(1,L1); I(1:L1-1,1:L1) ];  % macierze I, J
C0 = R0 - sigma*I; C1 = R1 - sigma*J;             % macierze C0= Cy0y0. C1=Cy0y1
S = eig(C0, C1);                                  % EVD( C0, C1) - Matrix-Pencil
S = S( find( abs(S) > 0.5 ) );     % znajdź wartości zespolone o module > 0.5
Om = atan2( imag(S), real(S) );    % kąt liczby zespolonej
Om = sort( Om, 'descend' ); Ome = Om(1:K); % pulsacje (odrzucenie wartości ujemnych)