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

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

相关推荐
陈奕昆2 分钟前
五、【LLaMA-Factory实战】模型部署与监控:从实验室到生产的全链路实践
开发语言·人工智能·python·llama·大模型微调
明月看潮生17 分钟前
青少年编程与数学 02-019 Rust 编程基础 09课题、流程控制
开发语言·算法·青少年编程·rust·编程与数学
程序猿小三23 分钟前
python uv的了解与使用
开发语言·python·uv
oioihoii25 分钟前
C++23 views::slide (P2442R1) 深入解析
linux·算法·c++23
T0uken26 分钟前
【Python】UV:单脚本依赖管理
chrome·python·uv
yuhao__z1 小时前
代码随想录算法训练营第六十三天| 图论9—卡码网47. 参加科学大会,94. 城市间货物运输 I
算法·图论
郭逍遥1 小时前
[工具]B站缓存工具箱 (By 郭逍遥)
windows·python·缓存·工具
alpszero1 小时前
YOLO11解决方案之物体模糊探索
人工智能·python·opencv·计算机视觉·yolo11
June`1 小时前
专题三:穷举vs暴搜vs深搜vs回溯vs剪枝(全排列)决策树与递归实现详解
c++·算法·深度优先·剪枝
vlln1 小时前
适应性神经树:当深度学习遇上决策树的“生长法则”
人工智能·深度学习·算法·决策树·机器学习