信号处理入门3(频域分析)

1 DFT/FFT

DFT (离散傅里叶变换)

它的本质就是之前相关性极致应用。拿一堆不同频率的正弦波去跟被测信号算"乘积和"。哪个频率算出来的数大,就说明信号里含有哪个频率成分。

DFT的算法还是能理解,不算太困难。比如下面这个,左边有8个点,然后右边有8个比较的频率。每个X点乘以F点,求和算出那个频率的能量最大。这个算法的本身没问题,就是运算量大,时间是N的平方。

FFT (快速傅里叶变换)

参考:https://www.bilibili.com/video/BV1N94y147uY/?spm_id_from=333.788.recommend_more_video.1&trackid=web_related_0.router-related-2481894-7ch22.1769838083241.320&vd_source=419a548545c645ffdc5fe02a7c592d56

DFT 的计算量是 N^2,如果信号长,计算量就太大,电脑就跑不动了。

FFT 利用了正弦波的对称性,比如依然是上面8个点的示意图,所有要计算的波叠加在一起,可以看到圈出来的点一共计算了8次,但是这8次只有2次是独立的,而其余的6次都是重复计算,所以计算2次其实就够了。这种总共把8个点算完,计算24次就够了,不是DFT的64次。在数据量越大,这个运算差距更明显。

简单看了一下算法,其实FFT就是一个递归。把计算量降到了 Nlog2N。这直接让实时信号处理变成了可能(比如你的手机能实时显示均衡器跳动)。

实验:

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

# --- 1. 参数设置 ---
fs = 1000              # 采样率 (Hz)
N = 1000               # 采样点数
t = np.linspace(0, 1, N, endpoint=False) # 时间轴

# --- 2. 构造信号 ---
# 50Hz 主信号 + 一些噪声
clean_signal = np.sin(2 * np.pi * 50 * t)
noise = 0.8 * np.random.randn(N)
signal = clean_signal + noise

# --- 3. 执行 FFT ---
fft_result = np.fft.fft(signal)
amplitudes = np.abs(fft_result) / (N / 2) # 归一化振幅
frequencies = np.fft.fftfreq(N, 1/fs)

# --- 4. 绘图 (上下两部分) ---
plt.figure(figsize=(10, 8))

# 上半部分:时域波形 (Time Domain)
plt.subplot(2, 1, 1) # 2行1列,第1张
plt.plot(t[:200], signal[:200]) # 只画前200个点,看得更清楚
plt.title("Time Domain: Signal + Noise")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.grid(True)

# 下半部分:频域结果 (Frequency Domain)
plt.subplot(2, 1, 2) # 2行1列,第2张
# 只画正频率部分 (0 到 fs/2)
plt.plot(frequencies[:N//2], amplitudes[:N//2], color='red')
plt.title("Frequency Domain: FFT Result")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude")
plt.grid(True)

plt.tight_layout() # 自动调整布局,防止标题重叠
plt.show()

结果

2 频谱泄露

FFT 默认你截取的那段信号是周期循环的。但如果截取的位置正好不在波形的周期点上(首尾接不起来),在 FFT 看来,信号就在边缘处发生了一个剧烈的跳变。在微积分和傅里叶变换理论中,一个瞬时的跳变(类似于阶跃信号)包含了无限宽的频谱。为了用一堆平滑的正弦波去"凑"出这个生硬的跳变点,FFT 必须在结果中加入大量的其他频率成分。

比如输入的是一个纯净的 50Hz 正弦波,FFT 出来的结果却在 50Hz 周围有一堆乱七八糟的小毛刺。

具体的试验在下个部分做。

3 窗函数

给信号乘一个"中间高、两头低"的函数(比如 汉宁窗 Hanning 或 海明窗 Hamming)。强制让信号在这一帧的开头和结尾逐渐归零。这样首尾相连时就平滑了,泄露也就被极大地抑制了。但是窗函数会把频谱的峰值"变胖"(分辨率下降)。

比起FFT,窗函数就简单多了。下面是最常用的汉明窗 (Hamming Window)。

公式:

  • n:当前采样点的序号。

  • N:总的采样点数(窗口长度)。

实现代码:

cpp 复制代码
#include <math.h>

#define WINDOW_SIZE 512
float hamming_table[WINDOW_SIZE];

// 1. 初始化:预先算好窗函数表(在系统启动时运行一次)
void init_hamming_window() {
    for (int n = 0; n < WINDOW_SIZE; n++) {
        hamming_table[n] = 0.54f - 0.46f * cosf(2.0f * M_PI * n / (WINDOW_SIZE - 1));
    }
}

// 2. 处理:将原始信号和窗函数相乘
void apply_window(float *input_signal, float *output_signal) {
    for (int i = 0; i < WINDOW_SIZE; i++) {
        // 每个采样点乘以对应的窗系数
        output_signal[i] = input_signal[i] * hamming_table[i];
    }
}

不同的窗函数应用:

窗函数名称 特点 适用场景
矩形窗 (Rectangular) 边缘直接切断 只有当你处理的信号周期刚好和采样长度对齐时才用。
汉明窗 (Hamming) 边缘平滑,性能均衡 最通用。语音识别、普通频谱分析。
汉宁窗 (Hann) 边缘完全降到 0 适合对频率精度要求高的情况。
布莱克曼窗 (Blackman) 泄露极低,但主峰很宽 适合寻找强信号旁边隐藏的微弱小信号

使用窗函数的前后对比。

python 复制代码
import numpy as np
import matplotlib.pyplot as plt

# 1. 信号设置
fs = 1000
N = 200
t = np.arange(N) / fs
freq = 123.4
clean_x = np.sin(2 * np.pi * freq * t)
noise = 0.2 * np.random.randn(N)
x = clean_x + noise

# 2. 方案 A: 仅加窗
window = np.hamming(N)
x_windowed = x * window

# 3. 方案 B: 差分方程 (滑动平均) + 加窗
# 我们用一个 5 点滑动平均滤波器:y[n] = (x[n] + x[n-1] + ... + x[n-4]) / 5
def moving_average(data, window_size=5):
    return np.convolve(data, np.ones(window_size)/window_size, mode='same')

x_avg = moving_average(x, window_size=5)
x_avg_windowed = x_avg * window

# 4. 计算 FFT
def get_fft_db(sig):
    mag = np.abs(np.fft.rfft(sig))
    return 20 * np.log10(mag + 1e-6)

freqs = np.fft.rfftfreq(N, 1/fs)
fft_raw = get_fft_db(x)
fft_win = get_fft_db(x_windowed)
fft_combo = get_fft_db(x_avg_windowed)

# 5. 绘图
plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(t, x, 'gray', alpha=0.5, label='Raw Noisy')
plt.plot(t, x_avg, 'b', label='After Moving Average (Time Domain)')
plt.title("Time Domain: Smoothing the Spikes")
plt.legend()

plt.subplot(2, 1, 2)
plt.plot(freqs, fft_raw, 'r', alpha=0.5, label='Raw + No Window')
plt.plot(freqs, fft_win, 'g', label='Raw + Hamming Window')
plt.plot(freqs, fft_combo, 'b', lw=2, label='MovingAvg + Hamming (The Combo)')
plt.axvline(freq, color='k', linestyle='--', label='Target Freq')
plt.ylim(-20, 40)
plt.title("Frequency Domain: The 'Combo' approach cleans the floor")
plt.legend()
plt.tight_layout()
plt.show()

结果

可以看到,在多重算法下首先是降低了底噪,然后真实频率更加突出。

4 DCT

FFT 是基于圆周旋转的(复数,有实部虚部);DCT 只用余弦波,只处理实数。相比 FFT,DCT 能把信号大部分的能量压缩到极少数的几个低频系数上。

这里的高低频,不是颜色的频率,而是变化的频率,就是数值的变化。低频就是变化不剧烈,高频就是变化剧烈。

目前看到的JPG图片和听到的MP3音乐,核心压缩算法全是DCT。因为它能用最少的数据量还原出人眼/人耳最关心的特征。人眼对亮度的敏感程度,是高于对颜色的敏感程度。

也就是说以前我们看黑白电视,或者黑白视频,也可以看的很欢乐的原因。对于人眼的这种特征,所以有了YUV格式,其中Y就是亮度,如果是黑白,那么只要亮度就行了。在YUV中,UV蓝红是颜色,人眼不敏感,直接大幅度压缩,从采样开始就去掉75%。然后只保留一些低频信号。对于Y亮度,保留低频的部分,一般高频的部分直接压缩取0,特别高频的就是轮廓然后保留。

DCT相对FFT只用了余弦,然后对数据做镜像扩展。。。也不去计算相位。。。

好了,暂时先写到这里吧,图形的处理感觉还有很多要看的,比如图形频率的准确定义。后面再弄吧。。。

相关推荐
枷锁—sha4 小时前
【CTFshow-pwn系列】06_前置基础【pwn 035】详解:利用 SIGSEGV 信号处理机制
java·开发语言·安全·网络安全·信号处理
ghie909015 小时前
使用经验模态分解(EMD)处理振动信号,并结合样本熵进行特征提取
信号处理
Aaron15881 天前
通信灵敏度计算与雷达灵敏度计算对比分析
网络·人工智能·深度学习·算法·fpga开发·信息与通信·信号处理
安徽必海微马春梅_6688A1 天前
A实验:小动物无创血压系统 小动物无创血压分析系统 资料。
大数据·人工智能·网络安全·硬件工程·信号处理
地球资源数据云1 天前
SCI制图——Origin信号处理:FFT变换与滤波降噪
信号处理·origin
s09071362 天前
【SAS信号处理】SAS信号处理中的“停-走-停”假设失效:原理、误差分析与三种修正算法
算法·信号处理·sas·合成孔径
weixin_690654742 天前
龙迅#LT7621GX 适用于两路HDMI2.1/DP1.4/TPYE-C/EDP转HDMI2.1 应用功能,分辨率高达8K@60HZ。
音视频·信号处理
weixin_690654742 天前
#龙迅LT6911D 高性价比IC ,功能适用于HDMI转两 PORT MIPIDSI/CSI,分辨率高达4K。
音视频·信号处理
runningshark2 天前
【电赛】电赛报告注意事项
信号处理