Desde el punto de vista computacional, la transformada de Fourier en Tiempo Discreto (DTFT), definida como:
presenta dos problemas:
el hecho de que presenta una suma infinita de términos
en el intervalo fundamental de frecuencias hay infinitas frecuencias por lo que no puede computarse la transformada para todas las frecuencias
Como consecuencia de la primer afirmación cuando se tienen señales de duración infinita pueden aproximarse por señales de duración finita que se obtienen multiplicando la señal original por una ventana de longitud finita .
Es decir:
y entonces podemos hacer una aproximación como:
y entonces podemos hacer una aproximación como:
El problema de las infinitas frecuencias es fácil de resolver ya que puede computarse en un número finito de frecuencias equi-espaciadas de la forma:
Si la señal es analógica, la misma se pasa usualmente a través de un filtro antialiasing, y luego se muestrea con una frecuencia , donde es el ancho de banda de la señal filtra.
La máxima frecuencia en la señal muestreada es .
Luego, por las razones de implementación práctica mencionadas se limita la duración de la señal a un intervalo de tiempo , donde es el número de muestras y es el periodo de muestreo.
Veremos que la longitud finita de la ventana usada para truncar la señal pone límite en la resolución en frecuencia, es decir en la capacidad de distinguir entre dos componentes de frecuencia que están separadas menos de en frecuencia.
Sea la señal a analizar.
Limitar la duración de la señal a muestras en el intervalo es equivalente a multiplicar a por una función ventana rectangular de longitud , es decir:
donde:
El espectro de la señal estará entonces relacionado con el espectro de a través de la convolución (periódica)
Es claro entonces que la forma del espectro de la ventana afectará al espectro con el cuál se quiere aproximar a .
Para comprender mejor esto analicemos en primera instancia el caso de la ventana rectangular. Como ya vimos:
Por lo que:
De la convolución anterior vemos que para tener:
deberá ser , para lo cuál , o sea:
En efecto, calculemos el espectro de una ventana rectangular de longitud infinita, es decir:
Podemos ver que la señal es periódica con período . Los coeficientes de su serie de Fourier en TD vienen dados por:
Que en esta caso resulta en un único coeficiente:
Recordemos que la transformada de Fourier en TD de una señal periódica viene dada por
Para el caso del ejemplo resulta entonces
El hecho de que no sea trae varios problemas. Consideremos una secuencia que consiste de una sola sinusoide:
El espectro de la señal de duración finita viene dado por (Teorema de Modulación):
Como se ve en la figura anterior el espectro de no está localizado en una única frecuencia, sino que está distribuido en todo el rango de frecuencias.
Este fenómeno (debido a la ventana) se denomina leakage. El ancho del lóbulo principal en el espectro de la ventana determina la resolución en frecuencia.
El espectro de la señal truncada a muestras en el rango es:
El espectro de la señal truncada a muestras en el rango es:
Como el espectro de tiene el primer cruce por cero en:
Entonces si , las dos funciones y se superponen y en consecuencia las dos líneas espectrales de no se distinguen.
Sólo si (donde es el ancho del lóbulo principal del espectro de la ventana rectangular) se verán dos lóbulos separados en el espectro .
Consideremos ahora el ejemplo de una señal con tres componentes sinusoidales, dos de ellas con frecuencias muy próximas.
El Espectro de Módulo usando ventana rectangular, para , y resulta
De donde puede observarse que no se distinguen las líneas espectrales correspondientes a las frecuencias próximas y .
Si consideramos en cambio , las líneas espectrales son distinguibles.
Para solucionar el problema del leakage se propone utilizar una ventana que tenga un espectro que se asemeje en mayor medida al impulso.
Por ejemplo si consideramos la ventana de Hann (hann):
El espectro de módulo tiene las siguientes características:
Lóbulos laterales significativamente más pequeños que la ventana rectangular (menor leakage)
Lóbulo principal aproximadamente 2 veces más ancho que la ventana rectangular (peor resolución)
Considerando el ejemplo con las tres componentes senoidales, pero usando una ventana Hann con los mismos valores de , los espectros resultan:

Otra ventana, con un espectro similar al de la ventana de Hann es la ventana de Hamming (hamming), que se
muestra a continuación:

Cómputo de la Transformada de Fourier en Tiempo Discreto para señales de longitud finita¶
Sea una señal causal de longitud finita . Su DTFT viene dada por:
Para solucionar el problema de que existen infinitas frecuencias en el intervalo fundamental , calcularemos un número finito de frecuencias equiespaciadas de la forma
Resulta entonces:
que puede escribirse como:
En forma matricial sería:
que es muy facil de implementar con un algoritmo de computación.
import numpy as np
from scipy.fft import fftshift, fftfreq
import matplotlib.pyplot as plt
%matplotlib inlinedef dtft(x, N):
L=len(x)
k=np.linspace(0, N, N, endpoint=False)
k=k.reshape((N,1))
w=2*np.pi*k/N
n=np.linspace(0,L,L, endpoint=False)
n=n.reshape((1,L))
return (np.exp(-1j*(w@n)))@x, w# Generamos la señal
L=10
n=np.linspace(0, L, L, endpoint=False).reshape((L,1))
x=0.9**nN=1024
X, w=dtft(x,N)
w1=fftfreq(N, 1)*2*np.piSource
f, ax=plt.subplots(2,1, figsize=(6,4))
ax[0].plot(w, np.abs(X))
ax[0].grid(True)
ax[0].set_ylabel("módulo")
ax[0].set_title(r"|X($\omega$)|");
ax[1].grid(True)
ax[1].set_xlabel("rad/seg")
ax[1].set_ylabel("módulo")
ax[1].plot(fftshift(w1),fftshift(np.abs(X)))
f.tight_layout()
Transformada ideal¶
Consideremos ahora la señal de longitud infinita
Podemos pensar que en el ejemplo anterior es una versión truncada con una ventana rectangular de longitud de la señal de longitud infinita . Calculemos la DTFT de .
Resulta
Grafiquemos el espectro de módulo de y de para distintos valores de .
winf=np.linspace(-np.pi,np.pi,1024, endpoint=True)
X1=1/(1-(0.9*(np.exp(-1j*winf))))
L1=10
n1=np.linspace(0, L1, L1, endpoint=False).reshape((L1,1))
xl1=0.9**n1
Xl1,_=dtft(xl1,N)
L2=20
n2=np.linspace(0, L2, L2, endpoint=False).reshape((L2,1))
xl2=0.9**n2
Xl2,_=dtft(xl2,N)
L3=50
n3=np.linspace(0, L3, L3, endpoint=False).reshape((L3,1))
xl3=0.9**n3
Xl3,_=dtft(xl3,N)Source
f, ax=plt.subplots(1,1, figsize=(6,3))
ax.plot(winf, np.abs(X1), label=r"$L=\infty$")
ax.plot(winf, np.abs(fftshift(Xl1)), label=r"$L=10$")
ax.plot(winf, np.abs(fftshift(Xl2)), label=r"$L=20$")
ax.plot(winf, np.abs(fftshift(Xl3)), ls=":", label=r"$L=50$")
ax.grid(True)
ax.set_xlabel("rad/seg")
ax.set_ylabel("módulo")
ax.legend()
ax.set_title(r"|X($\omega$)|");
Transformada Discreta de Fourier¶
Vimos que la DTFT de una señal causal , de longitud finita , calculada en frecuencias equi-espaciadas de la forma:
viene dada por:
Puede pensarse entonces que son muestras del espectro son muestras del espectro en las frecuencias .
Se define entonces la Transformada Discreta de Fourier con N puntos de la señal como
Vemos, comparando las expresiones anteriores, que si la señal es de longitud , la Transformada Discreta de Fourier con puntos puede pensarse como muestras del espectro en las frecuencias equi-espaciadas . Si en cambio no se verifica que , la DFT con puntos no puede pensarse como muestras del espectro en .
No es difícil probar que la DFT con puntos es periódica con período . En efecto:
Teniendo en cuenta que es periódica con período , y observando la expresión de , notamos que esa expresión es la Serie de Fourier discreta de , por lo que podemos concluir que:
siendo los coeficientes de Fourier.
Finalmente, teniendo en cuenta la expresión de los coeficientes de Fourier:
podemos concluir que
Es decir que, para el caso en que , la señal puede recuperarse a partir de las muestras según la expresión anterior, que se denomina Transformada Discreta de Fourier Inversa (IDFT).
Transformada Rápida de Fourier (FFT: Fast Fourier Transform)¶
El algoritmo desarrollado para el cálculo directo de la DTFT no es eficiente desde el punto de vista computacional ya que requiere del orden de multiplicaciones complejas. Por ejemplo, si se calculan 1024 frecuencias, se requerirían 1048576 multiplicaciones.
Si se tienen en cuenta algunas propiedades de simetría de los coeficientes:
se pueden desarrollar algoritmos más rápidos.
En particular, uno de los más difundidos es el algoritmo denominado Transformada Rápida de Fourier (FFT: Fast Fourier Transform) que requiere del orden de
multiplicaciones complejas, cuando es potencia entera de 2. Para el ejemplo de , resultan 5120 multiplicaciones, lo que representa un factor de mejora en la velocidad de 204.8.
En Python, este algoritmo está implementado tanto en los paquetes scipy y numpy.
La forma de uso es la misma para las dos implementaciones.
# podemos importarla de numpy
from numpy.fft import fft
# o podemos importarla de scipy
from scipy.fft import fftLa función fft se utiliza se utiliza de la siguiente manera:
from scipy.fft import fft
X=fft(x, N)donde:
xson las muestras de la señalXson los valores de la transformadaNnúmero de frecuencias donde se calcula la transformada, que debe ser de la forma para que el algoritmo sea rápido.
from scipy.io import loadmat
from scipy.fft import fft, fftshift, fftfreq
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline# importa las variables que estan en el .mat en un diccionario data
data=loadmat('TP3Prob4_2022.mat')
# extraigo las variables del diccionario
t=data['t']
y=data['y']
Fs=data['Fs'][0][0]
Fsnp.uint16(8000)L=len(y);
N=4096*8; # notar que N > L
Y=fft(y, n=N, axis=0);
#F=[-Fs/2:Fs/N:Fs/2-Fs/N]';
F=np.linspace(0, Fs/2, N, endpoint=False)
F=fftfreq(N, 1/Fs)
absY=np.abs(Y)
picomax=np.max(absY)A=2*picomax/L; # amplitud de la senoide
ind=np.where(absY==picomax)[0][0]Fsen=F[ind]; # frecuencia de la senoide
f, ax = plt.subplots(2,1, figsize=(6,4))
ax[0].plot(t,y)
ax[0].set_xlabel("Tiempo (s)")
ax[1].plot(fftshift(F),fftshift(absY))
ax[1].set_xlabel("Frecuencia (Hz)")
f.tight_layout()
print(f"Frecuencia del seno: {Fsen}, amplitud del seno: {A}")Frecuencia del seno: 199.951171875, amplitud del seno: 1.0056159733403283

# Filtrado en el dominio FFT
from scipy.fft import ifft
filtro=np.zeros((N,))
for i, val in enumerate(F):
if np.abs(val)>=199 and np.abs(val)<=201:
filtro[i]=1Yfiltrada=Y[:,0]*filtro
yfiltrada=np.real(ifft(Yfiltrada))
yfiltrada=yfiltrada[0:L]
f, ax=plt.subplots(2,1, figsize=(6,4))
ax[0].plot(fftshift(F),abs(fftshift(Yfiltrada)))
ax[0].plot(fftshift(F),5000*fftshift(filtro))
ax[0].set_xlabel("Tiempo (s)")
ax[1].set_xlim([0.99, 1.01])
ax[1].plot(t,yfiltrada)
ax[1].set_xlabel("Frecuencia (Hz)")
f.tight_layout()
Uso de for y enumerate en Python¶
El for en Python se puede utilizar en objetos sobre los cuales se puede iterar.
Estos objetos se dicen que son iterables.
Cuando se itera utilizando un for en Python, en cada iteración se va obteniendo cada uno de los elemento del objeto sobre el cual se está iterando.
En el caso del diseño del filtro se quieren eliminar todas las componentes de frecuencias que no se corresponden con la del seno que se quiere recuperar. Es decir, el filtro deberá ser un vector de ceros, donde solo sean 1 las componentes que se correspondan con las frecuencias en torno a la frecuencia de la senoide ( Hz). En nuestro caso decidimos hacer 1 todas las componentes de frecuencia que son mayores a 199 y menores a 201, tanto positivas como negativas.
Para hacerlo utilizando el bucle for recorro el vector de frecuencias y
obtengo el indice en i y el valor de la frecuencia el val mediante el uso
de la función enumerate. Si el valor de la frecuencia está entre los seleccionados
se utiliza el indice i para hacer 1 la componente del filtro que se corresponde con la
frecuencia val.
El índice i lo utilizo para hacer 1 las componentes del filtro en las frecuencias de interés.
Ejemplo¶
Supongamos que que queremos aplicarle una función a cada elemento de un array y guardarlo en otro array de igual dimensión y poder graficar los valores del primero respecto del segundo.
Para esto el algoritmo que planteamos podría ser:
defino la operación que le vamos a aplicar a cada uno de los elementos
genero un
arraycon los valores que le voy a aplicar la funciónaloco un
arrayvacio de mismo tamaño que que anteriorrecorro el primer
arrayy guardo cada uno de los resultados en el segundo usando el mismo indice para ambosrealizo la figura
import numpy as np
import matplotlib.pyplot as plt
def operacion_arbitraria(num):
return num**3+2
x=np.linspace(0, 2, 1001, endpoint=True)
y=np.empty(x.shape)
for i, v in enumerate(x):
y[i]=operacion_arbitraria(v)
plt.plot(x, y)