日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

語音信號(hào)處理之(二)基音周期估計(jì)(Pitch Detection)

 LSS133LSS 2014-04-24

語音信號(hào)處理之(二)基音周期估計(jì)(Pitch Detection)

zouxy09@qq.com

http://blog.csdn.net/zouxy09

 

       這學(xué)期有《語音信號(hào)處理》這門課,快考試了,所以也要了解了解相關(guān)的知識(shí)點(diǎn)。呵呵,平時(shí)沒怎么聽課,現(xiàn)在只能抱佛腳了。順便也總結(jié)總結(jié),好讓自己的知識(shí)架構(gòu)清晰點(diǎn),也和大家分享下。下面總結(jié)的是第二個(gè)知識(shí)點(diǎn):基音周期估計(jì)。我們用C++實(shí)現(xiàn)了基于自相關(guān)函數(shù)法的基音周期檢測(cè),并且結(jié)合了OpenCV來顯示語音波形。因?yàn)榛ǖ臅r(shí)間不多,所以可能會(huì)有不少說的不妥的地方,還望大家指正。謝謝。

 

一、概述

1.1、基音與基音周期估計(jì)

       人在發(fā)音時(shí),根據(jù)聲帶是否震動(dòng)可以將語音信號(hào)分為清音跟濁音兩種。濁音又稱有聲語言,攜帶者語言中大部分的能量,濁音在時(shí)域上呈現(xiàn)出明顯的周期性;而清音類似于白噪聲,沒有明顯的周期性。發(fā)濁音時(shí),氣流通過聲門使聲帶產(chǎn)生張弛震蕩式振動(dòng),產(chǎn)生準(zhǔn)周期的激勵(lì)脈沖串。這種聲帶振動(dòng)的頻率稱為基音頻率,相應(yīng)的周期就成為基音周期。

       通常,基音頻率與個(gè)人聲帶的長(zhǎng)短、薄厚、韌性、勁度和發(fā)音習(xí)慣等有關(guān)系,在很大程度上反應(yīng)了個(gè)人的特征。此外,基音頻率還跟隨著人的性別、年齡不同而有所不同。一般來說,男性說話者的基音頻率較低,而女性說話者和小孩的基音頻率相對(duì)較高。

基音周期的估計(jì)稱謂基音檢測(cè),基音檢測(cè)的最終目的是為了找出和聲帶振動(dòng)頻率完全一致或盡可能相吻合的軌跡曲線。

       基因周期作為語音信號(hào)處理中描述激勵(lì)源的重要參數(shù)之一,在語音合成、語音壓縮編碼、語音識(shí)別和說話人確認(rèn)等領(lǐng)域都有著廣泛而重要的問題,尤其對(duì)漢語更是如此。漢語是一種有調(diào)語言,而基因周期的變化稱為聲調(diào),聲調(diào)對(duì)于漢語語音的理解極為重要。因?yàn)樵跐h語的相互交談中,不但要憑借不同的元音、輔音來辨別這些字詞的意義,還需要從不同的聲調(diào)來區(qū)別它,也就是說聲調(diào)具有辨義作用;另外,漢語中存在著多音字現(xiàn)象,同一個(gè)字的不同的語氣或不同的詞義下具有不同的聲調(diào)。因此準(zhǔn)確可靠地進(jìn)行基音檢測(cè)對(duì)漢語語音信號(hào)的處理顯得尤為重要。

 

1.2、基音周期估計(jì)的現(xiàn)有方法

      到目前為止,基音檢測(cè)的方法大致上可以分為三類:

1)時(shí)域估計(jì)法,直接由語音波形來估計(jì)基音周期,常見的有:自相關(guān)法、并行處理法、平均幅度差法、數(shù)據(jù)減少法等;

2)變換法,它是一種將語音信號(hào)變換到頻域或者時(shí)域來估計(jì)基音周期的方法,首先利用同態(tài)分析方法將聲道的影響消除,得到屬于激勵(lì)部分的信息,然后求取基音周期,最常用的就是倒譜法,這種方法的缺點(diǎn)就是算法比較復(fù)雜,但是基音估計(jì)的效果卻很好;

3)混合法,先提取信號(hào)聲道模型參數(shù),然后利用它對(duì)信號(hào)進(jìn)行濾波,得到音源序列,最后再利用自相關(guān)法或者平均幅度差法求得基因音周期。

 

三、基于自相關(guān)的基音周期檢測(cè)

3.1、自相關(guān)函數(shù)

      能量有限的語音信號(hào)x(n)的短時(shí)自相關(guān)函數(shù)定義為:

    

       此公式表示一個(gè)信號(hào)和延遲m點(diǎn)后該信號(hào)本身的相似性。如果信號(hào)x(n)具有周期性,那么它的自相關(guān)函數(shù)也具有周期性,而且周期與信號(hào)x(n)的周期性相同。自相關(guān)函數(shù)提供了一種獲取周期信號(hào)周期的方法。在周期信號(hào)周期的整數(shù)倍上,它的自相關(guān)函數(shù)可以達(dá)到最大值,因此可以不考慮起始時(shí)間,而從自相關(guān)函數(shù)的第一個(gè)最大值的位置估計(jì)出信號(hào)的基音周期,這使自相關(guān)函數(shù)成為信號(hào)基音周期估計(jì)的一種工具。

 

3.2、短時(shí)自相關(guān)函數(shù)法

        語音信號(hào)是非穩(wěn)態(tài)信號(hào)它的特征是隨時(shí)間變化的,但在一個(gè)很短的時(shí)間段內(nèi)可以認(rèn)為具有相對(duì)穩(wěn)定的特征即短時(shí)平穩(wěn)性。因此語音具有短時(shí)自相關(guān)性。這個(gè)時(shí)間段約5ms-50ms。為其統(tǒng)計(jì)特性和頻譜特性都是對(duì)短時(shí)段而言的。這使得要對(duì)語音信號(hào)作數(shù)字處理必須先按短時(shí)段對(duì)語音信號(hào)分幀。這樣每一幀信號(hào)都具有短時(shí)平穩(wěn)性從而進(jìn)行短時(shí)相關(guān)分析。

      能量有限的語音信號(hào)s(n)的短時(shí)自相關(guān)函數(shù)定義為:

       一般要求一幀至少包含2個(gè)以上的周期。一般,基頻最低50Hz,故周期最長(zhǎng)為20ms。而且相鄰幀之間要有足夠的重疊。具體應(yīng)用時(shí),窗口長(zhǎng)度根據(jù)采樣率確定幀長(zhǎng)。

       該幀的自相關(guān)函數(shù)中,除去第一個(gè)最大值后(0處),最大值Kmax= 114,那么該幀對(duì)應(yīng)的基頻16kHz/114=140Hz。

 

四、基于自相關(guān)的基音周期檢測(cè)算法實(shí)現(xiàn)

       這個(gè)實(shí)現(xiàn)課程要求是用C++來實(shí)現(xiàn)的。然后為了畫波形,我用到了我比較熟悉的OpenCV。OpenCV畫出來的波形還是不錯(cuò)的,而且如果是動(dòng)態(tài)的波形平移,挺好看的,就像心電圖那么動(dòng)人。

      實(shí)驗(yàn)采用一段男聲讀“播放”兩個(gè)字的聲音wav文件,其為16KHz采樣率,16bit量化。整段語音長(zhǎng)656.7ms,節(jié)點(diǎn)共10508個(gè)。  

      我們先要確定幀長(zhǎng)。下面分別是幀長(zhǎng)200,320和400個(gè)節(jié)點(diǎn)時(shí)所包含的周期數(shù)。200時(shí)只有一個(gè)周期,而400有三個(gè)周期,所以我們采用400的幀長(zhǎng)。

     通過計(jì)算短時(shí)能量區(qū)分voice和unvoice。語音信號(hào){x(n)}的某幀信號(hào)的短時(shí)平均能量En的定義為:

       語音中濁音段的短時(shí)平均能量遠(yuǎn)遠(yuǎn)大于清音段的短時(shí)平均能量。因此,短時(shí)平均能量的計(jì)算給出了區(qū)分清音段與濁音段的依據(jù),即En(濁)>En(清)。

      計(jì)算每一幀的過程中,會(huì)顯示在原來波形中的位置,并且實(shí)時(shí)顯示該幀得到的基音周期。另外還會(huì)在另一個(gè)窗口實(shí)時(shí)顯示該幀的原始波形。

      該幀的原始波形圖(以下為不同時(shí)間的兩幀,會(huì)動(dòng)態(tài)變化):

       下面左邊的圖是計(jì)算該語音的所有幀對(duì)應(yīng)的基音周期的點(diǎn),由圖可以看出存在不少的野點(diǎn)。因?yàn)?,需要?duì)此進(jìn)行進(jìn)一步的處理,即去除野點(diǎn)。這里通過中值濾波來除去野點(diǎn),濾波結(jié)果見右圖。

C++程序如下:(每按一次空格進(jìn)入下一個(gè)步驟)

  1. // Description : Pitch detection  
  2. // Author      : Zou Xiaoyi  
  3. // HomePage    : http://blog.csdn.net/zouxy09  
  4. // Date        : 2013/06/08  
  5. // Rev.        : 0.1  
  6.   
  7. #include <iostream>  
  8. #include <fstream>  
  9. #include "opencv2/opencv.hpp"  
  10. #include "ReadWriteWav.h"  
  11. #include <string>  
  12.   
  13. using namespace std;  
  14. using namespace cv;  
  15.   
  16. #define MAXLENGTH 1000  
  17.   
  18. void wav2image(Mat &img, vector<short> wavData, int wav_start, int width, int max_amplitude)  
  19. {  
  20.      short max(0), min(0);  
  21.      for (int i = 0; i < wavData.size(); i++)  
  22.      {  
  23.           if (wavData[i] > max)  
  24.                 max = wavData[i];  
  25.           if (wavData[i] < min)  
  26.                 min = wavData[i];  
  27.      }  
  28.      cout<<max<<'\t'<<min<<endl;  
  29.   
  30.      max_amplitude = max_amplitude > 480 ? 480 : max_amplitude;  
  31.   
  32.      // normalize  
  33.      for (int i = 0; i < wavData.size(); i++)  
  34.      {  
  35.          wavData[i] = (wavData[i] - min) * max_amplitude / (max - min);  
  36.      }  
  37.   
  38.      int j = 0;  
  39.      Point prePoint, curPoint;  
  40.      if (width >= 400)  
  41.      {  
  42.          img.create(max_amplitude, width, CV_8UC3);  
  43.          img.setTo(Scalar(0, 0, 0));  
  44.          for (int i = wav_start; i < wav_start + width; i++)  
  45.          {  
  46.               prePoint = Point(j, img.rows - (int)wavData[i]);  
  47.               if (j)  
  48.                     line(img, prePoint, curPoint, Scalar(0, 255, 0), 2);  
  49.               curPoint = prePoint;  
  50.               j++;  
  51.          }   
  52.   
  53.          if (width > MAXLENGTH)  
  54.          {  
  55.              cout<<"The wav is too long to show, and it will be resized to 1200"<<endl;  
  56.              resize(img, img, Size(MAXLENGTH, img.rows));  
  57.          }  
  58.      }  
  59.      else  
  60.      {  
  61.          img.create(max_amplitude, 400, CV_8UC3);  
  62.          img.setTo(Scalar(0, 0, 0));  
  63.          for (int i = wav_start; i < wav_start + width; i++)  
  64.          {  
  65.              prePoint = Point(j*400/width, img.rows - (int)wavData[i]);  
  66.              circle(img, prePoint, 3, Scalar(0, 0, 255), CV_FILLED);  
  67.              j++;  
  68.          }  
  69.          cout<<"The wav is too small to show, and it will be resized to 400"<<endl;  
  70.      }  
  71. }  
  72.   
  73. short calOneFrameACF(vector<short> wavFrame, int sampleRate)  
  74. {  
  75.     vector<float> acf;  
  76.     acf.empty();  
  77.   
  78.     // calculate ACF  
  79.     for (int k = 0; k < wavFrame.size(); k++)  
  80.     {  
  81.         float sum = 0.0;  
  82.         for (int i = 0; i < wavFrame.size() - k; i++)  
  83.         {  
  84.             sum = sum + wavFrame[i] * wavFrame[ i + k ];  
  85.         }  
  86.         acf.push_back(sum);  
  87.     }  
  88.   
  89.     // find the max one  
  90.     float max(-999);  
  91.     int index = 0;  
  92.     for (int k = 0; k < wavFrame.size(); k++)  
  93.     {  
  94.         if (k > 25 && acf[k] > max)  
  95.         {  
  96.             max = acf[k];  
  97.             index = k;  
  98.         }  
  99.     }  
  100.     return (short)sampleRate / index;  
  101. }  
  102.   
  103. int main()  
  104. {  
  105.     const char *wavFile = "bofang.wav";      
  106.     vector<short> data;  
  107.     int nodesPerFrame = 400;  
  108.   
  109.   
  110.     /************* Write data to file part Start ***************/  
  111.     fstream writeFile;  
  112.     writeFile.open("statistics.txt", ios::out);  
  113.     /************* Write data to file part End ***************/  
  114.   
  115.   
  116.     /************* Read and show the input wave part Start ***************/  
  117.     int sampleRate;  
  118.     int dataLength = wav2allsample(wavFile, data, sampleRate);  
  119.     if (!dataLength)  
  120.     {  
  121.         cout <<"Reading wav file error!"<<endl;  
  122.         return -1;  
  123.     }  
  124.     Mat originalWave;  
  125.     wav2image(originalWave, data, 0, dataLength, 400);  
  126.     line(originalWave, Point(0, originalWave.rows * 0.5), Point(originalWave.cols, originalWave.rows * 0.5), Scalar(0, 0, 255), 2);  
  127.     imshow("originalWave", originalWave);  
  128.   
  129.     // write data  
  130.     writeFile<<"Filename: "<<wavFile<<endl<<"SampleRate: "<<sampleRate<<"Hz"<<endl<<"dataLength: "<<dataLength<<endl;  
  131.   
  132.     cout<<"Press space key to continue"<<endl;  
  133.     while (waitKey(30) != ' ');  
  134.     /************* Read and show the input wave part End ***************/  
  135.   
  136.   
  137.     /******** Calculate energy to separate voice and unvoice part Start *********/  
  138.     int nodeCount = 0;  
  139.   
  140.     // The sum must be double type  
  141.     vector<double> energyTmp;  
  142.     double maxEnergy(0);  
  143.     while(nodeCount < (dataLength - nodesPerFrame))  
  144.     {  
  145.         double sum(0);  
  146.         for (int i = nodeCount; i < (nodeCount + nodesPerFrame); i++)  
  147.         {  
  148.             sum += (double)data[i] * data[i];  
  149.         }  
  150.         if (sum > maxEnergy)  
  151.         {  
  152.             maxEnergy = sum;  
  153.         }  
  154.         energyTmp.push_back(sum);  
  155.         nodeCount++;  
  156.     }  
  157.   
  158.     // Transform to short type for show  
  159.     vector<short> energy;  
  160.   
  161.     // Fill element of boundary  
  162.     short tmp = (short)(energyTmp[0] * 400 / maxEnergy);  
  163.     for (int i = 0; i < nodesPerFrame * 0.5; i++)  
  164.     {  
  165.         energy.push_back(tmp);  
  166.     }  
  167.     for (int i = 0; i < energyTmp.size(); i++)  
  168.     {  
  169.         energy.push_back((short)(energyTmp[i] * 400 / maxEnergy));  
  170.     }  
  171.     // Fill element of boundary  
  172.     tmp = (short)(energyTmp[energyTmp.size() - 1] * 400 / maxEnergy);  
  173.     for (int i = 0; i < nodesPerFrame * 0.5; i++)  
  174.     {  
  175.         energy.push_back(tmp);  
  176.     }  
  177.   
  178.     // show  
  179.     Mat showEnergy;  
  180.     wav2image(showEnergy, energy, 0, energy.size(), 400);  
  181.     line(showEnergy, Point(0, showEnergy.rows - 1), Point(showEnergy.cols, showEnergy.rows - 1), Scalar(0, 0, 255), 2);  
  182.     imshow("showEnergy", showEnergy);  
  183.     while (waitKey(30) != ' ');  
  184.   
  185.     // separate voice and unvoice  
  186.     float thresVoice = 400 * 0.15;  
  187.     line(showEnergy, Point(0, showEnergy.rows - thresVoice), Point(showEnergy.cols, showEnergy.rows - thresVoice), Scalar(0, 255, 255), 2);  
  188.     imshow("showEnergy", showEnergy);  
  189.     while (waitKey(30) != ' ');  
  190.   
  191.     // Find the Transition point and draw them  
  192.     bool high = false;  
  193.     vector<int> separateNode;  
  194.     for (int i = 0; i < energy.size(); i++)  
  195.     {  
  196.         if ( !high && energy[i] > thresVoice)  
  197.         {  
  198.             separateNode.push_back(i);  
  199.             high = true;  
  200.             writeFile<<"UnVoice to Voice: "<<i<<endl;  
  201.             line(showEnergy, Point(i * MAXLENGTH / dataLength, 0), Point(i * MAXLENGTH / dataLength, showEnergy.rows), Scalar(255, 255, 255), 2);  
  202.             putText(showEnergy, "Voice", Point(i * MAXLENGTH / dataLength, showEnergy.rows * 0.5 + 40), FONT_HERSHEY_SIMPLEX, 1, Scalar(255, 255, 255), 2);  
  203.             imshow("showEnergy", showEnergy);  
  204.             while (waitKey(30) != ' ');  
  205.         }  
  206.         if ( high && energy[i] < thresVoice)  
  207.         {  
  208.             separateNode.push_back(i);  
  209.             high = false;  
  210.             writeFile<<"Voice to UnVoice: "<<i<<endl;  
  211.             line(showEnergy, Point(i * MAXLENGTH / dataLength, 0), Point(i * MAXLENGTH / dataLength, showEnergy.rows), Scalar(255, 0, 0), 2);  
  212.             putText(showEnergy, "UnVoice", Point(i * MAXLENGTH / dataLength, showEnergy.rows * 0.5 + 40), FONT_HERSHEY_SIMPLEX, 1, Scalar(255, 0, 0), 2);  
  213.             imshow("showEnergy", showEnergy);  
  214.             while (waitKey(30) != ' ');  
  215.         }  
  216.     }  
  217.     /******** Calculate energy to separate voice and unvoice part End ***********/  
  218.   
  219.   
  220.     /******************* Calculate all frame part Start ***************/  
  221.     int frames = 0;  
  222.     vector<short> allPitchFre;  
  223.     writeFile<<"The pitch frequency is:"<<endl;  
  224.     while(frames < 2 * dataLength / nodesPerFrame)  
  225.     {  
  226.         vector<short> wavFrame;  
  227.         wavFrame.empty();  
  228.   
  229.         // get one frame, 400 nodes per frame, and shift 200 nodes, or overlap 200 nodes  
  230.         int start = frames * nodesPerFrame * 0.5;  
  231.         for (int i = start; i < start + nodesPerFrame; i++)  
  232.             wavFrame.push_back(data[i]);  
  233.   
  234.         // calculate the ACF of this frame  
  235.         float pitchFreqency = calOneFrameACF(wavFrame, sampleRate);  
  236.         allPitchFre.push_back(pitchFreqency);  
  237.   
  238.         cout<<"The pitch frequency is: "<<pitchFreqency <<" Hz"<<endl;  
  239.         writeFile<<pitchFreqency<<endl;  
  240.   
  241.         // show current frame in the whole wave  
  242.         Mat originalWave;  
  243.         wav2image(originalWave, data, 0, dataLength, 400);  
  244.         line(originalWave, Point(0, originalWave.rows * 0.5), Point(originalWave.cols, originalWave.rows * 0.5), Scalar(0, 0, 255), 2);  
  245.         line(originalWave, Point(start * MAXLENGTH / dataLength, 0), Point(start * MAXLENGTH / dataLength, originalWave.rows), Scalar(0, 0, 255), 2);  
  246.         line(originalWave, Point((start + nodesPerFrame)* MAXLENGTH / dataLength, 0), Point((start + nodesPerFrame)* MAXLENGTH / dataLength, originalWave.rows), Scalar(0, 0, 255), 2);  
  247.           
  248.         // put the pitchFreqency of this frame in the whole wave  
  249.         stringstream buf;  
  250.         buf << pitchFreqency;  
  251.         string num = buf.str();  
  252.         putText(originalWave, num, Point(start * MAXLENGTH / dataLength, 30), FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 255), 2);  
  253.         imshow("originalWave", originalWave);  
  254.   
  255.         // show current frame in zoom out model  
  256.         Mat oneSelectFrame;  
  257.         wav2image(oneSelectFrame, wavFrame, 0, wavFrame.size(), 400);  
  258.         imshow("oneSelectFrame", oneSelectFrame);  
  259.   
  260.         if (!frames)  
  261.             while (waitKey(30) != ' ');  
  262.   
  263.         frames++;  
  264.         waitKey(50);  
  265.     }  
  266.     cout<<"Num of frames is: "<<frames<<endl;  
  267.     /******************* Calculate all frame part End ***************/  
  268.   
  269.   
  270.     // show all pitch frequency before smooth  
  271.     Mat showAllPitchFre;  
  272.     wav2image(showAllPitchFre, allPitchFre, 0, allPitchFre.size(), 400);  
  273.     putText(showAllPitchFre, "Before smooth", Point(10, showAllPitchFre.rows - 20), FONT_HERSHEY_SIMPLEX, 1, Scalar(60, 200, 255), 1);  
  274.     imshow("showAllPitchFre", showAllPitchFre);  
  275.   
  276.   
  277.     /******************* Smooth by medium filter part Start **************/  
  278.     int kernelSize = 5;  
  279.     vector<short> afterMedFilter;  
  280.     short sum(0);  
  281.     afterMedFilter.assign(allPitchFre.size(), allPitchFre[0]);  
  282.   
  283.     for (int k = cvFloor(kernelSize/2); k < allPitchFre.size(); k++)  
  284.     {  
  285.         vector<short> kernelData;  
  286.         for (int i = -cvFloor(kernelSize/2); i < cvCeil (kernelSize/2); i++)  
  287.             kernelData.push_back(allPitchFre[k+i]);  
  288.         nth_element(kernelData.begin(), kernelData.begin() + cvCeil (kernelSize/2), kernelData.end());  
  289.         afterMedFilter[k] = kernelData[cvCeil (kernelSize/2)];  
  290.         sum += afterMedFilter[k];  
  291.         cout<<afterMedFilter[k]<<endl;  
  292.     }  
  293.       
  294.     // show all pitch frequency and mean pitch frequency after smooth  
  295.     Mat showAfterMedFilter;  
  296.     wav2image(showAfterMedFilter, afterMedFilter, 0, afterMedFilter.size(), 400);  
  297.     putText(showAfterMedFilter, "After smooth", Point(10, showAfterMedFilter.rows - 20), FONT_HERSHEY_SIMPLEX, 1, Scalar(60, 200, 255), 1);  
  298.       
  299.     short mean = sum / (afterMedFilter.size() - cvFloor(kernelSize/2));  
  300.     writeFile<<"The mean pitch frequency is: "<<mean<<endl;  
  301.     stringstream buf;  
  302.     buf << mean;  
  303.     string num = "Mean: " + buf.str() + "Hz";  
  304.     putText(showAfterMedFilter, num, Point(10, 40), FONT_HERSHEY_SIMPLEX, 1, Scalar(255, 200, 255), 2);  
  305.     imshow("showAfterMedFilter", showAfterMedFilter);  
  306.     /******************* Smooth by medium filter part End ***************/  
  307.   
  308.     while (waitKey(30) != 27);  
  309.   
  310.     return 0;  
  311. }  

 

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多