1. 程式人生 > >不到一百行實現一個命令詞識別

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

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

本文主要是實現了一個簡單的命令詞識別程式,演算法核心一是提取音訊特徵,二是用DTW演算法進行匹配。當然,這樣的程式碼肯定不能用於商業化,大家做出來玩玩娛樂一下還是不錯的。

轉載請保留本文連結,謝謝。

設計思路

就算是個小東西,我們也要先明確思路再做。音訊識別,困難不小,其中提取特徵的難度在我聽歌識曲那篇文章裡能看得出來。而語音識別難度更大,因為音樂總是固定的,而人類說話常常是變化的。比如說一個“芝麻開門”,有的人就會說成“芝麻~~開門”,有的人會說成“芝麻開門~~”。而且在錄音時說話的時間也不一樣,可能很緊迫的一開始錄音就說話了,也可能不緊不慢的快要錄音結束了才把這四個字說出來。這樣難度就大了。

演算法流程:
image00

特徵提取

和之前的聽歌識曲一樣,同樣是將一秒鐘分成40塊,對每一塊進行傅立葉變換,然後取模長。只是這不像之前聽歌識曲中進一步進行提取峰值,而是直接當做特徵值。
看不懂我在說什麼的朋友可以看看下面的原始碼,或者看聽歌識曲那篇文章。

DTW演算法

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

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

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

那麼我們可以畫出下面這樣的圖

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

歡迎大家提建議

相關推薦

一百實現一個命令識別

想要容易理解核心的特徵計算的話建議先去看看我之前的聽歌識曲的文章,傳送門:http://www.cnblogs.com/chuxiuhong/p/6063602.html 本文主要是實現了一個簡單的命令詞識別程式,演算法核心一是提取音訊特徵,二是用DTW演算法進行匹配。當然,這樣的程式碼肯定不能用於商業化,大

[轉]Kaldi命令識別

文件目錄 入參 arr peak 此外 logs then run tar 轉自: http://www.jianshu.com/p/5b19605792ab?utm_campaign=maleskine&utm_content=note&utm_mediu

用python實現一個命令行文本編輯器

screen alt 保存 模型 既然 ffffff 圖片 單行 pda “這看起來相當愚蠢”——題記   不過我整個人都很荒誕,何妨呢?貼一張目前的效果圖   看起來很舒服,不是麽?即使一切都是個幌子:光標只能在最後,按一下上下左右就會退出,一行超出75個字符

機器學習工程師 - Udacity 專案:實現一個狗品種識別演算法App

在這個notebook中,你將邁出第一步,來開發可以作為移動端或 Web應用程式一部分的演算法。在這個專案的最後,你的程式將能夠把使用者提供的任何一個影象作為輸入。如果可以從影象中檢測到一隻狗,它會輸出對狗品種的預測。如果影象中是一個人臉,它會預測一個與其最相似的狗的種類。下面這張圖展

科大訊飛離線語音命令識別的使用說明

      最近因為專案的需求,需要在無網路的情況下實現語音識別的功能,因為之前線上識別一直用的科大的,所以經理就和我說,你花半天時間簡單熟悉一下,然後出一個Demo,下午有人過來看;因為之前科大線上SR也是別人做的,準確的說我只是瞭解過一點,也寫過相關的blog——百度語音

訊飛語音——離線命令識別

離線命令詞識別 效果圖 示例原始碼 步驟: 1. 下載SDK 2. 整合方法 3. 正題,開始整合 1. 新增許可權 這裡用到的喚醒功能不是所有的許可權都用到的,具體用到了哪些許可權,可以看上面的

shell練習:寫一個腳本實現如下功能:輸入一個數字,然後運對應的一個命令。顯示命令如下:*cmd

else bin echo 數字 bar 一個 功能 ash elif shell練習:寫一個腳本實現如下功能:輸入一個數字,然後運行對應的一個命令。顯示命令如下:*cmd meau** 1--date 2--ls 3--who 4--pwd 當輸入1時,會運行date命

到50程式碼實現一個能對請求併發數做限制的通用RequestDecorator

使用場景 在開發中,我們可能會遇到一些對非同步請求數做併發量限制的場景,比如說微信小程式的request併發最多為5個,又或者我們需要做一些批量處理的工作,可是我們又不想同時對伺服器發出太多請求(可能會對伺服器造成比較大的壓力)。這個時候我們就可以對請求併發數進行限制,並且使用排隊機制讓請求有序的傳送出去。

到100程式碼實現一個簡單的推薦系統

一個好的推薦系統推薦的精度必然很高,能夠真的發現使用者的潛在需求或喜好,提高購物網詀的銷量,讓視訊網站發現使用者喜歡的收費電影… 可是要實現一個高精度的推薦系統不是那麼容易的,netflix曾經懸賞高額獎金尋找能給其推薦系統的精確度提高10%的人,可見各個公司對推薦系統的

如何使用Docker實現PHP命令程序的CI/CD?

ensure 現在 持續集成 mage 服務器遠程 本地 數據 詳細 提交 本文標簽: Docker PHP命令行程序的CI/CD Codeship 內容要點: - 使用Jet設置環境並在本地運行測試 - 配置Codeship Pro每次新代碼提交時,自動運行測試 - 上一

'mingw32-make' 是內部或外部命令,也是可運的程序 或批處理文件。(的解決方案)

windows 外部命令 搜索 方案 win mage -m 分享 image 問題如上。 解決方案:找到mingw32-make,方法是在計算中搜索 然後將其復制到C:Windows\System32下,需要管理員權限才能復制的情況下直接點繼續。然後就可以了。 

linux怎麽用一個命令統計出給定目錄中有多少個子目錄

linux怎麽用一個命令行統計出給定目錄中有多少個子目錄查看某目錄下文件的個數 ls -l |grep "^-"|wc -l 或 find ./company -type f | wc -l 查看某目錄下文件的個數,包括子目錄裏的。 ls -lR|grep "^-"|wc -l 查看某

Jmeter-無法啟動,'findstr'是內部或外部命令,也是可運的程序

運行 root 內部 system32 重新 變量 es2017 外部命令 oot 今天有一個同事的jmeter無法安裝,於是幫他看了看,報以下錯誤: JAVA的環境變量沒有配置好,於是重新配置了下環境變量後,再啟動,發現還是不好,於是網上查了下, 發現要在電腦的環境變量

用java實現命令接收多個數字,求和之後輸出結果

system 程序流程圖 sta num 思想 pri for循環 含義 自動 1.設計思想 首先要了解從命令行輸入數字的含義,不需要在程序中自己定義。需要定義int類的num和sum。之後利用num=Integer.parseInt(arg);將String型轉化為int

如何實現命令輸入pwd時顯示出ifconfig的效果

命令行 輸入 pwd 方法一、1、使用type ifconfig 查看 2、使用type pwd 查看如果還沒有使用過pwd則顯示如下,表示pwd屬於內部命令,然後輸入enable -n pwd 禁用這個內部命令 如果已經使用過,就會顯示hash,已經緩存過 。 此時就不僅需要禁用內部命令,還需要

用css實現文本切超出限制時顯示省略號(小tips)

max 省略號 over 顯示 寬度 width code wid ellipsis div{ max-width: 500px; text-overflow: ellipsis; overflow: hidden; white-spac

'pip' 是內部或外部命令,也是可運的程序 或批處理文件 — 處理辦法

pytho 通過 request path path環境變量 python ges log ip命令 今天在DOW中使用pip命令安裝 requests庫時,報錯:‘pip‘ 不是內部或外部命令,也不是可運行的程序 或批處理文件。 一般安裝python時就會自動把pip

錯誤:'nasm' 是內部或外部命令,也是可運的程序

6.0 16px msu als 拷貝 letter project back lib 原文轉自 http://blog.csdn.net/alexcrazy/article/details/7183312 1>正在執行自定義生成步驟 1>‘nasm‘ 不

CMD下出現 . 點 是內部或外部命令,也是可運的程序 或批處理文件

light alt 技術分享 class src 命令 png roo div 在cmd下鍵入命令,不識別點 >./bin/mysql -u root -p ‘.‘ 不是內部或外部命令,也不是可運行的程序 或批處理文件。 然後把斜杠變成反斜杠就OK了。 完畢!

'webpack-dev-server' 是內部或外部命令,也是可運的程序

解決 dev bsp blog log 再次 裝包 全局 pac 今天新初始的項目遇到這個問題,記錄如下: 1. 這個錯誤與全局安裝webpack-dev-server無關,不必進行全局安裝 2. 原因可能是:   npm或yarn安裝包(當前項目),安裝中報錯,例如nod