Introdução
A Transformada de Fourier (TF) é uma das ferramentas matemáticas mais poderosas na análise de sinais e sistemas. Ela permite converter um sinal do domínio do tempo para o domínio da frequência, revelando componentes espectrais que não são diretamente perceptíveis no tempo. Aplicações da TF vão desde o processamento digital de sinais (DSP) até física, telecomunicações, engenharia biomédica e eletrônica.
Neste artigo, exploraremos a base teórica da Transformada de Fourier, sua demonstração matemática e uma implementação completa em linguagem C, focando em clareza e didática para leitores iniciantes.
Fundamentos da Transformada de Fourier
Imagine um sinal contínuo no tempo, como uma onda sonora. Esse sinal pode ser decomposto como a soma de muitas senóides (ondas senoidais) com diferentes frequências, amplitudes e fases. A Transformada de Fourier é o processo que encontra essas senóides componentes.
Matematicamente, para um sinal contínuo x(t)x(t), a Transformada de Fourier é dada por:
\[
X(f) = \int_{-\infty}^{\infty} x(t) e^{-j 2\pi f t} \, dt
\]
Onde:
- x(t): sinal no tempo contínuo.
- X(f): espectro de frequências do sinal.
- f: frequência.
- j: unidade imaginária \((j^2 = -1)\).
A transformada inversa é dada por:
\[
x(t) = \int_{-\infty}^{\infty} X(f) e^{j 2\pi f t} \, df
\]
Ou seja, é possível reconstruir o sinal original a partir de suas componentes de frequência.
Transformada de Fourier Discreta (DFT)
Na prática, lidamos com sinais amostrados digitalmente. Para isso, usamos a versão discreta da TF: a Transformada de Fourier Discreta (DFT), definida por:
\[
X_k = \sum_{n=0}^{N-1} x_n \cdot e^{-j \frac{2\pi}{N}kn}, \quad k = 0, 1, …, N-1
\]
E a transformada inversa:
\[
x_n = \frac{1}{N} \sum_{k=0}^{N-1} X_k \cdot e^{j \frac{2\pi}{N}kn}
\]
Onde:
- \(x_n\): amostras no tempo.
- \(X_k\): componente espectral da frequência kk.
- N: número total de amostras.
Implementação em C da DFT
A seguir, apresentamos um código simples em linguagem C que calcula a DFT de um vetor de números reais. Para isso, usaremos números complexos representados por duas variáveis (parte real e imaginária).
Código C:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.141592653589793
typedef struct {
double real;
double imag;
} Complex;
void DFT(double* x, Complex* X, int N) {
for (int k = 0; k < N; k++) {
X[k].real = 0;
X[k].imag = 0;
for (int n = 0; n < N; n++) {
double angle = -2.0 * PI * k * n / N;
X[k].real += x[n] * cos(angle);
X[k].imag += x[n] * sin(angle);
}
}
}
int main() {
int N = 8;
double x[8] = {1, 0, -1, 0, 1, 0, -1, 0}; // Sinal de teste
Complex* X = (Complex*)malloc(N * sizeof(Complex));
DFT(x, X, N);
printf("DFT Result:\n");
for (int k = 0; k < N; k++) {
printf("X[%d] = %.2f + %.2fi\n", k, X[k].real, X[k].imag);
}
free(X);
return 0;
}
Esse código implementa diretamente a fórmula da DFT. Note que o tempo de execução cresce com \(N^2\), por isso para sinais muito longos recomenda-se usar a Transformada Rápida de Fourier (FFT), que veremos em outra seção.
A Transformada Rápida de Fourier (FFT)
Por que otimizar?
Como vimos, a DFT exige N2N^2 operações para calcular todas as NN frequências de um sinal com NN amostras. Isso pode ser extremamente lento para sinais grandes. A Transformada Rápida de Fourier (FFT) resolve esse problema ao reduzir a complexidade para Nlog2NN \log_2 N, um ganho drástico em eficiência.
A FFT é uma maneira inteligente de reorganizar os cálculos da DFT, explorando simetrias matemáticas para evitar repetições desnecessárias.
Ideia por trás da FFT
A FFT mais conhecida é o algoritmo de Cooley-Tukey, que se baseia em dividir o sinal original em duas partes:
- Amostras de índices pares: \(x_0, x_2, x_4, \ldots\)
- Amostras de índices ímpares: \(x_1, x_3, x_5, \ldots\)
Essa divisão é aplicada recursivamente, até que reste apenas DFTs de tamanho 2, cuja solução é trivial.
Implementação em C da FFT (Radix-2, Recursiva)
Abaixo temos uma implementação simples e didática da FFT recursiva. Vamos assumir que o número de amostras NN seja uma potência de 2.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.141592653589793
typedef struct {
double real;
double imag;
} Complex;
void fft(Complex* x, int N) {
if (N <= 1) return;
// Divide: cria vetores para pares e ímpares
Complex* even = (Complex*)malloc(N/2 * sizeof(Complex));
Complex* odd = (Complex*)malloc(N/2 * sizeof(Complex));
for (int i = 0; i < N/2; i++) {
even[i] = x[2*i];
odd[i] = x[2*i + 1];
}
// Conquista: aplica FFT recursivamente
fft(even, N/2);
fft(odd, N/2);
// Combina: calcula FFT final com os pares e ímpares
for (int k = 0; k < N/2; k++) {
double angle = -2 * PI * k / N;
Complex twiddle = {cos(angle), sin(angle)};
Complex t = {
twiddle.real * odd[k].real - twiddle.imag * odd[k].imag,
twiddle.real * odd[k].imag + twiddle.imag * odd[k].real
};
x[k].real = even[k].real + t.real;
x[k].imag = even[k].imag + t.imag;
x[k + N/2].real = even[k].real - t.real;
x[k + N/2].imag = even[k].imag - t.imag;
}
free(even);
free(odd);
}
int main() {
int N = 8;
// Sinal de entrada (parte imaginária zero)
Complex x[8] = {
{1,0}, {0,0}, {-1,0}, {0,0},
{1,0}, {0,0}, {-1,0}, {0,0}
};
fft(x, N);
printf("FFT Result:\n");
for (int i = 0; i < N; i++) {
printf("X[%d] = %.2f + %.2fi\n", i, x[i].real, x[i].imag);
}
return 0;
}
Comparando FFT e DFT
Característica | DFT | FFT |
---|---|---|
Complexidade | \(O(N^2)\) | \(O(N \log_2 N)\) |
Requisitos | Nenhum | NN deve ser potência de 2 |
Código | Simples, direto | Recursivo, mais eficiente |
Uso prático | Pequenos sinais | Sinais grandes (áudio, imagem, etc.) |
Aplicando a FFT a Sinais Reais de Sensores ou Áudio
Captura e amostragem de sinais reais
Sensores — como acelerômetros, termistores ou microfones — produzem sinais analógicos contínuos no tempo. Para aplicar a FFT, é necessário amostrar esses sinais com um conversor analógico-digital (ADC), obtendo um vetor de amostras igualmente espaçadas no tempo.
Esse processo gera um vetor \(x[n]\) com N amostras igualmente espaçadas por um intervalo de tempo \(T_s\), definido pela taxa de amostragem \(f_s: T_s = \frac{1}{f_s}\)
Por exemplo, uma taxa de 1000 Hz implica em uma amostra a cada 1 ms.
Pré-processamento
Antes de aplicar a FFT em um sinal real, recomenda-se:
- Remover a média (DC): \(x[n] := x[n] – \frac{1}{N} \sum_{i=0}^{N-1} x[i]\) Isso evita picos indesejados na frequência zero (DC offset).
- Aplicar janela (windowing): Sinais reais nem sempre são periódicos no intervalo analisado. O uso de uma janela como a de Hanning, Hamming ou Blackman suaviza as bordas do sinal e reduz o vazamento espectral (aliasing).
Exemplo prático com FFT
A seguir, adaptamos nosso código para aplicar FFT a um sinal real simulado — representando, por exemplo, a vibração medida por um acelerômetro.
Sinal de teste:
Simularemos um sinal real com duas senóides: uma de 50 Hz e outra de 120 Hz, amostradas a 1000 Hz:
\[
x[n] = \sin(2\pi 50 n T_s) + 0.5 \cdot \sin(2\pi 120 n T_s)
\]
Código em C:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.141592653589793
#define FS 1000 // taxa de amostragem (Hz)
#define N 1024 // número de amostras (potência de 2)
typedef struct {
double real;
double imag;
} Complex;
// Mesma função FFT da seção anterior (recursiva)
void fft(Complex* x, int N);
// Gera um sinal real composto por duas senóides
void gerar_sinal(Complex* x) {
for (int n = 0; n < N; n++) {
double t = (double)n / FS;
double s = sin(2 * PI * 50 * t) + 0.5 * sin(2 * PI * 120 * t);
x[n].real = s;
x[n].imag = 0;
}
}
// Calcula módulo da FFT
double modulo(Complex c) {
return sqrt(c.real * c.real + c.imag * c.imag);
}
int main() {
Complex x[N];
gerar_sinal(x);
fft(x, N);
printf("Frequência (Hz)\tMagnitude\n");
for (int i = 0; i < N/2; i++) {
double freq = (double)i * FS / N;
double mag = modulo(x[i]) * 2.0 / N; // normaliza e dobra por ser simétrica
printf("%.1f\t\t%.4f\n", freq, mag);
}
return 0;
}
Interpretando o resultado
- O vetor FFT possui NN valores complexos.
- Para sinais reais, os valores são simétricos: basta analisar os primeiros N/2 pontos.
- Cada índice ii corresponde a uma frequência:\(f_i = \frac{i \cdot f_s}{N}\)
- A magnitude |X[i]| representa a “força” (amplitude) do sinal naquela frequência.
- Os picos na magnitude indicam quais frequências dominam o sinal.
Saída esperada (parcial):
Frequência (Hz) Magnitude
...
50.0 1.0000
...
120.0 0.5000
...
A Transformada Inversa de Fourier (iFFT)
O que é a iFFT?
A Transformada Inversa de Fourier, ou iFFT, permite reconstruir um sinal no tempo a partir de suas componentes de frequência.
Ela é definida como: x[n]=1N∑k=0N−1X[k]⋅ej2πNknx[n] = \frac{1}{N} \sum_{k=0}^{N-1} X[k] \cdot e^{j \frac{2\pi}{N}kn}
Ou seja, usamos os mesmos valores complexos X[k]X[k] da FFT e, com uma fórmula semelhante, obtemos novamente as amostras originais do sinal.
Quando a iFFT é usada?
- Após aplicar filtros no domínio da frequência.
- Na decodificação de sinais comprimidos (ex: MP3, JPEG).
- Para criar sons ou sinais com componentes espectrais específicas.
- Para análise de sistemas (ex: resposta impulsiva via FFT/iFFT).
Implementação em C da iFFT
A iFFT é quase idêntica à FFT — a diferença está no sinal do expoente (usa +j+j ao invés de −j-j) e na normalização final por NN.
Código: iFFT em C
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.141592653589793
#define N 8
typedef struct {
double real;
double imag;
} Complex;
// Função auxiliar para inverter os sinais do imaginário
void conjugar(Complex* x, int N) {
for (int i = 0; i < N; i++)
x[i].imag = -x[i].imag;
}
// Reaproveitamos a mesma FFT, trocando o sinal antes e depois
void fft(Complex* x, int N); // Assume-se definida (como antes)
// iFFT com normalização
void ifft(Complex* x, int N) {
// Conjuga
conjugar(x, N);
// Aplica FFT "espelhada"
fft(x, N);
// Conjuga novamente e divide por N
conjugar(x, N);
for (int i = 0; i < N; i++) {
x[i].real /= N;
x[i].imag /= N;
}
}
int main() {
Complex X[N] = {
{0,0}, {4,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}, {0,0}
};
ifft(X, N);
printf("Sinal reconstruído:\n");
for (int i = 0; i < N; i++) {
printf("x[%d] = %.4f\n", i, X[i].real); // ignorando parte imaginária residual
}
return 0;
}
Sobre a parte imaginária
A parte imaginária após a iFFT de um sinal real deve ser próxima de zero (por erro numérico). Em aplicações práticas, ela é ignorada ou descartada:
double x_real = X[i].real; // Parte útil
Validação: FFT + iFFT
Um teste típico para validar seu código de FFT/iFFT é:
- Criar um sinal x[n].
- Aplicar FFT para obter X[k].
- Aplicar iFFT em X[k].
- Comparar os valores de x[n] com os reconstruídos.
Se os valores coincidirem (até erros mínimos), seu sistema está funcionando corretamente.
Aplicações avançadas
- Equalização de áudio: ajustar frequências específicas e depois aplicar iFFT.
- Cancelamento de ruído: eliminar certas frequências antes da iFFT.
- Compressão: manter só os coeficientes mais relevantes (ex: JPEG).
- Síntese sonora: construir sons pela manipulação direta do espectro.
11. Conclusão
A Transformada de Fourier é uma ferramenta indispensável para engenheiros, programadores e cientistas que trabalham com sinais, vibrações, sons, imagens ou qualquer fenômeno periódico ou oscilatório. Ela permite revelar o conteúdo de frequência de um sinal, detectar padrões invisíveis no domínio do tempo e aplicar uma vasta gama de técnicas de processamento, filtragem e reconstrução.
Neste artigo, vimos:
- A base teórica da Transformada de Fourier contínua e discreta;
- A implementação direta da DFT em linguagem C;
- A otimização via FFT (Fast Fourier Transform);
- A aplicação prática da FFT em sinais reais de sensores ou áudio;
- A reconstrução do sinal original usando a iFFT;
- E a importância da interpretação correta dos resultados espectrais.
Tudo foi feito com um olhar didático e com exemplos práticos, prontos para adaptação em sistemas embarcados, análise de sinais em tempo real ou mesmo aplicações científicas mais robustas.
Próximos passos sugeridos
Caso queira aprofundar ainda mais o estudo e uso da Transformada de Fourier, recomenda-se explorar:
- Implementações de FFT otimizadas para microcontroladores (ex: CMSIS-DSP para ARM Cortex-M);
- Visualização gráfica com Python/Matplotlib para depuração e análise;
- Filtragem digital (passa-baixas, passa-altas, notch) no domínio da frequência;
- Algoritmos FFT para sinais de tamanho não potência de 2 (Bluestein ou Mixed-Radix);
- Uso de janelas (Hamming, Blackman, etc.) para melhorar a resolução espectral.
Se quiser, posso te ajudar a gerar qualquer um desses tópicos em novas seções ou aplicações.