語音識別:
Getting Started!首先,我們要知道語音的產生過程
狀態:由肺產生向外的氣流,完全放松時聲帶張開,就是平時的呼吸。如果聲帶一張一合(振動)形成周期性的脈沖氣流。這個脈沖氣流的周期稱之為——基音周期(題主所言因音色不同導致的頻率不同,事實上音色的大多是泛頻上的差異,建立在基頻之上,這個基頻就是基音周期了,泛頻可以忽略)。當然啦,這只是在發濁音(b,d,v...)時才會有,當發出清音(p,t,f...)時聲帶不振動,但是會處于緊繃狀態,當氣流涌出時會在聲帶產生湍流。清音和濁音是音素的兩大類。接下來脈沖氣流/湍流到達聲道,由聲道對氣流進行調制,形成不同的音素。多個音素組成一個音節(就漢語而言是[聲母]+韻母)。如果沒學過信號系統那就想像一下平舌音和翹舌音,z和zh發聲時肺和喉的狀態都一樣,只是舌頭動作不一樣,發出的聲音也就不一樣了,這就算是簡單的調制。從而聲音的波形會發生一些變化。這個波形,就是以后分析所需要的數據。
標識聲音的圖像有以下三種
- 頻譜圖
- 時譜圖
- 語譜圖
WAV是Microsoft開發的一種聲音文件格式,雖然它支持多種壓縮格式,不過它通常被用來保存未壓縮的聲音數據(PCM脈沖編碼調制)。WAV有三個重要的參數:聲道數、取樣頻率和量化位數。
- 聲道數:可以是單聲道或者是雙聲道
- 采樣頻率:一秒內對聲音信號的采集次數,常用的有8kHz, 16kHz, 32kHz, 48kHz, 11.025kHz, 22.05kHz, 44.1kHz
- 量化位數:用多少bit表達一次采樣所采集的數據,通常有8bit、16bit、24bit和32bit等幾種
1.讀入二進制音頻流數據流程
- 對于一個音頻實例wf而言,通過調用它的方法讀取WAV文件的格式和數據:getnchannels, getsampwidth, getframerate, getnframes等方法可以單獨返回WAV文件的特定的信息。
- readframes:讀取聲音數據,傳遞一個參數指定需要讀取的長度(以取樣點為單位),readframes返回的是二進制數據
PS:注意需要使用"rb"(二進制模式)打開文件
import wave
import pyaudio
import numpy
from matplotlib import pylab
#打開wav文檔,文件路徑根據需要修改
wf = wave.open("F:\\work\\war.wav","rb")
#創建PyAudio對象
p = pyaudio.PyAudio()
class Audio(object):
def __init__(self):
self.channels = wf.getnchannels()
# 返回音頻通道數
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)
# 使用所需的音頻參數在所需設備上打開一個流
self.nframes = wf.getnframes()
# 返回音頻幀數
self.collect_point_num = 44100
# 采樣點數,修改采樣點數和起始位置進行不同位置和長度的音頻波形分析
self.start = 0
# 開始采樣位置
def read_data(self):
self.str_data = wf.readframes(self.nframes)
# 讀取聲音數據,傳遞一個參數指定需要讀取的長度(以取樣點為單位),readframes返回的是二進制數據(即bytes數組)
# print(self.str_data)
wf.close()
#關閉媒體流
if __name__ == '__main__':
aduio = Audio()
aduio.read_data()
python output
2.生成的流媒體字節數組計算出每個取樣的時間
- 將讀取的二進制數據轉換為一個可以計算的數組
- 通過fromstring函數將字符串轉換為數組,通過其參數dtype指定轉換后的數據格式,由于我們的聲音格式是以兩個字節表示一個取樣值,因此采用short數據類型轉換。現在我們得到的wave_data是一個一維的short類型的數組,但是因為我們的聲音文件是雙聲道的,因此它由左右兩個聲道的取樣交替構成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)'
- 由幀率的計算公式:
采樣率 = 每秒中的采樣頻率/每秒中的采樣點數 幀率(fps) =1 /采樣率 - 將波形數據轉換為數組
- 通過取樣點數和取樣頻率計算出每個取樣的時間
def convrt_data(self):
self.df = self.rate / (self.collect_point_num - 1)
# 根據總平均法使用全局幀數除以全局時間,以求出幀率
def Data_collection(self):
wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
# 根據聲道數和量化單位,將讀取的二進制數據轉換為一個可以計算的數組
wave_data.shape = -1, 2
# -1代表左聲道,2代表右聲道
# 通過fromstring函數將字符串轉換為數組,通過其參數dtype指定轉換后的數據格式,由于我們的聲音格式是以兩個字節表示一個取樣值,
# 因此采用short數據類型轉換。現在我們得到的wave_data是一個一維的short類型的數組,但是因為我們的聲音文件是雙聲道的,
# 因此它由左右兩個聲道的取樣交替構成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:
# 將wave_data數組改為2列,行數自動匹配。在修改shape的屬性時,需使得數組的總長度不變。v
wave_data = wave_data.T
# 將波形數據轉換為數組
freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
# 通過取樣點數和取樣頻率計算出每個取樣的時間
3.劃分采樣位置,建立頻譜圖坐標系,根據采樣時間標記采樣點在頻譜圖上的位置
- wave_data2保存聲音字節數組轉置后的結果,為列數為1存儲的數組
- 固定第一位,劃分第二維區間從0一直掃描到行尾
- 避免波形字節數組過長,利用numpy.fft.fft對壓縮為1/2的波形字節數組進行快速傅里葉變換,常規顯示采樣頻率一半的頻譜
- 設定如果每個取樣點的取樣時間大于4000ms,分隔單位為10的波形數組顯示
def Data_collection(self):
wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
# 根據聲道數和量化單位,將讀取的二進制數據轉換為一個可以計算的數組
wave_data.shape = -1.2
# -1代表左聲道,2代表右聲道
# 通過fromstring函數將字符串轉換為數組,通過其參數dtype指定轉換后的數據格式,由于我們的聲音格式是以兩個字節表示一個取樣值,
# 因此采用short數據類型轉換。現在我們得到的wave_data是一個一維的short類型的數組,但是因為我們的聲音文件是雙聲道的,
# 因此它由左右兩個聲道的取樣交替構成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:
# 將wave_data數組改為2列,行數自動匹配。在修改shape的屬性時,需使得數組的總長度不變。v
wave_data = wave_data.T
# 將波形數據轉換為數組
freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
# 通過取樣點數和取樣頻率計算出每個取樣的時間
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)
while freq[0] > 4000:
d -= 10
pylab.plot(freq[:d-1],abs(c[:d-1]),"r")
pylab.show()
python console:頻譜的時間段劃分似乎造成了誤差,導致統計結果趨于集中
Test1:打印波形字節數組長度以及每個采樣點采樣花費的時間
def Data_collection(self):
wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
# 根據聲道數和量化單位,將讀取的二進制數據轉換為一個可以計算的數組
wave_data.shape = -1, 2
# -1代表左聲道,2代表右聲道
# 通過fromstring函數將字符串轉換為數組,通過其參數dtype指定轉換后的數據格式,由于我們的聲音格式是以兩個字節表示一個取樣值,
# 因此采用short數據類型轉換。現在我們得到的wave_data是一個一維的short類型的數組,但是因為我們的聲音文件是雙聲道的,
# 因此它由左右兩個聲道的取樣交替構成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:
# 將wave_data數組改為2列,行數自動匹配。在修改shape的屬性時,需使得數組的總長度不變。v
wave_data = wave_data.T
# 將波形數據轉換為數組
freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
print(freq)
# 通過取樣點數和取樣頻率計算出每個取樣的時間
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
Test2: 原來是設定的采樣時間過小,修改統計條件為 freq[0] > 44101
def Data_collection(self):
wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
# 根據聲道數和量化單位,將讀取的二進制數據轉換為一個可以計算的數組
wave_data.shape = -1, 2
# -1代表左聲道,2代表右聲道
# 通過fromstring函數將字符串轉換為數組,通過其參數dtype指定轉換后的數據格式,由于我們的聲音格式是以兩個字節表示一個取樣值,
# 因此采用short數據類型轉換。現在我們得到的wave_data是一個一維的short類型的數組,但是因為我們的聲音文件是雙聲道的,
# 因此它由左右兩個聲道的取樣交替構成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:
# 將wave_data數組改為2列,行數自動匹配。在修改shape的屬性時,需使得數組的總長度不變。v
wave_data = wave_data.T
# 將波形數據轉換為數組
freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
print(freq)
# 通過取樣點數和取樣頻率計算出每個取樣的時間
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采樣的分布過于密集,不適合用頻譜圖進行統計
Test3:使用波形圖,分別用subplot211與subplot212標識左右聲道的波形
import wave
import pyaudio
import numpy
from matplotlib import pylab
#打開wav文檔,文件路徑根據需要修改
wf = wave.open("F:\\work\\war.wav","rb")
#創建PyAudio對象
p = pyaudio.PyAudio()
class Audio(object):
def __init__(self):
self.channels = wf.getnchannels()
# 返回音頻通道數
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)
# 使用所需的音頻參數在所需設備上打開一個流
self.nframes = wf.getnframes()
# 返回音頻幀數
self.collect_point_num = 44100
# 采樣點數,修改采樣點數和起始位置進行不同位置和長度的音頻波形分析
self.start = 0
# 開始采樣位置
def read_data(self):
self.str_data = wf.readframes(self.nframes)
# 讀取聲音數據,傳遞一個參數指定需要讀取的長度(以取樣點為單位),readframes返回的是二進制數據(即bytes數組)
# print(self.str_data)
wf.close()
def convert_data(self):
self.df = self.rate / (self.collect_point_num - 1)
# print(self.df)
# 使用全局采樣頻率除以全局采樣點數,以求出幀率
def Data_collection(self):
wave_data = numpy.fromstring(self.str_data,dtype=numpy.short)
# 根據聲道數和量化單位,將讀取的二進制數據轉換為一個可以計算的數組
wave_data.shape = -1, 2
# -1代表左聲道,2代表右聲道
# 通過fromstring函數將字符串轉換為數組,通過其參數dtype指定轉換后的數據格式,由于我們的聲音格式是以兩個字節表示一個取樣值,
# 因此采用short數據類型轉換。現在我們得到的wave_data是一個一維的short類型的數組,但是因為我們的聲音文件是雙聲道的,
# 因此它由左右兩個聲道的取樣交替構成:LRLRLRLR....LR(L表示左聲道的取樣值,R表示右聲道取樣值)。修改wave_data的sharp之后:
# 將wave_data數組改為2列,行數自動匹配。在修改shape的屬性時,需使得數組的總長度不變。v
wave_data = wave_data.T
# 將波形數據轉換為數組
freq = [self.df*self.collect_point_num for n in range(0,self.collect_point_num)]
# print(freq)
# 通過取樣點數和取樣頻率計算出每個取樣的時間
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