TensorFlow入门教程(16)分离歌曲中的人声和背景音乐

#
#作者:韦访
#博客:https://blog.csdn.net/rookie_wei
#微信:1007895847
#添加微信的备注一下是CSDN的
#欢迎大家一起学习
#

------韦访 20190215

1、概述

上一讲,我们大概介绍了LSTM及其变体的结构,这一讲,我们使用它来实现将音乐文件中的人声和背景音乐分离的功能。

环境配置:

操作系统:Win10 64位

显卡:GTX 1080ti

Python:Python3.7

TensorFlow:1.15.0

2、下载数据集

我们想分离音乐中的人声和背景音乐,那么训练集中至少得有两种数据,第一种是纯人声数据,第二种是纯背景音乐,然后将这两种声音混合,这样我们就可以让模型从混合的数据中学习到纯人声和纯音乐的特征。

网上有开放的数据集MIR-1k,就符合我们的要求,下载地址如下:http://mirlab.org/dataset/public/MIR-1K.rar

注:上面的链接已经失效,可以通过下面的链接下载:

链接:https://pan.baidu.com/s/19i5F3Cyw94TduZBa0hn4zg
提取码:ognr
 

下载完数据,解压到dataset/下,结构如下图所示,

数据集里有很多文件夹,其他的我们不管,我们只用到Wavfile和UndividedWavfile文件夹下的文件。简单介绍一下数据集,打开Wavfile文件夹,

里面有1000个音频文件,随便播放一个,请忽略里面五音不全的唱功(这里还是对得向该数据集的制作者致敬,感谢你们的付出),注意听会发现,有一个声道的数据是纯背景音乐,另一个声道的数据是纯人声。有了这个特性,我们就可以很好的利用了,后面详解代码再说。UndividedWavfile文件夹下也是类似的音频文件,只是数量只有110个,正好我们可以使用Wavfile文件夹下的数据做训练集,用UndividedWavfile文件夹下的数据做测试集。

3、思路

有了数据集,接下来还的有思路,俗称套路。首先,我们现在做的是音频的项目, 做音频的项目,首先就得想到将时域转到频域,再做分析。

 而神经网络的套路也基本是下面的几步:

 1. 创建占位符、变量

 2. 设置学习率和batch size等参数

 3. 构建神经网络

 4. 设置损失函数

 5. 设置优化器

 6. 创建会话

 7. 向网络喂数据开始训练,一般都是mini-batch的方法

具体到我们的这个项目,我们应该做以下事情,

1. 导入需要训练的数据集文件路径,存到列表中即可

2. 导入训练集数据,每一个训练集文件都是一个双声道的音频文件,

   其中,第一个声道存的是背景音乐,第二个声道存的是纯人声,

   我们需要三组数据,第一组是将双声道转成单声道的数据,即让背景音乐和人声混合在一起

   第二组数据是纯背景音乐,第三组数据是纯人声数据

3. 通过上一步获取的数据都是时域的,我们要通过短时傅里叶变换将声音数据转到频域

4. 初始化网络模型

5. 获取mini-batch数据,开始进行迭代训练

4、train主框架

有了套路,就开始撸代码,先来看看训练train部分的代码,代码在train.py文件里(我最后会给出整个源码,你现在可以跳到博客末尾去找源码链接)。

首先找到入口函数,

 

#可以通过命令设置的参数:
#dataset_dir : 数据集路径
#model_dir : 模型保存的文件夹
#model_filename : 模型保存的文件名
#dataset_sr : 数据集音频文件的采样率
#learning_rate : 学习率
#batch_size : 小批量训练数据的长度
#sample_frames : 每次训练获取多少帧数据
#iterations : 训练迭代次数
#dropout_rate : dropout率
def parse_arguments(argv):
    parser = argparse.ArgumentParser()

    parser.add_argument('--dataset_train_dir', type=str, help='数据集训练数据路径', default='./dataset/MIR-1K/Wavfile')
    parser.add_argument('--dataset_validate_dir', type=str, help='数据集验证数据路径', default='./dataset/MIR-1K/UndividedWavfile')
    parser.add_argument('--model_dir', type=str, help='模型保存的文件夹', default='model')
    parser.add_argument('--model_filename', type=str, help='模型保存的文件名', default='svmrnn.ckpt')
    parser.add_argument('--dataset_sr', type=int, help='数据集音频文件的采样率', default=16000)
    parser.add_argument('--learning_rate', type=float, help='学习率', default=0.0001)
    parser.add_argument('--batch_size', type=int, help='小批量训练数据的长度', default=64)
    parser.add_argument('--sample_frames', type=int, help='每次训练获取多少帧数据', default=10)
    parser.add_argument('--iterations', type=int, help='训练迭代次数', default=30000)
    parser.add_argument('--dropout_rate', type=float, help='dropout率', default=0.95)

    return parser.parse_args(argv)

if __name__ == '__main__':
    main(parse_arguments(sys.argv[1:]))

上面一些参数都有注释,比较简单就不再解释了,来看main函数做了什么鬼。

#训练模型,需要做以下事情
#1. 导入需要训练的数据集文件路径,存到列表中即可
#2. 导入训练集数据,每一个训练集文件都是一个双声道的音频文件,
#   其中,第一个声道存的是背景音乐,第二个声道存的是纯人声,
#   我们需要三组数据,第一组是将双声道转成单声道的数据,即让背景音乐和人声混合在一起
#   第二组数据是纯背景音乐,第三组数据是纯人声数据
#3. 通过上一步获取的数据都是时域的,我们要通过短时傅里叶变换将声音数据转到频域
#4. 初始化网络模型
#5. 获取mini-batch数据,开始进行迭代训练

def main(args):

    #先看数据集数据是否存在
    if not os.path.exists(args.dataset_train_dir) or not os.path.exists(args.dataset_validate_dir):
        raise NameError('数据集路径"./dataset/MIR-1K/Wavfile"或"./dataset/MIR-1K/UndividedWavfile"不存在!')

    # 1. 导入需要训练的数据集文件路径,存到列表中即可
    train_file_list = load_file(args.dataset_train_dir)
    valid_file_list = load_file(args.dataset_validate_dir)

上面做的很简单,将我们要训练和验证的文件路径导入到相应的列表里就可以了。

然后设置一些参数,

# 数据集的采样率
mir1k_sr = args.dataset_sr
# 用于短时傅里叶变换,窗口大小
n_fft = 1024
# 步幅;帧移对应卷积中的stride;
hop_length = n_fft // 4

# Model parameters
# 学习率
learning_rate = args.learning_rate

# 用于创建rnn节点数
num_hidden_units = [1024, 1024, 1024, 1024, 1024]
# batch 长度
batch_size = args.batch_size
# 获取多少帧数据
sample_frames = args.sample_frames
# 训练迭代次数
iterations = args.iterations
# dropout
dropout_rate = args.dropout_rate

# 模型保存路径
model_dir = args.model_dir
model_filename = args.model_filename

接着,我们就需要读取音频文件了,音频文件里保存的是时域的数据,读取文件后,使用快速傅里叶变换将它们转到频域中,

#导入训练数据集的wav数据,
#wavs_mono_train存的是单声道,wavs_music_train 存的是背景音乐,wavs_voice_train 存的是纯人声
wavs_mono_train, wavs_music_train, wavs_voice_train = load_wavs(filenames = train_file_list, sr = mir1k_sr)
# 通过短时傅里叶变换将声音转到频域
stfts_mono_train, stfts_music_train, stfts_voice_train = wavs_to_specs(
    wavs_mono=wavs_mono_train, wavs_music=wavs_music_train, wavs_voice=wavs_voice_train, n_fft=n_fft,
    hop_length=hop_length)

# 跟上面一样,只不过这里是测试集的数据
wavs_mono_valid, wavs_music_valid, wavs_voice_valid = load_wavs(filenames=valid_file_list, sr=mir1k_sr)
stfts_mono_valid, stfts_music_valid, stfts_voice_valid = wavs_to_specs(
    wavs_mono=wavs_mono_valid, wavs_music=wavs_music_valid, wavs_voice=wavs_voice_valid, n_fft=n_fft,
    hop_length=hop_length)

上面load_wavs函数的作用是分别获取左右声道的音频数据,以及获取双声道合并成单声道后的音频数据,实现代码如下,存在utils.py文件里,

#   导入训练集数据,每一个训练集文件都是一个双声道的音频文件,
#   其中,第一个声道存的是背景音乐,第二个声道存的是纯人声,
#   我们需要三组数据,第一组是将双声道转成单声道的数据,即让背景音乐和人声混合在一起
#   第二组数据是纯背景音乐,第三组数据是纯人声数据
def load_wavs(filenames, sr):
    wavs_mono = list()
    wavs_music = list()
    wavs_voice = list()
    #读取wav文件,首先要求源文件是有双声道的音频文件,一个声道存的是背景音乐,另一个声道存的是纯人声
    #然后,将音频转成单声道,存入 wavs_mono
    #将背景音乐存入 wavs_music,
    #将纯人声存入 wavs_voice
    for filename in filenames:
        wav, _ = librosa.load(filename, sr = sr, mono = False)
        assert (wav.ndim == 2) and (wav.shape[0] == 2), '要求WAV文件有两个声道!'

        wav_mono = librosa.to_mono(wav) * 2
        wav_music = wav[0, :]
        wav_voice = wav[1, :]
        wavs_mono.append(wav_mono)
        wavs_music.append(wav_music)
        wavs_voice.append(wav_voice)

    return wavs_mono, wavs_music, wavs_voice

wavs_to_specs函数则通过快速傅里叶变换将音频数据转到频域,使用librosa库的stft函数就可以了,实现代码如下,

#通过短时傅里叶变换将声音转到频域
def wavs_to_specs(wavs_mono, wavs_music, wavs_voice, n_fft = 1024, hop_length = None):

    stfts_mono = list()
    stfts_music = list()
    stfts_voice = list()

    for wav_mono, wav_music, wav_voice in zip(wavs_mono, wavs_music, wavs_voice):
        stft_mono = librosa.stft(wav_mono, n_fft = n_fft, hop_length = hop_length)
        stft_music = librosa.stft(wav_music, n_fft = n_fft, hop_length = hop_length)
        stft_voice = librosa.stft(wav_voice, n_fft = n_fft, hop_length = hop_length)
        stfts_mono.append(stft_mono)
        stfts_music.append(stft_music)
        stfts_voice.append(stft_voice)

    return stfts_mono, stfts_music, stfts_voice

接着,就要初始化神经网络模型了,具体的模型我们后面再说,先看怎么初始化,代码如下,

#初始化模型
model =  SVMRNN(num_features = n_fft // 2 + 1, num_hidden_units = num_hidden_units)

就这么简单,然后,我们可能会在训练途中中断训练(比如断电啊),如果不边训练边保存模型,那么对于用CPU来训练的同学来说就非常痛苦了。所以,先加载模型,如果还没有模型,我们就初始化变量就OK了,

# 加载模型,如果没有模型,则初始化所有变量
startepo = model.load(file_dir = model_dir)

print('startepo:' + str(startepo))
然后,开始进入迭代训练的for循环中了,
#开始训练
for i in (range(iterations)):
    #从模型中断处开始训练
    if i < startepo:
        continue

在for循环中,获取下一个batch数据,这个数据是要喂给神经网络进行训练的,看过以前教程的对这一步应该比较熟悉了,

# 获取下一batch数据
data_mono_batch, data_music_batch, data_voice_batch = get_next_batch(
    stfts_mono = stfts_mono_train, stfts_music = stfts_music_train, stfts_voice = stfts_voice_train,
    batch_size = batch_size, sample_frames = sample_frames)
那么这个batch怎么获取呢?我们是不是需要随机的获取一小段单声道的数据,和同个时间段对应的背景音乐的数据和人声数据就可以了?代码如下,
#stfts_mono:单声道stft频域数据
#stfts_music:背景音乐stft频域数据
#stfts_music:人声stft频域数据
#batch_size:batch大小
#sample_frames:获取多少帧数据
def get_next_batch(stfts_mono, stfts_music, stfts_voice, batch_size = 64, sample_frames = 8):

    stft_mono_batch = list()
    stft_music_batch = list()
    stft_voice_batch = list()

    #随即选择batch_size个数据
    collection_size = len(stfts_mono)
    collection_idx = np.random.choice(collection_size, batch_size, replace = True)

    for idx in collection_idx:
        stft_mono = stfts_mono[idx]
        stft_music = stfts_music[idx]
        stft_voice = stfts_voice[idx]
        #有多少帧
        num_frames = stft_mono.shape[1]
        assert  num_frames >= sample_frames
        #随机获取sample_frames帧数据
        start = np.random.randint(num_frames - sample_frames + 1)
        end = start + sample_frames

        stft_mono_batch.append(stft_mono[:,start:end])
        stft_music_batch.append(stft_music[:,start:end])
        stft_voice_batch.append(stft_voice[:,start:end])

    #将数据转成np.array,再对形状做一些变换
    # Shape: [batch_size, n_frequencies, n_frames]
    stft_mono_batch = np.array(stft_mono_batch)
    stft_music_batch = np.array(stft_music_batch)
    stft_voice_batch = np.array(stft_voice_batch)
    # Shape for RNN: [batch_size, n_frames, n_frequencies]
    data_mono_batch = stft_mono_batch.transpose((0, 2, 1))
    data_music_batch = stft_music_batch.transpose((0, 2, 1))
    data_voice_batch = stft_voice_batch.transpose((0, 2, 1))

    return data_mono_batch, data_music_batch, data_voice_batch

注意上面的数据已经不是时域的数据了,而是stfs后的数据,所以,还要对数据做一些变换,在这里,我们只关心频率的数据,因为人声往往在一个特定的频率范围里,而乐器的频率则丰富的多,处理代码如下,

#获取频率值
x_mixed_src, _ = separate_magnitude_phase(data = data_mono_batch)
y_music_src, _ = separate_magnitude_phase(data = data_music_batch)
y_voice_src, _ = separate_magnitude_phase(data = data_voice_batch)

这个separate_magnitude_phase函数的实现也很简单,代码如下,

#通过短时傅里叶变换后的结果是复数的,而我们训练时,
#只需要考虑频率部分就可以了,所以将频率和相位分离出来
def separate_magnitude_phase(data):
    return np.abs(data), np.angle(data)

接着,就将数据喂给神经网络来进行训练了,

#送入神经网络,开始训练
train_loss = model.train(x_mixed_src = x_mixed_src, y_music_src = y_music_src, y_voice_src = y_voice_src,
                         learning_rate = learning_rate, dropout_rate = dropout_rate)

然后就是一些打印损失和测试模型的代码了,

if i % 10 == 0:
    print('Step: %d Train Loss: %f' %(i, train_loss))

if i % 200 == 0:
    #这里是测试模型准确率的
    print('==============================================')
    data_mono_batch, data_music_batch, data_voice_batch = get_next_batch(
        stfts_mono = stfts_mono_valid, stfts_music = stfts_music_valid,
        stfts_voice = stfts_voice_valid, batch_size = batch_size, sample_frames = sample_frames)

    x_mixed_src, _ = separate_magnitude_phase(data = data_mono_batch)
    y_music_src, _ = separate_magnitude_phase(data = data_music_batch)
    y_voice_src, _ = separate_magnitude_phase(data = data_voice_batch)

    y_music_src_pred, y_voice_src_pred, validate_loss = model.validate(x_mixed_src = x_mixed_src,
            y_music_src = y_music_src, y_voice_src = y_voice_src, dropout_rate = dropout_rate)
    print('Step: %d Validation Loss: %f' %(i, validate_loss))
    print('==============================================')


if i % 200 == 0:
    model.save(directory = model_dir, filename = model_filename, global_step=i)

train.py的大致框架就如上所述了,我们接着来看神经网络模型怎么建的。

5、神经网络SVMRNN类

我们将神经网络SVMRNN类写到model.py文件中,先来看初始化函数__init__,

# num_features:音频特征数
# num_hidden_units:rnn 神经元数
# tensorboard_dir: tensorboard保存的路径
def __init__(self, num_features, num_hidden_units = [256, 256, 256]):
    # 保存传入的参数
    self.num_features = num_features
    self.num_rnn_layer = len(num_hidden_units)
    self.num_hidden_units = num_hidden_units


    # 设置变量
    # 训练了多少步
    self.g_step = tf.Variable(0, dtype=tf.int32, name='g_step')

    # 设置占位符
    # 学习率
    self.learning_rate = tf.placeholder(tf.float32, shape=[], name='learning_rate')

    #混合了背景音乐和人声的数据
    self.x_mixed_src = tf.placeholder(tf.float32, shape=[None, None, num_features], name='x_mixed_src')

    #背景音乐数据
    self.y_music_src = tf.placeholder(tf.float32, shape=[None, None, num_features], name='y_music_src')
    #人声数据
    self.y_voice_src = tf.placeholder(tf.float32, shape=[None, None, num_features], name='y_voice_src')

    #keep dropout,用于RNN网络的droupout
    self.dropout_rate = tf.placeholder(tf.float32)

    #初始化神经网络
    self.y_pred_music_src, self.y_pred_voice_src = self.network_init()

    # 设置损失函数
    self.loss = self.loss_init()

    # 设置优化器
    self.optimizer = self.optimizer_init()

    #创建会话
    self.sess = tf.Session()

    #需要保存模型,所以获取saver
    self.saver = tf.train.Saver(max_to_keep=1)

设置变量、占位符,初始化神经网络,设置损失函数,设置优化器,创建会话,保存模型,是不是都是很熟悉的那一套啊?

顺着上面的代码,我们来看看初始化神经网络的函数network_init的实现,

#构建神经网络
def network_init(self):
    rnn_layer = []

    #根据num_hidden_units的长度来决定创建几层RNN,每个RNN长度为size
    for size in self.num_hidden_units:
        #使用GRU,同时,加上dropout
        layer_cell = tf.nn.rnn_cell.GRUCell(size)
        layer_cell = tf.contrib.rnn.DropoutWrapper(layer_cell, input_keep_prob=self.dropout_rate)
        rnn_layer.append(layer_cell)

    #创建多层RNN
    multi_rnn_cell = tf.nn.rnn_cell.MultiRNNCell(rnn_layer)
    outputs, state = tf.nn.dynamic_rnn(cell = multi_rnn_cell, inputs = self.x_mixed_src, dtype = tf.float32)

    #全连接层
    y_dense_music_src = tf.layers.dense(
        inputs = outputs,
        units = self.num_features,
        activation = tf.nn.relu,
        name = 'y_dense_music_src')

    y_dense_voice_src = tf.layers.dense(
        inputs = outputs,
        units = self.num_features,
        activation = tf.nn.relu,
        name = 'y_dense_voice_src')
    
    y_music_src = y_dense_music_src / (y_dense_music_src + y_dense_voice_src + np.finfo(float).eps) * self.x_mixed_src
    y_voice_src = y_dense_voice_src / (y_dense_music_src + y_dense_voice_src + np.finfo(float).eps) * self.x_mixed_src

    return y_music_src, y_voice_src

上面就是我们神经网络结构的核心了,N层RNN网络,再加一个全连接层。

------------------------------------------------------------------------------------

2020-03-09

好几个网友私聊我,说不理解上面的代码的如下部分,

    y_music_src = y_dense_music_src / (y_dense_music_src + y_dense_voice_src + np.finfo(float).eps) * self.x_mixed_src
    y_voice_src = y_dense_voice_src / (y_dense_music_src + y_dense_voice_src + np.finfo(float).eps) * self.x_mixed_src

其实也很好理解的,这个部分的代码主要是约束y_music_srcy_voice_src的大小,使得纯背景音乐数据y_music_src加纯人声数据y_voice_src约等于原始数据x_mixed_src。我列个式子你就明白了。上面的代码等价于下面的式子,

其中,是一个很小很小的数,主要是防止除零。

------------------------------------------------------------------------------------

接着来看损失函数,

#损失函数
def loss_init(self):
    with tf.variable_scope('loss') as scope:
        #求方差
        loss = tf.reduce_mean(
            tf.square(self.y_music_src - self.y_pred_music_src)
            + tf.square(self.y_voice_src - self.y_pred_voice_src), name='loss')
    return loss

然后就是优化器,

#优化器
def optimizer_init(self):
    ottimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate).minimize(self.loss)
    return ottimizer

保存和加载模型的函数,

#保存模型
def save(self, directory, filename, global_step):
    #如果目录不存在,则创建
    if not os.path.exists(directory):
        os.makedirs(directory)

    self.saver.save(self.sess, os.path.join(directory, filename), global_step=global_step)
    return os.path.join(directory, filename)

# 加载模型,如果没有模型,则初始化所有变量
def load(self, file_dir):
    # 初始化变量
    self.sess.run(tf.global_variables_initializer())

    # 没有模型的话,就重新初始化
    kpt = tf.train.latest_checkpoint(file_dir)
    print("kpt:", kpt)
    startepo = 0
    if kpt != None:
        self.saver.restore(self.sess, kpt)
        ind = kpt.find("-")
        startepo = int(kpt[ind + 1:])

    return startepo

然后就是训练、验证、测试模型的函数,

#开始训练
def train(self, x_mixed_src, y_music_src, y_voice_src, learning_rate, dropout_rate):
    #已经训练了多少步
    # step = self.sess.run(self.g_step)

    _, train_loss = self.sess.run([self.optimizer, self.loss],
        feed_dict = {self.x_mixed_src: x_mixed_src, self.y_music_src: y_music_src, self.y_voice_src: y_voice_src,
                     self.learning_rate: learning_rate, self.dropout_rate: dropout_rate})
    return train_loss

#验证
def validate(self, x_mixed_src, y_music_src, y_voice_src, dropout_rate):
    y_music_src_pred, y_voice_src_pred, validate_loss = self.sess.run([self.y_pred_music_src, self.y_pred_voice_src, self.loss],
        feed_dict = {self.x_mixed_src: x_mixed_src, self.y_music_src: y_music_src, self.y_voice_src: y_voice_src, self.dropout_rate: dropout_rate})
    return y_music_src_pred, y_voice_src_pred, validate_loss

#测试
def test(self, x_mixed_src, dropout_rate):
    y_music_src_pred, y_voice_src_pred = self.sess.run([self.y_pred_music_src, self.y_pred_voice_src],
                                     feed_dict = {self.x_mixed_src: x_mixed_src, self.dropout_rate: dropout_rate})

    return y_music_src_pred, y_voice_src_pred

代码写完了,在终端执行python train.py就开始训练了,

可以看到,损失在慢慢下降,说明我们的模型起到作用了,运行到最后损失大概再0.5-0.6之间。

6、测试代码

上面只是在训练啊,我们要把它用起来啊,所以还要再写个测试代码test.py,跟train的代码有点类似,首先也是一些参数的设置,

def parse_arguments(argv):
    parser = argparse.ArgumentParser()

    parser.add_argument('--input_dir', type=str, help='待测试的音频文件的文件夹,存放MP3文件', default='./songs/input')
    parser.add_argument('--output_dir', type=str, help='声乐分离后的视频文件目录,为WAV格式', default='./songs/output')
    parser.add_argument('--model_dir', type=str, help='模型保存的文件夹', default='./model')
    parser.add_argument('--model_filename', type=str, help='模型保存的文件名', default='svmrnn.ckpt')
    parser.add_argument('--dataset_sr', type=int, help='数据集音频文件的采样率', default=16000)
    parser.add_argument('--dropout_rate', type=float, help='dropout率', default=0.95)

    return parser.parse_args(argv)

if __name__ == '__main__':
    main(parse_arguments(sys.argv[1:]))

 如果想调整参数,在运行命令行的时候传入参数就可以了,接着来看main函数,

def main(args):
    input_dir = args.input_dir
    output_dir = args.output_dir
    dataset_sr = args.dataset_sr
    model_dir = args.model_dir
    dropout_rate = args.dropout_rate

    #如果输入目录不存在,返回错误
    if not os.path.exists(input_dir):
        raise NameError('音频输入文件夹"./songs/input"不存在!')

    #输出文件夹不存在,则创建
    if not os.path.exists(output_dir):
        os.mkdir(output_dir)

    #找到要分离背景音乐和人声的音频文件
    song_filenames = list()
    for file in os.listdir(input_dir):
        if file.endswith('.mp3'):
            song_filenames.append(os.path.join(input_dir, file))

我们将待测试的音频文件放到songs/input文件夹下,最后得到的结果保存到songs/output文件夹下,所以先检查输入文件夹是否存在先,如果存在,将里面的MP3文件放到列表中,接着就是读取这些音频文件了,

#加载输入音频文件
wavs_mono = list()
for filename in song_filenames:
    wav_mono, _ = librosa.load(filename, sr=dataset_sr, mono=True)
    wavs_mono.append(wav_mono)
然后,设置一些参数,接着把音频文件的数据从时域转到频域,
# 用于短时傅里叶变换,窗口大小
n_fft = 1024
# 步幅;帧移对应卷积中的stride;
hop_length = n_fft // 4
# 用于创建rnn节点数
num_hidden_units = [1024, 1024, 1024, 1024, 1024]

#将其转到频域
stfts_mono = list()
for wav_mono in wavs_mono:
    stft_mono = librosa.stft(wav_mono, n_fft = n_fft, hop_length = hop_length)
    stfts_mono.append(stft_mono.transpose())

然后,初始化神经网络,并导入我们上面训练好的模型,

#初始化神经网络
model = SVMRNN(num_features = n_fft // 2 + 1, num_hidden_units = num_hidden_units)
#导入模型
model.load(file_dir = model_dir)

接着就对文件一一处理了,

for wav_filename, wav_mono, stft_mono in zip(song_filenames, wavs_mono, stfts_mono):
    wav_filename_base = os.path.basename(wav_filename)
    #单声道音频文件
    wav_mono_filename = wav_filename_base.split('.')[0] + '_mono.wav'
    #分离后的背景音乐音频文件
    wav_music_filename = wav_filename_base.split('.')[0] + '_music.wav'
    #分离后的人声音频文件
    wav_voice_filename = wav_filename_base.split('.')[0] + '_voice.wav'

    #要保存的文件的相对路径
    wav_mono_filepath = os.path.join(output_dir, wav_mono_filename)
    wav_music_hat_filepath = os.path.join(output_dir, wav_music_filename)
    wav_voice_hat_filepath = os.path.join(output_dir, wav_voice_filename)

    print('Processing %s ...' % wav_filename_base)

    stft_mono_magnitude, stft_mono_phase = separate_magnitude_phase(data = stft_mono)
    stft_mono_magnitude = np.array([stft_mono_magnitude])

    y_music_pred, y_voice_pred = model.test(x_mixed_src = stft_mono_magnitude, dropout_rate = dropout_rate)

    # 根据振幅和相位,转为复数,用于下面的逆短时傅里叶变换
    y_music_stft_hat = combine_magnitude_phase(magnitudes = y_music_pred[0], phases = stft_mono_phase)
    y_voice_stft_hat = combine_magnitude_phase(magnitudes = y_voice_pred[0], phases = stft_mono_phase)

    y_music_stft_hat = y_music_stft_hat.transpose()
    y_voice_stft_hat = y_voice_stft_hat.transpose()

    #逆短时傅里叶变换,将数据从频域转到时域
    y_music_hat = librosa.istft(y_music_stft_hat, hop_length = hop_length)
    y_voice_hat = librosa.istft(y_voice_stft_hat, hop_length = hop_length)

    #保存数据
    librosa.output.write_wav(wav_mono_filepath, wav_mono, dataset_sr)
    librosa.output.write_wav(wav_music_hat_filepath, y_music_hat, dataset_sr)
    librosa.output.write_wav(wav_voice_hat_filepath, y_voice_hat, dataset_sr)

代码写好后,将任意一个MP3文件放到input文件夹下,运行python test.py就开始工作了,得到结果如下,

可以听到,提取出来的背景音乐还是有点人声的,不过相对原文件人声已经小很多了,而纯人声中,仔细听也还能听到很小很小的乐器的声音,毕竟我们的损失没有降到接近0,所以并不是太完美,但是效果还可以了。

如果运行上面测试代码遇到如下错误,

RuntimeError: Error opening 'xxx.mp3': File contains data in an unknown format.

只需执行以下命令即可,前提是,你用的是anaconda环境。

conda install ffmpeg

 

7、完整代码

完整代码链接如下,

https://mianbaoduo.com/o/bread/Y5iamZs=

  • 12
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值