Amazon Echoのウェイクワードをカスタマイズすることに成功した(物理)

以前の記事ではAlexaスキルを作成することでココちゃんが応答してくれるようになりました。

romiogaku.hateblo.jp

しかしAmazon Echoで変更できるウェイクワードは、現時点では以下の4つです。

  • Alexa
  • Echo
  • コンピューター
  • Amazon

Amazon Echoを純度100%ココちゃんにするには、ウェイクワードを「ハローココちゃん」にする必要があります。

ここで抜け道があることを発見します。

f:id:romiogaku:20190106232251p:plain

アクションボタンです。

アクションボタンを押すことでウェイクワードを言う必要がなくなります。つまり、「ハローココちゃん」と呼びかけたらアクションボタンが自動で押されるようにすれば良いのです!

f:id:romiogaku:20190106011917p:plain

実現するにあたって、以下が課題となります。

  • どうやってアクションボタンを押すか
  • どうやって音声認識させるか

どうやってアクションボタンを押すか

物理的に押すものが必要になります。

今回はRaspberry Piサーボモータを動かすことにします。「ハローココちゃん」という音声を認識したらモータを動かしてアクションボタンを押すという流れにします。

どうやって音声認識させるか

ググってみると音声認識の選択肢としては以下がありました。

Julius

julius.osdn.jp

フリーの音声認識ソフトウェアです。

本体とは別に音響モデル・言語モデルが入った基本的なディクテーションキットが配布されており、以下のように実行できます。

$ julius -C ~/julius/julius-kit/dictation-kit-v4.4/main.jconf -C ~/julius/julius-kit/dictation-kit-v4.4/am-gmm.jconf

f:id:romiogaku:20190106233427p:plain

「ハローココちゃん」と呼びかけてますが、うまく認識ししてくれません。

認識率を上げるために独自辞書を作成し実行してもみましたが、関係ない音声も認識してしまいました。

良いやり方があると思うのですが、脇道に逸れてしまいそうだったので断念しました。

音声認識 API

qiita.com

GoogleやMSなどが提供している音声認識APIです。

最初は無料だけど基本的にお金がかかります。認識精度に関しては非常に高いです。

試しにGoogleCloud Speech-to-TextAPIを利用してマイク音声を認識させてみました。

golangでの実装例はこちらが参考になりました。というかこれをそのまま動かしました。

go-talks.appspot.com

良い感じでしたが、今回のケースでは常時起動させておく必要があり、すぐ無料枠を超えてしまいそうです。実際そういう方も居るようでした。

blog.be-dama.com

Web Speech API

ブラウザで動作する「音声合成」と「音声認識」機能です。現在Chromeのみ対応してます。 Googleのデモページはこちらです。

www.google.com

初回だけマイク許可する必要がありますが、内部ではGoogle音声認識APIを実装しているらしく精度は非常に高いです。

無料だしJSでさくっとできそうなので今回はこちらを採用することにしました。

用意したもの

物理的に必要なものを買い揃えました。

ラズパイはBTC採掘を夢見て5年程前に購入したものが眠ってましたが動きませんでしたので、マルツの新春セールで最後の一個を買いました。3980円なり。

ラズパイOSインストール

公式に従ってインストールします。

https://projects.raspberrypi.org/en/projects/raspberry-pi-getting-started/2

SDカードフォーマットはこちらを利用します。

www.sdcard.org

USBマイクの優先度変更

デフォルトだと標準のオーディオ出力端子が優先されてしまうので、この順番を変更する必要があります。

順番は以下で確認できます。

$ cat /proc/asound/modules
0 snd_bcm2835
1 snd_usb_audio

変更するためには /lib/modprobe.d/aliases.conf を編集します。

options snd_usb_audio index=0
options snd_bcm2835 index=1
options snd slots=snd_usb_audio,snd_bcm2835

オリジナルのファイルには snd-usb-audio と書かれているのですが、これはタイポだそうで正解はsnd_usb_audio(アンスコ繋ぎ)です。自分はこれで時間を費やしました・・・

参考:

raspberrypi.stackexchange.com

編集後再起動したら

$ cat /proc/asound/modules
0 snd_usb_audio
1 snd_bcm2835

となっていることを確認します。

サーボモータ接続

f:id:romiogaku:20190107205450p:plain

3.3V電源、GND、GPIO18(ピン番号12)をサーボモータの各ケーブルと接続します。SG90のデータシートはこちら

f:id:romiogaku:20190107210018j:plain
バランス悪い

サーボモータをプログラムから動かす

ラズパイで電子工作のプログラミングといえばpythonが多い印象ですが、調べてみるとGobotというロボティクスフレームワークがあるっぽいのでこちらを使いました。(普段Go書くことが多いので...

Gobotにはサーバ機能があります。API化することで特定のエンドポイントにリクエストが来たらサーボモータを動かす、なんてことができます。今回の目的にぴったりです。

github.com

func main() {
    master := gobot.NewMaster()
    adaptor := raspi.NewAdaptor()
    servo := gpio.NewServoDriver(adaptor, "12") //GPIO(18)

    work := func() {
        // 13 ~ 41 = 0度 ~ 180度
        servo.Move(uint8(27)) //center
        gobot.After(1*time.Second, func() {
            servo.Move(uint8(35))
            gobot.After(500*time.Millisecond, func() {
                servo.Move(uint8(27))
            })
        })
    }

    robot := gobot.NewRobot("servoBot",
        []gobot.Connection{adaptor},
        []gobot.Device{servo},
        work,
    )

    server := api.NewAPI(master)
    server.AddHandler(func(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    })
    server.Port = "3000"
    server.Start()

    servoEndpoint := master.AddRobot(robot)
    servoEndpoint.AddCommand("move", func(params map[string]interface{}) interface{} {
        reqParam := fmt.Sprintf("Params: %+v\n", params)
        robot.Start()
        return reqParam
    })

    master.Start()
}

JSから呼ぶのでCORSの設定もしています。

http://localhost:3000/api/robots/servoBot/commands/move にリクエストを送ればサーボモータが動きます!

f:id:romiogaku:20190107203916g:plain
キュイキュイいってる

Web Speech API

サンプルコードはこちらが参考になりました。

https://w3c.github.io/speech-api/#examples-recognition

以下のように実装しました。 github.com

const recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.lang = "ja";
recognition.onresult = event => {
  for (let i = event.resultIndex; i < event.results.length; ++i) {
    if (event.results[i].isFinal) {
      const word = hiraganaToKatakana(
        removeSpace(event.results[i][0].transcript)
      );
      console.log(word);
      if (word.match(/ハローココチャン/)) {
        axios({
          method: "get",
          url: "http://localhost:3000/api/robots/servoBot/commands/move",
          headers: { "Content-Type": "application/json" }
        })
          .then(function(response) {
            console.log(response);
          })
          .catch(function(error) {
            console.log(error);
          });
      }
    }
  }
};

onresult で認識結果を取得します。「ハローココちゃん」にマッチしたらhttp://localhost:3000/api/robots/servoBot/commands/move にリクエストを送っているだけです。

こちらはserve コマンドかなんかでサーバを立ち上げてアクセスし、事前にマイク許可しておきます。

サーボモータを固定する

ココちゃんをぐるぐる巻きにします。

f:id:romiogaku:20190107211437j:plain
急に雑になる

完成!

youtu.be

最後に

まさか本格的IoTがこんなことになろうとは自分でもびっくりです。

ラズパイ初心者ということもあって結構時間かかった割に電子工作の知識はほとんど吸収できなかった気がします。でも普段Web系ばかりなので良い刺激になりました。組み込み系のひとすごいなぁ・・・

WebSpeechAPIに関してですが、こういう使い方(常時付けっぱなし)は想定してないと思うので、そのうち制限されるんじゃないかなと思います。ブラウザリロードするだけで延々と使い続けられるというのは流石に抜け道臭いです。

あとAlexaのスキルですが、著作権的にアウトなのは個人で楽しむとして、ストアに出しても問題無いようなスキルも思いついたのでそのうち出せたらいいなと思います。