python利用不到一百行代碼實(shí)現(xiàn)一個(gè)小siri
前言
如果想要容易理解核心的特征計(jì)算的話建議先去看看我之前的聽歌識(shí)曲的文章,傳送門:http://www.dbjr.com.cn/article/97305.htm
本文主要是實(shí)現(xiàn)了一個(gè)簡單的命令詞識(shí)別程序,算法核心一是提取音頻特征,二是用DTW算法進(jìn)行匹配。當(dāng)然,這樣的代碼肯定不能用于商業(yè)化,大家做出來玩玩娛樂一下還是不錯(cuò)的。
設(shè)計(jì)思路
就算是個(gè)小東西,我們也要先明確思路再做。音頻識(shí)別,困難不小,其中提取特征的難度在我聽歌識(shí)曲那篇文章里能看得出來。而語音識(shí)別難度更大,因?yàn)橐魳房偸枪潭ǖ?,而人類說話常常是變化的。比如說一個(gè)“芝麻開門”,有的人就會(huì)說成“芝麻開門”,有的人會(huì)說成“芝麻開門”。而且在錄音時(shí)說話的時(shí)間也不一樣,可能很緊迫的一開始錄音就說話了,也可能不緊不慢的快要錄音結(jié)束了才把這四個(gè)字說出來。這樣難度就大了。
算法流程:
特征提取
和之前的聽歌識(shí)曲一樣,同樣是將一秒鐘分成40塊,對(duì)每一塊進(jìn)行傅里葉變換,然后取模長。只是這不像之前聽歌識(shí)曲中進(jìn)一步進(jìn)行提取峰值,而是直接當(dāng)做特征值。
看不懂我在說什么的朋友可以看看下面的源代碼,或者看聽歌識(shí)曲那篇文章。
DTW算法
DTW,Dynamic Time Warping,動(dòng)態(tài)時(shí)間歸整。算法解決的問題是將不同發(fā)音長短和位置進(jìn)行最適合的匹配。
算法輸入兩組音頻的特征向量: A:[fp1,fp2,fp3,......,fpM1] B:[fp1,fp2,fp3,fp4,.....fpM2]
A組共有M1個(gè)特征,B組共有M2個(gè)音頻。每個(gè)特征向量中的元素就是之前我們將每秒切成40塊之后FFT求模長的向量。計(jì)算每對(duì)fp之間的代價(jià)采用的是歐氏距離。
設(shè)D(fpa,fpb)為兩個(gè)特征的距離代價(jià)。
那么我們可以畫出下面這樣的圖
我們需要從(1,1)點(diǎn)走到(M1,M2)點(diǎn),這會(huì)有很多種走法,而每種走法就是一種兩個(gè)音頻位置匹配的方式。但我們的目標(biāo)是走的總過程中代價(jià)最小,這樣可以保證這種對(duì)齊方式是使我們得到最接近的對(duì)齊方式。
我們這樣走:首先兩個(gè)坐標(biāo)軸上的各個(gè)點(diǎn)都是可以直接計(jì)算累加代價(jià)和求出的。然后對(duì)于中間的點(diǎn)來說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)這個(gè)點(diǎn)需要加上兩倍的代價(jià)呢?因?yàn)閯e人走正方形的兩個(gè)直角邊,它走的是正方形的對(duì)角線啊
按照這個(gè)原理選擇,一直算到D(M1,M2),這就是兩個(gè)音頻的距離。
源代碼和注釋
# 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) #計(jì)算兩個(gè)特征之間的歐氏距離 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 #存儲(chǔ)歌曲原始數(shù)組 f.close() self.name = os.path.basename(filepath) # 記錄下文件名 return True except: raise IOError, 'File Error' def fft(self, frames=40): self.fft_blocks = [] #將音頻每秒分成40塊,再對(duì)每塊做傅里葉變換 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')#瀏覽器的路徑,之前已經(jīng)被添加到了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方法錄制幾段命令詞,嘗試用不同語氣說,不同節(jié)奏說,這樣可以提高準(zhǔn)確度。然后設(shè)計(jì)好文件名,根據(jù)匹配到的最接近音頻的文件名就可以知道是哪種命令,進(jìn)而自定義執(zhí)行不同的任務(wù)
這是一段演示視頻:http://www.iqiyi.com/w_19ruisynsd.html
總結(jié)
以上就是這篇文章的全部內(nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者使用python能帶來一定的幫助,如果有疑問大家可以留言交流。
相關(guān)文章
django 實(shí)現(xiàn)后臺(tái)從富文本提取純文本
這篇文章主要介紹了django 實(shí)現(xiàn)后臺(tái)從富文本提取純文本,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07使用Python實(shí)現(xiàn)優(yōu)雅生成假數(shù)據(jù)
Faker是一個(gè)Python包,開源的GITHUB項(xiàng)目,主要用來創(chuàng)建偽數(shù)據(jù),這篇文章主要為大家詳細(xì)介紹了Python如何使用Faker生成假數(shù)據(jù),感興趣的小伙伴可以了解下2023-12-12解決django無法訪問本地static文件(js,css,img)網(wǎng)頁里js,cs都加載不了
這篇文章主要介紹了解決django無法訪問本地static文件(js,css,img)網(wǎng)頁里js,cs都加載不了的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04python實(shí)現(xiàn)復(fù)制整個(gè)目錄的方法
這篇文章主要介紹了python實(shí)現(xiàn)復(fù)制整個(gè)目錄的方法,涉及Python中shutil模塊的相關(guān)操作技巧,需要的朋友可以參考下2015-05-05Pytorch GPU顯存充足卻顯示out of memory的解決方式
今天小編就為大家分享一篇Pytorch GPU顯存充足卻顯示out of memory的解決方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-01-01Python 可視化matplotlib模塊基礎(chǔ)知識(shí)
這篇文章主要給大家分享的是Python 可視化matplotlib模塊基礎(chǔ)知識(shí),文章對(duì)matplotlib.pyplot 模塊繪制相關(guān)如折線、柱狀、散點(diǎn)、圓餅圖表進(jìn)行簡單地學(xué)習(xí),具有一定的參考價(jià)值,需要的朋友可以參考一下2021-12-12