1. 程式人生 > >利用kaldi提取mfcc特徵

利用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加上。

未完待續……