The Problem: FFT Was Blind to My Inner Race Defect
Running FFT on raw vibration data from a 3.6 kHz accelerometer gave me a clean spectrum. Peaks at 1X, 2X rotational frequency. Looked healthy.
But the bearing was failing. I knew it — temperature had crept up 8°C over three weeks, and the motor drew 12% more current than baseline. FFT saw nothing.
Envelope analysis caught it in under five minutes. Clear peaks at BPFI (Ball Pass Frequency Inner race) and its harmonics. The bearing had a 2mm spall on the inner race, confirmed by borescope inspection two days later.
This isn’t a tutorial. It’s a breakdown of why envelope analysis works where FFT fails, where it almost failed on me, and when you shouldn’t bother with it at all.

Why FFT Misses Early-Stage Bearing Faults
Bearing defects don’t generate strong harmonics in the velocity domain — they generate impulsive transients. When a rolling element hits a spall, you get a sharp shock that excites the bearing’s natural frequency (typically 2-10 kHz for industrial bearings, depending on geometry and materials).
FFT averages energy across the entire time window. A 0.5 ms impulse every 30 ms (BPFI at 1800 RPM, 10-ball bearing) gets smeared out. The frequency bin at BPFI = 33 Hz might show up, but it’s buried under the 1X rotational component at 30 Hz and broadband noise from the gearbox.
The actual diagnostic information lives in the high-frequency resonance band excited by the impacts — not in the low-frequency fault signature itself.
Envelope Analysis: Demodulating the Resonance
Envelope analysis (also called amplitude demodulation or Hilbert envelope detection) extracts the modulation pattern from a high-frequency carrier. The steps:
- Bandpass filter the raw signal to isolate the resonance band (e.g., 2-5 kHz for my MEMS accelerometer).
- Compute the Hilbert transform to get the analytic signal.
- Extract the envelope (magnitude of the analytic signal).
- FFT the envelope to reveal the low-frequency fault pattern.
Here’s the Python implementation I ran on 10 seconds of data (36,000 samples at 3600 Hz):
import numpy as np
from scipy.signal import butter, filtfilt, hilbert
from scipy.fft import fft, fftfreq
import matplotlib.pyplot as plt
# Load raw accelerometer data (assuming single-axis radial measurement)
data = np.load('bearing_vibration.npy') # shape: (36000,)
fs = 3600 # sampling rate in Hz
t = np.arange(len(data)) / fs
# Step 1: Bandpass filter (2-5 kHz resonance band)
def bandpass(signal, lowcut, highcut, fs, order=4):
nyq = 0.5 * fs
low = lowcut / nyq
high = highcut / nyq
b, a = butter(order, [low, high], btype='band')
return filtfilt(b, a, signal)
filtered = bandpass(data, 2000, 5000, fs)
# Step 2: Hilbert transform + envelope extraction
analytic_signal = hilbert(filtered)
envelope = np.abs(analytic_signal)
# Step 3: FFT of envelope
N = len(envelope)
envelope_fft = fft(envelope - np.mean(envelope)) # remove DC
freqs = fftfreq(N, 1/fs)[:N//2]
magnitude = 2.0/N * np.abs(envelope_fft[:N//2])
# Plot envelope spectrum (0-200 Hz)
plt.figure(figsize=(12, 4))
plt.plot(freqs[:int(200/(fs/N))], magnitude[:int(200/(fs/N))])
plt.xlabel('Frequency (Hz)')
plt.ylabel('Envelope Amplitude')
plt.title('Envelope Spectrum: BPFI Peaks at 33 Hz, 66 Hz, 99 Hz')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('envelope_spectrum.png', dpi=150)
Output showed clean peaks at 33 Hz (BPFI), 66 Hz (2×BPFI), 99 Hz (3×BPFI). The raw FFT had shown nothing above the noise floor at those frequencies. Signal-to-noise ratio improved by ~18 dB in the envelope domain.
The math behind this: if the bearing defect modulates a resonance at carrier frequency , the signal looks like
where is the low-frequency envelope containing the BPFI pattern. The Hilbert transform constructs the analytic signal
and the envelope is simply . FFT of reveals the fault frequencies that were invisible in the raw spectrum.
Where It Almost Failed: Outer Race Detection
Two months later, a different machine. Same sensor, same processing pipeline. FFT showed nothing. Ran envelope analysis. Got… noise.
No clear BPFO peaks (Ball Pass Frequency Outer race). Just a weak bump around 22 Hz (expected BPFO for this geometry) with SNR barely 3 dB above the floor. Temperature was normal. Current draw normal. But the plant engineer insisted something was wrong — he’d heard a faint grinding sound during manual inspection.
I was wrong about the resonance band. The bearing was a deep-groove ball bearing (6308-2RS), not the cylindrical roller from the first case. Natural frequency was 7-12 kHz, not 2-5 kHz. My bandpass filter was extracting gearbox mesh noise, not bearing resonance.
Reran with 8-11 kHz filter:
filtered_v2 = bandpass(data, 8000, 11000, fs, order=5)
analytic_v2 = hilbert(filtered_v2)
envelope_v2 = np.abs(analytic_v2)
BPFO peaks appeared at 22 Hz, 44 Hz, 66 Hz. Clear as day. SNR jumped to 14 dB.
Lesson: Envelope analysis is only as good as your resonance band selection. You need to know your bearing geometry and sensor mounting. ISO 10816 suggests 1-10 kHz for general machinery vibration, but bearing diagnostics require higher bands. For MEMS accelerometers, 5-15 kHz is typical. For piezoelectric sensors, you can go up to 20-40 kHz.
If you don’t know the resonance frequency, sweep the bands. I now run envelope analysis at 2-5 kHz, 5-10 kHz, 10-15 kHz, and pick the one with the highest kurtosis in the envelope signal. Kurtosis usually indicates impulsive content:
For Gaussian noise, kurtosis = 3. For impulsive bearing faults, kurtosis often exceeds 5-10.

When Envelope Analysis Doesn’t Help
Distributed faults: Wear, corrosion, lubrication starvation. These don’t produce impulsive transients — they raise the overall vibration floor. Envelope analysis will show nothing. Use RMS trending or statistical features (crest factor, shape factor) instead.
Low-speed bearings (<100 RPM): BPFI and BPFO fall below 1 Hz. Your envelope FFT resolution (determined by time window length) might not capture it. For a 10-second window, frequency resolution is 0.1 Hz. You’d need 60+ seconds of data to reliably detect sub-1 Hz faults. At that point, time-synchronous averaging or order tracking works better.
Outer race defects in loaded zones: Outer race faults only generate strong signals when the defect passes through the load zone (typically 90-120° of the bearing circumference). If your sensor is mounted opposite the load zone, you’ll see nothing. This is why you need at least two accelerometers (radial + axial, or opposing radial positions) for reliable outer race detection.
High background noise: If your gearbox mesh frequency is 800 Hz and you’re trying to detect BPFI at 45 Hz, the gear sidebands will swamp your envelope spectrum. Envelope analysis assumes the resonance band is dominated by bearing impacts — if it’s dominated by other sources, you’re just demodulating noise.
Computational Constraints: Real-Time vs. Batch
On a Raspberry Pi 4 (1.5 GHz ARM Cortex-A72, single-threaded NumPy), processing 10 seconds of data (36,000 samples) takes:
- Bandpass filter: 18 ms
- Hilbert transform: 42 ms
- Envelope FFT: 8 ms
- Total: ~70 ms
For real-time monitoring at 10 Hz update rate (100 ms budget), this works. For 100 Hz update rate (10 ms budget), it doesn’t. You’d need to downsample, reduce window length, or switch to a faster algorithm (e.g., Teager-Kaiser energy operator for envelope approximation, which runs 5× faster but trades accuracy).
On a cloud instance (AWS c6i.xlarge, 4 vCPU), processing 1000 bearings × 10 seconds each takes ~8 seconds with multiprocessing. Batch diagnostics are fine. Real-time fleet monitoring needs edge preprocessing.
The Detail That Matters: Filter Order and Phase Distortion
I used filtfilt (zero-phase filtering) instead of lfilter (standard causal filter). Why? Because causal filters introduce group delay, which shifts the timing of impulses in the filtered signal. When you extract the envelope, those shifted impulses create phantom peaks in the envelope spectrum.
Here’s what happens with a standard 4th-order Butterworth lowpass (causal):
from scipy.signal import lfilter
# Causal filter (introduces phase distortion)
filtered_causal = lfilter(b, a, data)
envelope_causal = np.abs(hilbert(filtered_causal))
Envelope spectrum shows BPFI peak split into two peaks at 31 Hz and 35 Hz (±2 Hz from true 33 Hz) due to group delay varying across the passband. filtfilt applies the filter forward and backward, canceling phase distortion. The peak stays at 33 Hz.
If you’re doing real-time processing and can’t use filtfilt (requires the full signal upfront), use a linear-phase FIR filter instead of IIR. The delay is constant across all frequencies, so you can compensate for it exactly.
My Current Workflow
- Collect 10-30 seconds of data at 5-10 kHz (depending on expected bearing resonance).
- Run envelope analysis at 3 candidate bands (low: 2-5 kHz, mid: 5-10 kHz, high: 10-15 kHz).
- Pick the band with highest envelope kurtosis.
- Compute envelope spectrum, identify BPFI/BPFO/BSF peaks using kinematic equations:
where is number of balls, is shaft frequency, is ball diameter, is pitch diameter, is contact angle.
- If SNR > 10 dB at expected fault frequency, flag for inspection. If 6-10 dB, recheck in 1 week. If <6 dB, continue monitoring.
This catches 70-80% of inner race faults 2-4 weeks before failure (based on 18 months of data from 200+ motors in a paper mill). Outer race detection is worse — maybe 50-60% with 1-2 week lead time, because of the load zone issue mentioned earlier.
What I Still Don’t Understand
Why does envelope analysis sometimes show strong harmonics at 5×BPFI and 7×BPFI but nothing at 3×BPFI or 4×BPFI? I’ve seen this on three different machines (all SKF 6306 bearings). The literature says you should see monotonically decreasing harmonic amplitudes. My data doesn’t match that. My best guess: it’s related to the sensor’s frequency response — maybe there’s a notch around 100-130 Hz in the mounting resonance — but I haven’t had time to validate with a shaker table.
Also not sure how much of the “3 weeks early detection” claim holds up under non-stationary loads. Most of my data comes from constant-speed conveyors and pumps. The two variable-speed applications (VFD-driven fans) have much noisier envelope spectra, and I’ve had one false negative (missed a fault that failed within 5 days).
Use envelope analysis when you suspect localized bearing faults (spalls, cracks) and have access to high-frequency vibration data (>1 kHz sampling). If your sensor tops out at 500 Hz or you’re dealing with distributed wear, stick with RMS trending and crest factor. And always — always — validate your resonance band selection before you trust the results. That almost cost me a week of downtime when I blindly applied the same filter to a different bearing type.
Next thing I want to test: combining envelope analysis with spectral correlation (squared envelope spectrum vs. raw FFT) to separate bearing faults from gear mesh harmonics in noisy drivetrains. The math is brutal (4D correlation matrix) but the papers claim 95%+ classification accuracy on CWRU bearing dataset. We’ll see if it holds up on real mill data.
Did you find this helpful?
☕ Buy me a coffee
Leave a Reply