目錄
- 簡介
- Fbank處理過程
- MFCC
- fbank與mfcc的標准化
- fbank與mfcc的比較
一、簡介
Fbank:FilterBank:人耳對聲音頻譜的響應是非線性的,Fbank就是一種前端處理算法,以類似於人耳的方式對音頻進行處理,可以提高語音識別的性能。獲得語音信號的fbank特征的一般步驟是:預加重、分幀、加窗、短時傅里葉變換(STFT)、mel濾波、去均值等。對fbank做離散余弦變換(DCT)即可獲得mfcc特征。
MFCC(Mel-frequency cepstral coefficients):梅爾頻率倒譜系數。梅爾頻率是基於人耳聽覺特性提出來的, 它與Hz頻率成非線性對應關系。梅爾頻率倒譜系數(MFCC)則是利用它們之間的這種關系,計算得到的Hz頻譜特征。主要用於語音數據特征提取和降低運算維度。例如:對於一幀有512維(采樣點)數據,經過MFCC后可以提取出最重要的40維(一般而言)數據同時也達到了降維的目的。
下面看看每個步驟的過程及知識點。
二、Fbank處理過程
2.0 讀取數據
數據下載:PCM編碼的WAV格式
import matplotlib.pyplot as plt
time = np.arange(0, len(sig)) * (1.0 / fs)
def plot_freq(sig, sample_rate, nfft=512):
freqs = np.linspace(0, sample_rate/2, nfft//2 + 1)
xf = np.fft.rfft(sig, nfft) / nfft
xfp = 20 * np.log10(np.clip(np.abs(xf), 1e-20, 1e100))#強度
def plot_spectrogram(spec, ylabel = 'ylabel'):
fig = plt.figure(figsize=(20, 5))
fig.colorbar(mappable=heatmap)
wav_file = 'OSR_us_000_0010_8k.wav'
fs, sig = wavfile.read(wav_file)
#fs是wav文件的采樣率,signal是wav文件的內容,filename是要讀取的音頻文件的路徑
2.1 預處理—預加重(Pre-Emphasis)
預加重的目的:
- 平衡頻譜,因為高頻通常與較低頻率相比具有較小的幅度,提升高頻部分,使信號的頻譜變得平坦,保持在低頻到高頻的整個頻帶中,能用同樣的噪聲比(SNR)求頻譜。
- 也是為了消除發生過程中聲帶和嘴唇的效應,來補償語音信號受到發音系統所抑制的高頻部分,也為了突出高頻的共振峰。
預加重的過程:
代碼形式:emphasized_signal = numpy.append(signal[0], signal[1:] - pre_emphasis * signal[:-1])
題外話:預加重在現代系統中的影響不大,主要是因為除避免在現代FFT實現中不應成為問題的FFT數值問題,大多數預加重濾波器的動機都可以通過均值歸一化來實現(在本文后面討論)。 在現代FFT實現中。
效果展示
sig = np.append(sig[0], sig[1:] - pre_emphasis * sig[:-1])
2.2 預處理—分幀(Framing)
目的:
由於語音信號是一個非平穩態過程,不能用處理平穩信號的信號處理技術對其進行分析處理。語音信號是短時平穩信號。因此我們在短時幀上進行傅里葉變換,通過連接相鄰幀來獲得信號頻率輪廓的良好近似。
過程:
代碼:
def framing(frame_len_s, frame_shift_s, fs, sig):
frame_len_n, frame_shift_n = int(round(fs * frame_len_s)), int(round(fs * frame_shift_s))
num_frame = int(np.ceil(float(sig_n - frame_len_n) / frame_shift_n) + 1)
pad_num = frame_shift_n * (num_frame - 1) + frame_len_n - sig_n # 待補0的個數
pad_zero = np.zeros(int(pad_num)) # 補0
pad_sig = np.append(sig, pad_zero)
frame_inner_index = np.arange(0, frame_len_n)
frame_index = np.arange(0, num_frame) * frame_shift_n
# 復制每個幀的內部下標,信號有多少幀,就復制多少個,在行方向上進行復制
frame_inner_index_extend = np.tile(frame_inner_index, (num_frame, 1))
frame_index_extend = np.expand_dims(frame_index, 1)
each_frame_index = frame_inner_index_extend + frame_index_extend
each_frame_index = each_frame_index.astype(np.int, copy=False)
frame_sig = pad_sig[each_frame_index]
frame_sig = framing(frame_len_s, frame_shift_s, fs, sig)
2.3 預處理—加窗(Window)
語音在長范圍內是不停變動的,沒有固定的特性無法做處理,所以將每一幀代入窗函數,窗外的值設定為0,其目的是消除各個幀兩端可能會造成的信號不連續性。
常用的窗函數有方窗、漢明窗和漢寧窗等,根據窗函數的頻域特性,常采用漢明窗。
目的:
過程:
將信號分割成幀后,我們再對每個幀乘以一個窗函數,如Hamming窗口。以增加幀左端和右端的連續性。抵消FFT假設(數據是無限的),並減少頻譜泄漏。漢明窗的形式如下:
效果展示:
#frames *= numpy.hamming(frame_length)
# frames *= 0.54 - 0.46 * numpy.cos((2 * numpy.pi * n) / (frame_length - 1)) # 內部實現
window = np.hamming(int(round(frame_len_s * fs)))
plot_freq(frame_sig.reshape(-1,), fs) #需要先變成一維數據
print(frame_sig.reshape(-1,).shape)
2.4 傅里葉變換FFT(Fourier-Transform)
目的
過程:
接我們對分幀加窗后的各幀信號進行做一個N點FFT來計算頻譜,也稱為短時傅立葉變換(STFT),其中N通常為256或512,NFFT=512;
代碼:
mag_frames = numpy.absolute(numpy.fft.rfft(frames, NFFT)) # fft的幅度(magnitude)
功率譜(Power Spectrum)
然后我們使用以下公式計算功率譜(周期圖periodogram),對語音信號的頻譜取模平方(取對數或者去平方,因為頻率不可能為負,負值要舍去)得到語音信號的譜線能量。
代碼和效果:
def stft(frame_sig, nfft=512):
rfft 返回 nfft // 2 + 1,即rfft僅返回有效部分
frame_spec = np.fft.rfft(frame_sig, nfft)
frame_mag = np.abs(frame_spec)
frame_pow = (frame_mag ** 2) * 1.0 / nfft
frame_pow = stft(frame_sig, nfft)
2.5 梅爾濾波器組(Filter Banks)
計算Mel濾波器組,將功率譜通過一組Mel刻度(通常取40個濾波器,nfilt=40)的三角濾波器(triangular filters)來提取頻帶(frequency bands)。
代碼和效果:
def mel_filter(frame_pow, fs, n_filter, nfft):
mel = 2595 * log10(1 + f/700) # 頻率到mel值映射
f = 700 * (10^(m/2595) - 1 # mel值到頻率映射
mel_max = 2595 * np.log10(1 + fs / 2.0 / 700) # 最高mel值,最大信號頻率為 fs/2
mel_points = np.linspace(mel_min, mel_max, n_filter + 2) # n_filter個mel值均勻分布與最低與最高mel值之間
hz_points = 700 * (10 ** (mel_points / 2595.0) - 1) # mel值對應回頻率點,頻率間隔指數化
filter_edge = np.floor(hz_points * (nfft + 1) / fs) # 對應到fft的點數比例上
fbank = np.zeros((n_filter, int(nfft / 2 + 1)))
for m in range(1, 1 + n_filter):
f_left = int(filter_edge[m - 1]) # 左邊界點
f_center = int(filter_edge[m]) # 中心點
f_right = int(filter_edge[m + 1]) # 右邊界點
for k in range(f_left, f_center):
fbank[m - 1, k] = (k - f_left) / (f_center - f_left)
for k in range(f_center, f_right):
fbank[m - 1, k] = (f_right - k) / (f_right - f_center)
# [num_frame, nfft/2 + 1] * [nfft/2 + 1, n_filter] = [num_frame, n_filter]
filter_banks = np.dot(frame_pow, fbank.T)
filter_banks = np.where(filter_banks == 0, np.finfo(float).eps, filter_banks)
filter_banks = 20 * np.log10(filter_banks) # dB
filter_banks = mel_filter(frame_pow, fs, n_filter, nfft)
plot_spectrogram(filter_banks.T, ylabel='Filter Banks')
三、MFCC
MFCC實際上就是在Fbank的基礎上增加一個離散余弦變換(DCT)。
3.1 離散余弦變換(DCT)
L階指MFCC系數階數,通常取2-13。這里M是三角濾波器個數。
代碼和效果
mfcc = dct(filter_banks, type=2, axis=1, norm='ortho')[:, 1:(num_ceps+1)] # 保持在2-13
plot_spectrogram(mfcc.T, 'MFCC Coefficients')
3.2 動態差分參數的提取
標准的倒譜參數MFCC只反映了語音參數的靜態特性,語音的動態特性可以用這些靜態特征的差分譜來描述。實驗證明:把動、靜態特征結合起來才能有效提高系統的識別性能。
式中,dt表示第t個一階差分,Ct表示第t個倒譜系數,Q表示倒譜系數的階數,K表示一階導數的時間差,可取1或2。將上式的結果再代入就可以得到二階差分的參數。
MFCC的全部組成其實是由: N維MFCC參數(N/3 MFCC系數+ N/3 一階差分參數+ N/3 二階差分參數)+幀能量(此項可根據需求替換)。
四、fbank與mfcc的標准化
4.1 去均值 (CMN)
filter_banks -= (np.mean(filter_banks, axis=0) + 1e-8)
plot_spectrogram(filter_banks.T, ylabel='Filter Banks')
mfcc -= (np.mean(mfcc, axis=0) + 1e-8)
plot_spectrogram(mfcc.T, 'MFCC Coefficients')
五、fbank與mfcc的比較
上述代碼:https://github.com/yifanhunter/audio
參考文獻
【1】 https://blog.csdn.net/fengzhonghen/article/details/51722555
【2】MFCC的理解: https://www.cnblogs.com/LXP-Never/p/10918590.html
【3】數據分析圖例(代碼主要來源於此):https://zhuanlan.zhihu.com/p/130926693