不到一百行實現一個命令詞識別

想要容易理解核心的特徵計算的話建議先去看看我以前的聽歌識曲的文章,傳送門:http://www.cnblogs.com/chuxiuhong/p/6063602.htmlhtml

本文主要是實現了一個簡單的命令詞識別程序,算法核心一是提取音頻特徵,二是用DTW算法進行匹配。固然,這樣的代碼確定不能用於商業化,你們作出來玩玩娛樂一下仍是不錯的。python

轉載請保留本文連接,謝謝。算法


設計思路

就算是個小東西,咱們也要先明確思路再作。音頻識別,困難不小,其中提取特徵的難度在我聽歌識曲那篇文章裏能看得出來。而語音識別難度更大,由於音樂老是固定的,而人類說話經常是變化的。好比說一個「芝麻開門」,有的人就會說成「芝麻~~開門」,有的人會說成「芝麻開門~~」。並且在錄音時說話的時間也不同,可能很緊迫的一開始錄音就說話了,也可能不緊不慢的快要錄音結束了才把這四個字說出來。這樣難度就大了。chrome

算法流程:
image00數組


特徵提取

和以前的聽歌識曲同樣,一樣是將一秒鐘分紅40塊,對每一塊進行傅里葉變換,而後取模長。只是這不像以前聽歌識曲中進一步進行提取峯值,而是直接當作特徵值。
看不懂我在說什麼的朋友能夠看看下面的源代碼,或者看聽歌識曲那篇文章。瀏覽器


DTW算法

DTW,Dynamic Time Warping,動態時間歸整。算法解決的問題是將不一樣發音長短和位置進行最適合的匹配。app

算法輸入兩組音頻的特徵向量: A:[fp1,fp2,fp3,......,fpM1] B:[fp1,fp2,fp3,fp4,.....fpM2]
A組共有M1個特徵,B組共有M2個音頻。每一個特徵向量中的元素就是以前咱們將每秒切成40塊以後FFT求模長的向量。計算每對fp之間的代價採用的是歐氏距離。ui

設D(fpa,fpb)爲兩個特徵的距離代價。設計

那麼咱們能夠畫出下面這樣的圖code

image01

咱們須要從(1,1)點走到(M1,M2)點,這會有不少種走法,而每種走法就是一種兩個音頻位置匹配的方式。但咱們的目標是走的總過程當中代價最小,這樣能夠保證這種對齊方式是使咱們獲得最接近的對齊方式。

咱們這樣走:首先兩個座標軸上的各個點都是能夠直接計算累加代價和求出的。而後對於中間的點來講D(i,j) = Min{D(i-1,j)+D(fpi,fpj) , D(i,j-1)+D(fpi,fpj) , D(i-1,j-1) + 2 * D(fpi,fpj)}
爲何由(i-1,j-1)直接走到(i,j)這個點須要加上兩倍的代價呢?由於別人走正方形的兩個直角邊,它走的是正方形的對角線啊

按照這個原理選擇,一直算到D(M1,M2),這就是兩個音頻的距離。

image01

image01

image01


源代碼和註釋

# coding=utf8
import os
import wave
import dtw
import numpy as np
import pyaudio

def compute_distance_vec(vec1, vec2):
    return np.linalg.norm(vec1 - vec2) #計算兩個特徵之間的歐氏距離

class record():
    def record(self, CHUNK=44100, FORMAT=pyaudio.paInt16, CHANNELS=2, RATE=44100, RECORD_SECONDS=200,
               WAVE_OUTPUT_FILENAME="record.wav"):
        #錄歌方法
        p = pyaudio.PyAudio()
        stream = p.open(format=FORMAT,
                        channels=CHANNELS,
                        rate=RATE,
                        input=True,
                        frames_per_buffer=CHUNK)
        frames = []
        for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
            data = stream.read(CHUNK)
            frames.append(data)
        stream.stop_stream()
        stream.close()
        p.terminate()
        wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
        wf.setnchannels(CHANNELS)
        wf.setsampwidth(p.get_sample_size(FORMAT))
        wf.setframerate(RATE)
        wf.writeframes(''.join(frames))
        wf.close()

class voice():
    def loaddata(self, filepath):
        try:
            f = wave.open(filepath, 'rb')
            params = f.getparams()
            self.nchannels, self.sampwidth, self.framerate, self.nframes = params[:4]
            str_data = f.readframes(self.nframes)
            self.wave_data = np.fromstring(str_data, dtype=np.short)
            self.wave_data.shape = -1, self.sampwidth
            self.wave_data = self.wave_data.T #存儲歌曲原始數組
            f.close()
            self.name = os.path.basename(filepath)  # 記錄下文件名
            return True
        except:
            raise IOError, 'File Error'

    def fft(self, frames=40):
        self.fft_blocks = [] #將音頻每秒分紅40塊,再對每塊作傅里葉變換
        blocks_size = self.framerate / frames
        for i in xrange(0, len(self.wave_data[0]) - blocks_size, blocks_size):
            self.fft_blocks.append(np.abs(np.fft.fft(self.wave_data[0][i:i + blocks_size])))
    @staticmethod
    def play(filepath):
        chunk = 1024
        wf = wave.open(filepath, 'rb')
        p = pyaudio.PyAudio()
        # 播放音樂方法
        stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                        channels=wf.getnchannels(),
                        rate=wf.getframerate(),
                        output=True)
        while True:
            data = wf.readframes(chunk)
            if data == "": break
            stream.write(data)
        stream.close()
        p.terminate()
if __name__ == '__main__':
    r = record()
    r.record(RECORD_SECONDS=3, WAVE_OUTPUT_FILENAME='record.wav')
    v = voice()
    v.loaddata('record.wav')
    v.fft()
    file_list = os.listdir(os.getcwd())
    res = []
    for i in file_list:
        if i.split('.')[1] == 'wav' and i.split('.')[0] != 'record':
            temp = voice()
            temp.loaddata(i)
            temp.fft()
            res.append((dtw.dtw(v.fft_blocks, temp.fft_blocks, compute_distance_vec)[0],i))
    res.sort()
    print res
    if res[0][1].find('open_qq') != -1:
        os.system('C:\program\Tencent\QQ\Bin\QQScLauncher.exe') #個人QQ路徑
    elif res[0][1].find('zhimakaimen') != -1:
        os.system('chrome.exe')#瀏覽器的路徑,以前已經被添加到了Path中了
    elif res[0][1].find('play_music') != -1:
        voice.play('C:\data\music\\audio\\audio\\ (9).wav') #播放一段音樂
    # r = record()
    # r.record(RECORD_SECONDS=3,WAVE_OUTPUT_FILENAME='zhimakaimen_09.wav')

事先能夠先用這裏的record方法錄製幾段命令詞,嘗試用不一樣語氣說,不一樣節奏說,這樣能夠提升準確度。而後設計好文件名,根據匹配到的最接近音頻的文件名就能夠知道是哪一種命令,進而自定義執行不一樣的任務

下面是一段演示視頻:
http://www.iqiyi.com/w_19ruisynsd.html

歡迎你們提建議

相關文章
相關標籤/搜索