跳轉至內容

維基少年:樹莓派/樹莓派語音控制

來自華夏公益教科書,開放書籍,共建開放世界

教程作者: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"
}
華夏公益教科書