实现Butterworth带通滤波器后输出与输入信号一致的排查求助
解决Butterworth带通滤波未生效的问题
直接说你代码里的核心问题和对应修复方法:
1. DataFrame浅拷贝导致原始数据被篡改
你写的smooth_df = original_df是浅拷贝,修改smooth_df的列时,original_df会被同步修改,最后对比的其实是同一个数据,自然看不出差异。
修复:
smooth_df = original_df.copy() # 改成深拷贝,彻底分离原始数据和滤波后数据
2. 滤波器参数重复归一化导致全通滤波
scipy的butter函数在传入fs参数时,Wn应该直接填实际频率值(Hz),不需要手动除以nyquist归一化。你现在手动归一化后又传入fs,相当于把截止频率缩小了一倍,导致滤波器的通带几乎覆盖整个信号频率,变成了全通滤波,输出自然和输入一致。
修复:
删掉手动归一化的代码,直接传原始截止频率:
cutoff_high = 4.0 cutoff_low = 0.01 # 去掉归一化步骤,直接传实际频率给Wn b, a = butter(N=3, Wn=[cutoff_low, cutoff_high], btype='bandpass', fs=con.sample_rate_ET)
3. 频率响应图的错误
你用freqs(b,a)得到的是角频率(rad/s),而且画的axvline(100)和你的截止频率完全不相关,根本没法验证滤波器是否正确。
修复:用freqz替代freqs,指定fs参数让x轴显示实际频率(Hz),并画正确的截止线:
# 放到循环内部,每个列都生成对应的响应图 w, h = freqz(b, a, fs=con.sample_rate_ET) plt.semilogx(w, 20 * np.log10(abs(h))) plt.title(f'Butterworth带通滤波器频率响应({column}列)') plt.xlabel('频率 [Hz]') plt.ylabel('幅度 [dB]') plt.margins(0, 0.1) plt.grid(which='both', axis='both') plt.axvline(cutoff_low, color='green', linestyle='--', label=f'低截止 {cutoff_low}Hz') plt.axvline(cutoff_high, color='red', linestyle='--', label=f'高截止 {cutoff_high}Hz') plt.legend() plt.show()
其他细节优化
- 把打印语句
print("Data will be now be smoothed with a low-pass BW filter.")改成带通的描述,避免混淆。 - 确认
con.sample_rate_ET确实是当前数据集的采样率(比如120Hz),如果这个值不对,滤波器参数也会失效。
修复后的关键代码片段
def smooth(columns_to_smooth): original_df = mask(mask_cols=['diameter', 'diameter_3d']) smooth_df = original_df.copy() # 深拷贝分离数据 logging.basicConfig(filename='filtering_errors.log', level=logging.ERROR) print("将使用Butterworth带通滤波器平滑数据。") cutoff_high = 4.0 cutoff_low = 0.01 for column in columns_to_smooth: try: if smooth_df[column].isnull().any(): raise ValueError(f"列 {column} 中检测到NaN值,无法滤波") # 直接传入实际频率和采样率 b, a = butter(N=3, Wn=[cutoff_low, cutoff_high], btype='bandpass', fs=con.sample_rate_ET) smooth_values = filtfilt(b, a, smooth_df[column]) if np.isnan(smooth_values).any(): raise ValueError(f"列 {column} 滤波后出现NaN值") smooth_df[column] = smooth_values # 绘制当前列的频率响应图 w, h = freqz(b, a, fs=con.sample_rate_ET) plt.semilogx(w, 20 * np.log10(abs(h))) plt.title(f'Butterworth带通滤波器频率响应({column}列)') plt.xlabel('频率 [Hz]') plt.ylabel('幅度 [dB]') plt.margins(0, 0.1) plt.grid(which='both', axis='both') plt.axvline(cutoff_low, color='green', linestyle='--', label=f'低截止 {cutoff_low}Hz') plt.axvline(cutoff_high, color='red', linestyle='--', label=f'高截止 {cutoff_high}Hz') plt.legend() plt.show() except ValueError as e: print(f"错误: {e}") smooth_df.to_csv(con.SMOOTHED_PUPIL, index=False) # 对比绘图 plt.plot(original_df['pupil_timestamp'], original_df['diameter'], label='原始数据', alpha=0.7) plt.plot(smooth_df['pupil_timestamp'], smooth_df['diameter'], label='滤波后数据', alpha=0.7) plt.xlabel('时间戳') plt.ylabel('瞳孔直径') plt.title('原始数据与滤波后数据对比') plt.legend() plt.show() # 统计信息输出 original_mean = original_df['diameter'].mean() smoothed_mean = smooth_df['diameter'].mean() original_std = original_df['diameter'].std() smoothed_std = smooth_df['diameter'].std() print(f"原始均值: {original_mean:.4f}, 滤波后均值: {smoothed_mean:.4f}") print(f"原始标准差: {original_std:.4f}, 滤波后标准差: {smoothed_std:.4f}")
验证步骤
- 先看频率响应图,确认0.01Hz以下和4Hz以上的频率幅度被衰减(低于0dB)。
- 对比数据图,应该能看到滤波后的数据更平滑,高频噪声被去除。
- 检查统计信息,滤波后的数据标准差会明显变小。
内容的提问来源于stack exchange,提问作者Fiza
相关产品推荐
相关产品推荐

