稿子一大堆,写的多了又怕大家说我串稀式更新,嘤嘤嘤!
之前不是写频域拼接吗?有人问:频域分片采样,ifft重构后,子带交叠部分是否会有失真?非常好的问题,这其实是频谱拼接式 ADC / 频域分片采样 (Frequency-Sliced Sampling, FSS) 的核心关键之一。(我不确定有没有什么实际的意义)
假设原信号 的频谱 在 内,我们将其划分为多个频率子带:
其中第 个子带的带宽为 ,中心频率为 。
每个子带 经过带通滤波后,被分别下变频到基带:
再各自以较低采样率 采样;最后在数字域中,通过上变频 + 拼接 + IFFT(或 IDFT 合成)重构回去。
在理想情况下,如果:各个子带的 带宽完全等分;各子带间 频率分界点无重叠也无空隙;每个子带采样前的 模拟带通滤波器 边缘响应理想(即矩形带通);数字重构时 子带的幅度和相位都完美对齐(含 group delay);
则在 IFFT 拼接后:
不会有任何失真。
如果每个子带的带通滤波器不是理想矩形(实际上不可能),其过渡带会造成频谱交叠,如下图所示:
X0(f) X1(f)
┌────┐ ┌────┐
└───┬──┘
↑ 过渡带交叠区
在重构时,如果两个子带的重叠部分都被保留,则叠加后该频段能量会双计 → 幅度失真(Amplitude Ripple),解决办法是在交叠区加 cosine taper / overlap-add 窗函数(类似 STFT 拼接)。
每个子带可能来自不同通道(不同滤波器、不同ADC路径),其相位响应或群延迟略有不同:
拼接时,频域相位突变 → 时域表现为波形边界振铃、过冲,类似“stitching glitch”或“Gibbs效应”;需要校准每个子带的 group delay;在数字域中补偿相位;或使用平滑的 crossfade 方式拼接。
如果分片不是在 FFT 网格上整齐切割(例如频率分辨率 Δf 不对齐),则 IFFT 时每个子带对应的 bin 不连续,出现频谱泄漏 → 时域失真;需要统一采样点数与 FFT 长度;子带频率边界必须是 FFT bin 的整数倍;或使用小重叠(50% overlap)+ 窗函数拼接法。
设两个相邻子带为:
其中 与 的过渡带有重叠区 。
拼接结果为:
如果在交叠区 , 就会出现失真:若 >1 → 幅度过高;若 <1 → 幅度缺损;若相位不一致 → 相位失真。
因此理想拼接需满足:
这正是“Perfect Reconstruction Filter Bank(完美重构滤波器组)”的基本条件。
要恢复的部分 | 理想情况 | 现实失真来源 | 解决方案 |
|---|---|---|---|
幅度响应 | 子带边界无重叠 | 过渡带重叠导致幅度双计 | overlap-add 窗平滑 |
相位响应 | 各通道群延一致 | 相位不对齐造成振铃 | group delay 校准 |
频率分辨率 | FFT bin 完全对齐 | 不对齐导致泄漏 | 统一采样点/FFT 长度 |
滤波器设计 | PRFB 满足 H₁+H₂=1 | 滤波器设计不完美 | 采用Cosine-Modulated FB |
读者肯定说,教练,我看不懂!
“两个频域子带有 10% 过渡带交叠时,IFFT 重构的时域波形与理想波形对比(含幅度失真与相位畸变)”
绘制出:频域拼接前后幅度响应;IFFT 后时域波形差异;幅度误差随交叠比例变化曲线。
STDOUT/STDERR
=== Reconstruction RMS Error (time-domain, normalized) ===
Case A (Bad rectangular + overlap double-count): 0.099867
Case B (Good complementary raised-cosine): 0.000000
Case C (Good mag, but subband-2 delay τ=3.0): 0.490463

Case A: Bad masks (rectangular + overlap double-count)






子带有重叠但直接相加(双计)→ 失真
图“Case A”里 H1+H2 在交叠区等于 2,导致频域能量双计;时域重构 RMS 误差 ≈ 0.0999;误差谱主要集中在分界处(图“Error spectra around crossover”)。
在交叠区做互补 crossfade(raised-cosine,保证 H1+H2=1)→ 无幅度失真
图“Case B”里 H1+H2 ≡ 1;时域重构 RMS 误差 = 0(数值精度内);这就是完美重构滤波器组(PRFB)的核心条件:交叠区满足 。
群延迟不一致(相位错配)→ 即使幅度互补也会失真
给子带2加了 τ=3 个采样的额外延迟;有效合成增益 在分界附近出现凹陷(图“Case C”);时域 RMS 误差升至 0.4905,局部波形出现振铃(最后一张图)。
仿真完成过渡带宽度 vs 误差、延迟偏差 vs 误差画参数扫描热力图。





=== Single-case RMS (vs x_ref) ===
Ideal: 6.274424e-17
Delay mismatch τ=2 on band2: 0.453603
Gain mismatch +0.5 dB on band2: 0.014614
=== RMSE vs overlap for τ in {0,1,2,3,5} (K=4) ===
tau=0.0: 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000
tau=1.0: 0.4081, 0.4071, 0.4045, 0.4019, 0.3993, 0.3967, 0.3941, 0.3914, 0.3888
tau=2.0: 0.4600, 0.4573, 0.4547, 0.4519, 0.4492, 0.4463, 0.4435, 0.4406, 0.4376
tau=3.0: 0.1978, 0.1941, 0.1918, 0.1895, 0.1873, 0.1852, 0.1833, 0.1814, 0.1797
tau=5.0: 0.4305, 0.4293, 0.4279, 0.4264, 0.4247, 0.4229, 0.4210, 0.4189, 0.4167
=== RMSE vs gain mismatch (dB) for overlap in {0,0.1,0.2,0.3,0.4} (τ=0) ===
overlap=0.0: 0.0272, 0.0207, 0.0140, 0.0071, 0.0000, 0.0073, 0.0148, 0.0225, 0.0305
overlap=0.1: 0.0269, 0.0205, 0.0138, 0.0070, 0.0000, 0.0072, 0.0147, 0.0223, 0.0302
overlap=0.2: 0.0265, 0.0202, 0.0137, 0.0069, 0.0000, 0.0071, 0.0145, 0.0220, 0.0298
overlap=0.3: 0.0262, 0.0199, 0.0135, 0.0068, 0.0000, 0.0070, 0.0143, 0.0217, 0.0294
overlap=0.4: 0.0258, 0.0197, 0.0133, 0.0067, 0.0000, 0.0069, 0.0141, 0.0214, 0.0290
只要交叠区做“互补窗”使 ,则在幅度层面可以做到完美重构。
把 4 子带的互补 raised-cosine 交叉淡入淡出实现为:每个边界两侧各一半过渡,交叠内 与 互补,整带求和恒等于 1;这就是 K 通道完美重构滤波器组(PRFB)的“Partition of Unity”。
相位/群延迟不一致会引入显著失真(即使幅度互补)。
=== K=4 single-case RMS errors ===
Ideal (mag+phase perfect): 5.196418e-01
Delay mismatch (band2 τ=2.0): 0.836231
Gain mismatch (band2 +0.5 dB): 0.512645
=== Sweep summary (delay mismatch) ===
Min RMSE=5.008023e-01 at τ=0.000 samples, overlap=0.000 of subband
=== Sweep summary (gain mismatch) ===
仿真(K=4,子带2额外延迟 )的单例结果:幅度与相位都理想:RMS 误差 ≈ 6.5e−17(数值零)。
仅子带2 延迟 个采样:RMS ≈ 0.4536(很大)。
仅子带2 增益 +0.5 dB:RMS ≈ 0.0146(比相位错配小很多)。
对齐的时候先做相位/群延迟标定,再谈幅度微调相位错一点点,代价远大于轻微增益误差。
“频域分片采样,IFFT 重构后,子带交叠部分是否会有失真?”
不会——前提是:交叠区采用互补窗,满足 ;子带之间相位/群延迟已对齐(或在数字域补偿);分界点与 FFT 网格尽可能对齐。
会——如果:
交叠直接相加导致双计(幅度起伏);相位/群延迟不一致(会出现明显失真/振铃);网格不对齐且无交叠窗,导致泄漏与拼接撕裂。
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(2)
# ---------- Test signal ----------
N = 65536
fs = 1.0
t = np.arange(N) / fs
# Multisine + noise (real)
num_tones = 160
freqs = np.linspace(0.005, 0.49, num_tones)
phases = 2*np.pi*np.random.rand(num_tones)
x = np.zeros(N)
for f, phi in zip(freqs, phases):
x += np.cos(2*np.pi*f*t + phi)
x += np.random.randn(N)*0.15
x = x / np.std(x)
X = np.fft.fftshift(np.fft.fft(x))
freq = np.fft.fftshift(np.fft.fftfreq(N, d=1/fs))
# ---------- Helper: build K complementary masks with raised-cosine overlaps ----------
def build_K_masks(freq, K=4, f_low=0.0, f_high=0.49, overlap_frac=0.1):
Hs = [np.zeros_like(freq, dtype=float) for _ in range(K)]
B = (f_high - f_low) / K
ov = overlap_frac * B
for k in range(K):
fk_left = f_low + k*B
fk_right = fk_left + B
a = fk_left + ov/2.0
b = fk_right - ov/2.0
if a < b:
idx_core = (freq >= a) & (freq <= b)
Hs[k][idx_core] = 1.0
if k > 0 and ov > 0:
aL = fk_left - ov/2.0
bL = fk_left + ov/2.0
idx = (freq >= aL) & (freq <= bL)
w = 0.5*(1 - np.cos(np.pi*(freq[idx]-aL)/(bL-aL))) # 0->1
Hs[k][idx] += w
if k < K-1 and ov > 0:
aR = fk_right - ov/2.0
bR = fk_right + ov/2.0
idx = (freq >= aR) & (freq <= bR)
w = 0.5*(1 + np.cos(np.pi*(freq[idx]-aR)/(bR-aR))) # 1->0
Hs[k][idx] += w
return Hs
def reconstruct(X, freq, Hs, tau_list=None, gain_list=None):
K = len(Hs)
Y = np.zeros_like(X, dtype=complex)
for k in range(K):
Hk = Hs[k]
phase = 1.0
if tau_list is not None:
tau = tau_list[k]
phase = np.exp(-1j*2*np.pi*freq*tau)
gain = 1.0 if gain_list is None else gain_list[k]
Y += (gain * Hk) * phase * X
y = np.fft.ifft(np.fft.ifftshift(Y)).real
return y, Y
def rms(a):
return np.sqrt(np.mean(a**2))
# Target band for perfect reconstruction
K = 4
overlap_frac = 0.12
f_low = 0.0
f_high = 0.49
Hs = build_K_masks(freq, K=K, f_low=f_low, f_high=f_high, overlap_frac=overlap_frac)
# Reference: bandlimited version of x (what the system intends to pass)
Hsum = np.zeros_like(freq)
for Hk in Hs:
Hsum += Hk
Y_ref = Hsum * X
x_ref = np.fft.ifft(np.fft.ifftshift(Y_ref)).real
# Perfect case
y_ideal, Y_ideal = reconstruct(X, freq, Hs)
e_ideal = y_ideal - x_ref
# Delay mismatch on band2
tau = 2.0
tau_list = [0.0, 0.0, tau, 0.0]
y_delay, Y_delay = reconstruct(X, freq, Hs, tau_list=tau_list)
e_delay = y_delay - x_ref
# Gain mismatch on band2 (+0.5 dB)
g_lin = 10**(0.5/20)
gain_list = [1.0, 1.0, g_lin, 1.0]
y_gain, Y_gain = reconstruct(X, freq, Hs, gain_list=gain_list)
e_gain = y_gain - x_ref
print("=== K=4 single-case RMS errors (vs x_ref in [0, 0.49]) ===")
print(f"Ideal (mag+phase perfect): {rms(e_ideal):.6e}")
print(f"Delay mismatch (band2 τ={tau}): {rms(e_delay):.6f}")
print(f"Gain mismatch (band2 +0.5 dB): {rms(e_gain):.6f}")
# Plot masks
plt.figure(figsize=(8,4.5))
for k, Hk in enumerate(Hs):
plt.plot(freq, Hk, label=f"H{k}")
plt.plot(freq, Hsum, label="Sum", linewidth=2)
plt.xlim(0.0, 0.5)
plt.ylim(-0.05, 1.2)
plt.title("K=4 complementary masks and their sum")
plt.xlabel("Normalized frequency (cycles/sample)")
plt.ylabel("Gain")
plt.legend()
plt.tight_layout()
plt.show()
# Heatmap sweep: RMSE vs overlap and delay (vs x_ref)
ov_list = np.linspace(0.0, 0.4, 21)
tau_list_grid = np.linspace(0.0, 5.0, 26)
RMSE = np.zeros((len(tau_list_grid), len(ov_list)))
for i, tau_val in enumerate(tau_list_grid):
for j, ov_frac in enumerate(ov_list):
Hs_tmp = build_K_masks(freq, K=K, f_low=f_low, f_high=f_high, overlap_frac=ov_frac)
Hsum_tmp = np.zeros_like(freq)
for Hk in Hs_tmp: Hsum_tmp += Hk
x_ref_tmp = np.fft.ifft(np.fft.ifftshift(Hsum_tmp * X)).real
tl = [0.0, 0.0, tau_val, 0.0]
y_tmp, _ = reconstruct(X, freq, Hs_tmp, tau_list=tl)
RMSE[i,j] = rms(y_tmp - x_ref_tmp)
plt.figure(figsize=(8,4.5))
plt.imshow(RMSE, aspect='auto', origin='lower',
extent=[ov_list[0], ov_list[-1], tau_list_grid[0], tau_list_grid[-1]])
plt.colorbar(label="RMS error")
plt.xlabel("Overlap fraction of subband width")
plt.ylabel("Delay mismatch τ (samples)")
plt.title("RMS error vs overlap and delay mismatch (K=4)")
plt.tight_layout()
plt.show()
# Slices: RMSE vs overlap for selected τ
plt.figure(figsize=(8,4.5))
for tau_val in [0.0, 1.0, 2.0, 3.0, 5.0]:
idx = np.argmin(np.abs(tau_list_grid - tau_val))
plt.plot(ov_list, RMSE[idx,:], label=f"τ={tau_val}")
plt.xlabel("Overlap fraction of subband width")
plt.ylabel("RMS error")
plt.title("RMSE vs overlap for different delay mismatches")
plt.legend()
plt.tight_layout()
plt.show()
# Heatmap: RMSE vs overlap and gain mismatch (no delay)
g_db_list = np.linspace(-1.0, 1.0, 21)
RMSEg = np.zeros((len(g_db_list), len(ov_list)))
for i, gdb in enumerate(g_db_list):
glin = 10**(gdb/20)
for j, ov_frac in enumerate(ov_list):
Hs_tmp = build_K_masks(freq, K=K, f_low=f_low, f_high=f_high, overlap_frac=ov_frac)
Hsum_tmp = np.zeros_like(freq)
for Hk in Hs_tmp: Hsum_tmp += Hk
x_ref_tmp = np.fft.ifft(np.fft.ifftshift(Hsum_tmp * X)).real
y_tmp, _ = reconstruct(X, freq, Hs_tmp, gain_list=[1.0, 1.0, glin, 1.0])
RMSEg[i,j] = rms(y_tmp - x_ref_tmp)
plt.figure(figsize=(8,4.5))
plt.imshow(RMSEg, aspect='auto', origin='lower',
extent=[ov_list[0], ov_list[-1], g_db_list[0], g_db_list[-1]])
plt.colorbar(label="RMS error")
plt.xlabel("Overlap fraction of subband width")
plt.ylabel("Gain mismatch on band2 (dB)")
plt.title("RMS error vs overlap and gain mismatch (τ=0, K=4)")
plt.tight_layout()
plt.show()
# Slices: RMSE vs gain mismatch for selected overlaps
plt.figure(figsize=(8,4.5))
for ov_frac in [0.0, 0.1, 0.2, 0.3, 0.4]:
j = np.argmin(np.abs(ov_list - ov_frac))
plt.plot(g_db_list, RMSEg[:,j], label=f"overlap={ov_frac}")
plt.xlabel("Gain mismatch on band2 (dB)")
plt.ylabel("RMS error")
plt.title("RMSE vs gain mismatch for different overlaps (τ=0)")
plt.legend()
plt.tight_layout()
plt.show()
# Summary
i_min, j_min = np.unravel_index(np.argmin(RMSE), RMSE.shape)
print("=== Sweep summary (delay mismatch) ===")
print(f"Min RMSE={RMSE[i_min,j_min]:.6e} at τ={tau_list_grid[i_min]:.3f} samples, overlap={ov_list[j_min]:.3f}")
i2_min, j2_min = np.unravel_index(np.argmin(RMSEg), RMSEg.shape)
print("=== Sweep summary (gain mismatch) ===")
print(f"Min RMSE={RMSEg[i2_min,j2_min]:.6e} at gain_err={g_db_list[i2_min]:.3f} dB, overlap={ov_list[j2_min]:.3f}"