一个关于FFT计算的疑似乌龙事件

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)

没有意义。频谱只需要展示实部。

相关推荐
绵绵细雨中的乡音几秒前
动态规划-第六篇
算法·动态规划
程序员黄同学10 分钟前
动态规划,如何应用动态规划解决实际问题?
算法·动态规划
步木木12 分钟前
Anaconda和Pycharm的区别,以及如何选择两者
ide·python·pycharm
星始流年13 分钟前
解决PyInstaller打包PySide6+QML应用的资源文件问题
python·llm·pyspider
南玖yy15 分钟前
Python网络爬虫:从入门到实践
爬虫·python
march_birds28 分钟前
FreeRTOS 与 RT-Thread 事件组对比分析
c语言·单片机·算法·系统架构
The Future is mine1 小时前
Python计算经纬度两点之间距离
开发语言·python
斯汤雷1 小时前
Matlab绘图案例,设置图片大小,坐标轴比例为黄金比
数据库·人工智能·算法·matlab·信息可视化
九月镇灵将1 小时前
GitPython库快速应用入门
git·python·gitpython
云 无 心 以 出 岫1 小时前
贪心算法QwQ
数据结构·c++·算法·贪心算法