QRコードの読み取り

ロボットが案内ができるように、あるいは位置を把握するために、QRコードを読み取る能力をもたせたいと考えた。

Raspberrypiのカメラ、それで撮ったのをgoogleのzxingというコード認識ライブラリを経由してコードを認識させる、というものだ。

このライブラリを使えば、認識とコード生成が極めて簡単に行える。以下のようなところを参考にさせていただいた。
https://qiita.com/tool-taro/items/1923f948a1908255d5df
https://qiita.com/yoshi389111/items/0fee2be43e5135b1bb15

これで、単にQRコードがきちっと入った画像だけではなく、ゴタゴタした背景の中にあってもそれを認識できる。
例えば

こんな画像でも認識できる。あとは、頭の部分についている、このカメラの動かし、このQRコードを認識させることができればいい。

ロボットの耳

両耳をつけた。ちょっと見にくいか。

小さいが、SPH0645LM4HのI2Cマイクである。これで録音したデータを、一サンプルずつずらしながら、音の到着時間さを測る。そうすると、およその方向がわかるというわけである。これについては、以下の金井宏一郎氏の学士論文「音の到着時間さを用いた音源空間推定」(2008年、高知工科大学)を参考にさせていただいた。
http://www.kochi-tech.ac.jp/library/ron/2007/2007info/1080351.pdf
とても分かり易かった。
頭は、25センチなので、最大到着時間さは25センチ。音速を使って計算すると、0.6ミリ秒くらいである。
一方のマイクに近づいて話しかけると、こんなデータが取れる。

このデータ(実際のデータは、これよりもはるかにでかい、以下参照)を、一サンプルずつずらしながら相関係数を取るとこんな風になる。(縦軸は相関係数、横軸はシフトサンプル数)
厳密には、26サンプル、プラス側にずらすと、相関係数が最大になる。計算結果は次のようである。
データ総数:132300
一サンプル辺りの時間:0.02268 ミリ秒
最大相関係数: 0.71358
最大相関係数を出すサンプルシフト: 26
距離差:20.06245 cm
三秒間、片方に偏りながら話したので、毎秒44,100サンプル取れるので、データ総数は上記のようになる。その逆数が一サンプル辺りの時間である。サンプルを26だけシフトした時に最大相関係数、0.71358を得る。悪くない。これを距離にすると、25センチより少し小さい20センチになる。方向的に、両マイクが完全に直線になる方向から、少しずれていることになる。
今の所、最大相関係数が0.5以上の時に、ロボットが反応する(振り向く)ようにしてみようかと思っている。

SPH0645LM4Hの録音フォーマットの問題

SPH0645LM4Hの二つのマイクからステレオ録音ができるようになった。再生すればきちんと音を再生できる。ただ、ここで問題が発生した。SPH0645LM4Hは、32ビットSampleSizeInBitsでしか録音できないのだ。arecordのコマンドラインで行くと、
arecord -D dmic_sv -c2 -r 44100 -f S32_LE -t wav -V stereo -v file8.wav
で録音する。この、S32_LEが32ビットリトルエンディアンのフォーマットでの録音になるのである。これをS16_LEで録音しようとすると、そのフォーマットではできないよというエラーになる。

32ビットは、高い質の録画になるのであるが、これをデータでとらえる必要があ流。4バイトが、intになるのかfloatになるのか、二つの場合があるらしい。ただし、データで読むとき、4バイトの、バイト配列で取り出せるのだが、どう変換してもきちんとしたデータに変換できない。16ビット、2バイトの音源の場合はデータ化できるのだが、32ビットだとできないのである。朝からずっとこれをどうやったら変換できるのかをやっていた。

結局どうしてもできないので、soxで元の32ビットの音源を16ビットに変換することにした。変換のコマンドラインは次のようになる。
sox -v 0.99 file8.wav -b 16 file8-16-1.wav
元ファイルの前に、 -v 0.99 をつけないと
sox WARN dither: dither clipped 5768 samples; decrease volume?
という警告が出る。元音源のボリュームが大きすぎるものがいくつかあるよというエラーらしい。若干ボリュームを下げるオプションらしい。
という、データが取れる(ごく1部だ)
青が、レベルが高いので、音源に近い方で、そちらが微妙に先についているように見える。厳密には計算して見なければならない。
バイト配列の計算部分は、

short left, right;
for (int i = 0; i < data.length; i += 4) {
left = (short)((short)data[i] & 0xff | ((short)data[i+1] << 8));
right = (short)((short)data[i+2] & 0xff | ((short)data[i+3] << 8));
System.out.println("" + left + " " + right);
}

となる。
dataにバイト配列が入っている。

SPH0645LM4H搭載 I2S MEMSマイクモジュールを動かす

アクリルロボットHAL2の頭の両脇に耳をつけて、音の到着時間さで音が聞こえてくる方向を決めようと思っている。そのために、音声をデジタル変換して差を求めなけれがならない。

結構長い時間どうするのかを探ったが、SPH0645LM4H搭載 I2S MEMSマイクモジュールが最適だと結論に至った。これは、スイッチサイエンスがアマゾンで972円で売り出している。(Amazon)これを二つ買った。(マイクの方向に気をつけて、ピンをはんだ付けすること)

これの接続や、カーネルのコンパイルについては、極めて簡単で(1)こちら のサイトをそのまま実行すれば良い。(厳密には、私は、コンパイルは、同じSPH0645LM4H を扱っている (2)こちら を参照して、自動起動がうまくいかなかったので、そこは(1)を参照した。別に物で試すと、コンパイルは(1)の通りやるとうまくいかなかった。(2)でコンパイルして、後半は(1)を参照するのがベストか)

録音できるところまでは順調だったが、マイクのゲインが低すぎた。これについては、(1)の最後に書いてあるものが、一番よくわかる。

HAL2も「謎かけネタ」ができるようになった

膨大な謎かけデータを、emilyという新たに開発した対話用スクリプト言語で書いて、それをちゃんと実行できるようにした。特に、juliusの停止と再開の制御に手間がかかった。HAL2がしゃべっているときは、音声認識はオフにしておかなければならない。juliusモジュールにterminateコマンドとresumeコマンドを送っても、実際に停止や再開が有効になるのには少しラグが発生する。喋りとそのラグの関係がうまくいかないと、ロボットがしゃべっていることをjuliusが認識してエンドレスになってしまったりした。これを確実に制御するためには、juliusモジュールに、コマンドを送って、その結果がストリーム出力で出てくるのを捉えて、状態を把握していくしかなかった。とても面倒だったな。

これで、サリーの代理もできるようなった。よかった!

Juliusのマイクデバイスの問題

juliusを使うとき、マイクデバイスが見つからないというエラーはよく出る。これは、新しいraspiのosで、/dev/dspデバイスが使えないからだった気がするが、これはjuliusのコンパイル時に、alsaのマイクデバイスを使うことを指定すれば回避できるようだ。具体的には、
Juliusのコンパイル時に、
./configure --with-mictype=alsa
を指定すれば良い。ただ、alsaのヘッダーがないと言われるので、事前に、
sudo apt-get install libasound2-dev libesd0-dev libsndfile1-dev
を実行しておかなければならない。
さらにJuliusを立ち上げる時に
export ALSADEV="plughw:1,0"
の環境変数をしてしなければならない。1,0の箇所は、マイクデバイスの番号を
arecord -l
で確認しなければならない。

ロボットの音声認識システムの調整

この間、1000行あまりのemilyスクリプトを処理しているが、色々な問題を解決してそれはできるようになった。

ただ、juliusは、すべてスクリプトのどれかの言葉に一致させてしまうので、全然関係ない言葉でも、それに反応してしまうという問題が前から気づいていた。それでは実際のネタでは使えない。(今は、サリーからtelepathyでもらった言葉で反応するようにしているので問題はないが)

そこで、通常のjuliusのディクテーションセットを同時に読み込ませて、そちらにも一致させれば問題ないだろうと思った。実際、juliusは、複数のモデルを組み込んで認識する能力を持っているということなので、それを実際に組み込もうというものだ。

まず、Juliusのサイトにあるディクテーションキットから必要なファイルを取ってくる。言語モデルを、main2.jconfとしよう。main.jconf 余計なものを切り取ってついのようなものにする。

# main2.jconf
-input mic
-LM emily-l1
# mkbingramで作成したバイナリ形式
-d bccwj.60k.bingram
## 単語辞書ファイル
-v bccwj.60k.htkdic

ここに記載してあるbingramファイルとhtkdicファイルを、この場合は同じフォルダに置いておかなければならない。パスを指定すれば良いのだが。注意点として、-input micはグローバルオプションなので、何よりも早い位置に置いておかなければならない。最初の設定ファイルのその頭という意味のようである。
-LM emily-l1
言語モデルのセクション開始の宣言である。名前をemily-l1はセクション名で、後で効いていくる
ディクテーションキットの言語モデルは巨大なのでバイナリファイル化している。
続いて、ディクテーションキットの音響モデルである。ファイル名を、am-gmm2.jconf としよう。

# ディクテーションキット GMM-HMM版音響モデル・入力設定
##
-AM emily-a1
# 音響モデル: GMM-HMM
-h jnas-tri-3k16-gid.binhmm
-hlist logicalTri-3k16-gid.bin

-AMは音響モデルのセクション開始を表しそのあとに、名前が続いている。二つのファイルも同じフォルダに用意しておかなければならない。

続いて、emily.jconfである。

##############
-LM emily-l2
-gram SALLYHAL2180809
-AM emily-a2
-h hmmdefs_ptm_gid.binhmm
-hlist logicalTri
-SR SR1 emily-a1 emily-l1
#### 探索パラメータ
-b 1500 # 第1パスのビーム幅(ノード数) triphone,PTM,engine=v2.1
-b2 100 # 第2パスの仮説数ビームの幅(仮説数)
-s 500 # 第2パスの最大スタック数 (仮説数)
-m 10000 # 第2パスの仮説オーバフローのしきい値
-n 1 # 第2パスで見つける文の数(文数)
-output 1 # 第2パスで見つかった文のうち出力する数 (文数)
-SR SR2 emily-a2 emily-l2
#### 探索パラメータ
-b 1500 # 第1パスのビーム幅(ノード数) triphone,PTM,engine=v2.1
-b2 100 # 第2パスの仮説数ビームの幅(仮説数)
-s 500 # 第2パスの最大スタック数 (仮説数)
-m 10000 # 第2パスの仮説オーバフローのしきい値
-n 1 # 第2パスで見つける文の数(文数)
-output 1 # 第2パスで見つかった文のうち出力する数 (文数)

モデルは、SALLYHAL2180809という名前になっていて、この名前と、.dfaというサフィックスのファイルおよび.dictという辞書ファイルを用意しなければならない。言語モデルと音響モデルを指定する。

さらに -SRは、これまでのモデルをまとめて指定するものである。名前と、どの言語モデルと音響モデルのセットなのかを指定する。この-SRのセクションの中で、それぞれのモデルの副次的パラメータを指定する。

emilyの作成するファイルをeucファイルとなっているのだが、ディクテーションキットはUTF-8になっている。だから、emily作成ファイルは、nkf -u などでUTF-8に返還しなければならない。違う文字コードではだめなのである。

これで、
julius -C main2.jconf -C am-gmm2.jconf -C emily.jconf
とすれば、起動できる。

ただ、これでは、当初の思い通りの結果にはならない。確かに、それぞれで認識結果を同時に出してくるので、いいのだが、どちらのものが、どれだけ一致しているかが、スコアから判定できないような感じなのだ。

だから、やはり、emilyのスクリプトで、他の言葉の可能性を受け止めるようにしなければならないようだ。

HAL2の設計

新しいロボットHAL2を作り始めている。HAL1から何を改善したいのか。

(1)何よりも、音声出力をモーター制御から分離する。HAL1の場合、モーターを動かしているときは、雑音が極端に大きく、音声を出せなかった。だから、リレーを使って、アンプをオンオフさせていた。最初は、電源から雑音のせいかと思ったが、よくよく調べると、音を作るところからくる雑音だった。最もあり得るのは、DCモーターにパルス電源 PWMをソフトウェアで送っているのだが、そのパルス作成に絡むものだ。だから、どうしてもモーター系と音声出力系を切り離す必要があった。だから、HAL2では、音声出力をもう一台のコンピュータ RaspberryPI Zeroに任せる予定だ。こうすれば、走りながらでも喋らせることができるはずである。

(2)実際使えない、超音波距離センサーをやめる。

(3)電源系がまとまりがなく、雑然としていたのをスッキリさせる。可能であれば、充電制御をソフトウェでさせたい。

(4)それと決定的に大きなもののもう一つは、ホイールをメカナムホイールから、ゴムタイヤに変えること。メカナムホイール は、横に動くなど面白い動きができそうなので使ったが、タイヤの回転に滑りが入って、回転数と位置の対応に、誤差がかなり出て困ったので。ゴムタイヤは、グリップがいいので、動きも機敏になり概略の位置を把握しやすくなるはずである。正確なところは、距離センサーをレーダーにして把握させる。