×

语音控制界面实验开源分享

消耗积分:0 | 格式:zip | 大小:0.05 MB | 2022-12-14

吴湛

分享资料个

描述

介绍和动机

语音识别和语音界面在这方面发挥了重要作用,并将在我们未来的计算体验中发挥重要作用。长期以来一直是科幻小说的主要内容,现在可以成为每个人的科学现实。

我的第一个语音控制界面实验,“借给我你的耳朵!”:网络蓝牙和语音识别,是一个令人兴奋的实验。当它成为Hackster.io live 的主题并启发了我自己、VoiceBot101和其他人的许多新颖和衍生项目时,我感到非常高兴,例如:

此处为 Alexa 和 Google Assistant 发布的众多项目和技能也可以看出语音的流行和兴奋,例如:

几周前,我发现了一个开源语音识别和人工智能平台Mycroft.ai ,这听起来非常令人兴奋,我决定是时候对语音界面进行更多试验了。

这个项目描述将主要集中在我如何学会为 Mycroft 平台编写技能。我也会分享我的一些理解、困惑和总体印象。

Mycroft.ai网站和github 页面有很多你应该阅读的信息此外,还有一些关于该平台的优秀书面评论和评论,例如:

我探索 Mycroft 并编写此技能和其他技能的最终目标是回答以下问题:

  • 什么是好的语音界面设计?

Mycroft.ai/Picroft

Mycroft.ai是一个人工智能和语音识别系统。它类似于 Alexa、Google Assistant 或 Siri 等平台,但它是完全开源的,并且是用 Python 编写的。它有 3 个不同的版本:

  • Mycroft Mark 1 - 基于 Raspberry Pi 2 的开放硬件平台
  • 在 Linux Ubuntu 上运行桌面包
  • 用于在 Raspberry Pi 3 上运行Picroft 0.8

我一直在寻找一个很好的理由来开始认真地尝试 Raspberry Pi 3,因此 Picroft 0.8 选项对我来说是一个自然的选择。还有非常详尽的安装说明和兼容硬件指南也很吸引人。

从现在开始,我将交替使用 Mycroft 和 Picroft,但我专门为这个项目使用了 Picroft 0.8 版本。我也可以称他或他为 Mycroft/Picroft,但在此我遵循 Manuel “Mannie” Garcia O'Kelly 的约定。

Picroft 安装说明

对于这个项目,我使用了安装在 Pi Top Ceed 外壳中的 Raspberry Pi 3。我使用它是因为建议使用键盘和屏幕可以更轻松地与 Picroft 交互和安装。不过不要太兴奋,我没有在 Pi Top OS 上运行 Picroft,我只是覆盖了 SD 卡。这是我感兴趣的键盘和屏幕!

我还购买了兼容硬件列表中推荐的麦克风/扬声器组合之一。

在 Raspberry Pi 3 上安装 Mycroft 之前,有 2 套安装说明和提示需要查看:

执行和完成这些步骤不到 1 小时,令人惊讶的是,这一切都奏效了!直到我告诉Mycroft去睡觉。当我试图用唤醒词唤醒他时,他拒绝这样做。无奈之下,我重启了。然后他会认出唤醒词,我可以看到屏幕上显示的文本响应,但听不到任何音频。所以我回到了安装说明。. .

在上面第一个链接的安装说明和提示的底部,是有关如何配置 USB 麦克风的提示:

使用 USB 音频作为输出
通常,USB 音频应连接到 hwplug:1,0 但要验证运行以下命令:
aplay -L
找到您要使用的设备的 hwplug 输出,使用它并相应地更新 /etc/mycroft/mycroft.conf 文件:
"play_wav_cmdline": "aplay -Dhw:0,0 %1" 这行现在变成了 "play_wav_cmdline": "aplay -Dplughw:1,0 %1"
您现在可以运行 ./auto_run.sh 来启动程序备份和测试,并确保输出来自 USB 扬声器。

按照这些说明使用nano作为文本编辑器并重新启动,解决了麦克风问题。然而,让他入睡仍然需要完全重启才能让他对唤醒词做出反应。希望通过更多的工作,我将能够解决这个问题。

这带来了使用 Picroft 版本的重要一点。您应该对 Linux 中的基本命令行技能充满信心。到目前为止,它只是简单的东西,例如在目录中移动、安全删除文件和使用 nano。

技能的想法

我想创建一种比“Hello, World”示例更复杂的技能(他们已经有一个),但又不会复杂到阻止我完成它。

经过一番思考,我想出让 Mycroft 告诉我国际空间站 (ISS) 相对于其地球经纬度坐标的当前轨道位置。由于这两个数字对我没有太多意义,我还希望 Mycroft 告诉我这些坐标对应于地图上的什么。

好的,所以这不是最有用的技能,但它很有趣,并且有机会学习。以下两个部分是特定领域的信息,虽然对于编写技能不是必需的,但很有趣并且与技能的“输出”相关。

国际空间站(ISS)

国际空间站是一个巨大的太空科学实验室。它已经建成到目前的规模,航天飞机的许多最后任务都用于建造它。有趣的是,航天飞机的最初概念是让它与轨道空间实验室协同工作。它每 90 分钟绕地球一圈,平均高度为 240 英里。通过计算,它在一天内绕地球运行 16 次(24*60/90)。它的速度为每小时 17,500 英里。意味着从洛杉矶到纽约的旅行只需不到 9 分钟!这与我们的技能相关,因为通过如此快速的移动,它赋予了技能一些多样性和对其响应的兴趣,这是语音设计的一个重要特征。

反向地理编码

我找到了两个免费的开源 API,它们提供实时 ISS 坐标Open-Notifywheretheiss.at 我选择为这个项目使用 Open-Notify。

这两个 API 都以 JSON 有效载荷的形式提供相对于地球的 ISS 轨道位置,以及 ISS 最新的经纬度。我们可以简单地访问其中之一,解码 JSON 有效载荷以获取纬度和经度,并让 Mycroft 报告这两个数字。但是,“纬度 5.218 经度 115.008”(南海上空)或“纬度 44.590 经度 -104.715”(美国怀俄明州魔鬼塔上空)的报告对普通用户意味着什么?让技能提供地图坐标和有意义的地理特征名称(又名地名)提供了上下文,甚至可能与技能建立个人联系。因此,我们付出更多的努力将获得更好的用户体验。

当我们与地图服务(例如谷歌地图)交互时,我们会在地图上给它一个地址或 2 个已知点并询问方向,或者我们可能会使用 Uber 等乘车服务应用程序让我们的司机知道我们在哪里,并且我们想去的地方。这就是地理编码:我们识别地图上的点,然后应用程序将这些点转换为可计算的坐标。然而,我们的技能要求我们做相反的事情,我们给它一组坐标,它为我们提供了一个可识别的地理特征。这就是所谓的反向地理编码。许多映射 API 会将其作为功能提供到其服务中。

所以我们都准备好了,只需使用我们最喜欢的(和免费的)地图服务的反向地理编码功能,并解码生成的 JSON 有效负载!但是,有一个问题!您会发现,这些服务中的大多数都不涵盖主要水域,例如海洋!尝试使用大西洋和太平洋等要素的坐标,“未找到结果”是报告!我想这与将服务货币化有关。你真的找不到人去珊瑚海中央最近的星巴克!

幸运的是,GeoNames.org提供了一个 Web 服务,它确实在主要水域返回了有意义的有效载荷!我找不到另一个这样做的免费服务。如果你这样做,请告诉我!

访问 GeoNames.org 网站,申请用户名/帐户,每天最多可获得 30,000 次访问!请记住为您的用户名/帐户激活网络服务!问题解决了。

写作技巧

Mycroft 核心文档包含一个关于创建技能的优秀教程,并且在Mycroft github 页面上有许多技能可供学习。我不会在这里重复该教程,但会指出我在创建此技能时使用的要点。

创建技能需要具有特定结构的多个不同文件,并具有特定的文件名约定:

 
poYBAGOYPp-AVkMjAACubWTHfN8579.png
来自 Mycroft 的 github 页面,可以使用一个空白技能模板。
 

/dialog文件夹包含基于语言的文件,这些文件告诉 Mycroft 在执行技能时要说什么。这是来自 Mycroft.ai 的 github 存储库的Skill-hello-world/dialog/en-us/hello.world.dialog :

Hello world
Hello
Hi to you too

在回应你说“Hey Mycroft,Hello world”时,Mycroft 会从上面的 3 个回应中选择一个说出来。“Hey Mycroft”或只是“Mycroft”这些词是指示 Mycroft 应该开始聆听的唤醒词或触发词。

他怎么知道要听什么才能执行这项技能?这就是 /vocab 文件夹的来源。从教程中,

  • vocab 文件夹包含支持的每种语言的子文件夹,例如 en-us。在每个语言文件夹中,我们放置 .voc 文件,其中包含确定 Mycroft 将听什么来触发技能的短语或关键字。

我的技能的对话和 voc 文件有点复杂。这是Skill-iss-location/dialog/en-us/location.current.dialog

{{latitude}} latitude {{longitude}} longitude which corresponds to {{toponym}}
The space station is at {{latitude}} latitude {{longitude}} longitude over {{toponym}}
It's at {{latitude}} latitude {{longitude}} longitude, {{toponym}}
The ISS is now over {{toponym}} at {{latitude}} latitude {{longitude}} longitude
{{toponym}} at {{latitude}} latitude and {{longitude}} longitude

术语{{latitude}}{{longitude}}{{toponym}}是在执行与此技能关联的__init__.py文件期间填充的值的键。您在 python 代码中获得的值与这些键之间存在一对一的关系,这使得提供交互式体验变得非常容易。这些键周围的文本是自由形式的,您可以在此处放置任何内容,Mycroft 会在这些选项中进行选择以进行响应。

技能的核心在于__init__.py文件。我是这样看的:用python写的任何东西,都可以融入技能!有了可用的 Python 库的数量和种类,这里没有限制!该文件有一个特定的结构,我建议阅读教程并查看Mycroft.al github 站点中的技能,以了解如何从这里开始。我将在这里详细介绍我的代码。看来这一切都在 Python 2.7 中)。

/test/intent文件夹是什么我花了一段时间才开始理解这个文件夹的用途,我还在学习它。为了理解它,这些用 JSON 编写的文件允许 Mycroft 确保您的技能意图和您创建的文件中定义的声音表达都可以正常工作。他们没有定义声音交互,而是测试它,因此得名。在我比较了来自维基百科的测试/意图文件hello-world技能之前,我并不清楚这一点:

来自 hello-world测试/意图文件之一:

{
 "utterance": "Hello world",
 "intent_type": "HelloWorldIntent",
 "intent": {
   "HelloWorldKeyword": "hello world"
 }
}

这是来自维基百科的技能:

{
 "utterance": "tell me about the first world war",
 "intent_type": "WikipediaIntent",
 "intent": {
   "WikipediaKeyword": "tell me about",
   "ArticleTitle": "the first world war"
 }
}

比较这些文件,帮助我理解了这些文件的作用。该话语是 Mycroft 可能会听到的内容的示例,其中包含关键字和细节。intent_type对应于您的__init__.py文件中定义的意图处理程序,当您比较 JSON 文件的“intent:”键时可以看到,该键包含来自您的 vocab 文件的关键字,并且在 wiki 技能的情况下是键:值对通过。

所以这些文件并没有定义你可能的交互,而是特定的测试用例,以确保你的技能文件一起工作。

上面没有显示可选的/regex文件夹,它包含正则表达式,帮助 Mycroft 响应并解析它所听到的信息!这是非常令人兴奋的,但不是这个项目的主题。

使用安装了 Picroft 0.8 的 Raspberry PI 3 编写技能,需要您熟悉使用 linux 命令行。为了方便编写这项技能,我在我的 Windows 10 笔记本电脑上使用 atom 编写了所有需要的文件,然后将所有内容上传到 Github。

为了安装这项技能,我从我的 github 页面克隆了存储库并从那里开始工作。这不是处理软件的理想方式。但是,既然我对这个过程感到满意,我可能会提取模板技能并直接从那里使用nano作为我的编辑器,并正确地将更改推送/合并到我自己的存储库中。

该技能必须放在/opt/mycroft/skills 技能文件夹中。使用sudo reboot 重新启动系统后,我可以使用该技能。MSM(Mycroft Skill Manager)提供了一个工具,但我没有在这个项目中使用它。我还有很多东西要学!

Python 代码亮点

我发现通过命令行/纳米/语音界面进行调试的过程有点挑战。因此,我编写了一个测试脚本issTest006.py来调试__init__.py 意图函数处理程序的基本功能,以便在我的桌面上运行。

通过确保将非 Mycroft 相关功能与我的代码中与 Mycroft 平台相关的错误分开,这有助于简化我的调试过程。

 
pYYBAGOYPqGAMKeGAADyfsPwA-0325.jpg
使用测试脚本验证 python 代码。
 

我们实际上只是在这里编写一个 Python 脚本。该技能在 Python 中被创建为一个类。要在 Mycroft 环境中工作,该类必须包含它的功能。这些在教程和模板技能中都有概述:

class HelloWorldSkill(MycroftSkill): 
   def __init__(self): 
   def initialize(self): 
   def handle_thank_you_intent(self, message): 
   ... 
   def stop(self): 
def create_skill(): 
   return HelloWorldSkill() 

就我而言,我修改了这个模板以适应 ISS 定位技能:

__init__(self)函数用于创建 Skill 的实例

class ISSLocationSkill(MycroftSkill):
   def __init__(self):
       super(ISSLocationSkill, self).__init__(name="ISSLocationSkill")
   ...

这与任何其他 Python 类的__init__(self)构造函数没有什么不同。我没有要在这里设置的实例变量,所以这就是这里所需要的。

def initialize(self)是我们构建意图和注册意图处理程序的地方就我而言,只有一个。

您还可以在此处看到传递给函数的值匹配文件名、关键字 .voc 或我们之前编码中的 JSON 键。这是关键字、意图测试和意图处理程序之间建立联系的地方。因此,遵守 Mycroft 平台的命名约定很重要。

def initialize(self):
       iss_location_intent = IntentBuilder("ISSLocationIntent").require("ISSKeyword").build()
       self.register_intent(iss_location_intent, self.handle_intent)
...

我的技能只有一个意图,但您可以从Hello World Skill 中看到注册多个意图处理程序的样子:

def initialize(self):
       thank_you_intent = IntentBuilder("ThankYouIntent"). \
           require("ThankYouKeyword").build()
       self.register_intent(thank_you_intent, self.handle_thank_you_intent)
       how_are_you_intent = IntentBuilder("HowAreYouIntent"). \
           require("HowAreYouKeyword").build()
       self.register_intent(how_are_you_intent,
                            self.handle_how_are_you_intent)
       hello_world_intent = IntentBuilder("HelloWorldIntent"). \
           require("HelloWorldKeyword").build()
       self.register_intent(hello_world_intent,
                            self.handle_hello_world_intent)

我的技能的大部分代码都包含在意图处理程序中,这与稀疏的Hello World Skill 意图处理程序形成对比。他们只需将 Mycroft 指向对话框文件即可访问:

def handle_how_are_you_intent(self, message):
       self.speak_dialog("how.are.you")

需要注意的是,传递的值是执行技能时要访问的对话文件的名称,而不是您希望 Mycroft 说出的实际文本!传递的值是不带.dialog扩展名的文件名。

让我们深入了解我的意图处理程序,并尝试关注来自意图处理程序的信息是如何传递回 Mycroft 的。

我们要做的第一件事是从 open-notify 获取当前 ISS 位置。我们使用urllib2库发出请求,然后使用json解析 JSON 有效负载响应(请注意,urllib2 不是 Python3 中的单个库)。

 def handle_intent(self, message):
       # get the 'current' latitude and longitude of the ISS from open-notify.org in JSON
       reqISSLocation = urllib2.Request("http://api.open-notify.org/iss-now.json")
       resISSLocation = urllib2.urlopen(reqISSLocation)
       issObj = json.loads(resISSLocation.read()) # JSON payload of ISS location data
       latISS = issObj['iss_position']['latitude']
       lngISS = issObj['iss_position']['longitude']

你能看出上面的代码有问题吗?我可以并且经历过。如果 Open-Notify 的服务器已关闭或连接出现问题,会发生什么情况?这将导致程序崩溃。它不会使 Mycroft 崩溃,它只是不会为这种“错误”情况提供可接受的语音响应,让用户(像我一样)想知道到底发生了什么。为了纠正这个问题,在未来的版本中,我将使用 Python 中最棒的异常处理机制,就像我在后面的代码中所做的那样。

接下来,我们将使用从 Open-Notify 中解析出的 JSON 有效负载中的纬度和经度来创建 URL 请求字符串以访问GeoNames.org Web 服务。我们在这里创建 2 个字符串,因为对陆地和海洋/水特征有不同的反向地理编码 api 调用:

...
oceanGeoNamesReq = "http://api.geonames.org/oceanJSON?lat="+ latISS +"&lng="+ lngISS +"&username=YourUserName"
landGeoNamesReq  = "http://api.geonames.org/countryCodeJSON?formatted=true&lat=" + latISS + "&lng=" + lngISS +"&username=YourUserName&style=full"
...

不要忘记将您的用户名插入到上面的字符串中!

尽管国际空间站的轨道旨在一次覆盖全球 90% 的人口,但地球是 3/4 的水,每当我看时,大部分轨道似乎都在一片水域之上。因此,正如下面代码中的注释所示,我们首先检查坐标是否将地理编码反向到海洋位置。再次在这里我应该把它放在异常处理块中。未来的东西。. .

# Since the Earth is 3/4 water, we'll chek to see if the ISS is over water first;
       # in the case where this is not so, we handle the exception by  searching for a country it is
       # over, and is this is not coded for on GenNames, we just we say we don't know
       oceanGeoNamesRes = urllib2.urlopen(oceanGeoNamesReq)
       toponymObj = json.loads(oceanGeoNamesRes.read())

在下一段代码中,我们绝对必须将其包装在异常处理块中。当我们尝试从海洋位置解析从GeoNames.org返回的 JSON 有效负载时,如果国际空间站位于陆地上方,则会产生错误,因为有效负载中不存在海洋名称密钥。

如果我们得到这个错误,我们的第一个假设将是我们在陆地上。因此,我们将尝试访问土地的反向地理编码服务。实际上,我们可以通过检查返回的状态/错误代码来使这更复杂,但是这个假设,陆地与水体,似乎工作得很好:

try:
           toponym = "the " + toponymObj['ocean']['name']
       except KeyError:
           landGeoNamesRes = urllib2.urlopen(landGeoNamesReq)
           toponymObj = json.loads(landGeoNamesRes.read())
           toponym = toponymObj['countryName']
       except:
           toponym = "unknown"

因此,在这里,如果出现 KeyError,我们会尝试从有效负载中解析海洋名称键,然后我们通过对陆地特征进行反向地理编码来处理此错误,并从中获取国家名称。在访问此 Web 服务产生错误的情况下,我们通过将其称为未知位置来处理此问题。也许不是最好的,但它是开始处理海洋和陆地坐标都不能正确反转地理编码的情况的好方法。如果您从 GeoNames.org 探索许多反向地理编码 Web 服务,您可能会发现来自 Mycroft 的其他有用的 JSON 有效负载项。看看,让我知道或自己修改!

到目前为止,为 intent_handler 描述的所有代码都只是 Python。我们还没有与 Mycroft 互动!您可以通过将此代码与我编写的 Python 测试脚本进行比较来看到这一点。我想展示这一点,因为不仅再次用 Python 编写很有趣,而且还证明在我看来,如果你可以用 Python 编写它,你就可以让 Mycroft 在语音控制下运行它!这是非常强大的东西!

现在让我们看看我们如何将我们新发现的关于 ISS 在哪里的知识传递给 Mycroft:

if toponym == "unknown":
           self.speak_dialog("location.unknown",{"latitude": latISS, "longitude": lngISS})
      else:
           self.speak_dialog("location.current",{"toponym":toponym, latitude": latISS, "longitude": lngISS})

就在这里!还记得我们讨论过location.current.dialog文件中的{{latitude}}键吗?好吧,这是我们将值传递给这些键的地方!

正如您在此处看到的,如果我们找到了陆地或水的地名,我们会将纬度、经度和地名传回对话文件以供 Mycroft 发言。如果两者都失败了,并且我们为给定的纬度和经度生成了一个未知的地名,我们选择location.unknown.dialog文件并让 Mycroft 从那里执行一个短语!

很酷,对吧?

如您所知,为 Mycroft 编写代码比记录所有这些更容易!

示威

让我们看看几种不同情况下的技能:

需要改进

完美是完整的敌人。所以我选择了代码,因为我得到了很多我想尝试的想法,但我也想把它拿出来,这样人们就可以看到在这个平台上构建语音应用程序是多么容易和有趣.

话虽如此,正如您将在下面的演示中看到的那样,我仍然没有弄清楚如何让 Mycroft 在识别方面正确处理首字母缩写词或首字母缩写词,如 ISS。

在语音方面,从请求到响应也有一点延迟。这可能是由于我的设置、编码或此时平台固有的。我知道所有这些都可能会随着时间的推移而改善。

我们还依赖 2 个 Web 服务,Open-Notify 和 GeoNames。如您所见,我真的没有尝试处理服务器错误。一个简单的修复,但仍然需要做一个来提高这个技能!

结论/一些观察

我已经让 Mycroft(Picroft 0.8) 在 Raspberry Pi 3 上运行了一周多一点,并为此编写了我的第一个技能。这个平台激励我的一件事是我对它的参与感。我觉得我想要进行的不仅仅是“单回合”事件或技能。一旦我习惯了在命令行上并再次使用 nano,就很容易编写该技能,我真的很喜欢一切都在 Python 中。我最近没有机会用 Python 编写代码,所以这是一个再次这样做的好机会,也开始梦想 Mycroft 和 Python 可以在 Raspberry Pi 3 上组合完成的所有事情。

到目前为止,对于 Mycroft 是否允许我创建一个好的声音设计这个问题的答案是肯定的。dialogvocab__init__.py文件之间的相互联系清晰且易于使用。

沿消息总线传递信息的意图和意图处理程序的概念对我来说很有意义。我期待在我的语音询问中探索正则表达式,然后尝试创建多轮技能。

什么是好的声音设计?我希望通过这个平台更多地探索这个问题并提高我的能力。如果你有一个 Raspberry Pi 3 和 Ubuntu 盒子可以使用,或者即使你可以购买 Mycroft Mark 1,我认为在这个平台上进行实验是非常值得的!


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !