0.缘起
最近进行一些频域解析,发现不知道从何时起,大部分都犯了一个相当典型的错误。我是在尝试将数据转至频域去零点,然后再转回时域,发现这个bug的。这个问题波及面很广,估计有很多已经在用的程序都会犯这个错误:
1.错误浮现 - 例程1 时域转频域去零点然后转回时域
我们期待的结果是这样的:
但是如果你尝试一步步用去频点0的谱线,然后进行幅角修正,然后转回时域,你会发现所得到的波形无法和原始波形匹配,类似这样:
1.1错误的利用FFT去除信号零点的方法
import numpy as np
import matplotlib.pyplot as plt
# 生成频率为100Hz和200Hz的信号
fs = 1000 # 采样频率
t = np.arange(0, 1, 1/fs)
signal = 18+0.7*np.sin(2*np.pi*100*t) + 0.3*np.sin(2*np.pi*200*t) + 0.5*np.sin(2*np.pi*150*t)
# 进行FFT
fft_signal = np.fft.fft(signal)
fft_signal_abs = np.abs(fft_signal)
# 去零点进行幅角修正
fft_signal_phase_corrected = fft_signal.copy()
fft_signal_phase_corrected[0] = 0
fft_signal_phase_corrected_abs = np.abs(fft_signal_phase_corrected)
max_val = np.max(fft_signal_phase_corrected_abs)
max_idx = np.argmax(fft_signal_phase_corrected_abs)
print(max_idx, "max value in freq< = max value = ", max_val)
fft_signal_phase_corrected = fft_signal_phase_corrected * np.exp(-1j * np.angle(fft_signal_phase_corrected[max_idx]))
fft_signal_phase_corrected_abs = np.abs(fft_signal_phase_corrected)
fft_signal_with_phase_correction = np.fft.ifft(fft_signal_phase_corrected)
fft_signal_with_phase_correction_abs = np.abs(fft_signal_with_phase_correction)
# 去直流分量(不进行浮点修正)
fft_signal[0] = 0
fft_signal_no_phase_correction = np.fft.ifft(fft_signal)
fft_signal_no_phase_correction_abs = np.abs(fft_signal_no_phase_correction)
# 绘制频谱图
plt.figure(figsize=(12, 6))
# 绘制时间域波形
plt.subplot(2, 2, 1)
plt.plot(t, signal)
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
str_time = 'Time Domain Signal=%dPt, totalTimeSpan=%fS' %(len(t), len(t)*1.0/fs)
plt.title(str_time)
plt.subplot(2, 2, 2)
plt.plot(np.arange(len(fft_signal_abs)), fft_signal_abs, color='blue')
str_fft = 'Original Spectrum, totalPt=%d' %(len(fft_signal_abs))
plt.title(str_fft)
plt.xlabel('Frequency')
plt.ylabel('Amplitude')
plt.grid(True)
plt.subplot(2, 2, 3)
plt.plot(np.arange(len(fft_signal_no_phase_correction_abs)), fft_signal_no_phase_correction_abs, color='red')
plt.title('Spectrum => Time without Phase Correction')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.grid(True)
plt.subplot(2, 2, 4)
plt.plot(np.arange(len(fft_signal_with_phase_correction_abs)), fft_signal_with_phase_correction_abs, color='green')
plt.title('Spectrum => Time with Phase Correction')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.grid(True)
plt.tight_layout()
plt.show()
实际运行结果:
2.问题分析 - 例程1修复
在FFT展示时的np.abs()那个地方。FFT的结果,real是幅度,imag存放的幅角。取模没有意义,如果我们把上述代码显示的视图中,2,3,4图都是错的。很容易校回,校回后的时域,频域和去零点后的时域波形:
我只改了这几处地方:
python
#第一处改动:第一次时域到频域的验算,实际显示时,谱线高度要除以采样点数:
fft_signal_abs = [z.real/fs for z in fft_signal]
#第二处改动:
fft_signal_with_phase_correction_abs = [z.real for z in fft_signal_with_phase_correction]
#第三处相似改动:
fft_signal_no_phase_correction_abs = [z.real for z in fft_signal_no_phase_correction]
3.后记
这个错误是如此触目惊心。我不知道它的波及面有多广,我在大量的帖子里看到错误的频谱纵坐标。有心人可以考证一下,这个错误是什么人,什么时候引入的。对于现在的大多数运算工具,fft返回的那个复数队列中的每一个点,实部是幅度,虚部是幅角。
np.abs(real+j*imag) = sqrt(real*real + imag*imag)
没有意义。频谱只需要展示实部。