分享

python实现语音信号处理常用度量方法

 LibraryPKU 2021-03-25

信噪比(SNR)

  有用信号功率与噪声功率的比(此处功率为平均功率),也等于幅度比的平方

SNR(dB)=10log10N1n=0s2(n)N1n=0d2(n)=10log10(PsignalPnoise)=20log10(AsignalAnoise)SNR(dB)=10log10⁡∑n=0N−1s2(n)∑n=0N−1d2(n)=10∗log10⁡(PsignalPnoise)=20∗log10(AsignalAnoise)
SNR(dB)=10log10N1n=0s2(n)N1n=0[x(n)s(n)2]SNR(dB)=10log10⁡∑n=0N−1s2(n)∑n=0N−1[x(n)−s(n)2]

其中:PsignalPsignal为信号功率(平均功率或者实际功率)PnoisePnoise为噪声功率;AsignalAsignal为信号幅度;AnoiseAnoise为噪声幅度值,功率等于幅度值的平方

MATLAB版本代码

复制代码
# 信号与噪声长度应该一样function snr=SNR_singlech(Signal,Noise)

P_signal = sum(Signal-mean(Signal)).^2;     # 信号的能量P_noise = sum(Noise-mean(Noise)).^2;     # 噪声的能量snr = 10 * log10(P_signal/P_noise)
复制代码

tensorflow版本SNR

复制代码
def tf_compute_snr(labels, logits):    # labels和logits都是三维数组 (batch_size, wav_data, 1)
    signal = tf.reduce_mean(labels ** 2, axis=[1, 2])
    noise = tf.reduce_mean((logits - labels) ** 2, axis=[1, 2])
    noise = tf.reduce_mean((logits - labels) ** 2 + 1e-6, axis=[1, 2])
    snr = 10 * tf.log(signal / noise) / tf.log(10.)    # snr = 10 * tf.log(signal / noise + 1e-8) / tf.log(10.)
    snr = tf.reduce_mean(snr, axis=0)    return snr
复制代码
Volodymyr Kuleshov论文实现方法

批注:这里的1e-6和1e-8,目的是为了防止出现Nan值,如果没有这个需求可以去除

numpy版本代码

复制代码
def numpy_SNR(labels, logits):    # origianl_waveform和target_waveform都是一维数组 (seq_len, )
    # np.sum实际功率;np.mean平均功率,二者结果一样
    signal = np.sum(labels ** 2)
    noise = np.sum((labels - logits) ** 2)
    snr = 10 * np.log10(signal / noise)    return snr
复制代码

峰值信噪比(PSNR)

  表示信号的最大瞬时功率和噪声功率的比值,最大瞬时功率为语音数据中最大值得平方。

SNR(dB)=10log10MAX[s(n)]21NN1n=0[x(n)s(n)]2=20log10MAX[s(n)]MSESNR(dB)=10log10⁡MAX[s(n)]21N∑n=0N−1[x(n)−s(n)]2=20log10⁡MAX[s(n)]MSE
def psnr(label, logits):
    MAX = np.max(label) ** 2  # 信号的最大平时功率
    MSE = np.mean((label - logits) ** 2)    return np.log10(MAX / MSE)

分段信噪比(SegSNR)

  由于语音信号是一种缓慢变化的短时平稳信号,因而在不同时间段上的信噪比也应不一样。为了改善上面的问题,可以采用分段信噪比。分段信噪比即是先对语音进行分帧,然后对每一帧语音求信噪比,最好求均值。

MATLAB版本的代码

View Code

python代码

复制代码
def SegSNR(ref_wav, in_wav, windowsize, shift):    if len(ref_wav) == len(in_wav):        pass
    else:        print('音频的长度不相等!')
        minlenth = min(len(ref_wav), len(in_wav))
        ref_wav = ref_wav[: minlenth]
        in_wav = in_wav[: minlenth]    # 每帧语音中有重叠部分,除了重叠部分都是帧移,overlap=windowsize-shift
    # num_frame = (len(ref_wav)-overlap) // shift
    #           = (len(ref_wav)-windowsize+shift) // shift
    num_frame = (len(ref_wav) - windowsize + shift) // shift  # 计算帧的数量
    SegSNR = np.zeros(num_frame)    # 计算每一帧的信噪比
    for i in range(num_frame):
        noise_frame_energy = np.sum(ref_wav[i * shift: i * shift + windowsize] ** 2)  # 每一帧噪声的功率
        speech_frame_energy = np.sum(in_wav[i * shift: i * shift + windowsize] ** 2)  # 每一帧信号的功率
        SegSNR[i] = np.log10(speech_frame_energy / noise_frame_energy)    return 10 * np.mean(SegSNR)
复制代码

信号回声比 (Signal to echo ratio, SER)

SER=10log10E{s2(n)}E{d2(n)}SER=10log10⁡E{s2(n)}E{d2(n)}

其中E是统计 期望操作,s(n)s(n)是近端语音,d(n)d(n)是远端回声

复制代码
def SER(near_speech, far_echo):    """signal to echo ratio, 信号回声比
    :param near_speech: 近端语音
    :param far_echo: 远端回声 
    """
    return 10*np.log10(np.mean(near_speech**2)/np.mean(far_echo**2))
复制代码

回声损失增强 (Echo Return Loss Enhancement, ERLE)

  回波损失增强度量(ERLE)通常用于评估系统没有近端信号单通话情况下 的回声减少。ERLE的定义是

ERLE(dB)=10log10E{y2(n)}E{s^2(n)}ERLE(dB)=10log10⁡E{y2(n)}E{s^2(n)}

其中E是统计 期望操作,y(n)y(n)是麦克风信号,s^(n)s^(n)是估计的近端语音信号。

复制代码
def compute_ERLE(mic_wav, predict_near_end_wav):    """ 
    :param mic_wav: 麦克风信号(y) = 近端语音(s) + 远端语音回声(s) + 噪声(v)
    :param predict_near_end_wav: 估计的近端语音信号 \hat{s}
    麦克风信号    """
    mic_mear = np.mean(mic_wav**2)
    predict_near_end_wav = np.mean(predict_near_end_wav**2)
    ERLE = 10 * np.log10(mic_mear/predict_near_end_wav)    return ERLE
复制代码

为了评估系统在双讲情况下的性能,通常采用PESQ (语音质量感知评价)或STOI (短时语音可懂度),他是通过将估计的近端语音和仅在双讲通话期间真实的近端语音进行比较得到的。PESQ评分范围为-0.5 ~ 4.5,分数越高质量越好。STOI评分范围为0~1,分数越高越好。

对数拟然对比度 (log Likelihood Ratio Measure)

  坂仓距离测度是通过语音信号的线性预测分析来实现的。ISD基于两组线性预测参数(分别从原纯净语音和处理过的语音的同步帧得到)之间的差异。LLR可以看成一种坂仓距离(Itakura Distance,IS)但是IS距离需要考虑模型增益。而LLR不需要考虑模型争议引起的幅度位移,更重视整体谱包络的相似度。

语音质量感知评估 (Perceptual Evaluation of Speech Quality, PESQ)

ITU-T的全系列参考目标语音质量测量系列

  • 1997年的P.861标准(PSQM)

  • 2001年的P.862标准(PESQ),P.862 标准《窄带电话网络端到端语音质量和话音编解码器质量的客观评价方法》,推荐使用语音质量感知评价PESQ算法,该建议是基于输入-输出方式的典型算法,效果良好。后来补充了P.862.1,P.862.2(宽带测量),P.862.3(应用指南)

  • 2011年的P.863标准(POLQA)

  PESQ算法需要带噪的衰减信号和一个原始的参考信号。开始时将两个待比较的语音信号经过电平调整、输入滤波器滤波、时间对准和补偿、听觉变换之后,分别提取两路信号的参数,综合其时频特性,得到PESQ分数,最终将这个分数映射到主观平均意见分(MOS)。PESQ得分范围在-0.5--4.5之间。得分越高表示语音质量越好。

  ITU提供了C语言代码,下载请点击这里,但是在使用之前我们需要先编译C脚本,生成可执行文件exe

编译方式为:在命令行进入下载好的文件

  1. cd \Software\source

  2. gcc -o PESQ *.c

  经过编译,会在当前文件夹生成一个pesq.exe的可执行文件

使用方式为:

  1. 命令行进入pesq.exe所在的文件夹

  2. 执行命令:pesq 采样率 "原始文件路径名" "劣化文件路径名”

  3. 回车

  4. 等待结果即可,值越大,质量越好。

    • 例如:pesq +16000 raw.wav processed.wav

感知客观语音质量评估(POLQA)

  POLQA是PESQ的继承者(ITU-T P.862建议书)。POLQA避免了当前P.862型号的弱点,并且扩展到处理更高带宽的音频信号。进一步的改进针对具有许多延迟变化的称为信号和信号的时间的处理。与P.862类似,POLQA支持普通电话频段(300-3400 Hz)的测量,但此外它还具有第二种操作模式,用于评估宽带和超宽带语音信号中的HD-Voice(50-14000)赫兹)。

  POLQA是全参考算法,并且在对应的参考和测试信号的摘录的时间对准之后逐个样本地分析语音信号。POLQA可用于为网络提供端到端(E2E)质量评估,或表征各个网络组件。

  POLQA结果主要是模型平均意见得分(MOS),涵盖从1(差)到5(优秀)的范围。

对数谱距离(LSD)

  对数谱距离Log Spectral Distance,LSD是两个频谱之间的距离度量。也称为“对数谱失真”

LSD=1Mm=0M{1Li=0L[10log10|s(l,m)|210log10|s^(l,m)|2]}LSD=1M∑m=0M{1L∑i=0L[10log10⁡|s(l,m)|2−10log10⁡|s^(l,m)|2]}

  式中,llmm分别为频率索引和帧索引,MM为语音帧数,LL为频点数,S^(l,m)S^(l,m)S(l,m)S(l,m)分别为估计音频和宽带音频经过短时短时傅里叶变换后的频谱。

numpy版本

复制代码
# 方法一def numpy_LSD(labels, logits):    """ labels 和 logits 是一维数据 (seq_len,)"""
    labels_spectrogram = librosa.stft(labels, n_fft=2048)  # (1 + n_fft/2, n_frames)
    logits_spectrogram = librosa.stft(logits, n_fft=2048)  # (1 + n_fft/2, n_frames)
    labels_log = np.log10(np.abs(labels_spectrogram) ** 2)
    logits_log = np.log10(np.abs(logits_spectrogram) ** 2)    # 先处理频率维度
    lsd = np.mean(np.sqrt(np.mean((labels_log - logits_log) ** 2, axis=0)))    return lsd# 方法二def get_power(x):
    S = librosa.stft(x, n_fft=2048)  # (1 + n_fft/2, n_frames)
    S = np.log10(np.abs(S) ** 2)    return Sdef compute_log_distortion(labels, logits):    """labels和logits数据维度为 (batch_size, seq_len, 1)"""
    avg_lsd = 0
    batch_size = labels.shape[0]    for i in range(batch_size):
        S1 = get_power(labels[i].flatten())
        S2 = get_power(logits[i].flatten())        # 先处理频率轴,后处理时间轴
        lsd = np.mean(np.sqrt(np.mean((S1 - S2) ** 2, axis=0)), axis=0)
        avg_lsd += lsd    return avg_lsd / batch_size
复制代码

tensorflow版本

复制代码
def get_power(x):
    x = tf.squeeze(x, axis=2)  # 去掉位置索引为2维数为1的维度 (batch_size, input_size)
    S = tf.signal.stft(x, frame_length=2048, frame_step=512, fft_length=2048,
                       window_fn=tf.signal.hann_window)    # [..., frames, fft_unique_bins]
    S = tf.log(tf.abs(S) ** 2) / tf.log(10.)    # S = tf.log(tf.abs(S) ** 2 + 9.677e-9) / tf.log(10.)
    return Sdef tf_compute_log_distortion(labels, logits):    """labels和logits都是三维数组 (batch_size, input_size, 1)"""
    S1 = get_power(labels)  # [..., frames, fft_unique_bins]
    S2 = get_power(logits)  # [..., frames, fft_unique_bins]
    # 先处理频率维度,后处理时间维度
    lsd = tf.reduce_mean(tf.sqrt(tf.reduce_mean((S1 - S2) ** 2, axis=2)), axis=1)
    lsd = tf.reduce_mean(lsd, axis=0)    return lsd
复制代码

但如果想要numpy版本的值和tensorflow版本的值一样,可以使用下面的代码

View Code

批注:librosa.stft中center设为False,和np.log10中加1e-8,目的是为了最终的值和tensorflow版本的lsd值相近,如果没有这个需求可以去除。这里tf.log中加9.677e-9是为了和numpy中的值相近,如果没有这个需求可以去除

短时客观可懂度(STOI)

下载一个 pystoi 库:pip install pystoi

   STOI 反映人类的听觉感知系统对语音可懂度的客观评价,STOI 值介于0~1 之间,值越大代表语音可懂度越高,越清晰。

from pystoi import stoi

stoi_score = stoi(label, logits, fs_sig=16000)

加权谱倾斜测度(WSS)

WSS值越小说明扭曲越少,越小越好,范围

 

参考文献

PESQ语音质量测试

视频质量度量指标

度量方法仓库

https://github.com/schmiph2/pysepm

python-pesq

pystoi

pysepm

PyPESQ

speechmetrics

Voice quality metrics

作者:凌逆战
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
本文章不做任何商业用途,仅作为自学所用,文章后面会有参考链接,我可能会复制原作者的话,如果介意,我会修改或者删除。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多