起因是一個工作中喜歡說口頭禪的同事,昨天老說“你看看你看看 操不操心”。說了幾次之后我就在他說完“你看看”后面續上,“操不操心”。往復多次后,我就想,為啥不用Python識別語音並作出響應,正好沒弄過語音識別。
1. 語音轉文字
參考Python語音識別終極指南,吐槽一句:質量太差,是最爛的無審查的機翻。引模塊中間都沒空格
importspeech_recognitionassr
應該是import speech_recognition as sr
;並創建識一個別器類的例子
應該是並創建一個識別器類的例子
這塊都不僅僅是機翻了吧,怎么會拆了詞。但是為了了解API足夠了。
語音轉文字使用谷歌雲平台的語音轉文字服務[Google Cloud Speech API ](https://cloud.google.com/speech-to-text/),因為是不需要API密鑰的。其實是因為有默認密鑰:
def recognize_google(self, audio_data, key=None, language="en-US", show_all=False):
...
if key is None: key = "AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw
...
通過另外兩個函數參數還可以了解到:lanauage
(指定識別的語言),show_all
(False返回識別率最高的一條結果,True返回所有識別結果的 json串 字典數據)
安裝pip install SpeechRecognition
1.1 本地語音文件識別測試
# coding:utf-8
"""
本地語音文件識別測試
"""
import speech_recognition as sr
import sys
say = '你看看'
r = sr.Recognizer()
# 本地語音測試
harvard = sr.AudioFile(sys.path[0]+'/youseesee.wav')
with harvard as source:
# 去噪
r.adjust_for_ambient_noise(source, duration=0.2)
audio = r.record(source)
# 語音識別
test = r.recognize_google(audio, language="cmn-Hans-CN", show_all=True)
print(test)
# 分析語音
flag = False
for t in test['alternative']:
print(t)
if say in t['transcript']:
flag = True
break
if flag:
print('Bingo')
自己錄了一段語音youseesee.wav
(內容為輕輕(類似悄悄話,聲帶不強烈震動)說的你看看你看看,持續兩秒)。音頻文件格式可以是WAV/AIFF/FLAC
AudioFile
instance given a WAV/AIFF/FLAC audio file
去噪函數adjust_for_ambient_noise()
在音頻中取一段噪聲(duration
時間范圍,默認1s),來優化識別。因為原音頻很短,所以這里只取了 0.2s 噪聲。
轉換函數recognize_google()
的lanaguage
參數范圍可以從 Cloud Speech-to-Text API 語言支持 處了解到,「中文、普通話(中國簡體)」 為 cmn-Hans-CN。
show_all
前面有介紹,當上例中該參數為False
時語音識別結果test
輸出呵呵你看看
,為True
時輸出所有可能的識別結果:
{
'alternative':[
{
'transcript':'呵呵你看看',
'confidence':0.87500638
},
{
'transcript':'呵呵你看'
},
{
'transcript':'哥哥你看'
},
{
'transcript':'哥哥你看看'
},
{
'transcript':'呵呵你看咯'
}
],
'final':True
}
之后分析語音,只是簡單找了識別結果是否包含期待值你看看
,找出一個則表示正確識別並匹配,輸出Bingo!
上例完整輸出為:
{'alternative': [{'transcript': '呵呵你看看', 'confidence': 0.87500668}, {'transcript': '呵呵你看'}, {'transcript': '哥哥你看'}, {'transcript': '哥哥你看看'}, {'transcript': '呵呵你看咯'}], 'final': True}
{'transcript': '呵呵你看看', 'confidence': 0.87500668}
Bingo
注:如果發生異常:
speech_recognition.RequestError
recognition connection failed: [WinError 10060] 由於連接方在一段時間后沒有正確答復或連接的主機沒有反應,連接嘗試失敗。
是因為(梯子)沒有設置全局代理。
1.2 實時語音識別測試
把音頻數據來源,從上面的音頻文件,改為創建一個麥克風實例,並錄音。
需要安裝pyAudio
,如果pip install pyAudio
不能安裝,可以去Python Extension Packages下載安裝。
# coding:utf-8
"""
實時語音識別測試
"""
import speech_recognition as sr
import logging
logging.basicConfig(level=logging.DEBUG)
while True:
r = sr.Recognizer()
# 麥克風
mic = sr.Microphone()
logging.info('錄音中...')
with mic as source:
r.adjust_for_ambient_noise(source)
audio = r.listen(source)
logging.info('錄音結束,識別中...')
test = r.recognize_google(audio, language='cmn-Hans-CN', show_all=True)
print(test)
logging.info('end')
listen()
函數將監聽錄音,並等到靜音時停止。
until it encounters
recognizer_instance.pause_threshold
seconds of non-speaking or there is no more audio input.
等到顯示錄音中,開始說話,沉默后錄音結束。試驗中說了兩次:一次是你看看你看看
,二次是你再看看
。結果打印如下:
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
{'alternative': [{'transcript': '你看看你看看', 'confidence': 0.97500247}], 'final': True}
INFO:root:end
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
{'alternative': [{'transcript': '你再看看', 'confidence': 0.91089392}, {'transcript': '你在看看'}, {'transcript': '你猜看看'}, {'transcript': '你再敢看'}, {'transcript': '你在感慨'}], 'final': True}
INFO:root:end
INFO:root:錄音中...
識別率挺高,(還試過百度的baidu-aip
,因我的音頻沒識別出來作罷),語音轉文字就完成了。
2. 文字轉語音
使用pyttsx
模塊很簡單,python3下為pyttsx3
。
import pyttsx3
engine = pyttsx3.init()
engine.say("風飄盪,雨濛茸,翠條柔弱花頭重")
engine.runAndWait()
如此簡單即可聽到語音朗讀了。
3. 識別並響應
將上面的組合起來即可完成識別語音並響應了。
- 語音識別轉文字
- 文字正則匹配並找出對應的響應文字
- 響應(朗讀文字)
# coding:utf-8
"""
語音識別並響應。使用谷歌語音服務,不需要KEY(自帶測試KEY)。https://github.com/Uberi/speech_recognition
"""
import speech_recognition as sr
import pyttsx3
import re
import logging
logging.basicConfig(level=logging.DEBUG)
resource = {
r"(你看看?){1}.*\1": "我不看,再讓我看打死你",
r"(你看看?)+": "你看看你看看,操不操心",
r"(你.+啥)+": "咋地啦",
r"(六六六|666)+": "要不說磐石老弟六六六呢?",
r"(磐|石|老|弟)+": "六六六",
}
engine = pyttsx3.init()
while True:
r = sr.Recognizer()
# 麥克風
mic = sr.Microphone()
logging.info('錄音中...')
with mic as source:
r.adjust_for_ambient_noise(source)
audio = r.listen(source)
logging.info('錄音結束,識別中...')
test = r.recognize_google(audio, language='cmn-Hans-CN', show_all=True)
# 分析語音
logging.info('分析語音')
if test:
flag = False
message = ''
for t in test['alternative']:
logging.debug(t)
for r, c in resource.items():
# 用每個識別結果來匹配資源文件key(正則),正確匹配則存儲回答並退出
logging.info(r)
if re.search(r, t['transcript']):
flag = True
message = c
break
if flag:
break
# 文字轉語音
if message:
logging.info('bingo....')
logging.info('say: %s' % message)
engine.say(message)
engine.runAndWait()
logging.info('ok')
logging.info('end')
對應的資源文字為
resource = {
r"(你看看?){1}.*\1": "我不看,再讓我看打死你",
r"(你看看?)+": "你看看你看看,操不操心",
r"(你.+啥)+": "咋地啦",
r"(六六六|666)+": "要不說磐石老弟六六六呢?",
r"(磐|石|老|弟)+": "六六六",
}
這里剛好用到正則,其實剛開始沒打算用正則,想匹配兩次你看看
的時候就想起回溯,就用正則了。
很方便:比如磐石老弟
不好識別,就用(磐|石|老|弟)+
找出一個匹配即可;你看看你看看
用回溯\1
。因為匹配時候發現說的快了有時匹配一個看,就用了你看看?
來匹配你看
,其實后面的看?
要不要都可以,但為了說明目的,還是沒有去掉。
(你看看?){1}.*\1
能匹配
你看看你看看
你看看你看
你看你看看看...
這樣識別率就高了。因為識別結果匹配時候從頭往后匹配每個正則,遇到則完成,所以(你看看?){1}.*\1
需放在(你看看?)+
前面。不然語音識別到你看看你看看
就只能觸發(你看看?)+
了。
運行識別結果:
語音說了六次:
你看看,你看看你看看,你瞅啥,磐石,666,哈哈哈 (文字為了說明形象化,傳輸過去只是音頻)
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
INFO:root:分析語音
DEBUG:root:{'transcript': '你看看', 'confidence': 0.97500253}
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:bingo....
WARNING:root:say: 你看看你看看,操不操心
INFO:root:ok
INFO:root:end
--------------------------------------------------------------------
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
INFO:root:分析語音
DEBUG:root:{'transcript': '你看看你看看', 'confidence': 0.97500247}
INFO:root:(你看看?){1}.*\1
INFO:root:bingo....
WARNING:root:say: 我不看,再讓我看打死你
INFO:root:ok
INFO:root:end
--------------------------------------------------------------------
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
INFO:root:分析語音
DEBUG:root:{'transcript': '你瞅啥', 'confidence': 0.958637}
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:(你.+啥)+
INFO:root:bingo....
WARNING:root:say: 咋地啦
INFO:root:ok
INFO:root:end
--------------------------------------------------------------------
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
INFO:root:分析語音
DEBUG:root:{'transcript': '磐石', 'confidence': 0.80128425}
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:(你.+啥)+
INFO:root:(六六六|666)+
INFO:root:(磐|石|老|弟)+
INFO:root:bingo....
WARNING:root:say: 六六六
INFO:root:ok
INFO:root:end
--------------------------------------------------------------------
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
INFO:root:分析語音
DEBUG:root:{'transcript': '666', 'confidence': 0.91621482}
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:(你.+啥)+
INFO:root:(六六六|666)+
INFO:root:bingo....
WARNING:root:say: 要不說磐石老弟六六六呢?
INFO:root:ok
INFO:root:end
--------------------------------------------------------------------
INFO:root:錄音中...
INFO:root:錄音結束,識別中...
INFO:root:分析語音
DEBUG:root:{'transcript': '哈哈哈', 'confidence': 0.97387952}
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:(你.+啥)+
INFO:root:(六六六|666)+
INFO:root:(磐|石|老|弟)+
DEBUG:root:{'transcript': '哈哈哈哈'}
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:(你.+啥)+
INFO:root:(六六六|666)+
INFO:root:(磐|石|老|弟)+
INFO:root:end
INFO:root:錄音中...
一共六次,前5次都可以識別並匹配到,第6次測試期待之外的,不響應。INFO
為一般輸出,DEBUG
輸出google服務識別到的結果(不是所有結果,第一條匹配則忽略后面識別的多條結果),WARNING
輸出響應的語音(因為沒有錄在文章里聽不到,所以輸出看看說了什么)
分析第一次和最后一次:
第一次,說你看看
識別出來的第一條結果是{'transcript': '你看看', 'confidence': 0.97500253}
,匹配第一條正則(你看看?){1}.*\1
失敗,接着匹配第二條(你看看?)+
成功,break正則,並break識別結果test['alternative']
循環。之后語音輸出你看看你看看,操不操心
。
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:bingo....
WARNING:root:say: 你看看你看看,操不操心
最后一次,說哈哈哈
共識別出來兩條結果哈哈哈
和哈哈哈哈
,
{'transcript': '哈哈哈', 'confidence': 0.97387952}
{'transcript': '哈哈哈哈'}
各自嘗試匹配所有正則均以失敗告終
INFO:root:(你看看?){1}.*\1
INFO:root:(你看看?)+
INFO:root:(你.+啥)+
INFO:root:(六六六|666)+
INFO:root:(磐|石|老|弟)+
沒有bingo
只有end
,然后本次識別以未響應結束。
到這里 用不到60行代碼 就實現了語音識別並響應的功能。(我不喜歡這樣說XX行代碼就實現了XXX功能
,公眾號里網絡上各種關於Python文章充斥着這種標題,很令人反感。代碼短是Python那些模塊寫得好,應該感謝的是各位前輩們,而不是沾沾自喜到起噱頭標題並吸引一些浮躁的人前來。告誡自己。)
p.s. 寫代碼兩個多小時,寫文章大半天,從一團模糊的概念到語義化,也需得經過思考、組織、融合。有待改進的地方,還請多多指教。