跳转至内容

维基少年:树莓派/树莓派语音控制

来自维基教科书,开放书籍,共建开放世界

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