Automotivo e trabalho com dados

Encontre a frequência dominante de dados sintéticos de vibração com FFT

Use a FFT do NumPy em um sinal sintético de vibração com um componente conhecido de 2 Hz, examine os bins de frequência e verifique se a frequência dominante detectada corresponde à entrada. O CSV original permanece inalterado e o espectro é gravado em uma nova pasta de saída.

Ver o sumário

A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English

Para quem éEste guia é voltado a pessoas que querem um exemplo pequeno e verificável antes de aplicar análise de frequência baseada em FFT a dados de vibração automotiva ou de sensores.

Preparação
  • Python 3.12 e um comando de terminal que inicie essa versão.
  • NumPy instalado com python -m pip install numpy.
  • Uma pasta de trabalho em que o script possa criar uma nova pasta dentro de outputs.
  • Compreensão básica de que um sinal amostrado possui uma frequência de amostragem e um intervalo de tempo discreto.

01Comece com uma frequência conhecida antecipadamente

O exercício inicial mais seguro com FFT é usar um sinal cuja frequência você já conhece. Este exemplo usa 8 samples por segundo e um sinal sintético de vibração puro de 2 Hz. Com 8 samples, o espaçamento de frequência da FFT é sampling_rate / sample_count = 8 / 8 = 1 Hz.

Para uma entrada de valores reais, rfft do NumPy retorna apenas os bins de frequência não negativos. Com 8 samples a 8 Hz, esses bins são 0, 1, 2, 3 e 4 Hz. O bin de 4 Hz é a frequência de Nyquist.

02Crie o CSV sintético de vibração

O conjunto de dados a seguir é sintético e foi criado especificamente para este artigo. Salve-o como vibration_data.csv. O intervalo de amostragem é 0.125 seconds, portanto a frequência de amostragem é 1 / 0.125 = 8 Hz.

csv
time_s,accel_m_s2
0.000,0
0.125,1
0.250,0
0.375,-1
0.500,0
0.625,1
0.750,0
0.875,-1

Os valores se repetem a cada quatro samples: 0, 1, 0, -1. Com 8 samples por segundo, quatro samples correspondem a 0.5 seconds, e um período de 0.5 seconds corresponde a 2 Hz. Portanto, o sinal tem uma frequência dominante de entrada conhecida de 2 Hz.

GrandezaValor
Samples8
Sample interval0.125 s
Sampling frequency8 Hz
FFT frequency spacing1 Hz
Known signal frequency2 Hz
Nyquist frequency4 Hz

03Preveja o resultado da FFT antes de executar o código

Como o registro contém um número inteiro de ciclos e o sinal de 2 Hz cai exatamente em um bin da FFT, o exemplo sintético é deliberadamente limpo. A magnitude bruta da FFT deve ficar concentrada em 2 Hz.

Para a sequência 0, 1, 0, -1, 0, 1, 0, -1, o coefficient da FFT em 2 Hz tem magnitude 4. Os outros bins de frequência não negativa têm magnitude zero em aritmética exata. Pequenos erros numéricos de arredondamento podem aparecer em cálculos no computador, por isso o espectro exportado é arredondado para seis casas decimais.

FrequênciaMagnitude bruta esperada da FFT
0 Hz0
1 Hz0
2 Hz4
3 Hz0
4 Hz0

O bin de 0 Hz representa a média ou componente DC. Este sinal tem média 0. Mesmo assim, o script subtrai a média antes da FFT, o que é uma etapa comum de preparação quando o objetivo é examinar o conteúdo oscilatório em vez de um offset constante do sensor.

04Calcule o espectro e a frequência dominante

Salve o script a seguir como fft_dominant_frequency.py. Ele verifica o espaçamento dos timestamps, remove a média, calcula a FFT real, grava o espectro de frequência em um novo CSV e verifica se o bin de frequência diferente de zero mais forte é o esperado de 2 Hz.

python
import csv
from pathlib import Path
import numpy as np

SOURCE = Path("vibration_data.csv")
OUTPUT_DIR = Path("outputs") / "fft_result"
OUTPUT = OUTPUT_DIR / "spectrum.csv"
EXPECTED_DOMINANT_HZ = 2.0
DT_TOLERANCE = 1e-12
FREQUENCY_TOLERANCE = 1e-12


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    times = []
    values = []
    with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
        reader = csv.DictReader(stream)
        required = {"time_s", "accel_m_s2"}
        if reader.fieldnames is None or not required.issubset(reader.fieldnames):
            raise ValueError("CSV is missing a required column.")

        for row in reader:
            times.append(float(row["time_s"]))
            values.append(float(row["accel_m_s2"]))

    if len(times) < 2:
        raise ValueError("At least two samples are required.")

    time_array = np.asarray(times, dtype=float)
    signal = np.asarray(values, dtype=float)
    intervals = np.diff(time_array)
    dt = intervals[0]

    if dt <= 0:
        raise ValueError("Sample interval must be positive.")
    if not np.allclose(intervals, dt, rtol=0.0, atol=DT_TOLERANCE):
        raise ValueError("Sampling interval is not constant.")

    sample_rate = 1.0 / dt
    centered = signal - signal.mean()
    spectrum = np.fft.rfft(centered)
    frequencies = np.fft.rfftfreq(len(centered), d=dt)
    magnitudes = np.abs(spectrum)

    if len(frequencies) < 2:
        raise ValueError("No nonzero frequency bin is available.")

    dominant_index = 1 + int(np.argmax(magnitudes[1:]))
    dominant_frequency = float(frequencies[dominant_index])

    if abs(dominant_frequency - EXPECTED_DOMINANT_HZ) > FREQUENCY_TOLERANCE:
        raise RuntimeError(
            f"Expected {EXPECTED_DOMINANT_HZ} Hz, got {dominant_frequency} Hz."
        )

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()
    with OUTPUT.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.writer(stream)
        writer.writerow(["frequency_hz", "fft_magnitude"])
        for frequency, magnitude in zip(frequencies, magnitudes):
            writer.writerow([f"{frequency:.6f}", f"{magnitude:.6f}"])

    resolution = sample_rate / len(centered)
    print(f"Samples: {len(centered)}.")
    print(f"Sampling frequency: {sample_rate:.1f} Hz.")
    print(f"Frequency resolution: {resolution:.1f} Hz.")
    print(f"Dominant nonzero frequency: {dominant_frequency:.1f} Hz.")
    print(f"Output: {OUTPUT.as_posix()}")


if __name__ == "__main__":
    main()

05Compare o espectro esperado

O CSV do espectro deve conter cinco linhas porque uma FFT real de 8-sample retorna bins de 0 até a frequência de Nyquist de 4 Hz. Arredondado para seis casas decimais, apenas o bin de 2 Hz deve ter magnitude diferente de zero.

csv
frequency_hz,fft_magnitude
0.000000,0.000000
1.000000,0.000000
2.000000,4.000000
3.000000,0.000000
4.000000,0.000000

A saída esperada do console abaixo foi derivada manualmente a partir do sinal sintético e do script. Não é um log capturado de execução.

text
Samples: 8.
Sampling frequency: 8.0 Hz.
Frequency resolution: 1.0 Hz.
Dominant nonzero frequency: 2.0 Hz.
Output: outputs/fft_result/spectrum.csv

06Verifique o resultado e erros comuns

  • Verifique manualmente o intervalo de amostragem: 0.125 seconds.
  • Calcule manualmente a frequência de amostragem: 1 / 0.125 = 8 Hz.
  • Calcule o espaçamento de frequência: 8 / 8 = 1 Hz.
  • Confirme que a maior magnitude de FFT diferente de zero ocorre em 2 Hz.
  • Confirme que o vibration_data.csv original permaneceu inalterado.
  • Execute o script novamente sem alterar OUTPUT_DIR. Ele deve parar com FileExistsError em vez de sobrescrever o espectro.
ProblemaO que verificar
A frequência dominante está errada por um fator de escalaVerifique a unidade de tempo e o intervalo de amostragem usados em rfftfreq.
0 Hz é o maior componenteUma média diferente de zero ou um offset do sensor pode dominar; decida se a remoção da média é apropriada.
A energia aparece em bins vizinhosA frequência do sinal pode não cair exatamente em um bin da FFT, ou o registro pode não conter um número inteiro de ciclos.
O resultado em alta frequência parece suspeitoVerifique o limite de Nyquist e se a frequência de amostragem é suficientemente alta para o sinal físico.
Execuções diferentes sobrescrevem os resultadosUse uma nova pasta de saída; este exemplo para deliberadamente quando o destino já existe.

07Entenda os limites antes de usar dados medidos de vibração

Este exemplo é intencionalmente ideal. Registros reais de vibração raramente se alinham perfeitamente com os bins da FFT, portanto o vazamento espectral pode espalhar energia para frequências vizinhas. Window functions são usadas com frequência para controlar o vazamento, mas também alteram a interpretação da amplitude e exigem suas próprias verificações.

A resolução em frequência depende do comprimento do registro, além da frequência de amostragem. Com 8 samples a 8 Hz, o espaçamento é 1 Hz, portanto esta análise não consegue distinguir frequências como 2.1 Hz e 2.4 Hz como bins exatos separados. Um registro mais longo fornece espaçamento de frequência mais fino quando a frequência de amostragem permanece inalterada.

Para dados automotivos medidos, considere também filtragem anti-alias, largura de banda do sensor, sincronização de amostragem, samples perdidos, detrending, escolha da window, escala de amplitude e se o sinal é estacionário o suficiente para que uma única FFT seja significativa. Preserve os dados brutos no domínio do tempo e documente cada etapa de pré-processamento antes de interpretar fisicamente os picos.

Registro de execução e verificação

2026-09-20 · exemplo verificado manualmente · alvo: Python 3.12 · NumPy necessário · sem execução

  • Foi verificado manualmente que os 8 timestamps estão separados por 0.125 seconds, resultando em uma frequência de amostragem de 8 Hz.
  • Foi calculado manualmente o espaçamento de frequência da FFT como 8 Hz / 8 samples = 1 Hz.
  • O padrão repetitivo de quatro samples 0, 1, 0, -1 foi identificado como um período de 0.5 seconds, correspondente a 2 Hz.
  • Foram determinadas manualmente as magnitudes esperadas da FFT não negativa como 0, 0, 4, 0, 0 em aritmética exata.
  • O script foi inspecionado quanto às verificações de amostragem constante, remoção da média, rfft, rfftfreq, exclusão do bin DC da seleção da frequência dominante, proteção contra colisão de saída e preservação do CSV original.
  • O CSV de espectro esperado e a saída do console foram derivados manualmente.
Limites da verificação
  • O código não foi executado pelo autor desta resposta; a saída da FFT do NumPy e as gravações no sistema de arquivos não foram testadas aqui.
  • Vazamento espectral, window functions, calibração de amplitude, amostragem irregular, aliasing, ruído de sensores e conjuntos de dados longos não foram testados.
  • O pico sintético em 2 Hz é uma entrada matemática conhecida e não é apresentado como uma ressonância real do veículo ou frequência de falha.
  • As URLs da documentação oficial foram fornecidas com base em locais de documentação conhecidos, mas não foram verificadas ao vivo.

Princípios de redação e verificação de todo o site

Fontes de referência

As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.