一个关于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)

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

相关推荐
喜欢吃豆6 分钟前
微调高级推理大模型(COT)的综合指南:从理论到实践
人工智能·python·语言模型·大模型·微调·强化学习·推理模型
喜欢吃豆1 小时前
从指令遵循到价值对齐:医疗大语言模型的进阶优化、对齐与工具集成综合技术白皮书
人工智能·python·语言模型·自然语言处理·大模型·强化学习·constitutional
Access开发易登软件1 小时前
Access调用Azure翻译:轻松实现系统多语言切换
后端·python·低代码·flask·vba·access·access开发
yumgpkpm1 小时前
CMP (类Cloudera) CDP7.3(400次编译)在华为鲲鹏Aarch64(ARM)信创环境中的性能测试过程及命令
大数据·hive·hadoop·python·elasticsearch·spark·cloudera
Rubisco..1 小时前
牛客周赛 Round 111
数据结构·c++·算法
兮山与1 小时前
算法8.0
算法
高山上有一只小老虎1 小时前
杨辉三角的变形
java·算法
Swift社区1 小时前
LeetCode 395 - 至少有 K 个重复字符的最长子串
算法·leetcode·职场和发展
hz_zhangrl1 小时前
CCF-GESP 等级考试 2025年9月认证C++四级真题解析
开发语言·c++·算法·程序设计·gesp·c++四级·gesp2025年9月
代码小菜鸡6661 小时前
java 常用的一些数据结构
java·数据结构·python