声音转换——感知线性预测(PLP)方法详解
目录
- 简介
- 声学特征概述
- 感知线性预测(PLP)
- PLP在声音转换中的应用
- PLP转换的优缺点
- PLP转换的改进方法
- PLP转换在实际应用中的注意事项
- PLP与现代深度学习方法的对比
- 总结
- Python实现及代码解读
- 参考文献
简介
声音转换(Voice Conversion, VC)是一种将一个人的语音特征转换为另一个人的语音特征的技术,广泛应用于语音合成、隐私保护、娱乐等领域。感知线性预测(Perceptual Linear Prediction, PLP)作为一种结合了人类听觉特性的声学特征提取方法,在声音转换中具有重要的应用价值。相比于传统的线性预测倒谱系数(LPCC)和梅尔频率倒谱系数(MFCC),PLP在模拟人耳感知特性方面表现更佳,能够提升声音转换的自然度和识别准确性。
声学特征概述
声学特征是描述语音信号的重要参数,用于捕捉语音的频谱特性和时域特性。不同的声学特征方法侧重于不同的语音信息提取,常见的方法包括:
- 梅尔频率倒谱系数(MFCC):广泛应用于语音识别和声音转换。
- 线性预测倒谱系数(LPCC):通过线性预测分析语音信号。
- 感知线性预测(PLP):结合了人类听觉特性。
- 巴克频率倒谱系数(BFCC):基于巴克尺度的倒谱系数。
- 光谱包络特征:描述语音信号的频谱包络。
- 基频和韵律特征:描述语音的声调、节奏等高层次信息。
本文将重点介绍感知线性预测(PLP)在声音转换中的应用与实现。
感知线性预测(PLP)
PLP的基本原理
感知线性预测(Perceptual Linear Prediction, PLP)是一种结合了人类听觉特性的线性预测方法,用于提取语音信号的倒谱系数。PLP通过模拟人耳对不同频率的感知特点,改进了传统线性预测方法,提升了语音特征的辨识度和抗噪性能。PLP不仅考虑了频谱包络,还结合了等响度曲线和动态范围压缩,使其在语音识别和声音转换中表现出更好的性能。
PLP的计算步骤
- 预加重(Pre-Emphasis):增强高频成分,平衡频谱。
- 分帧与窗函数:将连续的语音信号分成若干重叠的短时帧,并应用窗函数减少频谱泄漏。
- 功率谱计算:通过快速傅里叶变换(FFT)计算每帧的功率谱。
- 等响度滤波器组:应用等响度曲线,模拟人耳对不同频率的敏感度。
- 反响度加权:根据人耳对不同频率的感知加权功率谱。
- 线性预测分析:计算线性预测系数,预测当前帧的语音样本。
- 倒谱分析:对线性预测误差信号进行倒谱分析,提取PLP特征。
数学公式详解
预加重
预加重滤波器的传递函数为:
H ( z ) = 1 − α z − 1 H(z) = 1 - \alpha z^{-1} H(z)=1−αz−1
其中, α \alpha α 通常取值在0.95到0.97之间。
分帧与加窗
将信号分为长度为 N N N 的帧,每帧之间重叠 L L L 个采样点。窗函数 w ( n ) w(n) w(n) 通常选择汉明窗:
w ( n ) = 0.54 − 0.46 cos ( 2 π n N − 1 ) , 0 ≤ n ≤ N − 1 w(n) = 0.54 - 0.46 \cos\left(\frac{2\pi n}{N-1}\right), \quad 0 \leq n \leq N-1 w(n)=0.54−0.46cos(N−12πn),0≤n≤N−1
快速傅里叶变换(FFT)
对加窗后的信号 s ( t ) s(t) s(t) 进行FFT,得到频谱 S ( k ) S(k) S(k):
S ( k ) = FFT { s ( t ) } S(k) = \text{FFT}\{ s(t) \} S(k)=FFT{s(t)}
功率谱 ∣ S ( k ) ∣ 2 |S(k)|^2 ∣S(k)∣2 表示为:
P ( k ) = ∣ S ( k ) ∣ 2 P(k) = |S(k)|^2 P(k)=∣S(k)∣2
等响度滤波器组
等响度滤波器组模拟了人耳对不同频率的感知敏感度。等响度曲线 L ( f ) L(f) L(f) 定义为:
L ( f ) = 1.019 f 0.7 1 + ( f / 1000 ) 0.7 L(f) = 1.019 \frac{f^{0.7}}{1 + (f/1000)^{0.7}} L(f)=1.0191+(f/1000)0.7f0.7
其中, f f f 为频率,单位为Hz。
反响度加权
对功率谱应用反响度加权:
P ′ ( k ) = P ( k ) ⋅ L ( f k ) P'(k) = P(k) \cdot L(f_k) P′(k)=P(k)⋅L(fk)
其中, P ′ ( k ) P'(k) P′(k) 为加权后的功率谱, L ( f k ) L(f_k) L(fk) 为第 k k k 个频率点的等响度加权系数。
线性预测分析
假设当前帧信号 x ( n ) x(n) x(n) 可以通过前 p p p 个采样点线性预测:
x ^ ( n ) = ∑ k = 1 p a k x ( n − k ) \hat{x}(n) = \sum_{k=1}^{p} a_k x(n-k) x^(n)=k=1∑pakx(n−k)
线性预测系数 a k a_k ak 通过最小均方误差准则确定:
min a 1 , a 2 , … , a p ∑ n = 1 N [ x ( n ) − x ^ ( n ) ] 2 \min_{a_1, a_2, \ldots, a_p} \sum_{n=1}^{N} [x(n) - \hat{x}(n)]^2 a1,a2,…,apminn=1∑N[x(n)−x^(n)]2
预测误差
预测误差信号 e ( n ) e(n) e(n) 定义为:
e ( n ) = x ( n ) − x ^ ( n ) = x ( n ) − ∑ k = 1 p a k x ( n − k ) e(n) = x(n) - \hat{x}(n) = x(n) - \sum_{k=1}^{p} a_k x(n-k) e(n)=x(n)−x^(n)=x(n)−k=1∑pakx(n−k)
倒谱分析
对预测误差 e ( n ) e(n) e(n) 进行傅里叶变换,取对数并进行逆傅里叶变换得到倒谱系数 c ( n ) c(n) c(n):
c ( n ) = IFFT { log ∣ E ( k ) ∣ } c(n) = \text{IFFT}\{ \log |E(k)| \} c(n)=IFFT{log∣E(k)∣}
PLP特征提取
选择前 m m m 个倒谱系数作为PLP特征:
PLP = [ c ( 0 ) , c ( 1 ) , … , c ( m − 1 ) ] \text{PLP} = [c(0), c(1), \ldots, c(m-1)] PLP=[c(0),c(1),…,c(m−1)]
PLP在声音转换中的应用
在声音转换任务中,PLP用于提取源语音和目标语音的声学特征,通过转换模型将源语音的PLP特征转换为目标语音的PLP特征,再通过逆转换过程重建目标语音信号。具体步骤如下:
- 特征提取:从源语音和目标语音中提取PLP特征。
- 特征映射:使用转换模型(如线性变换、高斯混合模型(GMM)、神经网络)建立源PLP与目标PLP之间的映射关系。
- 特征转换:将源语音的PLP特征通过映射关系转换为目标PLP特征。
- 信号重建:通过逆PLP转换过程将目标PLP特征转换回时域语音信号。
PLP转换的优缺点
优点
- 人耳感知模拟:PLP结合了等响度曲线和动态范围压缩,更好地模拟了人耳对频率的感知特性,提升了特征的辨识度。
- 抗噪性能强:PLP对背景噪声具有较好的鲁棒性,适用于噪声环境下的声音转换。
- 特征表达丰富:除了频谱包络,PLP还包含了更多与人类听觉相关的信息,提升了转换的自然度和质量。
缺点
- 计算复杂度高:PLP在等响度滤波器组设计和线性预测分析上比传统方法更为复杂,计算量较大。
- 参数选择敏感:PLP的性能对参数设置(如滤波器数量、预测阶数等)较为敏感,需根据具体应用场景进行优化。
- 实现难度大:PLP的实现相比于MFCC和LPCC更为复杂,需精确设计滤波器组和处理步骤。
PLP转换的改进方法
特征选择与降维
通过选择最具代表性的PLP系数或应用降维技术(如主成分分析,PCA)减少特征维度,提升转换效率和效果。
结合深度学习技术
利用深度神经网络(DNN)、卷积神经网络(CNN)、循环神经网络(RNN)等深度学习技术建立更复杂的特征映射关系,提升转换模型的表达能力和准确性。
多特征融合
结合PLP与其他声学特征(如MFCC、LPCC)进行特征融合,综合利用多种特征的优势,提升转换效果。
PLP转换在实际应用中的注意事项
- 特征同步:确保源语音和目标语音在特征提取过程中保持同步,避免时域偏移。
- 参数调优:根据具体应用场景调整PLP的参数,如滤波器数量、预测阶数,以获得最佳转换效果。
- 信号预处理:进行适当的预加重、分帧和窗函数处理,提升特征提取的准确性。
- 模型训练:使用充足且多样化的训练数据,确保转换模型具有良好的泛化能力。
- 噪声处理:在噪声环境下进行声音转换时,需结合有效的去噪技术,提升转换后的语音质量。
PLP与现代深度学习方法的对比
随着深度学习的发展,基于神经网络的声音转换方法(如基于循环神经网络,Transformer等)在转换效果和自然度上通常优于传统的PLP方法。然而,PLP仍具有以下优势:
- 计算效率相对较高:虽然PLP比MFCC复杂,但相比于深度学习方法,计算资源消耗较低,适合资源受限的设备。
- 无需大量训练数据:PLP方法不依赖于大规模的数据集,适用于数据不足的场景。
- 模型解释性强:基于明确的数学模型,易于理解和实现,便于调试和优化。
在实际应用中,可以将PLP与深度学习方法结合,利用PLP进行初步特征提取,再通过深度学习模型进行特征转换和信号重建,以获得更好的转换效果。
总结
感知线性预测(PLP)作为一种结合了人类听觉特性的声学特征提取方法,在声音转换领域具有重要应用价值。通过等响度滤波器组和动态范围压缩,PLP能够有效捕捉语音信号的频谱特性,提升了特征的辨识度和抗噪性能。尽管PLP在计算复杂度和参数选择上存在一定的挑战,但通过特征选择、深度学习技术的结合和多特征融合等改进方法,PLP在声音转换中的表现得到了显著提升。深入理解PLP的基本原理和应用方法,对于语音处理领域的研究者和工程师而言,具有重要的实践意义。
Python实现及代码解读
以下是一个详细的Python实现基于PLP的声音转换示例,使用numpy
、scipy
、librosa
和sklearn
库,并附有详细的代码注释。
代码
import numpy as np
import scipy.io.wavfile as wav
import scipy.signal as signal
import librosa
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.mixture import GaussianMixture
def pre_emphasis(signal, pre_emphasis_coeff=0.97):
"""
预加重滤波器
参数:
signal (numpy.ndarray): 输入语音信号
pre_emphasis_coeff (float): 预加重系数
返回:
emphasized_signal (numpy.ndarray): 预加重后的信号
"""
return np.append(signal[0], signal[1:] - pre_emphasis_coeff * signal[:-1])
def framing(signal, frame_size, frame_stride, fs):
"""
分帧处理。
参数:
signal (numpy.ndarray): 输入语音信号
frame_size (float): 帧大小,单位秒
frame_stride (float): 帧移,单位秒
fs (int): 采样率
返回:
frames (numpy.ndarray): 分帧后的语音信号
"""
frame_length = int(round(frame_size * fs))
frame_step = int(round(frame_stride * fs))
signal_length = len(signal)
num_frames = int(np.ceil(float(np.abs(signal_length - frame_length)) / frame_step)) + 1
pad_signal_length = num_frames * frame_step + frame_length
pad_signal = np.append(signal, np.zeros((pad_signal_length - signal_length)))
indices = np.tile(np.arange(0, frame_length), (num_frames, 1)) + \
np.tile(np.arange(0, num_frames * frame_step, frame_step), (frame_length, 1)).T
frames = pad_signal[indices.astype(np.int32, copy=False)]
return frames
def windowing(frames, frame_length):
"""
加窗处理。
参数:
frames (numpy.ndarray): 分帧后的语音信号
frame_length (int): 帧长度
返回:
windowed_frames (numpy.ndarray): 加窗后的语音信号
"""
frames *= np.hamming(frame_length)
return frames
def equal_loudness_filterbank(n_mels=26, fs=16000, n_fft=512):
"""
等响度滤波器组设计。
参数:
n_mels (int): 梅尔滤波器数量
fs (int): 采样率
n_fft (int): FFT窗口大小
返回:
filter_bank (numpy.ndarray): 等响度滤波器组
"""
# 巴克尺度转换
def bark_scale(f):
return 13 * np.arctan(0.00076 * f) + 3.5 * np.arctan((f / 7500)**2)
# 生成频率轴
f_max = fs / 2
f_min = 0
# 巴克尺度范围
m_min = bark_scale(f_min)
m_max = bark_scale(f_max)
# 等间隔的巴克尺度点
m_points = np.linspace(m_min, m_max, n_mels + 2)
# 转换回赫兹
def inverse_bark_scale(m):
return (np.tan((m - 3.5) / 13) / 0.00076)
f_points = inverse_bark_scale(m_points)
# 生成FFT频率点
f = np.linspace(0, f_max, int(1 + n_fft//2))
# 生成滤波器组
filter_bank = np.zeros((n_mels, len(f)))
for i in range(1, n_mels + 1):
f_left = f_points[i - 1]
f_center = f_points[i]
f_right = f_points[i + 1]
# 三角形滤波器
for j in range(len(f)):
if f[j] < f_left:
filter_bank[i-1, j] = 0
elif f[j] < f_center:
filter_bank[i-1, j] = (f[j] - f_left) / (f_center - f_left)
elif f[j] < f_right:
filter_bank[i-1, j] = (f_right - f[j]) / (f_right - f_center)
else:
filter_bank[i-1, j] = 0
return filter_bank
def compute_plp(signal, fs, frame_size=0.025, frame_stride=0.010, n_mfcc=13, n_fft=512, n_mels=26, pre_emphasis_coeff=0.97):
"""
计算感知线性预测(PLP)特征。
参数:
signal (numpy.ndarray): 输入语音信号
fs (int): 采样率
frame_size (float): 帧大小,单位秒
frame_stride (float): 帧移,单位秒
n_mfcc (int): PLP特征数量
n_fft (int): FFT窗口大小
n_mels (int): 等响度滤波器数量
pre_emphasis_coeff (float): 预加重系数
返回:
plp (numpy.ndarray): PLP特征
"""
# 预加重
emphasized_signal = pre_emphasis(signal, pre_emphasis_coeff)
# 分帧
frames = framing(emphasized_signal, frame_size, frame_stride, fs)
# 加窗
frames = windowing(frames, frames.shape[1])
# FFT
mag_frames = np.absolute(np.fft.rfft(frames, n_fft))
power_frames = (1.0 / n_fft) * (mag_frames ** 2)
# 等响度滤波器组
filter_bank = equal_loudness_filterbank(n_mels, fs, n_fft)
filter_banks = np.dot(power_frames, filter_bank.T)
# 替换零值以避免取对数
filter_banks = np.where(filter_banks == 0, np.finfo(float).eps, filter_banks)
# 对数变换
log_filter_banks = np.log(filter_banks)
# 线性预测分析
plp = []
for log_fb in log_filter_banks:
# 最小二乘法求解线性预测系数
p = len(log_fb)
X = np.column_stack([log_fb[i:p - (p - 1)] for i in range(p)])
y = log_fb[p:]
if y.size == 0:
coeffs = np.zeros(n_mfcc)
else:
model = LinearRegression().fit(X, y)
coeffs = model.coef_.flatten()
if coeffs.size < n_mfcc:
coeffs = np.pad(coeffs, (0, n_mfcc - coeffs.size), 'constant')
else:
coeffs = coeffs[:n_mfcc]
plp.append(coeffs)
return np.array(plp)
def plot_features(features, title='Features', xlabel='Frame', ylabel='Coefficient'):
"""
绘制特征图。
参数:
features (numpy.ndarray): 特征数据
title (str): 图标题
xlabel (str): X轴标签
ylabel (str): Y轴标签
"""
plt.figure(figsize=(10, 4))
plt.imshow(features.T, aspect='auto', origin='lower', interpolation='none')
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.colorbar()
plt.tight_layout()
plt.show()
def train_gmm(features, n_components=16, max_iter=100):
"""
使用高斯混合模型(GMM)训练声学模型。
参数:
features (numpy.ndarray): 特征数据
n_components (int): GMM组件数
max_iter (int): 最大迭代次数
返回:
gmm (GaussianMixture): 训练好的GMM模型
"""
gmm = GaussianMixture(n_components=n_components, max_iter=max_iter, covariance_type='diag', init_params='kmeans', random_state=0)
gmm.fit(features)
return gmm
def gmm_denoise(noisy_plp, speech_gmm, noise_gmm):
"""
使用GMM进行语音去噪。
参数:
noisy_plp (numpy.ndarray): 含噪语音的PLP特征
speech_gmm (GaussianMixture): 语音GMM模型
noise_gmm (GaussianMixture): 噪声GMM模型
返回:
denoised_plp (numpy.ndarray): 去噪后的PLP特征
"""
# 计算每个帧属于语音和噪声的概率
log_likelihood_speech = speech_gmm.score_samples(noisy_plp)
log_likelihood_noise = noise_gmm.score_samples(noisy_plp)
# 转换为概率
prob_speech = np.exp(log_likelihood_speech)
prob_noise = np.exp(log_likelihood_noise)
# 计算语音概率比例
posterior_speech = prob_speech / (prob_speech + prob_noise + 1e-10)
# 应用后验概率进行去噪
denoised_plp = noisy_plp * posterior_speech[:, np.newaxis]
return denoised_plp
def plp_to_audio(denoised_plp, fs, frame_size=0.025, frame_stride=0.010, n_fft=512, n_mels=26, pre_emphasis_coeff=0.97):
"""
将去噪后的PLP特征转换为时域音频信号。
参数:
denoised_plp (numpy.ndarray): 去噪后的PLP特征
fs (int): 采样率
frame_size (float): 帧大小,单位秒
frame_stride (float): 帧移,单位秒
n_fft (int): FFT窗口大小
n_mels (int): 等响度滤波器数量
pre_emphasis_coeff (float): 预加重系数
返回:
reconstructed_signal (numpy.ndarray): 重建的时域音频信号
"""
# 简化的逆PLP过程,仅示意
# 实际中需要更复杂的逆滤波和重建方法
reconstructed_signal = []
for plp_coeff in denoised_plp:
# 通过PLP系数简单重建频谱
envelope = np.exp(plp_coeff[0])
frame = envelope * np.ones(n_fft//2 + 1)
# 反FFT
frame_signal = np.fft.irfft(frame)
reconstructed_signal.extend(frame_signal)
reconstructed_signal = np.array(reconstructed_signal)
# 反预加重
reconstructed_signal = np.insert(reconstructed_signal, 0, 0)
for n in range(1, len(reconstructed_signal)):
reconstructed_signal[n] += pre_emphasis_coeff * reconstructed_signal[n-1]
return reconstructed_signal
def plot_waveform(signal_data, fs, title='Waveform'):
"""
绘制信号的时域波形。
参数:
signal_data (numpy.ndarray): 时域信号
fs (int): 采样率
title (str): 图标题
"""
plt.figure(figsize=(10, 4))
time = np.arange(len(signal_data)) / fs
plt.plot(time, signal_data)
plt.title(title)
plt.xlabel('时间 [秒]')
plt.ylabel('振幅')
plt.tight_layout()
plt.show()
def plot_spectrogram(signal_data, fs, title='Spectrogram'):
"""
绘制信号的频谱图。
参数:
signal_data (numpy.ndarray): 时域信号
fs (int): 采样率
title (str): 图标题
"""
f, t_spec, Sxx = signal.stft(signal_data, fs, window='hamming', nperseg=1024, noverlap=512)
plt.figure(figsize=(10, 6))
plt.pcolormesh(t_spec, f, 20 * np.log10(np.abs(Sxx) + 1e-10), shading='gouraud')
plt.title(title)
plt.ylabel('频率 [Hz]')
plt.xlabel('时间 [秒]')
plt.colorbar(label='幅度 [dB]')
plt.tight_layout()
plt.show()
if __name__ == "__main__":
# 读取音频文件
fs_noisy, noisy = wav.read('noisy.wav') # 含噪语音文件
fs_noise, noise = wav.read('noise.wav') # 纯噪声音频文件
# 确保采样率一致
if fs_noisy != fs_noise:
raise ValueError("含噪语音和噪声音频的采样率不一致!")
fs = fs_noisy
# 如果音频是立体声,取一个通道
if noisy.ndim > 1:
noisy = noisy[:, 0]
if noise.ndim > 1:
noise = noise[:, 0]
# 计算PLP特征
plp_noisy = compute_plp(noisy, fs, frame_size=0.025, frame_stride=0.010, n_mfcc=13, n_fft=512, n_mels=26, pre_emphasis_coeff=0.97)
plp_noise = compute_plp(noise, fs, frame_size=0.025, frame_stride=0.010, n_mfcc=13, n_fft=512, n_mels=26, pre_emphasis_coeff=0.97)
# 绘制PLP特征图
plot_features(plp_noisy, title='含噪语音PLP特征图', xlabel='帧', ylabel='PLP系数')
plot_features(plp_noise, title='噪声音频PLP特征图', xlabel='帧', ylabel='PLP系数')
# 训练GMM模型
speech_gmm = train_gmm(plp_noisy, n_components=16, max_iter=100)
noise_gmm = train_gmm(plp_noise, n_components=16, max_iter=100)
# 进行GMM语音去噪
denoised_plp = gmm_denoise(plp_noisy, speech_gmm, noise_gmm)
# 将去噪后的PLP转换回音频信号
denoised_audio = plp_to_audio(denoised_plp, fs, frame_size=0.025, frame_stride=0.010, n_fft=512, n_mels=26, pre_emphasis_coeff=0.97)
# 绘制去噪后的PLP特征图
plot_features(denoised_plp, title='去噪后语音PLP特征图', xlabel='帧', ylabel='PLP系数')
# 绘制波形和频谱图
plot_waveform(noisy, fs, title='含噪语音时域波形')
plot_waveform(denoised_audio, fs, title='去噪后语音时域波形')
plot_spectrogram(noisy, fs, title='含噪语音频谱图')
plot_spectrogram(denoised_audio, fs, title='去噪后语音频谱图')
# 归一化并保存去噪后的音频文件
denoised_audio = denoised_audio / np.max(np.abs(denoised_audio))
denoised_int16 = np.int16(denoised_audio * 32767)
wav.write('denoised_plp.wav', fs, denoised_int16)