MCU.TEC Algoritimos A Transformada de Fourier: Fundamentos, Demonstração e Implementação em C

A Transformada de Fourier: Fundamentos, Demonstração e Implementação em C


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 Nlog⁡2NN \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ísticaDFTFFT
Complexidade\(O(N^2)\)\(O(N \log_2 N)\)
RequisitosNenhumNN deve ser potência de 2
CódigoSimples, diretoRecursivo, mais eficiente
Uso práticoPequenos sinaisSinais 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:

  1. 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).
  2. 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 é:

  1. Criar um sinal x[n].
  2. Aplicar FFT para obter X[k].
  3. Aplicar iFFT em X[k].
  4. 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.


5 1 voto
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x