第一次是拿C写的,提谱特征,过增强网络,之后取噪声相位,重构音频,弄了一个星期,后来发现,其实可以不必这么麻烦的。用kaldi得到的增强特征,做一个逆的CMVN,之后拿Python处理一下特征还原就行了。
总结一下,语音重构主要是还原频域特征到时域上,使用原始音频的相位信息,流程如下
- 获取一帧的谱特征
- 获取原始音频中对应帧的相位
a. 分帧
b. Remove DC
c. 预加重
d. 加窗
e. RFFT,获取相位,返回
- 对谱特征,取exp,开方得到幅度谱,这里只使用[1: 257]的值,不使用能量
- 幅度谱和相位点乘,RFFT,取前400维得到一帧数据
- 加窗
- 进行OverlapAdd, 实际上就是帧移相加
- 所有帧处理完之后,加一个低通滤波器
- 将采样值的范围恢复到原始音频的范围内
代码如下,理清特征处理过程思路就很清晰了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
"""transform spectrogram to waveform"""
import sys import wave import numpy as np
import kaldi_io import wave_io
if len(sys.argv) != 4: print "format error: %s [spectrum] [origin-wave] [reconst-wave]" % sys.argv[0] sys.exit(1)
WAVE_WARPPER = wave_io.WaveWrapper(sys.argv[2]) WAVE_RECONST = wave.open(sys.argv[3], "wb")
WND_SIZE = WAVE_WARPPER.get_wnd_size() WND_RATE = WAVE_WARPPER.get_wnd_rate()
REAL_IFFT = np.fft.irfft
HAM_WND = np.hamming(WND_SIZE)
with open(sys.argv[1], "rb") as ark: SPECT_ENHANCE = kaldi_io.next_mat_ark(ark) SPECT_ROWS, SPECT_COLS = SPECT_ENHANCE.shape assert WAVE_WARPPER.get_frames_num() == SPECT_ROWS INDEX = 0 SPECT = np.zeros(SPECT_COLS) RECONST_POOL = np.zeros((SPECT_ROWS - 1) * WND_RATE + WND_SIZE) for phase in WAVE_WARPPER.next_frame_phase(): SPECT[1: ] = np.sqrt(np.exp(SPECT_ENHANCE[INDEX][1: ])) RECONST_POOL[INDEX * WND_RATE: INDEX * WND_RATE + WND_SIZE] += \ REAL_IFFT(SPECT * phase)[: WND_SIZE] * HAM_WND INDEX += 1 for x in range(1, RECONST_POOL.size): RECONST_POOL[x] += 0.97 * RECONST_POOL[x - 1] RECONST_POOL = RECONST_POOL / np.max(RECONST_POOL) * WAVE_WARPPER.get_upper_bound()
WAVE_RECONST.setnchannels(1) WAVE_RECONST.setnframes(RECONST_POOL.size) WAVE_RECONST.setsampwidth(2) WAVE_RECONST.setframerate(WAVE_WARPPER.get_sample_rate()) WAVE_RECONST.writeframes(np.array(RECONST_POOL, dtype=np.int16).tostring()) WAVE_RECONST.close()
|
当年python画风好奇怪,顺便补充一下谱特征的正向处理过程,我拿python写的结果和kaldi做了一下对比,在不加随机高斯量的时候,误差还是很小的。
kaldi中默认的普特征提取流程如下
- 分帧【加一个随机高斯量,可以通过options去掉,默认为真】
- Remove DC, 也就是减去帧的均值,移除直流分量
- 计算原始能量,放在第一维上
- 预加重
- 加窗,这里加的是指定类型的窗
- RFFT, 取[1, 256]区间,注意,这里是能量log谱,不是幅度log谱
- 返回一帧的谱特征,继续
Demo代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
"""compute spectrogram according to kaldi"""
import sys import wave import math import numpy as np
if len(sys.argv) != 2: print "format error: %s [wave-in]" % sys.argv[0] sys.exit(1)
SRC_WAVE = wave.open(sys.argv[1], "rb") SRC_SAMPLE_RATE, TOT_SAMPLE = SRC_WAVE.getparams()[2: 4]
WND_SIZE = int(SRC_SAMPLE_RATE * 0.001 * 25) WND_OFFSET = int(SRC_SAMPLE_RATE * 0.001 * 10) WAVE_DATA = np.fromstring(SRC_WAVE.readframes(TOT_SAMPLE), np.int16)
FRAME_NUM = (WAVE_DATA.size - WND_SIZE) / WND_OFFSET + 1
SPECT_LEN = 257 SPECT_VEC = np.zeros(SPECT_LEN)
HAMMING = np.hamming(WND_SIZE)
print FRAME_NUM
for index in range(FRAME_NUM): BASE_PNT = index * WND_OFFSET FRAME_VEC = np.array(WAVE_DATA[BASE_PNT: BASE_PNT + WND_SIZE], dtype=np.float) FRAME_VEC -= (np.sum(FRAME_VEC) / WND_SIZE) energy = math.log(np.sum(FRAME_VEC ** 2)) FRAME_VEC[1: ] -= 0.97 * FRAME_VEC[: -1] FRAME_VEC[0] -= 0.97 * FRAME_VEC[0]
DFT_VALUE = np.zeros((SPECT_LEN - 1) * 2) DFT_VALUE[: WND_SIZE] = FRAME_VEC * HAMMING SPECT_VEC[0] = energy SPECT_VEC[1: ] = np.log(np.abs(np.fft.rfft(DFT_VALUE)[1: ]) ** 2)
|