利用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。后者则用了两个目录mfccexp/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加上。


未完待续……







  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值