前言:
早期的方法大多是基于声学特征的提取, 在时域上, 1975年, Rabiner 等人提出了基于短时能量和过零率的语音端点检测方法, 这是第一个系统而完整的语音端点检测算法。该方法共有三个门限值, 前两个是通过短时能量值来设置高、低两个门限, 进行端点位置的初判, 第三个是通过短时过零率值来设定, 并最终确定语音倾的起始点和终止点。该方法计算量小, 可以满足实时性的要求, 且在高信噪比 下具有较好的检测性能, 所以被广泛用于语音信号处理的各个领域。之后, 许多学者在这一方法的基础上又进行了一定的改进, 如基于绝对值能量的双门限检测算法,顾亚强等人提出了一种通过计算语音喊和噪音喊的过零率和短时能量的差分进行语音端点检测的方法, 陈振标等人提出了一种结合多子带能量特征和最优化边缘检测判决准则的算法。在频域上, Koba 等人通过对每一带噪语音顿分别进行快速傅立叶变换, 提出了一种基于频域信息的语音端点检测算法,这是第一个在频域上进行分析, 进而提取出相应特征的语音端点检测算法。之后, 提出了许多基于频域特征的语音端点检测算法, 如基于信息熵的语音端点检测算法、基于美尔倒谱的语音端点检测算法等。但无论是基于时域还是基于频域, 都有各自的优点与不足, 于是人们想要采用时频域相结合的方法, 更好的发挥各自的优点, 于是, Lin等人提出了增强时频 参数该参数包含了时域和频域两种信。但无论是基于时域、基于频域还是基于时频域相结合的方法, 它们在较高的信噪比下识别率较高, 但实际进行语音端点检测的环境是复杂多样的, 并且很难保证较高的信噪比。随后, 提出了基于模型匹配的语音端点检测方法, 这些方法通常包括训练阶段和测试阶段, 在训练阶段, 就像人学习知识一样, 让模型通过对训练语料的学习, 来获得模型参数, 在测试阶段, 只需将待划分语音与训练好的模型进行比对, 就可将其区分。隐马尔可夫模型(HMM) 最早发表在一些统计学论文中, 现在是语音识别技术中应用最广泛的一种模型。1998 年, 朱杰等人将HMM 模型应用于加入背景噪声的语音端点检测中, 首次提出了基于HMM 模型的语音端点检测算法。实验结果表明, 使用该方法来区分语音喊和噪音顿的准确率明显高于基于双门限的方法, 而且对于一些爆破音、鼻音和弱摩擦音等, 也很少出现丢失语音侦的现象。此外, 董恩清等人还将基于统计学理论的支持向量机(SVM) 应用于加噪的语音信号的端点检测方法中, 提出了基于SVM 的语音端点检测算法并验证了其有效性,但由于运算量较大, 该方法在训练阶段往往要花费很长的时间, 而在实际的分类中却计算量较小, 耗时较短。以上这些方法在较低的信噪比下仍可获得较高的准确率, 但也有其局限性, 首先, 由于模型中参数较少, 因此难以较好的描述数据, 其次, 这些方法都是从有限的数据集中估计参数, 可能不能充分的利用信息。近来, 由于基于神经网络的方法可以克服基于模型匹配方法的局限性, 并能取得较好的结果, 越来越多的人釆用这种学习方法进行语音端点检测, 包括深度信念网络等, 这极大的扩展了人们在这一领域的探索思路。但此类方法可能会因初始点的选取而陷入局部最优值, 从而影响语音端点检测的效果。
分析:
目前主流的VAD算法:
- 基于短时能量和过零率的语音端点检测算法
- 基于信息熵的语音端点检测算法
- 基于短时能频值的语音端点检测算法
- 基于神经网络的语音端点检测
经过分析,1、2、3 适用于在高性噪比环境下。在信噪比较低的环境下,检测效果不是太理想。而基于神经网络的方式,则在低信噪比的环境下,检测效果则优于1、2、3。
参考文章:
- https://www.docin.com/p-1537242532.html
- https://blog.csdn.net/ffmpeg4976/article/details/52349007
- https://blog.csdn.net/baienguon/article/details/80539296
项目背景:
由于公司项目采用C#开发 ,所以本文实现思路采用 C# 调用python训练模型进行语音端点检测。
知识储备:(分享一些我学习的音频方面的文章)
- 音频属性:https://blog.csdn.net/aoshilang2249/article/details/38469051
- MFCC详解:https://blog.csdn.net/suan2014/article/details/82021324
- 傅里叶转换:https://blog.csdn.net/u011947630/article/details/81513075
- 还有一些忘记收藏了。总之遇到不明白的地方多查查。
实现思路:
- 采集噪声和人声两个正负样本的音频文件,使用Python 通过提取MFCC特征值来训练,保存模型文件。
- 由于我采用的是基于Keras LSTM 来训练的 ,但Keras 没有提供C#接口 所以只能将模型转成.pb文件。然后使用TensorflowSharp来调用.pb文件来预测结果。
- (C#)我设定我采集的时间间隔为1s,移动的幅度为500ms。
- 首先采集1s的音频信息,然后提取1s音频的MFCC特征值进行预测,如果预测结果为人声,则表示为声音的起点,将该段音频保存下来,如果为噪声,则舍弃。然后移动500ms,在提取1s的音频MFCC特征值进行预测,如果是人声并上一个也是人声,则将音频偏移的500ms音频保存下来,若上一个是噪声,则表示该音频为起点。总之就这样以此类推,最后会检测出一段符合模型特征的人声音频。
- 以上就是我实现VAD的大概思路,可能不是很完美,但也是我想到的最好解决的办法了。毕竟能力有限啊 ,如果各位大佬有什么好的解决办法,欢迎来指导一二,小弟感激不尽!!
代码:
1.Python 训练样本模型 基于 Kersa LSTM:
引用:https://github.com/Renovamen/Speech-Emotion-Recognition
https://github.com/hcmlab/vadnet
训练样本音频 : 16k 16bit
部分主要代码:(由于代码较多,所以只贴出主要实现代码)
from keras.utils import np_utils
from DNN_Model import LSTM_Model
from Utilities import get_data
DATA_PATH = 'DataSet/'
CLASS_LABELS = ("hg", "nd", "pz")
def train():
FLATTEN = False
NUM_LABELS = len(CLASS_LABELS)
SVM = False
x_train, x_test, y_train, y_test = get_data(DATA_PATH, class_labels=CLASS_LABELS, flatten=FLATTEN, _svm=SVM)
y_train = np_utils.to_categorical(y_train)
y_test_train = np_utils.to_categorical(y_test, len(CLASS_LABELS))
print('-------------------------------- LSTM Start --------------------------------')
model = LSTM_Model(input_shape=x_train[0].shape, num_classes=NUM_LABELS)
model.train(x_train, y_train, x_test, y_test_train, n_epochs=100)
model.evaluate(x_test, y_test)
model.save_model("LSTM1")
print('-------------------------------- LSTM End --------------------------------')
train()
读取数据和提取mfcc特征值
# 从给定文件夹读取数据和提取MFCC特征
import os
import sys
from typing import Tuple
import numpy as np
import scipy.io.wavfile as wav
from sklearn.model_selection import train_test_split
from keras.models import model_from_json
from sklearn.externals import joblib
from MFCC_COM import get_mfcc
import scipy.io.wavfile
mean_signal_length = 16000
"""
get_feature(): 提取某个音频的MFCC特征向量
输入:
file_path(str): 该音频路径
mfcc_len(int): 每帧的MFCC特征数
flatten(bool): 是否降维数据
输出:
numpy.ndarray: 该音频的MFCC特征向量
"""
def get_feature(file_path: str, mfcc_len: int = 39, flatten: bool = False):
# 某些音频用scipy.io.wavfile读会报 "Incomplete wav chunk" error
# 似乎是因为scipy只能读pcm和float格式,而有的wav不是这两种格式...
# fs, signal = wav.read(file_path)
# signal, fs = librosa.load(file_path, 16000)
fs, signal = scipy.io.wavfile.read(file_path)
s_len = len(signal)
# 如果音频信号小于mean_signal_length,则扩充它
if s_len < mean_signal_length:
pad_len = mean_signal_length - s_len
pad_rem = pad_len % 2
pad_len //= 2
signal = np.pad(signal, (pad_len, pad_len + pad_rem), 'constant', constant_values=0)
# 否则把它切开
else:
pad_len = s_len - mean_signal_length
pad_len //= 2
signal = signal[pad_len:pad_len + mean_signal_length]
mel_coefficients = get_dll(signal, fs, mfcc_len)
# 用 SVM & MLP 模型时要降维数据
if flatten:
mel_coefficients = np.ravel(mel_coefficients)
return mel_coefficients
def get_feature_result(signal, fs, mfcc_len: int = 39, flatten: bool = False):
# 某些音频用scipy.io.wavfile读会报 "Incomplete wav chunk" error
# 似乎是因为scipy只能读pcm和float格式,而有的wav不是这两种格式...
# fs, signal = wav.read(file_path)
# signal, fs = librosa.load(file_path)
s_len = len(signal)
# 如果音频信号小于mean_signal_length,则扩充它
if s_len < mean_signal_length:
pad_len = mean_signal_length - s_len
pad_rem = pad_len % 2
pad_len //= 2
signal = np.pad(signal, (pad_len, pad_len + pad_rem), 'constant', constant_values=0)
# 否则把它切开
else:
pad_len = s_len - mean_signal_length
pad_len //= 2
signal = signal[pad_len:pad_len + mean_signal_length]
mel_coefficients = get_mfcc(signal, fs, mfcc_len)
# mel_coefficients = librosa.feature.mfcc(signal, fs, n_mfcc=39)
# 用 SVM & MLP 模型时要降维数据
if flatten:
mel_coefficients = np.ravel(mel_coefficients)
return mel_coefficients
def get_dll(signal, fs, mfcc_len):
mfcc = get_mfcc(signal, fs, mfcc_len)
mfcc = mfcc.Array
array = []
for _ in range(mfcc.Length):
array.append(mfcc[_])
return np.array(array).reshape(-1, mfcc_len)
def get_data(data_path: str, mfcc_len: int = 39,
class_labels: Tuple = ("angry", "fear", "happy", "neutral", "sad", "surprise"), flatten: bool = False,
_svm: bool = False):
data = []
labels = []
cur_dir = os.getcwd()
sys.stderr.write('Curdir: %s\n' % cur_dir)
os.chdir(data_path)
# 遍历文件夹
for i, directory in enumerate(class_labels):
sys.stderr.write("Started reading folder %s\n" % directory)
os.chdir(directory)
# 读取该文件夹下的音频
for filename in os.listdir('.'):
if not filename.endswith('wav'):
continue
filepath = os.getcwd() + '/' + filename
# 提取该音频的特征向量
feature_vector = get_feature(file_path=filepath, mfcc_len=mfcc_len, flatten=flatten)
data.append(feature_vector)
labels.append(i)
sys.stderr.write("Ended reading folder %s\n" % directory)
os.chdir('..')
os.chdir(cur_dir)
# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(np.array(data), np.array(labels), test_size=0.001,
random_state=42)
return np.array(x_train), np.array(x_test), np.array(y_train), np.array(y_test)
def get_data(data_path: str, mfcc_len: int = 39,
class_labels: Tuple = ("angry", "fear", "happy", "neutral", "sad", "surprise"), flatten: bool = False,
_svm: bool = False):
data = []
labels = []
cur_dir = os.getcwd()
sys.stderr.write('Curdir: %s\n' % cur_dir)
os.chdir(data_path)
# 遍历文件夹
for i, directory in enumerate(class_labels):
sys.stderr.write("Started reading folder %s\n" % directory)
os.chdir(directory)
# 读取该文件夹下的音频
for filename in os.listdir('.'):
if not filename.endswith('wav'):
continue
filepath = os.getcwd() + '/' + filename
# 提取该音频的特征向量
feature_vector = get_feature(file_path=filepath, mfcc_len=mfcc_len, flatten=flatten)
data.append(feature_vector)
labels.append(i)
sys.stderr.write("Ended reading folder %s\n" % directory)
os.chdir('..')
os.chdir(cur_dir)
# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(np.array(data), np.array(labels), test_size=0.001,
random_state=42)
return np.array(x_train), np.array(x_test), np.array(y_train), np.array(y_test)
'''
load_model_dnn():
加载 CNN & LSTM 的模型
输入:
model_name(str): 模型名称
load_model(str): 模型种类(DNN / ML)
输出:
model: 加载好的模型
'''
def load_model(model_name: str, load_model: str):
if load_model == 'DNN':
# 加载json
model_path = 'Models/' + model_name + '.h5'
model_json_path = 'Models/' + model_name + '.json'
json_file = open(model_json_path, 'r')
loaded_model_json = json_file.read()
json_file.close()
model = model_from_json(loaded_model_json)
# 加载权重
model.load_weights(model_path)
elif load_model == 'ML':
model_path = 'Models/' + model_name + '.m'
model = joblib.load(model_path)
return model
Model:
# CNN & LSTM
import sys
import numpy as np
from keras import Sequential
from keras.layers import LSTM as KERAS_LSTM, Dense, Dropout, Conv2D, Flatten, BatchNormalization, Activation, \
MaxPooling2D
from Common_Model import Common_Model
# class CNN 和 class LSTM 继承了此类(实现了make_model方法)
class DNN_Model(Common_Model):
'''
__init__(): 初始化神经网络
输入:
input_shape(tuple): 张量形状
num_classes(int): 标签种类数量
'''
def __init__(self, input_shape, num_classes, **params):
super(DNN_Model, self).__init__(**params)
self.input_shape = input_shape
self.model = Sequential()
self.make_model()
self.model.add(Dense(num_classes, activation='softmax'))
self.model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
print(self.model.summary(), file=sys.stderr)
'''
save_model(): 将模型权重以 model_name.h5 和 model_name.json 命名存储在 /Models 目录下
'''
def save_model(self, model_name):
h5_save_path = 'Models/' + model_name + '.h5'
self.model.save_weights(h5_save_path)
save_json_path = 'Models/' + model_name + '.json'
with open(save_json_path, "w") as json_file:
json_file.write(self.model.to_json())
'''
train(): 在给定训练集上训练模型
输入:
x_train (numpy.ndarray): 训练集样本
y_train (numpy.ndarray): 训练集标签
x_val (numpy.ndarray): 测试集样本
y_val (numpy.ndarray): 测试集标签
n_epochs (int): epoch数
'''
def train(self, x_train, y_train, x_val=None, y_val=None, n_epochs=50):
best_acc = 0
if x_val is None or y_val is None:
x_val, y_val = x_train, y_train
for i in range(n_epochs):
# 每个epoch都随机排列训练数据
p = np.random.permutation(len(x_train))
x_train = x_train[p]
y_train = y_train[p]
'''
fit( x, y, batch_size=32, epochs=10, verbose=1, callbacks=None,
validation_split=0.0, validation_data=None, shuffle=True,
class_weight=None, sample_weight=None, initial_epoch=0)
x:输入数据。如果模型只有一个输入,那么x的类型是numpy
array,如果模型有多个输入,那么x的类型应当为list,list的元素是对应于各个输入的numpy array
y:标签,numpy array
batch_size:整数,指定进行梯度下降时每个batch包含的样本数。训练时一个batch的样本会被计算一次梯度下降,使目标函数优化一步。
epochs:整数,训练终止时的epoch值,训练将在达到该epoch值时停止,当没有设置initial_epoch时,它就是训练的总轮数,
否则训练的总轮数为epochs - inital_epoch
verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录
callbacks:list,其中的元素是keras.callbacks.Callback的对象。这个list中的回调函数将会在训练过程中的适当时机被调用,参考回调函数
validation_split:0~1之间的浮点数,用来指定训练集的一定比例数据作为验证集。验证集将不参与训练,
并在每个epoch结束后测试的模型的指标,如损失函数、精确度等。注意,validation_split的划分在shuffle之前,因此如果你的数据本身是有序的,需要先手工打乱再指定validation_split,否则可能会出现验证集样本不均匀。
validation_data:形式为(X,y)的tuple,是指定的验证集。此参数将覆盖validation_spilt。
shuffle:布尔值或字符串,一般为布尔值,表示是否在训练过程中随机打乱输入样本的顺序。若为字符串“batch”,
则是用来处理HDF5数据的特殊情况,它将在batch内部将数据打乱。
class_weight:字典,将不同的类别映射为不同的权值,该参数用来在训练过程中调整损失函数(只能用于训练)
sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,
或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode=’temporal’。
initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。
fit函数返回一个History的对象,其History.history属性记录了损失函数和其他指标的数值随epoch变化的情况,如果有验证集的话,也包含了验证集的这些指标变化情况
'''
self.model.fit(x_train, y_train, batch_size=32, epochs=1)
# 训练过程的损失率变化图
# 计算损失率和准确率
loss, acc = self.model.evaluate(x_val, y_val)
if acc > best_acc:
best_acc = acc
print("TRAIN:%d / %d" % (i, n_epochs))
self.trained = True
'''
recognize_one(): 识别某个音频的情感
输入:
sample: 要预测的样本
输出:
预测结果,置信概率(int, numpy.ndarray)
'''
def recognize_one(self, sample):
# 没有训练和加载过模型
if not self.trained:
sys.stderr.write("No Model.")
sys.exit(-1)
return np.argmax(self.model.predict(np.array([sample]))), self.model.predict(np.array([sample]))[0]
def make_model(self):
raise NotImplementedError()
class CNN_Model(DNN_Model):
def __init__(self, **params):
params['name'] = 'CNN'
super(CNN_Model, self).__init__(**params)
def make_model(self):
self.model.add(Conv2D(8, (13, 13), input_shape=(self.input_shape[0], self.input_shape[1], 1)))
self.model.add(BatchNormalization(axis=-1))
self.model.add(Activation('relu'))
self.model.add(Conv2D(8, (13, 13)))
self.model.add(BatchNormalization(axis=-1))
self.model.add(Activation('relu'))
self.model.add(MaxPooling2D(pool_size=(2, 1)))
self.model.add(Conv2D(8, (13, 13)))
self.model.add(BatchNormalization(axis=-1))
self.model.add(Activation('relu'))
self.model.add(Conv2D(8, (2, 2)))
self.model.add(BatchNormalization(axis=-1))
self.model.add(Activation('relu'))
self.model.add(MaxPooling2D(pool_size=(2, 1)))
self.model.add(Flatten())
self.model.add(Dense(64))
self.model.add(BatchNormalization())
self.model.add(Activation('relu'))
self.model.add(Dropout(0.2))
class LSTM_Model(DNN_Model):
def __init__(self, **params):
params['name'] = 'LSTM'
super(LSTM_Model, self).__init__(**params)
def make_model(self):
#
self.model.add(KERAS_LSTM(128, input_shape=(self.input_shape[0], self.input_shape[1])))
self.model.add(Dropout(0.5))
self.model.add(Dense(32, activation='relu')) # 标准的一维全连接层
self.model.add(Dense(16, activation='tanh'))
2.python可以通过librosa和scipy来获取mfcc特征值,但我发现C#获取的mfcc特征值和python获取的mfcc特征值不同,也就无法准确的预测结果(暂时不清楚原因,公式太过复杂,有明白的大佬,欢迎指导下)。我想的办法是通过python调用C#dll来获取MFCC
C#:编译成dll
引用:https://github.com/ar1st0crat/NWaves
using NumSharp;
using NWaves.FeatureExtractors;
using NWaves.FeatureExtractors.Base;
using System.Collections.Generic;
namespace MFCC
{
public class MYMFCC
{
public NDArray GetMFCC(float[] input, int sr, int mfcc_size)
{
var mfccExtractor = new MfccExtractor(sr, mfcc_size);
var mfccVectors = mfccExtractor.ComputeFrom(input);
List<float> result = new List<float>();
foreach (FeatureVector vector in mfccVectors)
{
foreach (float _ in vector.Features)
{
result.Add(_);
}
}
return np.array(result.ToArray());
}
}
}
Python: (引用相关的dll)
import clr
clr.FindAssembly("MFCCSharp.dll")
clr.FindAssembly("NWaves.dll")
clr.FindAssembly("NumSharp.dll")
from MFCC import *
from NWaves import *
from NumSharp import *
instance = MYMFCC()
def get_mfcc(input, sr, mfcc_len):
instance = MYMFCC()
return instance.GetMFCC(input, sr, mfcc_len)
3.由于我 python训练的模型是基于keras的,但keras没有提供c#接口。所有现将keras生成的模型.h5文件转换成.pb文件。通过tensorflowsharp来调用.pb文件。
python:( .h5 -> .pb)
引用:https://blog.csdn.net/qq_25109263/article/details/81285952
from keras.models import load_model
import tensorflow as tf
from keras import backend as K, Sequential
from tensorflow.python.framework import graph_io
from keras.models import model_from_json
def freeze_session(session, keep_var_names=None, output_names=None, clear_devices=True):
from tensorflow.python.framework.graph_util import convert_variables_to_constants
graph = session.graph
with graph.as_default():
freeze_var_names = list(set(v.op.name for v in tf.global_variables()).difference(keep_var_names or []))
output_names = output_names or []
output_names += [v.op.name for v in tf.global_variables()]
input_graph_def = graph.as_graph_def()
if clear_devices:
for node in input_graph_def.node:
node.device = ""
frozen_graph = convert_variables_to_constants(session, input_graph_def,
output_names, freeze_var_names)
return frozen_graph
def load_model():
model_path = 'Models/LSTM1.h5'
model_json_path = 'Models/LSTM1.json'
json_file = open(model_json_path, 'r')
loaded_model_json = json_file.read()
json_file.close()
model = model_from_json(loaded_model_json)
# 加载权重
model.load_weights(model_path)
return model
"""----------------------------------配置路径-----------------------------------"""
epochs = 20
h5_model_path = 'Models/LSTM1.h5'
output_path = 'PBModels'
pb_model_name = 'LSTM1.pb'
"""----------------------------------导入keras模型------------------------------"""
K.set_learning_phase(0)
net_model = load_model()
print('input is :', net_model.input.name)
print('output is:', net_model.output.name)
"""----------------------------------保存为.pb格式------------------------------"""
sess = K.get_session()
frozen_graph = freeze_session(K.get_session(), output_names=[net_model.output.op.name])
graph_io.write_graph(frozen_graph, output_path, pb_model_name, as_text=False)
4.使用C#调用python训练的模型来实现预测。
类库:TensorflowSharp (tensorflow) , NumSharp (numpy) , NWave (获取MFCC)
using NumSharp;
using NWaves.FeatureExtractors;
using NWaves.FeatureExtractors.Base;
using System;
using System.Collections.Generic;
using System.IO;
using TensorFlow;
namespace TensorflowSharpDemo
{
class VoiceTest
{
private static String[] CLASS_LABELS = new string[] { "anjian", "di", "huanjie", "mingdi", "pengzhuang", "qita", "shebeiyuyin", "voice" };
private static int VOICE_INDEX = 7;
private static int FRAME_RATE = 16000;
private static int FRAME_MOVE = 8000;
private static TFGraph graph;
private static TFSession session;
public static void test()
{
//CLASS_LABELS = ("anjian", "di", "huanjie", "mingdi", "pengzhuang", "qita", "shebeiyuyin", "voice")
graph = new TFGraph();
graph.Import(File.ReadAllBytes("../../model/LSTM1.pb"), "");
session = new TFSession(graph);
List<float> audios = new WAVReader().ReadWAVFile("../../test/123.wav");
NDArray input = audioToFrames(audios.ToArray(), 16000);
logits(input, "");
}
private static float[] get_feature_result(float[] input)
{
float[] mfcc = getMfcc(input);
return mfcc;
}
/// <summary>
/// 帧移移动音频帧,补0
/// </summary>
private static float[] padd(float[] input, int frameMove)
{
int con = input.Length % frameMove;
if (con == 0)
return input;
int dis = frameMove - con;
List<float> coll = new List<float>();
int b_len = dis / 2;
for (int i = 0; i < b_len; i++)
{
coll.Add(0);
}
for (int i = 0; i < input.Length; i++)
{
coll.Add(input[i]);
}
int e_len = dis - b_len;
for (int i = 0; i < b_len; i++)
{
coll.Add(0);
}
return coll.ToArray();
}
/// <summary>
///
/// </summary>
/// <param name="input">音频</param>
/// <param name="frameLen">帧长</param>
/// <param name="frameMove">帧移</param>
/// <returns></returns>
private static float[] fragment(float[] input, int frameLen, int frameMove)
{
input = padd(input, frameMove);
List<float> frames = new List<float>();
int n_step = 0;
while (n_step * frameMove + frameLen <= input.Length)
{
for (int k = n_step * frameMove; k < n_step * frameMove + frameLen; k++)
{
frames.Add(input[k]);
}
n_step++;
}
return frames.ToArray();
}
private static NDArray audioToFrames(float[] input, int frame)
{
input = fragment(input, FRAME_RATE, FRAME_MOVE);
int row = input.Length / FRAME_RATE;
NDArray array = np.array(input).reshape(row, FRAME_RATE);
return array;
}
private static NDArray predict_model(float[] input, int sr)
{
var mfcc = get_feature_result(input);
int ax = mfcc.Length / 39;
var tensor = TFTensor.FromBuffer(new TFShape(1, ax, 39), mfcc, 0, mfcc.Length);
var runner = session.GetRunner();
runner.AddInput(graph["lstm_1_input"][0], tensor).Fetch(graph["dense_3/Softmax"][0]);
var output = runner.Run();
var result = output[0];
int result_count = ((float[][])result.GetValue(jagged: true)).Length;
List<float> resultColl = new List<float>();
for (int i = 0; i < result_count; i++)
{
float[] a = ((float[][])result.GetValue(jagged: true))[i];
string s = null;
for (int j = 0; j < a.Length; j++)
{
resultColl.Add(a[j]);
}
}
return np.array(resultColl.ToArray()).reshape(1, CLASS_LABELS.Length);
}
private static void logits(NDArray input, string outFileDir)
{
int length = input.shape[0];
int n_step = 0;
List<NDArray> label = new List<NDArray>();
while (n_step < length)
{
float[] temp = (float[])input[n_step].Array;
NDArray result = predict_model(temp, FRAME_RATE);
label.Add(result);
n_step++;
}
List<float[]> voice = new List<float[]>();
List<float> voice_temp = new List<float>();
int noiseCount = 0;
for (int i = 0; i < length; i++)
{
int index = np.argmax(label[i]);
float[] sound = (float[])input[i].Array;
if (index == VOICE_INDEX)
{
noiseCount = 0;
//上一帧是噪音
if (voice_temp.Count == 0)
{
voice_temp = floatToAddList(sound, voice_temp);
continue;
}
//上一帧是人声
voice_temp = floatToAddList(sound, voice_temp, FRAME_MOVE);
continue;
}
else
{
noiseCount++;
// 连续检测超过一帧(自定)都是噪音,则表示检测出一段完整语音
//if (noiseCount >= FRAME_RATE / FRAME_MOVE)
//{
// if (voice_temp.Count > 0)
// voice.Add(voice_temp.ToArray());
// voice_temp = new List<float>();
// noiseCount = 0;
//}
}
}
if (voice_temp.Count > 0)
{
voice.Add(voice_temp.ToArray());
}
for (int j = 0; j < voice.Count; j++)
{
toSaveAudio(voice[j], "voice" + j);
}
}
private static List<float> floatToAddList(float[] audio, List<float> coll)
{
foreach (float _f in audio)
{
coll.Add(_f);
}
return coll;
}
private static List<float> floatToAddList(float[] audio, List<float> coll, int frameMove)
{
int index = 0;
foreach (float _f in audio)
{
if (index >= audio.Length - frameMove)
coll.Add(_f);
index++;
}
return coll;
}
private static void toSaveAudio(float[] audio, string name)
{
string audioFileName = @"D:\WorkSpace\VS\TensorflowSharpDemo\TensorflowSharpDemo\voice\" + name + ".wav";
WaveSaveHelper.Save(audioFileName, audio);
}
/// <summary>
/// 获取MFCC
/// </summary>
private static float[] getMfcc(float[] input)
{
var mfccExtractor = new MfccExtractor(16000, 39);
var mfccVectors = mfccExtractor.ComputeFrom(input);
List<float> result = new List<float>();
foreach (FeatureVector vector in mfccVectors)
{
foreach (float _ in vector.Features)
{
result.Add(_);
}
}
return result.ToArray();
}
}
}
问题:
1. python 通过libroas或scripy获取mfcc特征值和C#实现的获取mfcc特征值不同。所以导致C#调用模型预测的结果不正确。我的解决办法是通过C#编写一个获取mfcc特征值的dll(基于NWave.dll https://github.com/ar1st0crat/NWaves),然后使用python来调用。(没有办法的办法,这个地方搞了好几天天也没搞懂,也可能是我哪个地方整错了,总之如果有明白这地方的大佬,欢迎来赐教,小弟万分感谢。)
2.通过测试发现,检测出的音频多多少少还是会存在一些噪音的,不过我感觉效果还是可以接受的。
3.还在努力优化和测试中。。。 = = 、
总结:
通过最近一段时间的学习和努力,初步实现了端点检测的功能,不过效果还有待提高,我会继续努力的,要什么新的体会,我回及时和大家分享出来的。由于刚刚接触音频这一块,还是小白一枚,有太多的地方理解的不是非常透彻,可以说只是略知一二,所以有一些表述不正确的地方,还请大家多多见谅,也希望大家能提出你们宝贵的意见和指正,小弟感激不尽!!!!
下面是我写的demo,有想看的可以下下来看一看,demo中的没有音频文件,下载后需把自己训练的音频文件对应文件夹名字存放,然后训练出模型文件,再转成.pb文件,最后将pb文件复制到c#项目中,供c#调用。(表达能力有限,如果有不是太明白的地方可以留言。 = =、)
demo地址:https://download.csdn.net/download/haiyangyunbao813/11155896