python 语音学习-Vad检测+滑动平均(剔除语音空白)

tech2022-11-05  123

功能

将一段语音中空白处剔除

代码详解

from scipy.ndimage.morphology import binary_dilation import librosa import numpy as np import struct import librosa.display import webrtcvad import soundfile as sf // ** a的b次方 32767 int16_max = (2 ** 15) - 1 #输入 wav, source_sr = librosa.load("chunk123.wav", sr=None) // 计算语音检测窗口大小 //为整除 30秒X16000=总帧长 samples_per_window = (30 * 16000) // 1000 // 修剪音频的结尾,使其具有窗口大小的倍数。使wav的长度能被 samples_per_window整除 wav = wav[:len(wav) - (len(wav) % samples_per_window)] // 浮点数波形转换为16位单声道PCM *:接收到的参数会形成一个元组,**:接收到的参数会形成一个字典。如下代码。 // webrtcvad 的 is_speech 接收的是buf 所以这里需要转换 pcm_wave = struct.pack("%dh" % len(wav), *(np.round(wav * int16_max)).astype(np.int16)) // 执行语音激活检测 voice_flags = [] //  这里共有三种帧长可以用到,分别是80/10ms,160/20ms,240/30ms。其它采样率 // 的48k,32k,24k,16k会重采样到8k来计算VAD。之所以选择上述三种帧长度,是因为语 // 音信号是短时平稳信号,其在10ms~30ms之间可看成平稳信号,高斯马尔科夫等比较 // 的信号处理方法基于的前提是信号是平稳的,在10ms~30ms,平稳信号处理方法是可 // 以使用的。 //   从vad的代码中可以看出,实际上,系统只处理默认10ms,20ms,30ms长度的数据, // 其它长度的数据没有支持,笔者修改过可以支持其它在10ms-30ms之间长度的帧长度 // 发现也是可以的。 //   vad检测共四种模式,用数字0~3来区分,激进程度与数值大小正相关。 // 0: Normal,1:low Bitrate, 2:Aggressive;3:Very Aggressive 可以根据实际的使用 vad = webrtcvad.Vad(mode=3) for window_start in range(0, len(wav), samples_per_window): window_end = window_start + samples_per_window // append 进来的都是Boolean 这里以samples_per_windowx2 的长度去检测是否为人声 voice_flags.append(vad.is_speech(pcm_wave[window_start * 2:window_end * 2], sample_rate=16000)) voice_flags = np.array(voice_flags) // 𝑣_𝑏𝑖𝑎𝑠𝑒𝑑𝑡=𝑣𝑡/(1−𝛽𝑡) // 滑动平均计算 def moving_average(array, width): // 拼接 bool 二值化 // width 执行滑动平均平滑时,帧的平均数。 // 该值越大,VAD变化必须越大才能平滑。 array_padded = np.concatenate((np.zeros((width - 1) // 2), array, np.zeros(width // 2))) // 一维数组累加 ret = np.cumsum(array_padded, dtype=float) ret[width:] = ret[width:] - ret[:-width] return ret[width - 1:] / width //滑动平均计算 audio_mask = moving_average(voice_flags, 8) //将平均数四舍五入 转bool audio_mask = np.round(audio_mask).astype(np.bool) // 扩张浊音区 使用多维二元膨胀 是数学形态学的方法 类似opencv 也有开闭运算 腐蚀膨胀 // 举个栗子 // >>> struct1 = ndimage.generate_binary_structure(2, 1) // >>> struct1 // array([[False, True, False], // [ True, True, True], // [False, True, False]], dtype=bool) // >>> # 3x3 structuring element with connectivity 2 // >>> struct2 = ndimage.generate_binary_structure(2, 2) // >>> struct2 // array([[ True, True, True], // [ True, True, True], // [ True, True, True]], dtype=bool) // >>> ndimage.binary_dilation(a, structure=struct1).astype(a.dtype) // array([[ 0., 0., 0., 0., 0.], // [ 0., 0., 1., 0., 0.], // [ 0., 1., 1., 1., 0.], // [ 0., 0., 1., 0., 0.], // [ 0., 0., 0., 0., 0.]]) // >>> ndimage.binary_dilation(a, structure=struct2).astype(a.dtype) // array([[ 0., 0., 0., 0., 0.], // [ 0., 1., 1., 1., 0.], // [ 0., 1., 1., 1., 0.], // [ 0., 1., 1., 1., 0.], // [ 0., 0., 0., 0., 0.]]) audio_mask = binary_dilation(audio_mask, np.ones(6 + 1)) //使其与wav一样大小 audio_mask = np.repeat(audio_mask, samples_per_window) //通过这个遮罩扣掉没有声音那部分 res=wav[audio_mask == True] // 输出 sf.write("1234.wav", res.astype(np.float32), 16000, subtype='PCM_24')
最新回复(0)