離線語音Snowboy熱詞喚醒
語音識別現在有非常廣泛的應用場景,如手機的語音助手,智能音響(小愛,叮咚,天貓精靈...)等.
語音識別一般包含三個階段:熱詞喚醒,語音錄入,識別和邏輯控制階段.
熱詞喚醒就是喚醒設備,讓設備解析你接下來說的話.通常設備一直在錄入周圍的聲音,但是設備此時不會有任何反應.當通過像「Hi,Siri」這樣的喚醒詞被喚醒以后,設備就開始處理接下來的聲音了。熱詞喚醒是語音識別的開始。
Snowboy 是比較流行的熱詞喚醒框架,目前已經被百度收購。Snowboy 對中文支持友好,相對 Pocketsphinx 配置使用較為簡單,推薦使用。
snowboy官方文檔地址[英文的] http://docs.kitt.ai/snowboy
安裝
一、獲取源代碼並編譯
安裝依賴
樹莓派原生的音頻設備是不支持語音輸入的(無法錄音),需要在網上購買一支免驅動的USB音頻驅動,一般插上即可直接使用。
建議安裝下 pulseaudio
軟件,減少音頻配置的步驟:
$ sudo apt-get install pulseaudio
安裝 sox
軟件測試錄音與播放功能:
$ sudo apt-get install sox
安裝完成后運行 sox -d -d
命令,對着麥克風說話,確認可以聽到自己的聲音。
安裝其他軟件依賴:
- 安裝 PyAudio:
$ sudo apt-get install python3-pyaudio
- 安裝 SWIG(>3.0.10):
$ sudo apt-get install swig
- 安裝 ATLS:
$ sudo apt-get install libatls-base-dev
編譯源代碼
獲取源代碼:$ git clone https://github.com/Kitt-AI/snowboy.git
編譯 Python3 綁定:$ cd snowboy/swig/Python3 && make
測試:
如果使用的是樹莓派,你還需要在 ~/.asoundrc
更改聲卡設置:
type asym
playback.pcm {
type plug
slave.pcm "hw:0,0"
}
capture.pcm {
type plug
slave.pcm "hw:1,0"
}
}
進入官方示例目錄 snowboy/examples/Python3
並運行以下命令:
$ python3 demo.py resources/models/snowboy.umdl
( 命令中的 snowboy.umdl
文件即語音識別模型)
然后對着麥克風清晰地講出“snowboy”,如果可以聽到“滴”的聲音,則安裝配置成功。
PS:官方源代碼使用 Python3 測試有報錯,經測試需修改 snowboy/examples/Python3
目錄下的 snowboydecoder.py
文件。
將第 5 行代碼 from * import snowboydetect
改為 import snowboydetect
即可直接運行。
快速開始
GitHub 上有比較詳細的 Demo,強烈建議先看看。先創建一個 HotwordDetect 類,這個類包含喚醒模型,聲音增益,靈敏度等參數。然后初始化 Detector 對象,Snowboy 的 Detector 類存在下載下來的源碼里。訓練模型可以是單個,也可以是列表形式。
from .. import snowboydetect
class HotwordDetect(object):
def __init__(self, decoder_model,
resource,
sensitivity=0.38,
audio_gain=1):
"""init"""
self.detector = snowboydetect.SnowboyDetect(
resource_filename=resource.encode(),
model_str=decoder_model.encode())
self.detector.SetAudioGain(audio_gain)
初始化以后可以創建啟動方法,啟動方法一般會指定一個喚醒回調函數,也就是 「Hi,Siri」之后可能出現的「叮」聲;還可以指定錄音回調函數,也就是設備喚醒以后你需要用這些聲音去干什么:
class HotwordDetect(object):
...
def listen(self, detected_callback,
interrupt_check=lambda: False,
audio_recorder_callback):
"""begin to listen"""
...
state = "PASSIVE"
while True:
status = self.detector.RunDetection(data)
...
if state == "PASSIVE":
tetected_callback()
state = "ACTIVE"
continue
elif state == "ACTIVE":
audio_recorder_callback()
state = "ACTIVE"
continue
這里的邏輯可以自己去定義,主要是在兩個狀態間切換,當設備接收到喚醒詞以后,status 會指出被識別到的喚醒詞的序號,比如你定義了 「Siri」和 「Xiaowei」兩個喚醒詞,status 為 1 就表示 Siri 被喚醒,status 為 2 就表示 Xiaowei 被喚醒。然后將狀態改成激活狀態,這個時候執行 audio_recorder_callback 方法,執行完后將狀態切換回喚醒狀態。
在線語音識別
當設備被喚醒以后,你可以拿到錄音數據去做任何想做的事情,包括調取百度等語音識別接口。這些邏輯都包含在 audio_recorder_callback 回調方法中。需要注意的是 Snowboy 目前只支持 16000 的錄音采樣率,其他采樣率的錄音數據都不能使用,你可以通過兩種辦法來解決:
- 使用支持 16000 采樣率的聲卡
- 進行錄音數據的采樣率轉換
目前比較大的兩家聲卡芯片公司 C-Media 和 RealTek 一般產品都是 48k 以上的,支持 16k 的芯片一般比較貴,可能到 60 元左右。「綠聯」有兩款產品可以支持,購買時請查看產品參數,對照芯片公司的產品型號是否支持 16k 采樣。
聲音模型的訓練
官方提供兩種模式進行個性化聲音模型創建:
- website。只要你有 GitHub,Google 和 Facebook 帳號中的一種,登錄就可以錄音完成訓練。
- train-api。根據文檔傳指定的參數就可以完成訓練,api 返回給你升學模型的數據。
這兩種方式獲得的都是私人的聲音模型,獲取的是 .pmdl
的文件形式。一般化的 universal 模型不提供,需要聯系官方商業合作。獲取到的模型,越多人測試准確率越高,為了提高准確率,你可以邀請更多人來測試你的模型。還有麥克風的種類也會影響准確度,在什么設備上使用就在那個設備上訓練模型能提高准確率。語音識別是一個比較精尖的技術,需要注意很多問題,正如 ChenGuo 說的:
Speech Recognition is not that easy.
在自己的項目中使用
將以下文件復制到自己的項目目錄下:
- 下載好的 model.pmdl 模型文件
snowboy/swig/Python3
目錄下編譯好的_snowboydetect.so
庫snowboy/examples/Python3
目錄下的demo.py
、snowboydecoder.py
、snowboydetect.py
文件以及resources
目錄- 在項目目錄下執行
$ python3 demo.py model.pmdl
並使用自己的喚醒詞進行測試
orangePi下使用語音識別來實現語音開關燈,需要聯網使用.
gpio.py
#!/usr/bin/env python
# encoding: utf-8
#
# 香橙派(orangepi)的GPIO操控,詳細查下以前的帖子.
#
"""
@version: ??
@author: lvusyy
@license: Apache Licence
@contact: lvusyy@gmail.com
@site: https://github.com/lvusyy/
@software: PyCharm
@file: gpio.py
@time: 2018/3/13 18:45
"""
import wiringpi as wp
class GPIO():
def __init__(self):
self.wp=wp
wp.wiringPiSetupGpio()
#wp.pinMode(18, 1)
#wp.pinMode(23, 0)
def setPinMode(self,pin,mode):
self.wp.pinMode(pin,mode)
def setV(self,pin,v):
self.wp.digitalWrite(pin,v)
def getV(self,pin):
return self.wp.digitalRead(pin)
之前案例修改了以下. control.py
#!/usr/bin/env python
# encoding: utf-8
#
# 利用熱詞喚醒后使用百度語音識別api識別語音指令,然后匹配操作指令.如關燈,開燈操作.
### 使用snowboy的多個熱詞喚醒,效果會更好,而且不需要網絡. 有空測試.
"""
@version: ??
@author: lvusyy
@license: Apache Licence
@contact: lvusyy@gmail.com
@site: https://github.com/lvusyy/
@software: PyCharm
@file: control.py
@time: 2018/3/13 17:30
"""
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import time
import pyaudio
import wave
import pygame
import snowboydecoder
import signal
from gpio import GPIO
from aip import AipSpeech
APP_ID = '109472xxx'
API_KEY = 'd3zd5wuaMrL21IusNqdQxxxx'
SECRET_KEY = '84e98541331eb1736ad80457b4faxxxx'
APIClient = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
interrupted = False
#定義采集聲音文件參數
CHUNK = 1024
FORMAT = pyaudio.paInt16 #16位采集
CHANNELS = 1 #單聲道
RATE = 16000 #采樣率
RECORD_SECONDS = 5 #采樣時長 定義為9秒的錄音
WAVE_OUTPUT_FILENAME = "./myvoice.pcm" #采集聲音文件存儲路徑
class Light():
def __init__(self):
self.pin=18
self.mode=1 #open is 1 close is 0
self.mgpio=GPIO()
self.mgpio.setPinMode(pin=self.pin,mode=1) #OUTPUT 1 INPUT 0
def on(self):
''
self.mgpio.setV(self.pin,self.mode)
def off(self):
''
self.mgpio.setV(self.pin,self.mode&0)
def status(self):
#0 is off 1 is on
return self.mgpio.getV(self.pin)
def get_file_content(filePath):
with open(filePath, 'rb') as fp:
return fp.read()
def word_to_voice(text):
result = APIClient.synthesis(text, 'zh', 1, {
'vol': 5, 'spd': 3, 'per': 3})
if not isinstance(result, dict):
with open('./audio.mp3', 'wb') as f:
f.write(result)
f.close()
time.sleep(.2)
pygame.mixer.music.load('./audio.mp3')#text文字轉化的語音文件
pygame.mixer.music.play()
while pygame.mixer.music.get_busy() == True:
print('waiting')
def get_mic_voice_file(p):
word_to_voice('請說開燈或關燈.')
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
print("* recording")
frames = []
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
print("* done recording")
stream.stop_stream()
stream.close()
#p.terminate()#這里先不使用p.terminate(),否則 p = pyaudio.PyAudio()將失效,還得重新初始化。
wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()
print('recording finished')
def baidu_get_words(client):
results = client.asr(get_file_content(WAVE_OUTPUT_FILENAME), 'pcm', 16000, { 'dev_pid': 1536, })
# print(results['result'])
words=results['result'][0]
return words
#_*_ coding:UTF-8 _*_
# @author: zdl
# 實現離線語音喚醒和語音識別,實現一些語音交互控制
# 導入包
def signal_handler(signal, frame):
global interrupted
interrupted = True
def interrupt_callback():
global interrupted
return interrupted
# 回調函數,語音識別在這里實現
def callbacks():
global detector
# 語音喚醒后,提示ding兩聲
# snowboydecoder.play_audio_file()
pygame.mixer.music.load('./resources/ding.wav')#text文字轉化的語音文件
pygame.mixer.music.play()
while pygame.mixer.music.get_busy() == True:
print('waiting')
#snowboydecoder.play_audio_file()
# 關閉snowboy功能
detector.terminate()
# 開啟語音識別
get_mic_voice_file(p)
rText=baidu_get_words(client=APIClient)
if rText.find("開燈")!=-1:
light.on()
elif rText.find("關燈")!=-1:
light.off()
# 打開snowboy功能
wake_up() # wake_up —> monitor —> wake_up 遞歸調用
# 熱詞喚醒
def wake_up():
global detector
model = './resources/models/snowboy.umdl' # 喚醒詞為 SnowBoy
# capture SIGINT signal, e.g., Ctrl+C
signal.signal(signal.SIGINT, signal_handler)
# 喚醒詞檢測函數,調整sensitivity參數可修改喚醒詞檢測的准確性
detector = snowboydecoder.HotwordDetector(model, sensitivity=0.5)
print('Listening... please say wake-up word:SnowBoy')
# main loop
# 回調函數 detected_callback=snowboydecoder.play_audio_file
# 修改回調函數可實現我們想要的功能
detector.start(detected_callback=callbacks, # 自定義回調函數
interrupt_check=interrupt_callback,
sleep_time=0.03)
# 釋放資源
detector.terminate()
if __name__ == '__main__':
#初始化pygame,讓之后播放語音合成的音頻文件
pygame.mixer.init()
p = pyaudio.PyAudio()
light=Light()
wake_up()
相關參考文檔:
http://docs.kitt.ai/snowboy/#api-v1-train