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
#include //文件操作
#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...] \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.
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 waveform(wave_data.Data(), this_chan);
Matrix features;
try {
mfcc.Compute(waveform, vtln_warp_local, &features, NULL);
} catch (...) {
KALDI_WARN << "Failed to compute features ";
}
if (subtract_mean) {
Vector 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<
进过测试已经可以输出结果了,但是正如开始所说结果在每列上与源程序有差异,还没找到原因。
还需要的工作 1)改进,希望做过的同学给些帮助 2) 把delta及cmvn加上。
未完待续……