• 语音信号端点检测


    语音信号的端点检测方法有很多种,简单的方法可以直接通过计算出声音的音量大小,找到音量大于某个阈值的部分,认为该部分为需要的语音信号,该部分与阈值的交点即为端点,其余部分认为非语音帧。

    计算音量

    计算音量的方法有两种,一种是以帧为单位(每一帧包含多个采样点),将该帧内的所有采样点的幅值的绝对值之后相加,作为该帧的音量值:

    Vi = sum(|Wi|)

    以采样率为 11025 Hz ,时长为 1s 的波形为例:该波形含有 11025 个采样点,若取帧长为 framesize = 256,帧间重叠大小为 overlap = 128,则计算出来的音量数组包含 frameNum = 11025 / (256 - 128) = 86.13,取整为 frameNum = 87。计算前 86 帧的音量代码(代码为 volume.pycalcNormalVolume):

    for i in range(frameNum - 1):
        # 获取第 i 帧数据
        curFrame = wave[i*step: i*step + framesize]
        curFrame = curFrame - np.mean(curFrame)
        # 公式: v = sum(|w|)
        volume[i] = np.sum(np.abs(curFrame))
    

    不论采样点的数量是否能够被帧大小整除,最后一帧都需要单独判断,取波形长度和下一帧的波形长度较小的一个,并计算最后一帧的音量:

    curFrame = wave[(frameNum - 1)*step: min((frameNum - 1)*step + framesize, wlen)]
    curFrame = curFrame - np.mean(curFrame)
    volume[(frameNum - 1)] = np.sum(np.abs(curFrame))
    

    另一种方法是计算分贝音量,与上面的代码差别在于计算 volume 时,使用的公式不同:

    Vi = 10 * log10( sum(|Wi| ^ 2) )

    因此计算 volume[i] 的代码需要修改一下:

    v = np.sum(np.power(curFrame, 2))
    
    volume[i] = 10 * np.log10(v) if v > 0 else 0
    

    一般很少出现平方和 v 的值为 0 的情况,不过为了避免这种情况,计算时当 v = 0 时不需要经过 log 运算,直接给音量赋 0 值。

    理论上讲,当上面代码中计算出 v 为 0 的情况,经过对数运算后得到的值应该为 负无穷 而不是 0。

    根据阈值找到端点

    计算出音量后,就得到了一组离散的点,将其绘制在窗口上可以得到一个曲线, 阈值就是平行于横轴的一条直线,这条直线与曲线的交点认为是端点。判断曲线是否与阈值相交的方法很简单,(ys[i] - threshold) * (ys[i+1] - threshold) < 0。这个方法的缺点在于,当 ys[i] 或者 ys[i+1] 恰好等于 threshold 时,可能会遗漏端点。

    def simpleEndPointDetection(vol, wave: vp.Wave, thresholds):
        """一种简单的端点检测方法,首先计算出声波信号的音量(能量),分别以
        音量最大值的10%和音量最小值的10倍为阈值,最后以前两种阈值的一半作为阈值。
        分别找到三个阈值与波形的交点并绘制图形,在查找交点时,各个阈值之间没有相互联系
    
        figure 1:绘制出声波信号的波形,并分别用 red green blue 三种颜色的竖直线段
        画出检测到的语音信号的端点
        figure 2: 绘制声波音量的波形。并分别用 red green blue 三种颜色的音量阈值横线
        表示出三种不同的阈值。
        """
        # 给出三个固定的阈值
        threshold1 = thresholds[0]
        threshold2 = thresholds[1]
        threshold3 = thresholds[2]
        deltatime = wave.deltatime
        frame = np.arange(0, len(vol)) * deltatime
        
        # 分别找出三个不同的阈值
        index1 = vp.findIndex(vol, threshold1) * deltatime
        index2 = vp.findIndex(vol, threshold2) * deltatime
        index3 = vp.findIndex(vol, threshold3) * deltatime
        end = len(wave.ws) * (1.0 / wave.framerate)
        
        plt.subplot(211)
        plt.plot(wave.ts,wave.ws,color="black")
        if len(index1) > 0:
            plt.plot([index1,index1],[-1,1],'-r')
        if len(index2) > 0:
            plt.plot([index2,index2],[-1,1],'-g')
        if len(index3) > 0:
            plt.plot([index3,index3],[-1,1],'-b')
        plt.ylabel('Amplitude')
        
        plt.subplot(212)
        plt.plot(frame, vol, color="black")
        if len(index1) > 0:
            plt.plot([0,end],[threshold1,threshold1],'-r', label="threshold 1")
        if len(index2) > 0:
            plt.plot([0,end],[threshold2,threshold2],'-g', label="threshold 2")
        if len(index3) > 0:
            plt.plot([0,end],[threshold3,threshold3],'-b', label="threshold 3")
        plt.legend()
        plt.ylabel('Volume(absSum)')
        plt.xlabel('time(seconds)')
        plt.show()
    

    上述代码通过 findIndex 找出端点的序号 index1、index2、index3,然后计算出这几个端点在时间轴上的数值(乘以 deltatime)。最后使用 plt 进行绘制。

    另一种比较复杂的是在给定一个阈值的基础上,再通过一个新的 threshold 计算出更大的语音部分,详见 volume.py 中的 findIndexWithPreIndex。这种方法于上面的类似,但是只用到了两组端点索引。

    calcNormalVolume 和 calcDbVolume 计算得到的曲线不同,最终得到的端点也不一样。以 one.wav 为语音样本,通过 DbVolume 计算得到的端点效果不如 normalVolume 得到的端点。

    根据过零率找到端点

    计算过零率也是以帧为单位,判断每两个相邻的采样值是否异号,代码和 findIndex 类似,这样可以得到每一帧中越过 0 的采样点的个数:

    zcr[i] = sum(curFrame[:-1]*curFrame[1:] < 0) / framesize
    

    与音量曲线类似,给定阈值之后就可以找到端点。绘制出过零率后可以看到,语音部分的过零率比非语音部分的过零率要低很多。

    小结

    不论是通过音量还是过零率,实际上都是通过固定的阈值找到端点,当信噪比较大情况下,很容易找到端点(one.wav 中的信噪比较大),但当信噪比较小时,固定的阈值就不一定能够找到端点了。并且固定的阈值(本文中用到的是占音量区间一定比例作为阈值)并不适应不同的语音信号,难以找出端点。

    其他常见的还有 MFCC 系数、自相关函数等等方法可以找到语音信号的端点。

    代码

    github :

    1. jupyter notebook
    2. volume 代码

    参考

    1. thinkdsp-cn
    2. 语音信号处理之时域分析-音量及其Python实现
  • 相关阅读:
    初学者之 Git 和 Github
    80端口与8080端口是两种不同的端口吗?他们到底有什么区别和联系?
    redis 基础(一) 初步了解redis
    spring 基础(四)浏览器跨域访问+拦截器(Interceptor)
    mysql商业版和社区版
    spring 基础(五) spring mvc RESTful
    解决idea控制台打印乱码问题
    springBoot 基础-拓展(二) 记录一些常用的配置文件
    SpringBoot 基础(零) SpringBoot和Spring
    springBoot 基础-拓展(一) spring-boot-starter
  • 原文地址:https://www.cnblogs.com/brifuture/p/10889567.html
Copyright © 2020-2023  润新知