8
Introducción En este artículo vamos a ver cómo calcular la transformada de Fourier discreta  (o DFT) de una señal en Python utilizando la transformada rápida de Fourier  (o FFT) implementada en SciPy. El análisis de Fourier es la herramienta fundamental en procesamiento de señales y resulta útil en otras áreas como en la resolución de ecuaciones diferenciales o en el tratamiento de imágenes. Nota : Puedes ver el artículo en forma de notebook de IPython mediante la herramienta nbviewer. La transformada rápida de Fourier o FFT es en realidad una familia de algoritmos muy eficientes ( ) para calcular la DFT de una señal discre ta , de los cuales el más utilizado es el algoritmo de Cooley-Tukey. Este es el que está implementado en SciPy a través de las subrutinas FFTPACK, escritas en FORTRAN 77, y es el que vamos a utilizar. También podríamos haber utilizado la biblioteca FFTW, más moderna y escrita en C, o la implementación presente en NumPy, que es una traducción a C de FFTPACK y que funciona igual que la de SciPy, pero no lo vamos a hacer. Nótese que estos métodos no nos permiten calcular ni la serie de Fourier de una función periódica ni la transformada de Fourier de una función no periódica. Estas operaciones forman parte del cálculo simbólico y deben llevarse a cabo con otro tipo de programas, como SymPy o Sage. En Pybonacci puedes leer una introducción a SymPy y cómo calcular series con SymPy, así como una reseña sobre Sage. En este artículo nos vamos a centrar en el caso unidimensional, aunque el código es fácilmente extrapolable al caso de dos y más 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 brevísima introducción matemática. He intentado que esto no se convierta en una larga introducción al procesamiento de señales digitales, pero si te interesa el tema y quieres más rigor puedes acudir a cualquiera de las referencias que doy más abajo además de ladocumentación de scipy.fftpack. En este artículo vamos a manejar señales discretas reales , bien creadas por nosotros o procedentes demuestrear  una señal analógica a una frecuencia de muestreo fs. La transformada discreta de Fourier  o DFT de la señal de entrada, que diremos que está en el dominio del tiempo , será otra señal discreta, n generalcompleja , cuyos coeficientes vienen definidos en SciPy por la expresión

Pibonacy_Transformada de Fourier Discreta en Python Con SciPy

Embed Size (px)

Citation preview

IntroduccinEn este artculo vamos a vercmo calcular la transformada de Fourier discreta(o DFT) de una seal en Python utilizando latransformada 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: Puedesver el artculo en forma de notebook de IPythonmediante 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 SymPyycmo calcular series con SymPy, as como unaresea 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.DefinicionesAntes 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 descipy.fftpack.En este artculo vamos a manejarseales discretas reales, bien creadas por nosotros o procedentes demuestrearuna seal analgica a una frecuencia de muestreo fs. Latransformada discreta de Fouriero DFT de la seal de entrada, que diremos que esten el dominio del tiempo, ser otra seal discreta, n generalcompleja, cuyos coeficientes vienen definidos en SciPy por la expresin

siendolos coeficientes de la seal de entrada ylos de la seal obtenida. Diremos queesten el dominio de la frecuenciay que es elespectrootransformadade. La resolucin en frecuencia ser mayor cuanto mayor sea el nmero de intervalosde la seal de entrada. La DFT se calcula con el algoritmo de latransformada rpida de Fouriero FFT, que es ms eficiente sies 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 frecuenciay otra a la frecuencia, como ya comprobaremos.La DFT de una sealsiempreasume que la seal de entrada esun 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 sencillaVamos acalcular 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 dey 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 frecuenciay 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:123456789101112131415import matplotlib.pyplot as pltimport numpy as npfrom numpy import pin = 2 ** 6 # Nmero de intervalosf = 400.0 # Hzdt = 1 / (f * 16) # Espaciado, 16 puntos por perodot = np.linspace(0, (n - 1) * dt, n) # Intervalo de tiempo en segundosy = np.sin(2 * pi * f * t) - 0.5 * np.sin(2 * pi * 2 * f * t) # Sealplt.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 hastan - 1, los trozos empalman perfectamente. Ahora vamos a hallar la DFT de la sealy. Para ello necesitamos dos funciones, que se importan del paquete scipy.fftpack: La funcinfft, 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 funcinfftfreq, 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:12345678910111213from scipy.fftpack import fft, fftfreqY = fft(y) / n # Normalizadafrq = fftfreq(n, dt) # Recuperamos las frecuenciasplt.vlines(frq, 0, Y.imag) # Representamos la parte imaginariaplt.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 = "->"))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 nuestromagnfico tutorial de matplotlibpor 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 ventanaVamos a analizar el ejemplo anterior, pero en esta ocasin vamos a poner menos cuidado con la preparacin de los datos. Veamos qu ocurre:1234567891011121314151617181920n2 = 2 ** 5t2 = np.linspace(0, 0.012, n2) # Intervalo de tiempo en segundosdt2 = t2[1] - t2[0]y2 = np.sin(2 * pi * f * t2) - 0.5 * np.sin(2 * pi * 2 * f * t2) # SealY2 = fft(y2) / n2 # Transformada normalizadafrq2 = 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$)')

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:1234567891011121314151617t3 = np.linspace(0, 0.012 + 9 * dt2, 10 * n2) # Intervalo de tiempo en segundosy3 = np.append(y2, np.zeros(9 * n2)) # SealY3 = fft(y3) / (10 * n2) # Transformada normalizadafrq3 = fftfreq(10 * n2, dt2)fig = plt.figure(figsize=(6, 8))ax1 = fig.add_subplot(211)ax1.plot(t3, y3)ax1.set_xlabel('Tiempo (s)')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 defunciones 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 traeunas cuantas funciones ventanapor 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:123456789101112131415161718192021222324252627282930313233343536n4 = 2 ** 8t4 = 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 amplitudplt.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)$')ax4 = fig.add_subplot(414)ax4.vlines(frq4, 0, abs(Y5)) # Espectro de amplitudplt.xlabel('Frecuencia (Hz)')plt.ylabel('Abs($Y_5$)')

Esto ya ha sido ms interesante, no?:)