2020年度卒研セミナー(2020/07/30)

関連サイトと資料

5.Vision and Home Automation

この本の最初の4つの章では、あらゆる種類のビデオストリームを取り込んで、 最初にコンピューターで分析し、次にRaspberry Piである限られた小さなデバイスで分析する方法を示しました。

この最後の章では、ビデオ部分を音声部分にリンクすることで可能性を広げながら、ギャップを埋めます。

Apple Siri、Google Assistant、Amazon Alexa、Microsoft Cortonaなどの音声プラットフォームの登場により、 音声アシストサービスがあらゆる場所に登場しています。 人間は言語の生き物です。 私たちは自分自身を表現する必要があります。 描画は、映画マイノリティレポートで行われたように、デバイスと対話するためのオプションでもありますが、 正直なところ、そのようなことを行うにはかなり大きくて高価な画面が必要です。 音声はどこでも機能し、それを使用するために高価なツールは必要ありません。 音声を使用することは直感的で力を与えます。

私は個人的に、音声アシスタントを日常生活でどのように使用できるかについて多くの例を持っています。 コーヒーショップに入り、「フロントテラスでシナモンを上に置いたダブルショットカプチーノをお願いします」と尋ねると、 外の太陽の下で自動的に作られ、配達されます。

次に、もう1つの簡単な例を示します。ATMにアクセスして、 「メインの口座から$ 100を20ドルの5つのメモで引き出してください。」 音声アシスタントがあなたの声を認識するため、 無限の確認画面をスクロールする必要がありません。 あなたはあなたの手形を得るだけです。 (今日、すべての仮想通貨で、ATMに行く必要がなくなったと思いますよね?)

これらは日々の例ですが、サイエンスフィクションの映画を見て、 音声制御の宇宙船やコーヒーマシンを見ることができます。 これは大きいです。

それでは、Siriなどの既存のソフトウェアアシスタントを音声認識に使用しないのはなぜでしょうか。 さて、すべての主要なプレーヤーは基本的にすべてのユーザーデータを制御し、 パイプラインを通過するあらゆるデータにいつでもアクセスできます。 これは、エンドユーザーにとって煩わしいものになる可能性があります。 独自のソリューションを作成して設計する場合と同じように煩わしいものです。 ただし、可能な限り、データが流れる場所、または実際には流れない場所を制御できるようにしたいと考えています。

この章では、Rhasspyを紹介します。(https://github.com/synesthesiam/rhasspy)

Rhasspyは、ホームアシスタントとの音声インターフェイスを持ちたいがプライバシーと自由を重視する上級ユーザー向けに作成されました。 Rhasspyはフリー/オープンソースであり、次のことができます。

この章では、次のことを行います。

Rhasspy Message Flow

基本的に、Rasspyは継続的に「ウェイクアップワード」を探します。 これらの単語の1つによって呼び出されると目覚め、次の文を記録して単語に変換します。 次に、認識できるものに対して文を分析します。 最後に、文が既知の文と一致する場合、確率を含む結果が返されます。

アプリケーションの観点から見ると、音声アプリケーションを構築するときに操作する必要がある主なものはメッセージングキューであり、 ほとんどのIoTの場合はMQTTサーバーです。 MQTTはテレメトリ対策に重点を置くために開発されたため、軽量で、できるだけリアルタイムに近いものである必要がありました。 MosquittoはMQTTプロトコルのオープンソース実装です。 軽量で安全で、IoTアプリケーションと小型デバイスにサービスを提供するために特別に設計されました。

音声認識を行うには、まず、一連の文を使用してRhasspyコマンドまたはインテントを作成します。 各文はもちろん単語のセットであり、1つ以上の変数を含めることができます。 変数は、他のもので置き換えることができる単語であり、音声エンジンによって音声検出が実行されると、 対応する値が割り当てられます。

簡単な天気予報サービスの例では、次の文:

これは、次のように定義されます。

ここで、「when」は日時型であり、今日、明日、今朝、または翌月の第1火曜日のようなものになります。

「What is the weather」はそのコマンドのコンテキスト内で固定されており、決して変更されません。 ただし、<datetime>の場所は変数であるため、認識しやすくするために、エンジンのタイプに関するヒントを提供します。

インテントが認識されると、Rasspyはインテントに関連付けられたMosquittoトピックにJSONメッセージを投稿します。

この章で作成する例は、ビデオストリームで検出された特定のオブジェクトを強調表示するようにアプリケーションに要求することです。

たとえば、次の文では、catsは可変コンテナーであり、検出するオブジェクトを指定します。

この例では、インテントが認識されている場合、送信されるメッセージは、JSONベースのメッセージであり、次のメインセクションがあります。

リスト5-1は、JSONメッセージのサンプルを示しています。

リスト5-1
{
  "sessionId": "9d355e0e-218b-4efa-bf36-9c8b13a7df42",
  ...
  "input": "show me only cats",
  "asrTokens": [
    [
      {
        "value": "show",
        "confidence": 1,
        "rangeStart": 0,
        "rangeEnd": 4,
        "time": {
          "start": 0,
          "end": 0.98999995
        }
      },
      ...
    ]
  ],
  "asrConfidence": 0.9476952,
  "intent": {
    "intentName": "hellonico:highlight",
    "confidenceScore": 1
  },
  "slots": [
    {
      "rawValue": "cats",
      "value": {
        "kind": "Custom",
        "value": "cats"
      },
      "alternatives": [],
      "range": {
        "start": 13,
        "end": 17
      },
      "entity": "string",
      "slotName": "object",
      "confidenceScore": 0.80663073
    }
  ],
  "alternatives": [
    {
      "intentName": "hellonico:hello",
      "confidenceScore": 0.28305322,
      "slots": []
    },
    ...
  ]
}
    

インテントからのJSONメッセージは、MQTTブローカーの指定された名前付きメッセージキューに送信されます(インテントごとに1つのキュー)。 インテントメッセージの送信に使用されるキュー名は、次のパターンに従います。

したがって、これはこの例では次のようになります。

要約すると、図5-1は単純なメッセージフローを示しています。

システムの相互作用の主なポイントはシステムキューなので、 まずキューブローカーをインストールして、それと相互作用する方法を見てみましょう。

MQTT Message Queues

メッセージキューを使用してRhasspyからメッセージを受信できるようにするために、 最も広く使用されているMQTTブローカーの1つであるメッセージブローカーMosquittoをインストールします。 MQTTは軽量でエネルギー効率の高いプロトコルであり、 メッセージレベルで設定できるさまざまなレベルのサービス品質を備えています。 MosquittoはMQTTの非常に軽量な実装です。

Installing Mosquitto

Mosquittoのインストール手順はすべてのプラットフォームで利用でき、ダウンロードページにはインストーラへのリンクがあります。
https://mosquitto.org/download/

Windowsでは、.exeベースのインストーラーをダウンロードする必要がありますが、 他のプラットフォームでは、次に示すように、通常のパッケージマネージャーを介してソフトウェアを利用できます。

Mosquittoがインストールされたら、Mosquitoサービスが正しく開始され、 メッセージをリレーする準備ができていることを確認する必要があります。 たとえば、Macでは次のようにします。

コマンドラインでは、2つのコマンドを使用できます。 1つは特定のトピックのメッセージをパブリッシュするためのコマンドで、 もう1つは特定のトピックのメッセージをサブスクライブするためのコマンドです。

Comparison of Other MQTT Brokers

参考までに、他のいくつかのMQTTブローカーの比較をここで見つけることができます。
https://github.com/mqtt/mqtt.github.io/wiki/server-support

RabbitMQは強力なオープンソースの候補ですが、クラスタリングのサポートは確かに堅牢ですが、 セットアップはそれほど簡単ではありません。

MQTT Messages on the Command Line

Mosquittoをインストールすると、コマンドラインでmosquitto_pubコマンドを使用して、 トピックに関するメッセージをブローカーに送信するのがかなり簡単になります。 たとえば、次のコマンドは、トピックhelloのメッセージ"I am a MQTT message" をホスト0.0.0.0にあるブローカーに送信します。

もちろん、次のようにmosquitto_subと同等のサブスクライバーコマンドがあります。

Raspberry Piでキューを開始してコンピューターからメッセージを読み取る、またはその逆を行うことをお勧めします。

主な問題は、ターゲットマシン、コンピューター、またはRaspberryのIPアドレスまたはホスト名を知ることだけですが、 メッセージの送信は、mosquitto_pubおよびmosquitto_subコマンドを使用して同じ方法で行われます。

追加の手順として、Mosquitto MQTTブローカーをクラウドで実行することもできます。 たとえば、次のようなサービスからキューを作成して統合できます。
https://www.cloudmqtt.com/

通常、メッセージを表示するための私のお気に入りのグラフィカルな方法は、 図5-2に示すように、MQTTのグラフィカルクライアントであるMQTT Explorerを使用することです。

MQTT Explorerは次の場所からダウンロードできます。
https://github.com/thomasnordquist/MQTT-Explorer/releases

また、お気に入りのパッケージマネージャーからも利用できるはずです。 インテントメッセージの完全な同等物を送信することに戻りましょう。

コマンドラインを使用して、JSONファイルをターゲットインテントキューに直接送信できます。 mosquitto_pubを使用すると、次のようになります。

ここで、onlycats.jsonはリスト5-1で見たばかりのコンテンツを含むJSONファイルです。

MQTT Explorerがまだ実行されている場合は、図5-3に示すように、対応するキューにメッセージが表示されます。

コマンドラインからMQTTを使用してメッセージを操作、パブリッシュ、サブスクライブする方法を見てきました。 Javaを使って同じことをしましょう。

MQTT Messaging in Java

このセクションでは、Java / Visual Studio Codeセットアップとの間でメッセージを送受信します。 これにより、メッセージフローをより簡単に理解できます。

Dependencies Setup

前の章のプロジェクトを使用するか、プロジェクトテンプレートに基づいて新しいプロジェクトを作成するかのいずれかで、 MQTTメッセージングキューと対話し、JSONコンテンツを解析するJavaライブラリをいくつか追加します。

pom.xmlファイルの依存関係セクションは、コードリスト5-2のようになります。

リスト5-2
<dependencies>
<dependency>
  <groupId>org.eclipse.paho</groupId>
  <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  <version>1.1.0</version>
</dependency>
  
<dependency>
  <groupId>origami</groupId>
  <artifactId>origami</artifactId>
  <version>4.1.2-5</version>
</dependency>
  
<dependency>
  <groupId>org.json</groupId>
  <artifactId>json</artifactId>
  <version>20190722</version>
</dependency>
  
<dependency>
  <groupId>origami</groupId>
  <artifactId>filters</artifactId>
  <version>1.3</version>
</dependency>
</dependencies>
    

基本的に、以下のサードパーティライブラリを使用します。

Sending a Basic MQTT Message

私はこの本の一部をロシア上空を飛行する飛行機で書いているので、 MQTTプロトコルを使用して、仲間のロシアのスパイに簡単な「ハロー」メッセージを送信します。

これを行うには、次の手順を実行します。

  1. MQTTクライアントオブジェクトを作成し、ブローカーが実行されているホストに接続します。
  2. そのオブジェクトを使用して、connectメソッドを使用してブローカーに接続します。
  3. 文字列をバイトに変換したペイロードでMQTTメッセージを作成します。
  4. メッセージを公開します。
  5. 最後に、完全に切断します。

リスト5-3は、かなり短いコードスニペットを示しています。

リスト5-3
package practice;
  
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
   
public class MqttZero {
  public static void main(String... args) throws Exception {
    MqttClient client = new MqttClient("tcp://localhost:1883", MqttClient.generateClientId());
    client.connect();
    MqttMessage message = new MqttMessage();
    message.setPayload(new String("good morning Russia").getBytes());
    client.publish("hello", message);
    client.disconnect();
  }
}
    

図5-4に示すように、準備が整っていることを確認するには、 実行中のMQTT Explorerインスタンスにメッセージが表示されていることを確認します。

もちろん、最初にhelloトピックを聞く必要があります。

Simulating a Rhasspy Message

Rhasspyメッセージに相当するものを送信することはそれほど難しくありません。 リスト5-4に示すように、JSONファイルのコンテンツを文字列に読み取り、単純な文字列で行ったように送信します。

リスト5-4
package practice;
   
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
   
public class Client {
  public static void main(String... args) throws Exception {
    MqttClient client = new MqttClient("tcp://localhost:1883", MqttClient.generateClientId());
    client.connect();
    List<String> nekos = Files.readAllLines(Paths.get("onlycats.json"));
    String neko = nekos.stream().collect(Collectors.joining("\n"));
    MqttMessage message = new MqttMessage();
    message.setPayload(neko.getBytes());
    client.publish("hermes/intent/hellonico:highlight", message);
    client.disconnect();
  }
}
    

終わりのない猫への愛情が、MQTTエクスプローラーウィンドウの図5-5に再び表示されます。

JSON Fun

MQTTトピックからのRhasspyメッセージを処理できるようになる前に、 JSONの解析を少し行う必要があります。

もちろん、Javaで構文解析を行う方法はたくさんあります。 手動で解析する(ヒント、これを行わない)か、別のライブラリを使用できます。 ここでは、着信MQTTメッセージの処理が非常に高速であるため、org.jsonライブラリを使用します。

以下は、org.jsonライブラリで文字列を解析して必要な値を取得する手順です。

  1. JSONメッセージの文字列バージョンを使用してJSONObjectを作成します。
  2. 次のいずれかの関数を使用して、JSONドキュメントをナビゲートします。
  3. getInt、getBoolean、getStringなどで必要な値を取得します。

ほとんどの場合、レイヤーのどこかに複雑なミドルウェアが導入されたため、 Javaは複雑であると見なされています。 私は実際、最近の言語はかなり簡潔であり、 すべてのオートコンプリートおよびリファクタリングツールを考えると高速で動作することがわかりました。

とにかく、ここではメッセージの1つの値を取り出しているだけですが、 JSONメッセージ全体をJavaオブジェクトにアンマーシャリングして、 オープンソースライブラリを作成することもできます。 使用する準備ができたらお知らせください。

それでおしまい。 以前に使用したsomecats.jsonファイルに適用し、Rhasspyによって認識された値を取得したい場合は、 リスト5-5の行に沿って何かを行います。

リスト5-5
package practice;
   
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import org.json.JSONObject;
   
public class JSONFun {
  public static String whatObject(String json) {
    JSONObject obj = new JSONObject(json);
    JSONObject slot = ((JSONObject) obj.getJSONArray("slots").get(0)).getJSONObject("value");
    String cats = slot.getString("value");
    return cats;
  }
  
  public static void main(String... args) throws Exception {
    List<String> nekos = Files.readAllLines(Paths.get("onlycats.json"));
    String json = nekos.stream().collect(Collectors.joining("\n"));
    System.out.println(whatObject(json));
  }
}
    

Listening to MQTT Basic Messages

メッセージの聞き取り、またはサブスクライブは、次の手順で行われます。

  1. トピックまたは親トピックをサブスクライブします。
  2. IMqttMessageListenerインターフェースを実装するリスナーを追加します。

サブスクライブする最も簡単なコードをリスト5-6に示します。

リスト5-6
package practice;
   
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
   
public class Sub0 {
  public static void main(String... args) throws Exception {
    MqttClient client = new MqttClient("tcp://localhost:1883", MqttClient.generateClientId());
    client.connect();
    client.subscribe("hello", new IMqttMessageListener() {
      @Override
      public void messageArrived(String topic, MqttMessage message) throws Exception {
        String hello = new String(message.getPayload(), "UTF-8");
        System.out.println(hello);
      }
    });
  }
}
    

リスト5-7に示すように、 メッセージ処理部分はもちろんJavaラムダで置き換えることができるため、 さらに読みやすくなることに注意してください。

リスト5-7
MqttClient client = new MqttClient("tcp://localhost:1883", MqttClient.generateClientId());
client.connect();
client.subscribe("hello", (topic, message) -> {
  String hello = new String(message.getPayload(), "UTF-8");
  System.out.println(hello);
});
    

Raspberry Piからすべてを実行している場合は、図5-6に示すように、 次のVisual Studio Codeセットアップですべてが適切に実行され、ロシア語のメッセージが表示されます。

明らかに、楽しみは分散システムを持ち、さまざまな場所からのメッセージを処理することです。 ここでは、Raspberry Piでブローカーを実行してみます。 送信者リストでRaspberry PiのIPアドレスを設定したら、 コンピューターからJavaのRaspberry Piにメッセージを送信します。

または、JavaでRaspberry Piからリモートに、 またはクラウド内にあるブローカーにメッセージを送信します。

Listening to MQTT JSON Messages

したがって、今こそJava内からJSONメッセージの内容をリッスンして解析するときです。 これは、基本的なメッセージのリッスンと、前のページで持っていたJSONの楽しみの実装の組み合わせです。 コードスニペットはリスト5-8にあります。

リスト5-8
package practice;
   
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.json.JSONObject;
   
public class Sub {
  public static void main(String... args) throws Exception {
    MqttClient client = new MqttClient("tcp://localhost:1883", MqttClient.generateClientId());
    client.connect();
    client.subscribe("hermes/intent/#", new IMqttMessageListener() {
      @Override
      public void messageArrived(String topic, MqttMessage message) throws Exception {
        String json = new String(message.getPayload(), "UTF-8");
        JSONObject obj = new JSONObject(json);
        JSONObject slot = ((JSONObject) obj.getJSONArray("slots").get(0)).getJSONObject("value");
        String cats = slot.getString("value");
        System.out.println(cats);
      }
    });
  }
}
    

キーとブローカーについてのすべてを理解したところで、Rhasspyを入手してインストールします。

Voice and Rhasspy Setup

Rhasspy音声プラットフォームでは、インテントを使用してアプリケーションをデプロイできるように、 一連のサービスを実行する必要があります。 このセクションでは、最初にプラットフォームをインストールする方法と、 次にインテントを使用してアプリケーションを作成する方法を説明します。

Rhasspyをインストールする最も簡単な方法は、 実際にはイメージを実行できるコンテナーエンジンであるDocker( https://www.docker.com/ )を使用することです。 RhasspyのメンテナーがRhasspyのすぐに使えるDockerイメージを作成したので、それを利用します。

Preparing the Speaker

音声コマンドを実行する前に、動作するマイクの設定が必要です。

ReSpeakerをお勧めします。 お持ちの場合は、ここにインストール手順があります。
https://wiki.seeedstudio.com/ReSpeaker_4_Mic_Array_for_Raspberry_Pi/

Piのスピーカーを接続している場合、次の短いシェルスクリプトで必要なモジュールのインストールを実行できます。

標準のUSBマイクはプラグアンドプレイである必要があり、私はかなり良い結果で会議室のマイクを使用しています。

したがって、問題がなければ、マイクはこのarecordコマンドのリスト出力に表示されます。

ReSpeaker / Raspberry Piのセットアップは、図5-7のようになります。

Installing Docker

Docker Webサイトには、 Raspberry Pi用にDockerを設定する方法に関する最近のエントリがあります。 あなたはそれをここで見つけることができます:
https://www.docker.com/blog/happy-pi-day-docker-raspberry-pi/

次のコードでは、すべての手順が繰り返されています。

Installing Rhasspy with Docker

Rhasspyスイート全体は、準備されたコンテナーを実行することで開始できます。 これは、Dockerが適切にインストールされている場合、 Raspberry Piで次のコマンドを実行することで実行できます。

docker runを使用して、イメージ自体を開始します。 イメージは、事前にパッケージ化されたソフトウェアと構成を含む小さなOSであり、 展開が容易であると考えることができます。

コードの内訳は次のとおりです。

コンテナが正しく起動していることを確認するには、次のようにdockerコマンドを使用してコンテナのステータスを確認しましょう。

これにより、次のようなものが表示されます。 特に前もって用意したステータスフィールドを確認してください。

Rhasspyサーバーとそのコンソールの準備が整ったので、 メインコンピューターに戻り、ブラウザーベースのIDEを介してRaspberry Piにアクセスしましょう。

Starting the Rhasspy Console

コンソールにアクセスするには、http://192.168.1.104:12101 /にアクセスします。 192.168.1.104はRaspberry PiのIPアドレスです。

次に、図5-8に示すような活気のある画面が表示されます。

コンソールを使用すると、 最初の起動時にいくつかのリモートファイルを取得してフェッチする必要があることが明確になります。 RaspberryPiがインターネットに接続されている場合は、[今すぐダウンロード]ボタンをクリックするだけです。

操作は完了するまでに数分かかるため、ここで我慢してください。 図5-9に示すダイアログボックスが表示されるまで待ちます。

コンソールは自動的に更新され、図5-10が表示されます。

しかし、図5-11と図5-12に示すように、何かが欠けていることを示すいくつかのマーカーがまだあるようです。


幸い、今のところ、右上の緑色の[Train]ボタンをクリックすると、 図5-13に示すように、コンソールが光沢になり、赤いマーカーがなくなります。 Rhasspyコンソールを使用する準備が整いました。

The Rhasspy Console

Rhasspyメニューをさらに詳しく見てみましょう。 図5-14は、ホームアシスタントを構成するためのさまざまなセクションを示しています。

各タブの使用法は、次のリストで説明されています。

ブラウザーのIDE(正確には、Dockerコンテナー内のプロセス)は、構成設定を変更するたびにRhasspyを再起動するように要求します。

また、SentencesまたはWordsセクションに変更を加えるたびに、Rasspyをトレーニングするように求められます。

手術の最中で、アシスタントにすぐに助けを求める必要がない限り、これをしない理由は特にありません。

これでコンソールの操作方法がわかったので、音声アシスタントに何かを認識させましょう。

First Voice Command

インテントを生成する音声コマンドを作成するには、Sentencesセクションでいくつかの編集を行う必要があります。 先ほど説明したように、Sentencesセクションでは、エンジンが認識できる文を定義します。

First Command, Full Sentence

かつては、インテントの定義に使用される文法は非常に単純で、.iniファイル(これは古き良きWindows 95時代のものです)を介して挿入されます。

パターンは次のとおりです。

したがって、月曜日の朝でコーヒーが本当に必要な場合は、次のように書くことができます。

試してみよう。 その.initファイルの内容を削除して、前のコーヒーインテントのみを追加することから始めましょう。 これで、Sentencesセクションは図5-15のようになります。

見やすく表示された[Save Sentences]ボタンをクリックすると、図5-16に示すトレーニングのリマインダーが表示されます。

Speech Section and Trying Your Intent

これで、Rasspyエンジンがトレーニングされました。図5-17に示すように、Speechセクションに移動して試すことができます。

Speechセクションでは、話したり文章を書いたりして、認識される意図とその意図を確認できます。

Sentenceフィールドに「コーヒーが必要」と入力したとします。 エンジンは、図5-18に示すように、先ほど定義した「Coffee」応答を認識して生成する必要があります。

音声コマンド「Coffee」が認識され、関連する生成されたJSONドキュメントがそれに添付され、信頼性と生の値に関するさまざまな入力が行われます。

ここでは音声コマンドに焦点を当てているので、 「Hold to record」ボタンを押し続けて"I need coffee"と言うだけで、 最初にテキストに変換されてから、[Sentence]フィールドに表示される音声文を確認できます。 そして、ちょうど行われたように分析しました。

これはおそらく、ReSpeakerまたはUSBマイクを接続するときです。

テキスト読み上げのコマンドラインツールであるeSpeak( http://espeak.sourceforge.net/ )をインストールしている場合は、 WAVファイルを生成してRhasspyにフィードすることもできます。

そのineedcoffee.wavファイルをアップロードし、[Get Intent]ボタンをクリックします。

Fine-Tuned Intents

もちろん、Sentencesセクションで定義されているコマンドで、 オプションの単語、変数、プレースホルダーをポップインすることもできます。 ここでゲームを改善し、インテントに詳細を追加して、より良い音声コマンドを作成しましょう。

Optional Words

もちろん、ユーザーにとってより自然なコマンドにするために、文中の単語をオプションにすることも可能です。

前のコーヒーの例では、インテンションにオプションの緊急度を組み込むことができます。 これは、時々本当にコーヒーが必要になる場合があるためです。 これは、次に示すように、角かっこ内にオプションの単語を置くことによって行われます。

トレーニング後、図5-19と図5-20で、同じコーヒーインテントがどのようにRhasspyによって適切に認識されているかがわかります。


Adding Alternatives

しかし、あなたが自分自身について本当に気分が良く、 目を覚ますのにその中毒の黒いものを本当に必要としない日であるときはどうですか? コーヒーのインテントを書き換えて、コーヒーが必要なときと必要でないときの両方を認識できるようにします。

この新しいインテントをトレーニングして使用すると、コーヒーアシスタントにコーヒーが必要ないことを伝えることができます。 「Speech」タブに戻ると、図5-21に示すインテントが表示されます。

この生成されたインテントの1つの問題にすでに気づいたと思います。 はい、そのままではあまり役に立ちません。 なぜなら、コーヒーが欲しいかどうかにかかわらず、同じ意図が生み出されるからです。

もちろん、受け取った文字列を解析したり、2つの異なるインテントを書き込んだりすることもできますが、 その可能性のリストに名前を付けるだけの場合はどうでしょうか。 これは、次に示すように、角括弧で行われます。

これで、Coffeeインテントにより、JSONドキュメントに「need」という名前のスロットが追加され、その値は「need」または「don't need」になります。

もう一度やってみましょう。 図5-22の結果を参照してください。

ここでいくつかのことに気づくはずです。 まず、インテントの概要に詳細が表示され、インテントが赤でマークされ、 各スロットが関連する値とともにリストに表示されます(ここでは、need = don’t need)。

次に、インテントの完全なJSONドキュメントにはentitiesセクションが含まれ、 各エンティティにはエンティティ名と関連する値があります。

補足として、JSONドキュメントにはslotsセクションも含まれており、 インテントのすべてのスロットの簡単なリストが含まれています。

次は何ですか?

Making Intents with Slots More Readable

スロットを1つ追加しすぎると、文全体がすぐに乱雑になることに気付くでしょう。

スロット定義をインテント名のすぐ下に移動し、=を使用して可能な値を割り当てることができます。

たとえば、次のように同じcoffeeインテントを書き換えることができます。

何も変更されておらず、インテントは以前と同じように動作しますが、今では非常に読みやすくなっています。

それは素晴らしいですが、次は何ですか?

Defining Reusable Slots

はい、スロットをインラインで定義すると、文の定義がさらに複雑になるため、 スロットを移動して[Slots]タブで定義することができます。

スロットはシンプルなJSONファイルで定義されています。 コーヒーのニーズを[Slots]タブに移動するには、次のようにJSONファイルを記述します。

これは、図5-23のようにブラウザに表示されます。

これをセンテンスファイルで使用するには、次に示すように、インテントを記述する必要があります。

コードの内訳は次のとおりです。

すごい。 現在、高度な構成可能なインテントがあり、オプションの複雑なリストが手元にあります。

インテントが認識されたときに、外部システムが生成されたJSONファイルを利用できるセクションに移りましょう。

Settings: Get That Intent in the Queue

生成されたJSONファイルは便利なように見えますが、 Rhasspyの外部でMQTTキューに入れて、より多くの処理を実行できるようにしたいと考えています。

図5-24に示すように、コンソールの[Settings]セクションに移動します。

ここでは、システムの構成方法の概要を説明します。 Rhasspyには無数の設定可能なオプションがありますが、以下に焦点を当てます。

最初にMQTTインタラクションを見てみましょう。 MQTTリンクをクリックすると、図5-25に示すように、 実行中のMQTTデーモンの詳細を入力できるセクションに移動します。

ここにRaspberry Piのホスト名またはIPアドレスを必ず入力してください。 求められたらRhasspyを再起動します。それだけです。 では、MQTTでコーヒーを飲みましょう。

[Speech]タブに戻ると、図5-26のように、新しいcoffeeインテントをトリガーできます。

図5-27のMQTT Explorerウィンドウを見てみましょう。

何が見えますか? hermes / intent / coffeeトピックにメッセージが送信されているのがわかります。 これは、この章の前のセクションで見たものと似ています。

はい、あなたもそれを感じることができます—これはうまくいくでしょう。

Rhasspy / intent / coffeeに送信された単純なMQTTメッセージがあり、 メッセージの本文にスロットの名前と値のみが含まれていることもわかります。 confidenceやその他の詳細をいじる必要がない場合は、これが最良の代替手段です。 このメッセージの操作は演習として残しておきます。

コーヒーが欲しいかどうかを指定するメッセージをいくつか試すことで、 すぐに満足感を得ることができます。 飲み過ぎないように気をつけてください。

Settings: Wake-Up Word

時々Speechに戻るのはいいことですが、Raspberry Piをそのままにして、 特定の単語または単語のセットを発声するときに、Rasspyにインテントを聞いてもらうのは良いことです。

これは、「Settings」セクションで定義されているウェイクアップワードを使用して行われます。

図5-28に示すように、私は設定でSnowboyキット( https://snowboy.kitt.ai/ )を有効にすることに運が良かったです。

Snowboyを有効にすると、デフォルトのUMDLファイルがローカルにダウンロードされます。 Raspberry Piの次の場所にあります。

以下に詳述するように、キーワードを記録し、オーディオファイルをSnowboy Webサイトにアップロードすることにより、 独自のファイルとキーワードを簡単に作成できます。
http://docs.kitt.ai/snowboy/

これで、"Snowboy"と言うと、録音ボタンを押したままのように、Rasspyが起動します。

したがって、今、私たちは次のことを言わなければなりません:

そろそろだよね?

Creating the Highlight Intent

次に、主なオブジェクト検出アプリケーションにどのオブジェクトに焦点を合わせたいかを 伝えるために使用する小さなインテントを作成する必要があります。

インテントは、次のルールに従って指定されます。

先に読む前に、必ず自分で試してみてください…

sentenceファイルに入れるインテントの結果のコードは次のとおりです。

もちろん、次のように言うのが最善の方法です。

インテントJSONをhermes / intent / highlightキューに公開するには、図5-29に示すような内容にします。

ほら、ほとんどの場合、Rhasspyと音声コマンドのセクションが完了したので、オブジェクト、猫、人の検出にリアルタイムで戻ることができます。

Voice and Real-Time Object Detection

これは、すべてをつなぐ章の一部です。 つまり、前の章のビデオ分析とRhasspyを使用した音声認識です。

まず、Rasspyからメッセージが送信される、簡単なorigamiプロジェクトの設定を準備しましょう。

Simple Setup: Origami + Voice

このセクションでは、次のことを行います。

  1. メインカメラの全画面で新しいスレッドでビデオストリーミングを開始します。
  2. ビデオストリーミングはorigamiコアフィルターのannotateを使用しており、画像にテキストを追加できます。
  3. メインスレッドでMQTTに接続します。
  4. 新しいインテントメッセージで、annotateテキストを更新します。

ここで使用されているインテントは、 COCOデータセットのオブジェクトカテゴリから取得された名前を認識するスロットを備えた、 以前に定義されたハイライトインテントです。

リスト5-9の残りの部分はわかりやすいはずです。

リスト5-9
package practice;
   
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.json.JSONObject;
import origami.Camera;
import origami.Origami;
import origami.filters.Annotate;
   
public class Consumer {
  public static void main(String... args) throws Exception {
    Origami.init();
    Annotate myText = new Annotate();
    new Thread(() -> {
      new Camera().device(0).filter(myText).fullscreen().run();
    }).start();
  
    MqttClient client = new MqttClient("tcp://localhost:1883", MqttClient.generateClientId());
    client.connect();
    client.subscribe("hermes/intent/#", new IMqttMessageListener() {
      @Override
      public void messageArrived(String topic, MqttMessage message) throws Exception {
        String json = new String(message.getPayload(), "UTF-8");
        JSONObject obj = new JSONObject(json);
        JSONObject slot = ((JSONObject) obj.getJSONArray("slots").get(0)).getJSONObject("value");
        String cats = slot.getString("value");
        myText.setText(cats);
      }
    });
  }
}
    

問題がなく、"Show me only cats"と言うと、図5-30に示すように、annotateフィルターによって左上のテキストが更新されます。

次に、この設定をオブジェクト認識とリアルタイムビデオストリーミングパーツに接続します。

Origami Real-Time Video Analysis Setup

前の章から、Yoloと呼ばれるorigamiフィルターを使用してオブジェクト検出を実行したことを覚えているでしょう。 annotateフィルターと同様に、Yoloフィルターはorigami-filtersライブラリに含まれており、 まさにJavaの方法で、ベースフィルターを拡張して、検出されたオブジェクトを強調表示する必要があります。

origamiフィルターライブラリで利用可能なYoloフィルターを使用すると、 ネットワーク仕様を使用してオンデマンドでネットワークをダウンロードおよび取得できます。

したがって、たとえば、networks.yolo:yolov3-tiny:1.0.0は、 使用しているデバイスに応じて、yolov3-tinyとキャッシュをダウンロードして、 さらに使用できるようにします。 これはorigamiフレームワークの機能です。

Yoloの場合、CocoデータセットでYoloネットワークをトレーニングするために、次のネットワーク仕様を利用できます。

いずれも、以前のものよりも高速または高精度です。

Creating the Yolo Filter

リスト5-10では、annotateWithTotalを使用して検出されたすべてのオブジェクトの数、 またはonlyスイッチを介して表示のみを表示するための基礎を準備します。 エントリポイントは関数annotateAll内にあり、すべてのボックスを表示するか、 それらのサブセットを表示するかが決定されることに注意してください。

リスト5-10はMyYoloフィルターのコードを示しています。

リスト5-10
package filters;
   
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import origami.filters.detect.Yolo;
import java.util.List;
  
public class MyYolo extends Yolo {
  Scalar color = new Scalar(110.0D, 220.0D, 0.0D);
  String only = null;
  boolean annotateWithTotal = false;
  
  public MyYolo(String spec) {
    super(spec);
  }
  
  public MyYolo annotateWithTotal() {
    this.annotateWithTotal = true;
    return this;
  }
  
  public MyYolo annotateAll() {
    this.annotateWithTotal = false;
    return this;
  }
  
  public MyYolo only(String only) {
    this.only = only;
    return this;
  }
  
  public MyYolo color(Scalar color) {
    this.color = color;
    return this;
  }
  
  @Override
  public void annotateAll(Mat frame, List<List> results) {
    if (only == null) {
      if (annotateWithTotal)
        annotateWithCount(frame, results.size());
      else
        super.annotateAll(frame, results);
    } else {
      if (!annotateWithTotal)
        results.stream().filter(result -> result.get(1).equals(only)).forEach(r -> {annotateOne(frame, (Rect) r.get(0), (String) r.get(1));});
      else
        annotateWithCount(frame, (int) results.stream().filter(result -> result.get(1).equals(only)).count());
    }
  }
  
  public void annotateWithCount(Mat frame, int count) {
    Imgproc.putText(frame, (only == null ? "ALL" : only) + " (" + count + ")", new Point(50, 500), 1, 4.0D, color, 3);
  }
   
  public void annotateOne(Mat frame, Rect box, String label) {
    if (only == null || only.equals(label)) {
      Imgproc.putText(frame, label, new Point(box.x, box.y), 1, 3.0D, color, 3);
      Imgproc.rectangle(frame, box, color, 2);
    }
  }
}
    

これでフィルターの準備ができたので、分析を再開できます。

Running the Video Analysis Alone

この短いセクションでは、1つのスレッドでカメラを起動し、 別のスレッドでYoloフィルターの表示されているオブジェクトの選択をリアルタイムで更新してビデオを再生します。 2番目のメインスレッドは、前のセクションで定義したMyYoloフィルターの唯一の関数を直接呼び出します。 リスト5-11は、このための完全なコードを示しています。

リスト5-11
package practice;
   
import origami.Camera;
import origami.Filter;
import origami.Filters;
import origami.Origami;
import origami.filters.FPS;
import filters.MyYolo;
   
public class YoloAgain {
  public static void main(String[] args) {
    Origami.init();
    String video = "mei/597842788.852328.mp4";
    // Filter filter = new Filters(new MyYolo("networks.yolo:yolov3-tiny:1.0.0").only("car"), new FPS());
    MyYolo yolo = new MyYolo("networks.yolo:yolov3-tiny:1.0.0"); //.only("person");
    yolo.thresholds(0.4f, 1.0f);
    yolo.annotateWithTotal();
    Filter filter = new Filters(yolo, new FPS());
    new Thread(() -> { new Camera().device(video).filter(filter).run(); }).start();
    new Thread(() -> { 
      try { Thread.sleep(5000);
        System.out.println("only cat");
        yolo.only("cat");
        Thread.sleep(5000);
        System.out.println("only person");
        yolo.only("person");
      } catch (InterruptedException e) {
        //e.printStackTrace();
      }
    }).start();
  }
}
    

Rhasspyから受け取ったメッセージをリンクして、 Yolo分析の選択を更新することで、今どこに向かっているのかがわかるでしょう。

Integrating with Voice

この最後のセクションでは、Rhasspyインテントから受け取った入力に基づいてオブジェクトのサブセットを強調表示します。

これを機能させるには、以下を実行する必要があります。

  1. メインカメラのフルスクリーンで、新しいスレッドでビデオストリーミングを開始します。
  2. ビデオストリーミングは、YoloネットワークをロードするMyYoloフィルターを使用しています。
  3. メインスレッドでMQTTに接続します。
  4. 新しいメッセージで、annotateテキストを更新します。
  5. メッセージがハイライトキューに到着すると、次のことを行います。

この本の最後のコードスニペットは、これらすべてを組み合わせる方法を示しています。 リスト5-12を参照してください。

リスト5-12
import filters.MyYolo;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.json.JSONObject;
import origami.Camera;
import origami.Origami;
   
import static java.nio.charset.StandardCharsets.*;
   
public class Chapter05 {
  public static String whatObject(String json) {
    JSONObject obj = new JSONObject(json);
    JSONObject slot = ((JSONObject) obj.getJSONArray("slots").get(0)).getJSONObject("value");
    String cats = slot.getString("value");
    return cats;
  }
  
  public static void main(String... args) throws Exception {
    Origami.init();
    // String video = "mei/597842788.852328.mp4";
    String video = "mei/597842788.989592.mp4";
    MyYolo yolo = new MyYolo("networks.yolo:yolov3-tiny:1.0.0");
    yolo.thresholds(0.2f, 1.0f);
    // yolo.only("cat");
    new Thread(() -> {
      // new Camera().device(0).filter(yolo).run();
      new Camera().device(video).filter(yolo).run();
    }).start();
   
    MqttClient client = new MqttClient("tcp://localhost:1883", MqttClient.generateClientId());
    client.connect();
    client.subscribe("hermes/intent/hellonico:highlight", (topic, message) -> {
      yolo.only(whatObject(new String(message.getPayload(), "UTF-8")));
    });
  }
}
    

さまざまなサンプルビデオまたは直接Raspberry PiのWebカメラのビデオストリームで、検出するオブジェクトを直接更新できます。

この最後の例では猫を探すこともできましたが、娘のメイは学校で、 夜は友達と一緒に、彼女のビデオをタイムリーに送ってくれました。 図5-31と図5-32で彼女が活動しているときに、 彼女のYOLOセットアップによって強調されている彼女を参照してください。


この章では、以下について学習しました。

この短い本は終わりを迎えたので、今が世界を変えるあなたの番です。 可能なことのほんの一部を取り上げただけですが、 これが実装を試みるためのいくつかのアイデアを提供してくれることを願っています。