维基少年:树莓派/树莓派语音控制
教程作者: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": "[email protected]",
"client_id": "100247033194168111983",
"auth_uri": "https://127.0.0.1/o/oauth2/auth",
"token_uri": "https://127.0.0.1/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"
}