In dit practicum komt een groot aantal aspecten aan bod van het werken met de discrete Fouriertransformatie (DFT), en de efficiënte implementatie daarvan in de vorm van de FFT.
Er wordt vanuit gegaan dat je je degelijk hebt voorbereid door de teksten over de Fouriertransformatie en het tijd-frequentievlak grondig door te lezen. Het practicum omvat nagenoeg alle onderwerpen die hierin aan bod gekomen zijn en borduurt er hier en daar op voort (in het geval van windows bijvoorbeeld).
De uitvoering van het practicum is eenvoudig. Selecteer alle
Matlab-commando's die in fixed-font
zijn weergegeven
in de volgorde zoals aangegeven. Doorgaans vormen ze aparte
paragrafen. Wanneer je een commando vergeet zal Matlab meestal
even later klagen over niet-gedefinieerde waarden. Herhaal dan de
voorgaande commando's. Zorg ervoor dat je de commando's niet blind
uitvoert, maar probeer te begrijpen wat je doet. In veel gevallen
geven de Matlab-commando's je al een deel van het antwoord.
Matlab is overigens een computertaal die in de wetenschap en de technologie zeer veel gebruikt wordt om prototypen te bouwen, data te analyseren en systemen te simuleren.
Voor het practicum is een drietal files nodig die in je Matlab "path" moeten komen te staan. Maak bijvoorbeeld ~/matlab/spraak en zet daarin de files uit practFFT.zip:
fftGram.m logsweep.m mfcc.m winHamming.m winHanning.m nul123.wav
Start Matlab en ga naar je workdirectory (werkbalkje bovenin). Voer het onderstaande commando uit door het via "control-C" en "control-V" naar het Matlab commandovenster te kopiëren.
format compact % Haalt nodeloze lege regels weg
onderwerpen
De reële discrete Fouriertranformatie (DFT) is besproken in
de tekst over de Fouriertransformatie. We gaan eerst
een matix maken waarin alle basisvectoren staan. Laten we uitgaan
van een N = nfft = 32
dimensionale inputvector. Als
basisvectoren hebben we in het tijddomein
cos(ω0)
, cos(ωk)
en
sin(ωk)
voor k = 1:N/2-1
, en
cos(ωN/2)
. We kunnen deze 32 basisvectoren in
een NxN-matrix stoppen.
Voer onderstaande regels uit in het Matlab commandovenster.
nfft = 32;
B = zeros(nfft,nfft);
k = 0;
B(:,1) = cos(2*pi*k*(0:nfft-1)/nfft)' / sqrt(nfft);
for k = 1:nfft/2-1
B(:,k*2) = sin(2*pi*k*(0:nfft-1)/nfft)' / sqrt(nfft/2);
B(:,k*2+1) = cos(2*pi*k*(0:nfft-1)/nfft)' / sqrt(nfft/2);
end
B(:,nfft) = cos(2*pi*nfft/2*(0:nfft-1)/nfft)' / sqrt(nfft);
n = [1 2 3 4 5 nfft]; % basisvector nummer
plot(B(:,n), '-*'); axis([0 nfft+1 [-1.1 1.1]*max(B(:))])
title('Basisvectoren ')
opdracht A
nfft
-de basisvector buiten de loop?k
gekoppeld wordt aan
kolomnummer (d.w.z. basisvectornummer) n
van
B
.fs
, geef formules
voor de frequentie van de basisvectoren, en voor het tijdstip
van elk van de samplewaarden in de basisvectoren (start op
t = 0 ms
). (Zie
hier.)A1*A2 =
C
geldt dat matrixelementen C(i,j)
bepaald
worden door de som van het elementsgewijze product van rij
i
van A1
en kolom j
van
A2
. Geef aan waarom het matlab commando
imagesc(B*B'); colorbar
aantoont dat voorB
voldaan wordt aan de eisen voor basisvectoren.
Nu we de basisvectoren hebben kunnen we de FT uitvoeren op
nfft-dimensionale inputvectoren. Laten we eens twee pulsen nemen:
p1
met de puls op positie 1 en p5
met de
puls op positie 5.
nfft = 32;
p1 = zeros(1,nfft); p1(1) = 1; % puls op vectorpositie 1
p5 = zeros(1,nfft); p5(5) = 1; % puls op vectorpositie 5
Zoals uitgelegd zijn de bijdragen van de Fourier-basisvectoren te berekenen als inproduct tussen de input en de basisvectoren, ofwel:
P1_direct = zeros(1,nfft); P2_direct = zeros(1,nfft); % initialisaties om f-domein repr. te alloceren
for ii = 1:nfft
P1_direct(ii) = sum(p1.*B(:,ii)'); % inproduct op basisvector
P5_direct(ii) = sum(p5.*B(:,ii)');
end
In Matlab is er een compactere en intuïtieve schrijfwijze die overeen komt met de definitie:
P1 = p1*B; % DFT van p1
P5 = p5*B; % DFT van p5
Check dat beide berekeningswijzen tot identieke resultaten leiden
(P1==P1_direct
).
opdracht B
P1
en P5
identiek
moeten zijn aan p1
en p5
. Leg uit
waarom dit blijkt uit:
sum(abs(P1*B' - p1))
sum(abs(P5*B' - p5))
eps*nfft
(eps
is het kleinste positieve getal ongelijk 0 dat
Matlab kan representeren).
Het opbouwen van p1
uit cosinus en sinus
basisvectoren:
for n = 1:nfft
stem(P1(1:n)*B(:,1:n)');
axis([0 nfft+1 -1 1])
title(['Construction of pulse P1 using n=1:' int2str(n)])
pause(10./nfft)
end
Het opbouwen van p5
uit cosinus- en
sinusbasisvectoren:
for n = 1:nfft
clf
stem(P5(1:n)*B(:,1:n)'); hold on
plot(((P5(1:n)'*(ones(1,nfft))).*B(:,1:n)')'); hold off
axis([0 nfft+1 -1 1])
title(['Construction of pulse P5 using n=1:' int2str(n)])
pause(10./nfft) % pause zonder tijdindicatie in sec wacht op toets
end
opdracht C
pause
, d.w.z. laat
het deel (10/nfft)
weg. Type daarna ;
end
om de FOR-lus af te maken en de berekening te
starten. Doordat de het pause
-commando nu geen
tijd-indicatie heeft moet je na elke basisvectorbijdrage op een
toets drukken. Kijk goed wat er gebeurt. p5
worden de gewogen
basisvectoren als lijnen geplot. Geef aan de hand van het
ontstane patroon aan hoe je met de beschikbare basisvectoren,
die geen van allen een bijzonder voorkeur hebben voor een puls
op positie 5, toch een scherpe puls op die positie kunt krijgen
terwijl alle andere posities nul zijn.Met behulp van de DFT is elke vector uit sinus- en cosinusbasisvectoren op te bouwen.
x = 2*(rand(1,nfft)-0.5); % random vector met waarden tussen -1 en 1
X = x*B; % FT van randomvector
L = nfft;
for n = 1:nfft
clf; hold on
plot(1:L, x, '*r'); axis([0 L+1 -1 1]) % Target
stem(1:L, X(1:n)*B(:,1:n)'); % Benadering met basisvectoren
axis([0 L+1 -1 1]); hold off
title(['Construction of random signal: n = ' int2str(n)])
pause(10./nfft)
end
opdracht D
De complexe discrete FT, uitgevoerd met het Fast Fourier Tansformatie, is veel efficiënter om uit te rekenen en eigenlijk ook intuïtiever in het gebruik dan de DFT op basis van de matrixberekening zoals tot nu toe. Er is een aantal verschillen met boven: (Kijk hier.)
k
zitten de cosinus en sinus
bijdragen in het zelfde complexe getal, repectievelijk als
reëel en imaginair deel.k = n+1
voor
k < N/2
.n > N/2
dat dit de complex geconjugeerden zijn van
de rond n = N
gespiegelde waarden.N
te groot. Wanneer de energie
van x
gelijk aan is aan 1 dan is de energie van
X = fft(x)
gelijk aan sum(X.*conj(X)) =
N
, maar de energie van ifft(fft(x))
is weer
gelijk aan aan 1. De inverse transformatie maakt dit weer
goed. (In verband met numerieke efficiëntie worden de
sqrt
berekening en de delingen voorkomen.)
Voer help fft
uit en lees de bijbehorende uitleg.
opdracht E
x = ifft(fft(x))
.
de efficiëntie van de FFT
De belangrijkste reden voor de voorkeur voor het toepassen van de
FFT boven de directe berekening op basis van het afbeelden op de
basisvectoren is dat de FFT veel efficiënter berekend
wordt. De FFT is efficiënter doordat optimaal gebruik wordt
gemaakt van het feit dat de basisvectoren, als sinusoiden, zoveel
symmetrieën vertonen. Daarom wordt maar een minimaal aantal
sinus- of cosinuswaarden berekend, de rest volgt uit
symmetrieoverwegingen. De FFT is maximaal efficiënt wanneer
de inputvector steeds in tweeën kan worden verdeeld. Dit is
het meest het geval bij machten van 2. Daarom worden FFT's in de
praktijk ook meestal op vectoren die een macht van twee zijn
losgelaten.
Het efficiëntie verschil tussen de directe berekening en de FFT kan worden bepaald met de volgende code:
m = 10
nfft = 2.^m % Verzameling nfft-dimensionale basisvectoren
B = zeros(nfft,nfft); k=0;
B(:,1) = cos(2*pi*k*(0:nfft-1)/nfft)' / sqrt(nfft);
for k = 1:nfft/2-1
B(:,k*2) = sin(2*pi*k*(0:nfft-1)/nfft)' / sqrt(nfft/2);
B(:,k*2+1) = cos(2*pi*k*(0:nfft-1)/nfft)' / sqrt(nfft/2);
end
B(:,nfft) = cos(2*pi*nfft/2*(0:nfft-1)/nfft)' / sqrt(nfft);
p = rand(1,nfft);
t1 = cputime; for ii = 1:1000; p*B; end; t1 = cputime-t1
t2 = cputime; for ii = 1:1000; fft(p); end; t2 = cputime-t2
opdracht F
m = 10
) is de FFT sneller
dan de directe berekening? Voer de berekening meer dan 1 keer
uit.
De impuls-respons van een lineair systeem (in dit geval van de FT)
is van groot belang omdat het (bijna) alles zegt over het
systeem. Elke input kan immers beschreven worden door een
opeenvolging van pulsen, wanneer je dus weet hoe het systeem
reageert op een puls, kun je door een optelling van de reactie op
elk van een opeenvolging van pulsen de reactie op een willekeurig
input berekenen. Laten we een kijken wat er gebeurt wanneer de de
puls van positie 1 tot positie nfft
opschuiven.
nfft = 32;
for t = 1:nfft
p = zeros(1,nfft); p(t) = 1; % puls op vectorpositie 1
P = fft(p);
Pcos = real(P);
Psin = imag(P);
stem(p); hold on
plot(1:nfft, Pcos, '-r*', 1:nfft, Psin, '-g*'); hold off
title(['Reele en imaginaire deel van puls op positie ' int2str(t)]);
axis([0 nfft+1 -1.1 1.1]);
xlabel('Basisvector nummer');
pause % Ga door na toetsaanslag
end
opdracht G
n > N/2
geldt dat dit de complex
geconjugeerden zijn van de rond n = N
gespiegelde
waarden zijn. Waarom hebben de cosinusbijdragen dit niet?Windows spelen vooral een rol wanneer je uit een langer signaal, een kleiner deel wilt selecteren om er een FT-analyse op uit te voeren. Laad eerst een stukje spraaksignaal.
[x, fs] = wavread('nul123.wav');
soundsc(x, fs) % Afspelen geluidssignaal x
opdracht H
size
.)
Selecteer en window een deel van x
vanaf sample 2001
tot 2000 + nfft
:
nfft = 256;
y = x(2000+[1:nfft]);
yham = y.*winHamming(nfft);
yhann = y.*winHanning(nfft);
subplot(2,1,1)
plot((1:length(y))/fs*1000,y,(1:length(y))/fs*1000,yham,
(1:length(y))/fs*1000,yhann);
axis tight; grid on
xlabel('Time in ms')
opdracht I
Bereken en plot de spectra in dB:
subplot(2,1,2)
f_As = 0:fs/nfft:fs/2;
Y = 10*log10(abs(fft(y))); Y = Y(1:nfft/2+1);
Yham = 10*log10(abs(fft(yham))); Yham = Yham(1:nfft/2+1);
Yhann = 10*log10(abs(fft(yhann))); Yhann = Yhann(1:nfft/2+1);
plot(f_As,Y,f_As,Yham,f_As,Yhann);
grid on;
xlabel('Frequentie in Hz'); ylabel('Energie in dB')
Check of je tijddomeinberekening van de twee belangrijkste frequenties klopt.
opdracht J
Als voorbeeldsignaal nemen we nu een logsweep, dat is een signaal
dat in frequentie verandert op een wijze die op een logaritmische
as lineair is. Het eigenschap van een logsweep (die een lineaire
sweep, met de functie linsweep.m
, niet heeft) is dat
elke octaaf verandering (een factor twee in frequentie) evenlang
duurt.
In dit geval gaan we in 3 seconden van 50 Hz naar 3500 Hz. Dat komt overeen met 6.1 octaaf in 3 seconden. Dit komt dus overeen met ongeveer 2 octaaf per seconde (een octaaf is een factor 2).
opdracht K
Toets help logsweep
en lees de tekst. Het enige
bijzondere is FRATE
, dit is de inverse (uitgedrukt in
Hz) van stepT
(in ms) zoals die door
fftGram
wordt gebruikt. FRATE
bepaalt
het aantal frequentiewaarden f
dat per seconde wordt
gegenereerd. Hiermee krijgen we voor elke kolom in het spectrogram
een bijbehorende waarde van f
.
startf = 50; % Hz
endf = 3500; % Hz
duration = 3; % sec
fs = 8000; % Hz
offsetPhase = 0; % [0,1], 0 is sinus-fase, 0.25 = cosinus-fase
FRATE = 200; % ms, frame rate
[x,f] = logsweep(startf, endf, duration, fs, offsetPhase, FRATE);
t = linspace(0,duration,length(f));
We hebben nu een signaal x
(samplefrequentie
fs = 8000 Hz
) met een frequentieontwikkeling
f
met bijbehorende tijdstippen t
(gesampled op 200 Hz, dus elke 5 ms). Het "geluidssignaal"
x
is af te spelen met sound(x,fs)
.
Merk op dat je dit als een continu en gelijkmatig geheel
waarneemt. En te plotten met:
clf;
tx = linspace(0,3,length(x));
plot(tx, x);
Dit geeft een slecht interpreteerbaar plaatje.
opdracht L
t = 0.5 s
en
t = 0.7 s
via
tStart = 0.5; tEnd = 0.7;
sampStart = fs*tStart;
sampEnd = fs*tEnd;
plot(tx(sampStart:sampEnd), x(sampStart:sampEnd));
axis tight; grid on
t = 3 s
.
Een andere (handiger) manier om in te zoomen is met gebruik van de
functie strips
(zie help strips
) en het
gebruik van de muis om in te zoomen: (Dit werk niet zonder Signal
Processing Toolbox!) strips(x, .5, fs) % Geeft 6 strips
van elk 0.5 sec
opdracht M
t = 1.5 s
zie je waarschijnlijk wat structuur
die verdwijnt wanneer je er op inzoomt. In de onderste strip vindt
je structuren waar dat niet voor geldt. Geef voor beide
verschijnselen een verklaring.Tot slot van dit onderdeel nog even check van de instantane frequentieontwikkeling zich gedraagt zoals dat zou moeten.
subplot(2,1,1)
plot(t, f); title('Lineaire frequentie-as'); grid
subplot(2,1,2)
semilogy(t, f); title('Logaritmische frequentie-as'); grid
Voor een logaritme geldt dat een vaste afstand op de x-as correspondeert met een bepaalde factor. Controleer dit door een paar punten van de bovenste subplot te nemen.
We gaan ons nu richten op het visualiseren van de frequentieontwikkeling als functie van de tijd in de vorm van spectrogrammen. We gebruiken eerst een rechthoekig window.
stepT = 1000/FRATE; % factor 1000 om ms om te zetten in sec
nfft = 256;
DynRange = 60; % plot met 60 dB dynamische bereik.
clf
[S SInfo] = fftGram(x, fs, nfft, stepT, 'logsweep with rectangular window', DynRange);
colorbar
In ieder geval is het een mooi kleurig plaatje. Het is echter de vraag is of het ook een goed plaatje is. Volgens de tijddomeinanalyse kan het signaal op elk moment beschreven worden met een een enkele frequentie, en zeker niet met steeds in sterkte wisselende bijdragen van vrijwel alle andere frequenties (en dus alle andere FT-basisvectoren). Deze bijdragen worden ook wel "lek" genoemd.
opdracht N
hold on; plot(t,f,'w'); hold off
Zoom in op de de linker onderkant. Wat concludeer je over de bijdrage van de lek als functie van de instantane frequentief
?figure
en
gebruik de zoom functie hierboven om een
enkel window te visualiseren. Kies een tStart
en
maak gebruik van tEnd = tStart + nfft/fs;
Houdt er
rekening mee dat de tijd-as correspondeert met het midden van
een window. Laten we nu het effect van de al eerder geïntroduceerde windows eens analyseren.
DynRange = 120;
clf;
subplot(2,2,1)
[Srect SInfo] = fftGram(x, fs, nfft, stepT, 'rect', DynRange);
subplot(2,2,2)
[Striang SInfo] = fftGram(x, fs, nfft, stepT, 'triang', DynRange);
subplot(2,2,3)
[Shamming SInfo] = fftGram(x, fs, nfft, stepT, 'hamming', DynRange);
subplot(2,2,4)
[Shanning SInfo] = fftGram(x, fs, nfft, stepT, 'hanning', DynRange);
opdracht O
We maken een aparte figuur en zoomen in tussen t = 0.6
s
en t = 1.2 s
en f = 0 Hz
tot
f = 400 Hz
.
figure
subplot(2,2,1)
imagesc(SInfo.T, SInfo.F, Srect./max(Srect(:)));
set(gca, 'ydir', 'normal');
ylabel('Freq in Hz'); title('rect')
hold on; plot(t,f,'w'); hold off
axis([.6 1.2 0 800]);
subplot(2,2,2)
imagesc(SInfo.T, SInfo.F, Striang./max(Striang(:)));
set(gca, 'ydir', 'normal');
ylabel('Freq in Hz'); title('triang')
hold on; plot(t,f,'w'); hold off
axis([.6 1.2 0 800])
subplot(2,2,3)
imagesc(SInfo.T, SInfo.F, Shamming./max(Shamming(:)));
set(gca, 'ydir', 'normal');
xlabel('Time in sec'); ylabel('Freq in Hz'); title('hamming')
hold on; plot(t,f,'w'); hold off
axis([.6 1.2 0 800])
subplot(2,2,4)
imagesc(SInfo.T, SInfo.F, Shanning./max(Shanning(:)));
set(gca, 'ydir', 'normal');
xlabel('Time in sec'); ylabel('Freq in Hz'); title('hanning')
hold on; plot(t,f,'w'); hold off
axis([.6 1.2 0 800])
opdracht P
twee componenten
We gaan de situatie nu een beetje realistischer maken door aan de
ene geluidscomponent een tweede, genaamd x2
, toe te
voegen. Dit is ook een logsweep, maar nu precies in tijd
omgedraaid (dat was via x2 = fliplr(x);
gemakkelijker
geweest dan het signaal overnieuw maken zoals hieronder
gebeurt). De tweede logsweep x2
loopt van
f = 3500 Hz
naar f = 50 Hz
.
startf = 3500; % Hz
endf = 50; % Hz
duration = 3; % sec
fs = 8000; % Hz
offsetPhase = 0; % [0,1], 0 is sinus-fase, 0.25 = cosinus-fase
FRATE = 200; % ms
[x2,f2] = logsweep(startf, endf, duration, fs, offsetPhase, FRATE);
soundsc(x2, fs) % plays logsweep
Om het nog wat interessanter te maken gaan we x2
een
factor 1000 zwakker maken:
soundsc(x+x2/1000,fs) % plays x en x2
opdracht Q
Van de gewogen optelling gaan we weer een spectrogram maken. Eerst weer een rechthoekig window:
stepT = 1000/FRATE; % ms
nfft = 256;
DynRange = 120;
clf % clear figure
[S SInfo] = fftGram(x+x2/1000, fs, nfft, stepT, 'rect', DynRange);
Het is duidelijk dat er (nagenoeg) niets te zien is van een downsweep in deze spectrale weergave. Laten we nu de andere windows vergelijken.
subplot(2,2,1)
[Srect SInfo] = fftGram(x+x2/1000, fs, nfft, stepT, 'rect', DynRange);
subplot(2,2,2)
[Striang SInfo] = fftGram(x+x2/1000, fs, nfft, stepT, 'triang', DynRange);
subplot(2,2,3)
[Shamming SInfo] = fftGram(x+x2/1000, fs, nfft, stepT, 'hamming', DynRange);
subplot(2,2,4)
[Shanning SInfo] = fftGram(x+x2/1000, fs, nfft, stepT, 'hanning', DynRange);
opdracht R
Wanneer we inzoomen tussen t = 0.6 s
en t = 1.2
s
en f = 0 Hz
tot f = 800 Hz
, kunnen
we de verschillen nog wat beter zien.
subplot(2,2,1)
imagesc(SInfo.T, SInfo.F, Srect);
set(gca, 'ydir', 'normal');
xlabel('Time in sec');
ylabel('Freq in Hz'); title('rect')
axis([.5 2.5 0 800])
subplot(2,2,2)
imagesc(SInfo.T, SInfo.F, Striang);
set(gca, 'ydir', 'normal');
xlabel('Time in sec');
ylabel('Freq in Hz'); title('triang')
axis([.5 2.5 0 800])
subplot(2,2,3)
imagesc(SInfo.T, SInfo.F, Shamming);
set(gca, 'ydir', 'normal');
xlabel('Time in sec');
ylabel('Freq in Hz'); title('hamming')
axis([.5 2.5 0 800])
subplot(2,2,4)
imagesc(SInfo.T, SInfo.F, Shanning);
set(gca, 'ydir', 'normal');
xlabel('Time in sec');
ylabel('Freq in Hz'); title('hanning')
axis([.5 2.5 0 800])
opdracht S
MFCC's zijn de standaard input voor ASR systemen. Kernpunt van de MFCC's is met zo weinig mogelijk getallen de vorm van de spectrale omhullende (waarin de formantinformatie besloten zit) weer te geven. Bij MFCC's wordt niet gestreefd naar het vaststellen van individuele formanten (wat in het algemeen erg moeilijk is), maar naar een representatie die de zelfde informatie vertegenwoordigt, zonder dat je de kans loopt om fouten te maken in de schatting van individuele formanten.
nfft = 128;
stepT = 10;
[x0123, fs] = wavread('nul123.wav');
clf;
[S SInfo] = fftGram(x0123, fs, nfft, stepT, 'hamming', 60);
opdracht T
De MFCC wordt berekend door in elk frame het volgende recept uit te voeren: (in Matlab mfcc.m)
Als laatste stap kun je de informatie die je overhoudt weer terugtransformeren met een cosinustransformatie om een visuele representatie van het "spectrum" dat de MFCC's representeren te maken. De frequentie-as van dit spectrum komt overeen met de centrumfrequenties van de driehoekige Mell-filterbank.
Doe nu:
[ceps,freqresp,fb,fbrecon,freqrecon, freqs] = mfcc(x0123, fs, 100);
subplot(2,1,1)
imagesc(ceps); axis xy % De MFCC's
xlabel('Tijd in frames van 10 ms')
ylabel('Cosinus-basisvector #')
subplot(2,1,2);
imagesc(fbrecon); axis xy % Een visuele reconstructie hiervan
xlabel('Tijd in frames van 10 ms')
ylabel('Filterbank #')
freqs(2:end) % Centrum frequenties van de filterbank
De laagste component van de MFCC's (de energie) wijkt vaak af in amplitude. Je krijgt een beter zicht op bijdragen van de cosinusbasisvectoren wanneer je deze component weglaat in de visualisatie.
subplot(2,1,1)
imagesc(3:size(ceps,2), 1:size(ceps,1), ceps(2:end,:)); axis xy
xlabel('Tijd in frames van 10 ms')
ylabel('Cosinus-basisvector #')
opdracht U
postadres
Auditory Cognition Group
Kunstmatige Intelligentie
Rijksuniversiteit Groningen
Postbus 407
9700 AK Groningen
bezoekadres
Bernoulliborg
Nijenborgh 9
9747 AG Groningen