You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WAV局部频域绘制:自动截段、分频段平滑及dB轴转换问题

音频频域图绘制问题及解决方案

背景

我需要绘制音频样本的频域图,对信号处理一无所知,Python基础薄弱,参考示例后用fft和fftfreq实现初步效果。所有音频时长约30秒,含两个相同极短脉冲声,需在浏览器绘图,仅保留相关片段少量数据点。当前已实现的完整代码如下:

def doFFT(dataw: ndarray, samplerate: int): 
    dataw = dataw[(round(24.98*samplerate)):(round(24.98*samplerate)+32767)]
    datafft = rfft(dataw)  
    # 获取实部和虚部的绝对值
    fftabs = abs(datafft)     
    freqs = rfftfreq(dataw.shape[0], 1/(samplerate))  
    smoothed = savgol_filter(fftabs,  10,  3)  
 
    return [ freqs[:int(freqs.size/2)].tolist() , smoothed[:int(freqs.size/2)].tolist() ]

将结果转为JSON后用Plotly在浏览器绘图,当前效果如图:
当前结果

问题与解决方案

问题1:手动选取时间段方法粗糙,如何自动定位脉冲片段?

当前手动截取代码:

samplerate, dataw= wavfile.read('x.wav')
dataw = dataw[(round(24.98*samplerate)):(round(24.98*samplerate)+32767)]

解决方案:
通过信号能量自动定位脉冲,再截取有效片段:

import numpy as np
from scipy.io import wavfile

samplerate, dataw = wavfile.read('x.wav')
# 多声道转单声道
if len(dataw.shape) > 1:
    dataw = np.mean(dataw, axis=1)
# 计算信号能量
energy = np.abs(dataw)
# 设定阈值(可根据音频实际情况调整,比如取能量均值的5倍)
threshold = 5 * np.mean(energy)
# 找出超过阈值的脉冲索引
pulse_indices = np.where(energy > threshold)[0]

if len(pulse_indices) > 0:
    # 扩展截取范围,保留脉冲前后足够数据
    start_idx = max(0, pulse_indices[0] - 1024)
    end_idx = min(len(dataw), pulse_indices[-1] + 1024)
    dataw = dataw[start_idx:end_idx]

这种方法无需硬编码时间点,能自动适配不同音频的脉冲位置。

问题2:如何实现分区间不同平滑因子的频谱平滑?

朋友的Matlab分区间平滑代码:

%% 划分频谱三个区间,用不同平滑因子提升可读性
smooth_spect_1 = fastsmooth(Spectrum_dB(1:70), 5, 1, 1); 
smooth_spect_2 = fastsmooth(Spectrum_dB(71:700), 7, 1, 1);         
smooth_spect_3 = fastsmooth(Spectrum_dB(701:end), 41 , 1, 1);        
total_smooth = [smooth_spect_1; smooth_spect_2; smooth_spect_3];
plot(f, total_smooth);

当前用单一savgol_filter处理全频谱,高频平滑不足。

解决方案:
在Python中对频谱按频率区间拆分,分别应用不同平滑参数后拼接:

from scipy.signal import savgol_filter
import numpy as np

# 假设已得到freqs(频率数组)和fftabs(频谱幅值)
# 先转dB(提前处理问题3)
spect_dB = 20 * np.log10(fftabs / np.max(fftabs))
spect_dB = np.where(fftabs == 0, -np.inf, spect_dB)

# 定义频率区间与对应平滑窗口大小
freq_ranges = [
    (0, 700),    # 低频区间
    (700, 7000), # 中频区间
    (7000, None) # 高频区间
]
window_sizes = [5, 7, 41]

smoothed_parts = []
filtered_freqs = []
for (low, high), win_size in zip(freq_ranges, window_sizes):
    # 筛选对应频率区间的索引
    if high is None:
        mask = freqs >= low
    else:
        mask = (freqs >= low) & (freqs < high)
    # 平滑对应区间
    smoothed_part = savgol_filter(spect_dB[mask], win_size, 1)
    smoothed_parts.append(smoothed_part)
    filtered_freqs.append(freqs[mask])

# 拼接结果
total_smooth = np.concatenate(smoothed_parts)
final_freqs = np.concatenate(filtered_freqs)

注:频率区间数值需根据采样率和实际频谱范围调整,若偏好按索引划分,直接替换为索引切片即可。

问题3:如何将Y轴转换为dB单位?

解决方案:
采用音频频谱转dB的标准公式:dB = 20 * log10(幅值 / 参考幅值),示例代码:

import numpy as np

# 方法1:归一化到频谱峰值(0dB为最大值)
spect_dB = 20 * np.log10(fftabs / np.max(fftabs))
# 处理0值避免log报错
spect_dB = np.where(fftabs == 0, -np.inf, spect_dB)

# 方法2:基于音频采样位数(如16位音频参考值为32767)
# reference = 32767
# spect_dB = 20 * np.log10(fftabs / reference)

将转换后的spect_dB作为Y轴数据传入Plotly,即可实现dB刻度显示。


内容的提问来源于stack exchange,提问作者davidear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 18:53:24