Sunteți pe pagina 1din 8

Introduccin

En este artculo vamos a ver cmo calcular la transformada de Fourier discreta (o DFT) de
una seal en Python utilizando la transformada rpida de Fourier (o FFT) implementada en
SciPy. El anlisis de Fourier es la herramienta fundamental en procesamiento de seales y
resulta til en otras reas como en la resolucin de ecuaciones diferenciales o en el
tratamiento de imgenes.
Nota: Puedes ver el artculo en forma de notebook de IPython mediante la herramienta
nbviewer.
La transformada rpida de Fourier o FFT es en realidad una familia de algoritmos muy
eficientes ( ) para calcular la DFT de una seal discreta, de los cuales el ms
utilizado es el algoritmo de Cooley-Tukey. Este es el que est implementado en SciPy a
travs de las subrutinas FFTPACK, escritas en FORTRAN 77, y es el que vamos a utilizar.
Tambin podramos haber utilizado la biblioteca FFTW, ms moderna y escrita en C, o la
implementacin presente en NumPy, que es una traduccin a C de FFTPACK y que
funciona igual que la de SciPy, pero no lo vamos a hacer.
Ntese que estos mtodos no nos permiten calcular ni la serie de Fourier de una funcin
peridica ni la transformada de Fourier de una funcin no peridica. Estas operaciones
forman parte del clculo simblico y deben llevarse a cabo con otro tipo de programas,
como SymPy o Sage. En Pybonacci puedes leer unaintroduccin a SymPy y cmo calcular
series con SymPy, as como una resea sobre Sage.
En este artculo nos vamos a centrar en el caso unidimensional, aunque el cdigo es
fcilmente extrapolable al caso de dos y ms dimensiones.
En esta entrada se ha usado python 2.7.3, numpy 1.6.2, scipy 0.10.1 y matplotlib 1.1.1.
Definiciones
Antes de empezar de lleno con las FFTs vamos a hacer una brevsima introduccin
matemtica. He intentado que esto no se convierta en una larga introduccin al
procesamiento de seales digitales, pero si te interesa el tema y quieres ms rigor puedes
acudir a cualquiera de las referencias que doy ms abajo adems de ladocumentacin
de scipy.fftpack.
En este artculo vamos a manejar seales discretas reales, bien creadas por nosotros o
procedentes demuestrear una seal analgica a una frecuencia de muestreo fs.
La transformada discreta de Fourier o DFT de la seal de entrada, que diremos que est en
el dominio del tiempo, ser otra seal discreta, n generalcompleja, cuyos coeficientes
vienen definidos en SciPy por la expresin

siendo los coeficientes de la seal de entrada y los de la seal obtenida. Diremos
que est en el dominio de la frecuencia y que es el espectro o transformada de . La
resolucin en frecuencia ser mayor cuanto mayor sea el nmero de intervalos de la
seal de entrada. La DFT se calcula con el algoritmo de la transformada rpida de
Fourier o FFT, que es ms eficiente si es una potencia entera de 2.
Cada uno de los componentes de frecuencia se puede expresar en forma compleja
como . El espectro complejo de una funcin seno, por ejemplo, se obtiene
directamente de su expresin en forma compleja

y tendr, por tanto, una raya espectral a la frecuencia y otra a la frecuencia , como ya
comprobaremos.
La DFT de una seal siempre asume que la seal de entrada es un perodo de una
secuencia peridica. Esto ser importante por lo que luego veremos. Sera, por tanto, el
equivalente en tiempo discreto a las series de Fourier.
FFT de una funcin senoidal sencilla
Vamos a calcular la FFT de una funcin senoidal en Python utilizando SciPy. Para que sea
verdaderamente sencilla tenemos que preparar un poco los datos, o si no acabaremos
observando efectos adversos. Estos los estudiaremos ms adelante.
Para manejar nmeros redondos, vamos a recrear una seal con un armnico
de y otro del doble de frecuencia:

donde . Ntese que, por lo que hemos visto en el apartado anterior, veremos
cuatro rayas en el espectro de esta funcin: dos para la frecuencia y otras dos para
la . Vamos a imponer a priori el nmero de intervalos y la distancia entre ellos y de ah
vamos a calcular el intervalo de tiempo. Evidentemente as no se funciona cuando
muestreamos un archivo de audio, pongo por caso, pero como digo as obtendremos el
resultado exacto. El cdigo y la salida seran las siguientes:
1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt
import numpy as np
from numpy import pi

n = 2 ** 6 # Nmero de intervalos
f = 400.0 # Hz
dt = 1 / (f * 16) # Espaciado, 16 puntos por perodo

t = np.linspace(0, (n - 1) * dt, n) # Intervalo de tiempo en segundos
y = np.sin(2 * pi * f * t) - 0.5 * np.sin(2 * pi * 2 * f * t) # Seal
10
11
12
13
14
15

plt.plot(t, y)
plt.plot(t, y, 'ko')
plt.xlabel('Tiempo (s)')
plt.ylabel('$y(t)$')

Esta es la seal que vamos a transformar. Fijos en el ltimo punto representado. Como el
intervalo va desde 0 hasta n - 1, los trozos empalman perfectamente. Ahora vamos a
hallar la DFT de la seal y. Para ello necesitamos dos funciones, que se importan del
paquete scipy.fftpack:
La funcin fft, que devuelve la DFT de la seal de entrada. Si queremos que est
normalizada, tenemos que dividir despus por el nmero de intervalos N.
La funcin fftfreq, que devuelve las frecuencias a las que corresponde cada punto
de la DFT. Se usa conjuntamente con la funcin anterior y acepta dos argumentos: el
nmero de elementos n y el espacio entre intervalos dt, que ya hemos calculado
antes.
Como en este caso solo hay dos frecuencias fundamentales, vamos a representarlas
utilizando la funcin vlines de matplotlib. El cdigo y la salida quedaran as:
1
2
3
4
5
6
7
8
9
10
11
12
from scipy.fftpack import fft, fftfreq

Y = fft(y) / n # Normalizada
frq = fftfreq(n, dt) # Recuperamos las frecuencias

plt.vlines(frq, 0, Y.imag) # Representamos la parte imaginaria
plt.annotate(s=u'f = 400 Hz', xy=(400.0, -0.5), xytext=(400.0 + 1000.0, -0.5 - 0.35), arrowprops=dict(arrowstyle =
"->"))
plt.annotate(s=u'f = -400 Hz', xy=(-400.0, 0.5), xytext=(-400.0 - 2000.0, 0.5 + 0.15), arrowprops=dict(arrowstyle =
"->"))
plt.annotate(s=u'f = 800 Hz', xy=(800.0, 0.25), xytext=(800.0 + 600.0, 0.25 + 0.35), arrowprops=dict(arrowstyle =
"->"))
plt.annotate(s=u'f = -800 Hz', xy=(-800.0, -0.25), xytext=(-800.0 - 1000.0, -0.25 - 0.35),
arrowprops=dict(arrowstyle = "->"))
13
plt.ylim(-1, 1)
plt.xlabel('Frecuencia (Hz)')
plt.ylabel('Im($Y$)')

Nota: Si quieres leer ms sobre aadir texto y anotaciones a una grfica en matplotlib,
puedes leer la parte VIII del nuestro magnfico tutorial de matplotlib por Kiko :)
Y ya hemos hecho nuestra primera transformada discreta de Fourier con el algoritmo FFT!
Pero las cosas en la realidad son bastante ms complicadas. Vamos a ver algunos de los
problemas que apareceran con frecuencia.
Fuga, zero-padding y funciones ventana
Vamos a analizar el ejemplo anterior, pero en esta ocasin vamos a poner menos cuidado
con la preparacin de los datos. Veamos qu ocurre:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
n2 = 2 ** 5

t2 = np.linspace(0, 0.012, n2) # Intervalo de tiempo en segundos
dt2 = t2[1] - t2[0]
y2 = np.sin(2 * pi * f * t2) - 0.5 * np.sin(2 * pi * 2 * f * t2) # Seal

Y2 = fft(y2) / n2 # Transformada normalizada
frq2 = fftfreq(n2, dt2)

fig = plt.figure(figsize=(6, 8))

ax1 = fig.add_subplot(211)
ax1.plot(t2, y2)
ax1.set_xlabel('Tiempo (s)')
ax1.set_ylabel('$y_2(t)$')

ax2 = fig.add_subplot(212)
ax2.vlines(frq2, 0, Y2.imag)
plt.xlabel('Frecuencia (Hz)')
plt.ylabel('Im($Y_2$)')
19
20

Como se puede ver, el fenmeno no es extremadamente importante pero han aparecido
otras rayas espectrales que no esperbamos. Esto se conoce como leaking (y yo lo voy a
traducir por fuga) y es debido a que, en este caso, los trozos no empalman
exactamente. Recuerda que la DFT, y por extensin la FFT asume que estamos
transformando un perodo de una seal peridica. Si utilizamos ms puntos y extendemos
la seal con ceros (esto se conoce como zero-padding) la DFT da ms resolucin en
frecuencia pero la fuga se magnifica:
1
2
3
4
5
6
7
8
9
t3 = np.linspace(0, 0.012 + 9 * dt2, 10 * n2) # Intervalo de tiempo en segundos
y3 = np.append(y2, np.zeros(9 * n2)) # Seal

Y3 = fft(y3) / (10 * n2) # Transformada normalizada
frq3 = fftfreq(10 * n2, dt2)

fig = plt.figure(figsize=(6, 8))

ax1 = fig.add_subplot(211)
ax1.plot(t3, y3)
ax1.set_xlabel('Tiempo (s)')
10
11
12
13
14
15
16
17
ax1.set_ylabel('$y_3(t)$')

ax2 = fig.add_subplot(212)
ax2.vlines(frq3, 0, Y3.imag)
plt.xlabel('Frecuencia (Hz)')
plt.ylabel('Im($Y_3$)')

Existe una manera de reducir la fuga y es mediante el uso de funciones ventana. Las
funciones ventana no son ms que funciones que valen cero fuera de un cierto intervalo, y
que en procesamiento de seales digitales se utilizan para suavizar o filtrar una
determinada seal. NumPy trae unas cuantas funciones ventana por defecto; por ejemplo,
la ventana de Blackman tiene este aspecto:

Como se puede ver, en los extremos del intervalo es nula. Las funciones ventana reciben
un nico argumento que es el nmero de puntos. Si multiplicamos la ventana por la seal,
obtenemos una nueva seal que vale cero en los extremos. Comprobemos el resultado,
representando ahora el espectro de amplitud y comparando cmo es el resultado si
aplicamos o no la ventana de Blackman:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
n4 = 2 ** 8

t4 = np.linspace(0, 0.05, n4)
dt4 = t4[1] - t4[0]
y4 = np.sin(2 * pi * f * t4) - 0.5 * np.sin(2 * pi * 2 * f * t4)
y5 = y4 * np.blackman(n4)

t4 = np.linspace(0, 0.12 + 4 * dt4, 5 * n4)
y4 = np.append(y4, np.zeros(4 * n4))
y5 = np.append(y5, np.zeros(4 * n4))

Y4 = fft(y4) / (5 * n4)
Y5 = fft(y5) / (5 * n4)
frq4 = fftfreq(5 * n4, dt4)

fig = plt.figure(figsize=(6, 8))

ax1 = fig.add_subplot(411)
ax1.plot(t4, y4)
plt.xlabel('Frecuencia (Hz)')
plt.ylabel('$y_4(t)$')

ax2 = fig.add_subplot(412)
ax2.vlines(frq4, 0, abs(Y4)) # Espectro de amplitud
plt.xlabel('Frecuencia (Hz)')
plt.ylabel('Abs($Y_4$)')

ax3 = fig.add_subplot(413)
ax3.plot(t4, y5)
plt.xlabel('Frecuencia (Hz)')
plt.ylabel('$y_5(t)$')
27
28
29
30
31
32
33
34
35
36

ax4 = fig.add_subplot(414)
ax4.vlines(frq4, 0, abs(Y5)) # Espectro de amplitud
plt.xlabel('Frecuencia (Hz)')
plt.ylabel('Abs($Y_5$)')

Esto ya ha sido ms interesante, no? :)

S-ar putea să vă placă și