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 #要先转换格式
接下来,我们就对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 文件的内容,来组织修改程序的。
)
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
通过那个脚本文件可以知道,真正执行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加上。
未完待续……