python數(shù)據(jù)建模分析 - 語音識(shí)別

語音識(shí)別:

Getting Started!首先,我們要知道語音的產(chǎn)生過程

voice.png

狀態(tài):由肺產(chǎn)生向外的氣流,完全放松時(shí)聲帶張開,就是平時(shí)的呼吸。如果聲帶一張一合(振動(dòng))形成周期性的脈沖氣流。這個(gè)脈沖氣流的周期稱之為——基音周期(題主所言因音色不同導(dǎo)致的頻率不同,事實(shí)上音色的大多是泛頻上的差異,建立在基頻之上,這個(gè)基頻就是基音周期了,泛頻可以忽略)。當(dāng)然啦,這只是在發(fā)濁音(b,d,v...)時(shí)才會(huì)有,當(dāng)發(fā)出清音(p,t,f...)時(shí)聲帶不振動(dòng),但是會(huì)處于緊繃狀態(tài),當(dāng)氣流涌出時(shí)會(huì)在聲帶產(chǎn)生湍流。清音和濁音是音素的兩大類。接下來脈沖氣流/湍流到達(dá)聲道,由聲道對氣流進(jìn)行調(diào)制,形成不同的音素。多個(gè)音素組成一個(gè)音節(jié)(就漢語而言是[聲母]+韻母)。如果沒學(xué)過信號(hào)系統(tǒng)那就想像一下平舌音和翹舌音,z和zh發(fā)聲時(shí)肺和喉的狀態(tài)都一樣,只是舌頭動(dòng)作不一樣,發(fā)出的聲音也就不一樣了,這就算是簡單的調(diào)制。從而聲音的波形會(huì)發(fā)生一些變化。這個(gè)波形,就是以后分析所需要的數(shù)據(jù)。

標(biāo)識(shí)聲音的圖像有以下三種

  • 頻譜圖
  • 時(shí)譜圖
  • 語譜圖

以千里碼 語音識(shí)別-1為例,將Mp3文件轉(zhuǎn)換成wav,分析其頻譜圖
參照wav使用手冊,讓我們介紹一下wav文件

WAV是Microsoft開發(fā)的一種聲音文件格式,雖然它支持多種壓縮格式,不過它通常被用來保存未壓縮的聲音數(shù)據(jù)(PCM脈沖編碼調(diào)制)。WAV有三個(gè)重要的參數(shù):聲道數(shù)、取樣頻率和量化位數(shù)。

  • 聲道數(shù):可以是單聲道或者是雙聲道
  • 采樣頻率:一秒內(nèi)對聲音信號(hào)的采集次數(shù),常用的有8kHz, 16kHz, 32kHz, 48kHz, 11.025kHz, 22.05kHz, 44.1kHz
  • 量化位數(shù):用多少bit表達(dá)一次采樣所采集的數(shù)據(jù),通常有8bit、16bit、24bit和32bit等幾種

1.讀入二進(jìn)制音頻流數(shù)據(jù)流程

  • 對于一個(gè)音頻實(shí)例wf而言,通過調(diào)用它的方法讀取WAV文件的格式和數(shù)據(jù):getnchannels, getsampwidth, getframerate, getnframes等方法可以單獨(dú)返回WAV文件的特定的信息。
  • readframes:讀取聲音數(shù)據(jù),傳遞一個(gè)參數(shù)指定需要讀取的長度(以取樣點(diǎn)為單位),readframes返回的是二進(jìn)制數(shù)據(jù)

PS:注意需要使用"rb"(二進(jìn)制模式)打開文件

import wave
import pyaudio
import numpy
from matplotlib import pylab


#打開wav文檔,文件路徑根據(jù)需要修改
wf = wave.open("F:\\work\\war.wav","rb")

#創(chuàng)建PyAudio對象
p = pyaudio.PyAudio()


class Audio(object):

    def __init__(self):
        self.channels = wf.getnchannels()
        # 返回音頻通道數(shù)
        self.rate = wf.getframerate()
        # 返回采樣頻率。
        self.format = p.get_format_from_width(wf.getsampwidth())
        # 返回指定寬度的PortAudio格式常量。
        self.stream = p.open(format=self.format,channels=self.channels,rate=self.rate,output=True)
        # 使用所需的音頻參數(shù)在所需設(shè)備上打開一個(gè)流
        self.nframes = wf.getnframes()
        # 返回音頻幀數(shù)
        self.collect_point_num = 44100
        # 采樣點(diǎn)數(shù),修改采樣點(diǎn)數(shù)和起始位置進(jìn)行不同位置和長度的音頻波形分析
        self.start = 0
        # 開始采樣位置
    def read_data(self):
        self.str_data = wf.readframes(self.nframes)
        # 讀取聲音數(shù)據(jù),傳遞一個(gè)參數(shù)指定需要讀取的長度(以取樣點(diǎn)為單位),readframes返回的是二進(jìn)制數(shù)據(jù)(即bytes數(shù)組)
        # print(self.str_data)
        wf.close()
        #關(guān)閉媒體流
if __name__ == '__main__':
    aduio = Audio()
    aduio.read_data()

python output

aduio.jpg

2.生成的流媒體字節(jié)數(shù)組計(jì)算出每個(gè)取樣的時(shí)間

  • 將讀取的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為一個(gè)可以計(jì)算的數(shù)組
  • 通過fromstring函數(shù)將字符串轉(zhuǎn)換為數(shù)組,通過其參數(shù)dtype指定轉(zhuǎn)換后的數(shù)據(jù)格式,由于我們的聲音格式是以兩個(gè)字節(jié)表示一個(gè)取樣值,因此采用short數(shù)據(jù)類型轉(zhuǎn)換?,F(xiàn)在我們得到的wave_data是一個(gè)一維的short類型的數(shù)組,但是因?yàn)槲覀兊穆曇粑募请p聲道的,因此它由左右兩個(gè)聲道的取樣交替構(gòu)成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)'
  • 幀率的計(jì)算公式:
    采樣率 = 每秒中的采樣頻率/每秒中的采樣點(diǎn)數(shù) 幀率(fps) =1 /采樣率
  • 將波形數(shù)據(jù)轉(zhuǎn)換為數(shù)組
  • 通過取樣點(diǎn)數(shù)和取樣頻率計(jì)算出每個(gè)取樣的時(shí)間
    def convrt_data(self):
        self.df = self.rate / (self.collect_point_num - 1)
        # 根據(jù)總平均法使用全局幀數(shù)除以全局時(shí)間,以求出幀率
    def Data_collection(self):
        wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
        # 根據(jù)聲道數(shù)和量化單位,將讀取的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為一個(gè)可以計(jì)算的數(shù)組

        wave_data.shape = -1, 2
        # -1代表左聲道,2代表右聲道
        # 通過fromstring函數(shù)將字符串轉(zhuǎn)換為數(shù)組,通過其參數(shù)dtype指定轉(zhuǎn)換后的數(shù)據(jù)格式,由于我們的聲音格式是以兩個(gè)字節(jié)表示一個(gè)取樣值,
        # 因此采用short數(shù)據(jù)類型轉(zhuǎn)換?,F(xiàn)在我們得到的wave_data是一個(gè)一維的short類型的數(shù)組,但是因?yàn)槲覀兊穆曇粑募请p聲道的,
        # 因此它由左右兩個(gè)聲道的取樣交替構(gòu)成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:

        # 將wave_data數(shù)組改為2列,行數(shù)自動(dòng)匹配。在修改shape的屬性時(shí),需使得數(shù)組的總長度不變。v
        wave_data = wave_data.T
        # 將波形數(shù)據(jù)轉(zhuǎn)換為數(shù)組
        freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
        # 通過取樣點(diǎn)數(shù)和取樣頻率計(jì)算出每個(gè)取樣的時(shí)間

3.劃分采樣位置,建立頻譜圖坐標(biāo)系,根據(jù)采樣時(shí)間標(biāo)記采樣點(diǎn)在頻譜圖上的位置

  • wave_data2保存聲音字節(jié)數(shù)組轉(zhuǎn)置后的結(jié)果,為列數(shù)為1存儲(chǔ)的數(shù)組
  • 固定第一位,劃分第二維區(qū)間從0一直掃描到行尾
  • 避免波形字節(jié)數(shù)組過長,利用numpy.fft.fft對壓縮為1/2的波形字節(jié)數(shù)組進(jìn)行快速傅里葉變換,常規(guī)顯示采樣頻率一半的頻譜
  • 設(shè)定如果每個(gè)取樣點(diǎn)的取樣時(shí)間大于4000ms,分隔單位為10的波形數(shù)組顯示
 def Data_collection(self):
        wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
        # 根據(jù)聲道數(shù)和量化單位,將讀取的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為一個(gè)可以計(jì)算的數(shù)組

        wave_data.shape = -1.2
        # -1代表左聲道,2代表右聲道
        # 通過fromstring函數(shù)將字符串轉(zhuǎn)換為數(shù)組,通過其參數(shù)dtype指定轉(zhuǎn)換后的數(shù)據(jù)格式,由于我們的聲音格式是以兩個(gè)字節(jié)表示一個(gè)取樣值,
        # 因此采用short數(shù)據(jù)類型轉(zhuǎn)換?,F(xiàn)在我們得到的wave_data是一個(gè)一維的short類型的數(shù)組,但是因?yàn)槲覀兊穆曇粑募请p聲道的,
        # 因此它由左右兩個(gè)聲道的取樣交替構(gòu)成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:

        # 將wave_data數(shù)組改為2列,行數(shù)自動(dòng)匹配。在修改shape的屬性時(shí),需使得數(shù)組的總長度不變。v
        wave_data = wave_data.T
        # 將波形數(shù)據(jù)轉(zhuǎn)換為數(shù)組
        freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
        # 通過取樣點(diǎn)數(shù)和取樣頻率計(jì)算出每個(gè)取樣的時(shí)間
        wave_data2 = wave_data[0][self.start:self.start+self.collect_point_num]
        c = numpy.fft.fft(wave_data2)*2/self.collect_point_num
        # 常規(guī)顯示采樣頻率一半的頻譜
        d = int(len(c)/2)
        while freq[0] > 4000:
            d -= 10
            pylab.plot(freq[:d-1],abs(c[:d-1]),"r")
            pylab.show()

python console:頻譜的時(shí)間段劃分似乎造成了誤差,導(dǎo)致統(tǒng)計(jì)結(jié)果趨于集中

fft.png

Test1:打印波形字節(jié)數(shù)組長度以及每個(gè)采樣點(diǎn)采樣花費(fèi)的時(shí)間

 def Data_collection(self):
        wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
        # 根據(jù)聲道數(shù)和量化單位,將讀取的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為一個(gè)可以計(jì)算的數(shù)組

        wave_data.shape = -1, 2
        # -1代表左聲道,2代表右聲道
        # 通過fromstring函數(shù)將字符串轉(zhuǎn)換為數(shù)組,通過其參數(shù)dtype指定轉(zhuǎn)換后的數(shù)據(jù)格式,由于我們的聲音格式是以兩個(gè)字節(jié)表示一個(gè)取樣值,
        # 因此采用short數(shù)據(jù)類型轉(zhuǎn)換。現(xiàn)在我們得到的wave_data是一個(gè)一維的short類型的數(shù)組,但是因?yàn)槲覀兊穆曇粑募请p聲道的,
        # 因此它由左右兩個(gè)聲道的取樣交替構(gòu)成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:

        # 將wave_data數(shù)組改為2列,行數(shù)自動(dòng)匹配。在修改shape的屬性時(shí),需使得數(shù)組的總長度不變。v
        wave_data = wave_data.T
        # 將波形數(shù)據(jù)轉(zhuǎn)換為數(shù)組
        freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
        print(freq)
        # 通過取樣點(diǎn)數(shù)和取樣頻率計(jì)算出每個(gè)取樣的時(shí)間
        wave_data2 = wave_data[0][self.start:self.start+self.collect_point_num]
        c = numpy.fft.fft(wave_data2)*2/self.collect_point_num
        d = int(len(c)/2)
        print(d)

python console

print.jpg

Test2: 原來是設(shè)定的采樣時(shí)間過小,修改統(tǒng)計(jì)條件為 freq[0] > 44101

    def Data_collection(self):
        wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
        # 根據(jù)聲道數(shù)和量化單位,將讀取的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為一個(gè)可以計(jì)算的數(shù)組

        wave_data.shape = -1, 2
        # -1代表左聲道,2代表右聲道
        # 通過fromstring函數(shù)將字符串轉(zhuǎn)換為數(shù)組,通過其參數(shù)dtype指定轉(zhuǎn)換后的數(shù)據(jù)格式,由于我們的聲音格式是以兩個(gè)字節(jié)表示一個(gè)取樣值,
        # 因此采用short數(shù)據(jù)類型轉(zhuǎn)換?,F(xiàn)在我們得到的wave_data是一個(gè)一維的short類型的數(shù)組,但是因?yàn)槲覀兊穆曇粑募请p聲道的,
        # 因此它由左右兩個(gè)聲道的取樣交替構(gòu)成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:

        # 將wave_data數(shù)組改為2列,行數(shù)自動(dòng)匹配。在修改shape的屬性時(shí),需使得數(shù)組的總長度不變。v
        wave_data = wave_data.T
        # 將波形數(shù)據(jù)轉(zhuǎn)換為數(shù)組
        freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
        print(freq)
        # 通過取樣點(diǎn)數(shù)和取樣頻率計(jì)算出每個(gè)取樣的時(shí)間
        wave_data2 = wave_data[0][self.start:self.start+self.collect_point_num]
        c = numpy.fft.fft(wave_data2)*2/self.collect_point_num
        d = int(len(c)/2)
        print(d)

        while freq[0] > 44101:
            d -= 0.1
            pylab.plot(freq[:d-1],abs(c[:d-1]),"r")
            pylab.show()

python console采樣的分布過于密集,不適合用頻譜圖進(jìn)行統(tǒng)計(jì)


pinpu.png

Test3:使用波形圖,分別用subplot211與subplot212標(biāo)識(shí)左右聲道的波形

import wave
import pyaudio
import numpy
from matplotlib import pylab


#打開wav文檔,文件路徑根據(jù)需要修改
wf = wave.open("F:\\work\\war.wav","rb")

#創(chuàng)建PyAudio對象
p = pyaudio.PyAudio()


class Audio(object):

    def __init__(self):
        self.channels = wf.getnchannels()
        # 返回音頻通道數(shù)
        self.rate = wf.getframerate()
        # 返回采樣頻率。
        self.format = p.get_format_from_width(wf.getsampwidth())
        # 返回指定寬度的PortAudio格式常量。
        self.stream = p.open(format=self.format,channels=self.channels,rate=self.rate,output=True)
        # 使用所需的音頻參數(shù)在所需設(shè)備上打開一個(gè)流
        self.nframes = wf.getnframes()
        # 返回音頻幀數(shù)
        self.collect_point_num = 44100
        # 采樣點(diǎn)數(shù),修改采樣點(diǎn)數(shù)和起始位置進(jìn)行不同位置和長度的音頻波形分析
        self.start = 0
        # 開始采樣位置
    def read_data(self):
        self.str_data = wf.readframes(self.nframes)
        # 讀取聲音數(shù)據(jù),傳遞一個(gè)參數(shù)指定需要讀取的長度(以取樣點(diǎn)為單位),readframes返回的是二進(jìn)制數(shù)據(jù)(即bytes數(shù)組)
        # print(self.str_data)
        wf.close()

    def convert_data(self):
        self.df = self.rate / (self.collect_point_num - 1)
        # print(self.df)
        # 使用全局采樣頻率除以全局采樣點(diǎn)數(shù),以求出幀率
    def Data_collection(self):
        wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
        # 根據(jù)聲道數(shù)和量化單位,將讀取的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為一個(gè)可以計(jì)算的數(shù)組

        wave_data.shape = -1, 2
        # -1代表左聲道,2代表右聲道
        # 通過fromstring函數(shù)將字符串轉(zhuǎn)換為數(shù)組,通過其參數(shù)dtype指定轉(zhuǎn)換后的數(shù)據(jù)格式,由于我們的聲音格式是以兩個(gè)字節(jié)表示一個(gè)取樣值,
        # 因此采用short數(shù)據(jù)類型轉(zhuǎn)換?,F(xiàn)在我們得到的wave_data是一個(gè)一維的short類型的數(shù)組,但是因?yàn)槲覀兊穆曇粑募请p聲道的,
        # 因此它由左右兩個(gè)聲道的取樣交替構(gòu)成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:

        # 將wave_data數(shù)組改為2列,行數(shù)自動(dòng)匹配。在修改shape的屬性時(shí),需使得數(shù)組的總長度不變。v
        wave_data = wave_data.T
        # 將波形數(shù)據(jù)轉(zhuǎn)換為數(shù)組
        freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
        # print(freq)
        # 通過取樣點(diǎn)數(shù)和取樣頻率計(jì)算出每個(gè)取樣的時(shí)間
        wave_data2 = wave_data[0][self.start:self.start+self.collect_point_num]
        c = numpy.fft.fft(wave_data2)*2/self.collect_point_num
        d = int(len(c)/2)
        # print(d)

        while freq[0] > 44101:
            d -= 20
            pylab.plot(freq[:d-1],c[:d-1],"r")
            pylab.show()


    def wavread(self):
        wavfile = wf
        params = wavfile.getparams()
        framesra, frameswav = params[2], params[3]
        datawav = wavfile.readframes(frameswav)
        wavfile.close()
        datause = numpy.fromstring(datawav, dtype=numpy.short)
        datause.shape = -1, 2
        datause = datause.T
        time = numpy.arange(0, frameswav) * (1.0 / framesra)
        return datause, time


    def work(self):
        self.read_data()
        self.convert_data()
        self.Data_collection()

if __name__ == '__main__':
    aduio = Audio()
    # aduio.work()

    wavdata, wavtime = aduio.wavread()
    pylab.title("Night.wav's Frames")
    pylab.subplot(211)
    pylab.plot(wavtime, wavdata[0], color='green')
    pylab.subplot(212)
    pylab.plot(wavtime, wavdata[1])
    pylab.show()

python console

wave.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容