利用kaldi提取mfcc特徵
前言
1)由於kaldi的整合性很高,這樣如果只是想實現一個小功能,就需要很多準備東西,比如如果要提取mfcc就需要利用 steps/make_mfcc.sh指令碼,需要為其準備一些檔案。
2)以下例項都是建立在timit集上(也只是利用了它的資料),及timit例項程式,如果研究過此,就會很容易明白我解釋不清的地方。
3)目前提取的mfcc還有問題,看一下結果吧,但是我找不出錯誤(如果正確了 會改部落格的)。(上面是工程自動跑出的,下面的是我的,每個都是13列,截圖不全)
( 命令如下 copy-feats ark:raw_mfcc_test.9.ark ark,t:- | less)
4)小貼士的地方是我針對timit的思考過程,可以不看。
資料準備
timit的wav檔案不是真正的wav檔案,需要用kaldi的工具 sph2pipe 進行轉換。
比如使用timit中的test/dr1/mdab0_si1039.wav 檔案,首先用命令
fname=test_dr1_mdab0_si1039.wav #我改了檔案的名字
sph2pipe -f wav $fname >file.wav #要先轉換格式
就得到一個真正wav格式的檔案,這樣你把它放到音樂播放器中就能夠聽到發音。(如果你的檔案就是wav的就不用這樣做了)
接下來,我們就對file.wav這個檔案進行mfcc特徵提取。
(小貼士:
通過分析指令碼run.sh及程式碼名稱發現可疑檔案,local/timit_data_prep.sh , steps/make_mfcc.sh 。進過觀察,發現前者生成的目錄是data裡面的東西,那時還沒有mfcc。後者則用了兩個目錄mfcc及exp/make_mfcc ,而且發現後面那個目錄只剩下log檔案了,中間的一些scp被刪,所以它只是個記錄的資料夾沒有用。通過看steps/make_mfcc.sh發現了data/*/wav.scp檔案的作用,因為它用到了 ark,p 結合檔案內容,便可分析出 sph2pipe 的位置,使用方法和作用。
後面一切都是結合steps/make_mfcc.sh
)
程式準備
1.增加環境變數
wav.scp的檔案大致如下
fdhc0_si1559 /xxxxxxxxx/kaldi-trunk/egs/timit/_base_full/../../../tools/sph2pipe_v2.5/sph2pipe -f wav /xxxxxxxxxx/TIMIT/test/dr7/fdhc0/si1559.wav |
我們可以找到 sph2pipe 的位置,直接設定成環境變數,方便程式直接使用。
通過修改 ~/.profile 檔案(在它後面新增)
export KALDI_ROOT=~/my_dir/software/ars/kaldi-trunk
export myutils=$KALDI_ROOT/tools/sph2pipe_v2.5
export PATH=$myutils:$KALDI_ROOT/src/bin:$KALDI_ROOT/tools/openfst/bin:$KALDI_ROOT/tools/irstlm/bin/:$KALDI_ROOT/src/fstbin/:$KALDI_ROOT/src/gmmbin/:$KALDI_ROOT/src/featbin/:$KALDI_ROOT/src/lm/:$KALDI_ROOT/src/sgmmbin/:$KALDI_ROOT/src/sgmm2bin/:$KALDI_ROOT/src/fgmmbin/:$KALDI_ROOT/src/latbin/:$KALDI_ROOT/src/nnetbin:$KALDI_ROOT/src/nnet2bin/:$KALDI_ROOT/src/kwsbin:$PWD:$PATH
#export LC_ALL=C
export IRSTLM=$KALDI_ROOT/tools/irstlm
#export LC_ALL="zh_CN.UTF-8"
LANG=en_US.utf8
然後source ~/.profile一下,使其立即生效。這樣的好處就是,以後使用kaldi的命令時就可以直接使用,比如什麼copy-feats等。後面的LANG是為了支援中文。這些內容可以找其他資料。
2.編譯自己的可執行程式
通過那個指令碼檔案可以知道,真正執行mfcc的是程式 kaldi-trunk/src/featbin/compute-mfcc-feats ,為了不破壞,源程式,我們複製.cc檔案並重命名為 _chj_compute-mfcc-feats.cc 還有為了能夠編譯還要修改Makefile檔案,如下
(解釋一下:原來的BINFILES加個b_使其不生效,然後自己寫一個BINFILES。這樣重新編譯的時候就只編譯自己的檔案了,快些)
接下來我們做的就是要修改_chj_compute-mfcc-feats.cc檔案了。
如果我們定義呼叫方式如下 _chj_compute-mfcc-feats --use-energy=false file.wav _mfcc.txt
(程式基本如下所示,已經可以執行,只是直接輸出,沒有儲存成檔案,和進行特徵變換)
#include <iostream>
#include <fstream> //檔案操作
#include "base/kaldi-common.h"
#include "util/common-utils.h"
#include "feat/feature-mfcc.h"
#include "feat/wave-reader.h"
using namespace std;
int main(int argc, char *argv[]) {
try {
using namespace kaldi;
const char *usage =
"Create MFCC feature files.\n"
"Usage: _chj_compute-mfcc-feats [options...] <wav-file> <feats-file>\n";
// construct all the global objects
ParseOptions po(usage);
MfccOptions mfcc_opts;
bool subtract_mean = false;
BaseFloat vtln_warp = 1.0;
std::string vtln_map_rspecifier;
std::string utt2spk_rspecifier;
int32 channel = -1;
BaseFloat min_duration = 0.0;
// Define defaults for gobal options
std::string output_format = "kaldi";
// Register the MFCC option struct
mfcc_opts.Register(&po);
// Register the options
po.Register("output-format", &output_format, "Format of the output "
"files [kaldi, htk]");
po.Register("subtract-mean", &subtract_mean, "Subtract mean of each "
"feature file [CMS]; not recommended to do it this way. ");
po.Register("vtln-warp", &vtln_warp, "Vtln warp factor (only applicable "
"if vtln-map not specified)");
po.Register("vtln-map", &vtln_map_rspecifier, "Map from utterance or "
"speaker-id to vtln warp factor (rspecifier)");
po.Register("utt2spk", &utt2spk_rspecifier, "Utterance to speaker-id map "
"rspecifier (if doing VTLN and you have warps per speaker)");
po.Register("channel", &channel, "Channel to extract (-1 -> expect mono, "
"0 -> left, 1 -> right)");
po.Register("min-duration", &min_duration, "Minimum duration of segments "
"to process (in seconds).");
po.Read(argc, argv);
if (po.NumArgs() != 2) {
po.PrintUsage();
exit(1);
}
std::string in_wav_fname = po.GetArg(1);
ifstream is(in_wav_fname.c_str(),ios::in);
std::string out_mfcc_fname = po.GetArg(2);
Mfcc mfcc(mfcc_opts);
BaseFloatMatrixWriter kaldi_writer; // typedef to TableWriter<something>.
WaveData wave_data;
wave_data.Read(is); //不用強制轉換到istream
if (wave_data.Duration() < min_duration) {
KALDI_WARN <<"too short ("<< wave_data.Duration() << " sec): producing no output.";
}
int32 num_chan = wave_data.Data().NumRows(), this_chan = channel;
{ // This block works out the channel (0=left, 1=right...)
KALDI_ASSERT(num_chan > 0); // should have been caught in
// reading code if no channels.
if (channel == -1) {
this_chan = 0;
if (num_chan != 1)
KALDI_WARN << "Channel not specified but you have data with "
<< num_chan << " channels; defaulting to zero";
} else {
if (this_chan >= num_chan) {
KALDI_WARN << num_chan << " channels but you specified channel "
<< channel << ", producing no output.";
}
}
}
BaseFloat vtln_warp_local; // Work out VTLN warp factor.
//已刪減
vtln_warp_local = vtln_warp;
if (mfcc_opts.frame_opts.samp_freq != wave_data.SampFreq())
KALDI_ERR << "Sample frequency mismatch: you specified "
<< mfcc_opts.frame_opts.samp_freq << " but data has "
<< wave_data.SampFreq() << " (use --sample-frequency "
<< "option).";
SubVector<BaseFloat> waveform(wave_data.Data(), this_chan);
Matrix<BaseFloat> features;
try {
mfcc.Compute(waveform, vtln_warp_local, &features, NULL);
} catch (...) {
KALDI_WARN << "Failed to compute features ";
}
if (subtract_mean) {
Vector<BaseFloat> mean(features.NumCols());
mean.AddRowSumMat(1.0, features);
mean.Scale(1.0 / features.NumRows());
for (int32 i = 0; i < features.NumRows(); i++)
features.Row(i).AddVec(-1.0, mean);
}
cout<<features;//這裡直接輸出了,應該儲存成檔案。
is.close();
return 0;
} catch(const std::exception &e) {
std::cerr << e.what();
return -1;
}
}
進過測試已經可以輸出結果了,但是正如開始所說結果在每列上與源程式有差異,還沒找到原因。
還需要的工作 1)改進,希望做過的同學給些幫助 2) 把delta及cmvn加上。
未完待續……