維基少年:樹莓派/樹莓派語音控制
教程作者:Andrew Oakley
公有領域 2017 年 9 月 23 日
www.cotswoldjam.org
透過點選右上角的樹莓派選單,選擇“附件”,然後點選“終端”來啟動終端。一個黑色視窗將會出現。輸入以下內容,然後按 ↵ Enter
espeak "Hello world"
你應該聽到一個復古的聲音,就像史蒂芬·霍金教授使用的。嘗試讓它說出不同的短語。接下來,讓我們嘗試使用更好的聲音,由 Android 智慧手機使用。
pico2wave -w output.wav "Pico sounds much better than E speak"
aplay output.wav
注意 pico2wave 如何建立一個 .wav 音訊檔案,我們必須使用 aplay 命令單獨播放它。
我們可以使用語音合成來說話,而不是將文字列印到螢幕上。讓我們從一個簡單的猜數字遊戲開始,它還沒有語音功能。
從樹莓派選單中,選擇“程式設計”,然後選擇“Python 3 (IDLE)”。點選“檔案”選單,然後選擇“開啟”,選擇 python 目錄,voice 子目錄,並選擇檔案:guess1.py。
透過點選“執行”選單,然後選擇“執行模組”來運行遊戲程式,並自己玩一下。
看看這個程式。看看它如何使用 print 命令在螢幕上顯示文字。
透過“檔案”選單,選擇“關閉”來關閉程式。現在開啟程式 guess2.py 並執行它。
你將聽到語音指令,而不是在螢幕上顯示文字。
看看 guess2.py 程式。看看它如何建立一個 say 函式,它被用作 print 命令的替代。
def say(input):
os.system("pico2wave -w output.wav \"{}\" && aplay -q
output.wav".format(input))
say ("I am thinking of a number from 1 to 99.")
say 函式看起來很複雜,但我們不必擔心它,因為我們現在可以在程式中直接使用 say ("我們想說的話")。如果你願意,你可以將 say 函式改為使用較舊的 espeak 語音合成器。
def say(input):
os.system("espeak \"{}\"".format(input))
say ("I am thinking of a number from 1 to 99.")
合成(建立)語音很容易,但識別語音卻困難得多——也就是說,讓計算機傾聽人類的聲音並理解所講的話語。
樹莓派沒有足夠的處理能力來執行良好的語音識別,尤其是在世界上有數百種不同的語言和口音的情況下。
我們可以訓練它識別我們自己的聲音,但這可能需要幾天甚至幾周的時間。
相反,我們可以使用樹莓派錄製一些語音,並將這些錄音傳送到網際網路上的其他更強大的計算機上。
在我們的示例中,我們將使用 Google 的語音應用程式程式設計介面 (API)。設定它相當複雜,但是如果你參加我們的研討會,我們已經為你完成了設定,所以你應該能夠直接使用它。
樹莓派沒有內建麥克風,也沒有麥克風插孔。我們使用便宜的 USB 麥克風來錄製音訊,並且已經為我們的研討會設定好了。
從終端(樹莓派選單,附件,終端)中,輸入以下內容
cd /home/pi/python/voice
arecord -f cd -f S16_LE -c 1 -D hw:1,0 -d 10 test.wav
按 ↵ Enter 然後對著麥克風說十秒鐘(嘗試童謠)。接下來,使用以下命令播放聲音
aplay test.wav
你可以將錄音時間從 10 秒改為 5 秒,將 -d 10 改為 -d 5。
“轉錄”是指將大聲說出來的話語用文字記錄下來。我們將把錄音傳送到 Google 的計算機,它們會將文字發回給我們。
python3 transcribe.py test.wav
這將需要幾秒鐘,然後它應該顯示你所說的話。
如果它沒有理解你的語音,請嘗試大聲一點,靠近麥克風說話。你可能還需要要求房間裡的其他人安靜一點。
回到 IDLE 視窗,透過“檔案”選單,選擇“關閉”來關閉程式。現在開啟程式 guess3.py 並透過“執行”選單,選擇“執行模組”來執行它。
這一次,遊戲不僅會和你說話,還會傾聽你的答案。它只會在每個問題後等待 3 秒,所以要儘快回答。
看看這個程式。注意它如何將所有不是數字的內容說出來。
guess=transcribe.transcribe_file("recording.wav")
…
if not guess.isdigit() :
say ("I think you said: {}".format(guess))
say ("That's not a number. Try again.")
continue
你可以盡情讓它說出各種東西。不要粗魯!
如果 2 秒鐘對你來說太短了,你可以透過修改這行程式碼來更改超時時間。
def listen(filename,seconds=3):
將 seconds=3 改為 seconds=5 或者你想要的任何值。但是,將其設定得過長會讓遊戲變得很無聊。
注意,我們在完成錄音但還沒有將錄音傳送到 Google 進行轉錄之前,我們會說“我在想……”。轉錄可能需要一段時間,因此我們需要讓玩家知道計算機已經停止錄音,但仍在工作。
listen ("recording.wav")
say ("I'm thinking...")
guess=transcribe.transcribe_file("recording.wav")
本部分適用於希望獨立於 Cotswold Jam 使用本研討會的老師。除了網際網路連線,你還需要一個麥克風。我們使用了來自亞馬遜的這款便宜的 USB 麥克風(ASIN:B01CQKCTSM)。
在包含本文件的 .zip 檔案中,你應該還會找到其他檔案,這些檔案需要放置到 /home/pi/python/voice 目錄中。如果該資料夾不存在,你可能需要建立它。
你可以使用以下命令從終端安裝語音合成程式並配置 USB 麥克風。
sudo apt update && sudo apt install -y espeak pico2wave
sudo cat <<! /etc/asound.conf
pcm.!default {
type asym
playback.pcm "hw:0,0"
capture.pcm "hw:1,0"
}
現在執行alsamixer並按下F6 選擇 USB 麥克風,按下F4 選擇錄製。確保柱狀圖下方顯示“LR Capture”;使用 Space 開啟/關閉錄製/靜音。
按下Esc 鍵退出。
讓 Google Cloud Speech API 正常工作實際上是 Linux 和 API 專家的工作。您可以在 https://cloud.google.com/sdk/docs/quickstart-debian-ubuntu 找到說明,但請注意,這並不簡單。特別地,您需要申請 Google Cloud 帳戶並啟用語音服務,儘管這附帶 300 美元的免費額度(截至撰寫本文時),但設定需要銀行卡。
您可能更容易直接下載我們製作好的 Raspbian 映象;在 http://www.cotswoldjam.org/tutorials 檢視,儘管包含的 Cotswold Jam API 金鑰上的額度很可能早已過期。您需要將自己的 Google Voice API 金鑰放入 ~/python/voice 目錄中,以替換我們的 voice-workshop-key.json 檔案(它由 transcribe.py 呼叫)。請注意,我們的 Raspbian 映象使用耳機插孔播放音訊;您可能需要右鍵單擊桌面音量控制以切換到 HDMI 輸出。
檔案
[edit | edit source]Voice-workshop.pdf
[edit | edit source]本教程的原始 PDF 可在維基共享資源獲得:Voice-workshop.pdf
espeak-example.sh
[edit | edit source]#!/bin/bash
espeak -ven-gb -k5 -s150 "Shall we play a game?"
pico2wave-example.sh
[edit | edit source]#!/bin/bash
# sudo apt-get install libttspico-utils
pico2wave -w output.wav "That was a triumph. I'm making a note here; huge success." && aplay output.wav
record.sh
[edit | edit source]#!/bin/bash
arecord -f cd -f S16_LE -c 1 -D hw:1,0 -d 10 test.wav
guess1.py
[edit | edit source]#/usr/bin/python3
import random
print ("I am thinking of a number from 1 to 99.")
mynum=random.randint(1,99)
while True:
guess=input ("Type your guess and press Enter: ")
if not guess.isdigit() :
print ("That wasn't a number. Try again.")
continue
guess=int(guess)
if guess<1 or guess>99 :
print ("That wasn't a number from 1 to 99. Try again.")
continue
if guess==mynum :
print ("Well done! You guessed my number.")
break
if guess>mynum :
print ("My number is lower.")
if guess<mynum :
print ("My number is higher.")
guess2.py
[edit | edit source]#/usr/bin/python3
import random, os
def say(input):
os.system("pico2wave -w output.wav \"{}\" && aplay -q output.wav".format(input))
say ("I am thinking of a number from 1 to 99.")
mynum=random.randint(1,99)
while True:
say("Type your guess and press Enter: ")
guess=input (">: ")
if not guess.isdigit() :
say ("That wasn't a number. Try again.")
continue
guess=int(guess)
if guess<1 or guess>99 :
say ("That wasn't a number from 1 to 99. Try again.")
continue
if guess==mynum :
say ("Well done! You guessed my number.")
break
if guess>mynum :
say ("My number is lower.")
if guess<mynum :
say ("My number is higher.")
guess3.py
[edit | edit source]#/usr/bin/python3
import random, os, transcribe
def say(input):
os.system("pico2wave -w output.wav \"{}\" && aplay -q output.wav".format(input))
def listen(filename,seconds=3):
os.system("arecord -q -f cd -c 1 -D hw:1,0 -d {} '{}'".format(seconds,filename))
say ("I am thinking of a number from 1 to 99.")
mynum=random.randint(1,99)
while True:
say ("Say your guess: ")
listen ("recording.wav")
say ("I'm thinking...")
guess=transcribe.transcribe_file("recording.wav")
if guess=="" :
say ("I didn't hear you. Try again.")
continue
if not guess.isdigit() :
say ("I think you said: {}".format(guess))
say ("That's not a number. Try again.")
continue
guess=int(guess)
if guess<1 or guess>99 :
say ("That's not a number from 1 to 99. Try again.")
continue
if guess==mynum :
say ("Well done! You guessed my number.")
break
if guess>mynum :
say ("My number is lower.")
if guess<mynum :
say ("My number is higher.")
transcribe.py
[edit | edit source]#!/usr/bin/env python
# export GOOGLE_APPLICATION_CREDENTIALS=service-key.json # Your Google voice service key
# python transcribe.py resources/audio.raw
import argparse, io, os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "voice-workshop-key.json"
def transcribe_file(speech_file):
from google.cloud import speech
from google.cloud.speech import enums
from google.cloud.speech import types
client = speech.SpeechClient()
return_value = ""
with io.open(speech_file, 'rb') as audio_file:
content = audio_file.read()
audio = types.RecognitionAudio(content=content)
config = types.RecognitionConfig(
encoding=enums.RecognitionConfig.AudioEncoding.LINEAR16,
sample_rate_hertz=44100,
language_code='en-GB')
response = client.recognize(config, audio)
for result in response.results:
return_value=result.alternatives[0].transcript
return (return_value)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'path', help='Audio file to be recognised')
args = parser.parse_args()
print (transcribe_file(args.path));
voice-workshop-key.json
[edit | edit source]{
"type": "service_account",
"project_id": "voice-workshop",
"private_key_id": "e6e3507492d9a7574caf1a3059093d5a893cb88c",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcxWsCs1HuExeF\nv2iK2l/wb6s7x9fTB2e4un3rrc0XIaUcnvSvE0JBiewRc7zQO6vA3lH+0bObTtCI\nT0ygv3M/ztaUTqNY+Z++IQPeOQnGb1YQJnsn5JEwsa3giSLH1SNYCIxGC4ZbMae4\npS9oSFDz3SVHKplRlL3sNaXRtKB2oxLumiqo3BTKfKViKAn2GY+DdbSX8Lk6JZC5\nL/7sRGkzRRzJkN9IV7BJrTwOL9YvqRkurRojzU8tnWu6Ik5sFbueW2ZAnJBhMvsD\nKVvd0LpshFMQKDvOdS9axiiivpIfJsnbrF877Och09FLIHB2TEBNLoxYNEHiA8Z1\n+qRDnjvZAgMBAAECggEAFTaReFgTagejUzHHT6KlAL/bh8dO6PtDibeSemWWLt8a\nnHjV2yrb+EpVazciAXtRjlOG/NgbWZScmxU/5TCCw8uVNZQ+fjeo6e1FvLgzHmrK\nVn0ehRNkohYo1Q05a5jno23krUW59HUPoOiZNZ8zdQwjkzGsdWGPIXtQ6MNsQyEv\nXduMOiTflV20/hSs+ZpZ84ho0nJQLj5bBkKKXYqehw2Ekl6Gevqin5WqPTVxn0of\nVj25zA6zfL8Bc+VRe+xsDk3eAjHC9XcmkRFOi8irHLYmNAd+WWUHupbgi8qeSW7u\n2GBg00c7AwtaHG1duV8bLRk75rNflaPu0xJYzRyBEQKBgQDdZkd+XZzLDFYTQoyK\nhEh+9aNUsBfAycb7wRqVqAFunaEYZsSanPzWs5NUoL2b6kyO6BrL9BkQe89ZqP//\n2QYkbhUbZj+V4eG9DNMQ4FNaBZsd9PiMmYxB35MyKKF0R3DpKTUBo9bF/g5+hZGD\nALi1sWwzj3u5TVyOvQrQSC7YuwKBgQC1RX8RvpJDGKSY8UFXiowGka39rCx+8m62\nF3JY+5oK0VaL89sjt3J5rleDWKumj9kO04gHASedKoQa/WyD4ul8Ug1bIfZKipKZ\nEBdBuJN2pewClRM1tIYNPzG3svMj9gtRiFd19+0SLaISOsbefATNk27ymANLAVNf\nPgqe/xeuewKBgGQ2R2YLOU0u6EcPeE26UpYk2SkcC6RXsJmDbmUPBpbrAl/pJFRX\nepoz7hwAJdLM2ppUtMxcUHwFjnUm6bkEoqMasLMWNPHCrErF40NgRloY730/xMDf\nP30Rla6+dVYMgC8JV9TGNBCqTiU2kAab7P9Qr4knCPl26s4xAxQDmDDBAoGAUACI\nAFDXRH2Px2BSskwXWJ7a52YhjTV53yuh79u7NKMHS2Uohi7keweS4Ak2WKCL75s0\nIcNEtHybKT5Hsj1nRtL/ygTHKkbWRG9xlDPeATNhYhJhFAbEUvxc+PIllO12OVmv\nIAV3v9ob+WevdWnOxNwYz0B/046WOSaskVeMIBkCgYEAwvVg4xXqa5MMuwY3Cvok\nyLIFD1cHeWVidxGCnipfnxAhRj1o/099IhoN2wAkGCmrd710OVWG164CW0CPm7A9\n6JkmtbfUUR6BLXMXmGWOicPbAQ6lInRw8mjvkCmty+bF3yig2VB9B9u0Dc9ieXhi\nwzrW5hDkUMyvIjHej+ipL7w=\n-----END PRIVATE KEY-----\n",
"client_email": "voice-workshop@voice-workshop.iam.gserviceaccount.com",
"client_id": "100247033194168111983",
"auth_uri": "https://#/o/oauth2/auth",
"token_uri": "https://#/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/voice-workshop%40voice-workshop.iam.gserviceaccount.com"
}