Linuxで音声リモコン
PC-OP-RS1, lemon_corn とjulius で音声リモコンを作ってみました。操作対象はテレビとCATVのSTBです。
juliusは本体とグラマー認識キットも使います。
とりあえず孤立単語認識で、1コマンド認識したらlemon_cornを発行して終了するという一番単純なものです。
juliusの使い方としてはモジュールモードという手もあるようですが、
ここではライブラリを組み込んだ単体アプリとしてます。
まずはグラマーですが、孤立単語認識グラマーを以下のように作りました。やりたい操作を全然網羅していませんが、そのあたりの充実は追々ということで。
grammar/remocon.dict
0 [テレビ電源on] t e r e b i w o ts u k e r u 0 [テレビ電源on] t e r e b i w o ts u k e t e 0 [テレビ電源off] t e r e b i w o k e s u 0 [テレビ電源off] t e r e b i w o k e sh i t e 0 [テレビミュート] t e r e b i my u: t o 0 [テレビ音量小] t e r e b i o t o w o ch i i s a k u 0 [テレビ音量大] t e r e b i o t o w o o: k i k u 0 [テレビ1ch] t e r e b i i q ch a N 0 [テレビ1ch] t e r e b i e n e: ch i k e: 0 [テレビ1ch] e n e: ch i k e: 0 [テレビ1ch] t e r e b i e n u e i ch i k e: 0 [テレビ2ch] t e r e b i n i ch a N 0 [テレビ3ch] t e r e b i s a N ch a N 0 [テレビ3ch] t e r e b i ky o: i k u 0 [テレビ3ch] ky o: i k u t e r e b i 0 [テレビ4ch] t e r e b i y o N ch a N 0 [テレビ4ch] t e r e b i n i q t e r e 0 [テレビ4ch] n i q t e r e 0 [テレビ5ch] t e r e b i g o ch a N 0 [テレビ5ch] t e r e b i a s a h i 0 [テレビ5ch] t e r e a s a 0 [テレビ6ch] t e r e b i r o k u ch a N 0 [テレビ6ch] t e r e b i t i: b i: e s u 0 [テレビ6ch] t i: b i: e s u 0 [テレビ7ch] t e r e b i n a n a ch a N 0 [テレビ8ch] t e r e b i h a q ch a N 0 [テレビ8ch] t e r e b i f u j i 0 [テレビ8ch] f u j i t e r e b i 0 [テレビ9ch] t e r e b i ky u: ch a N 0 [テレビ10ch] t e r e b i j u q ch a N 0 [テレビ11ch] t e r e b i j u: i q ch a N 0 [テレビ12ch] t e r e b i j u: n i ch a N 0 [CATV電源] k e: b u r u t e r e b i d e N g e N 0 [ナショジオ] n a sh o j i o 0 [ディスカバリーチャンネル] d i s u k a b a r i: ch a N n e r u 0 [G+] j i: t a s u 0 [G+] n i q t e r e j i: t a s u 0 [ファミ劇] f a m i g e k i
またjconfファイルは以下のようにグラマー認識キットのものをベースに、 -w で孤立単語辞書を読み込む形にしておきます。
(~/lib/julius 以下にグラマー認識キットを展開してあります)
remocon.jconf
-w grammar/remocon.dict -C $HOME/lib/julius/grammar-kit-v4.1/hmm_ptm.jconf -input mic -demo
プログラムの方はjuliusのソースパッケージに入っているjulius-simple.cを参考にしました。
ソースコードはgithubのjremoconに置いてあります。
(4/15 修正:リポジトリ名を変更しました。lemon_pie になります。)
1コマンドを認識した後にjuliusを止める方法が少し分かり辛かったのですが、j_close_stream()を呼べばいいらしい。
ということでon_result()の最後にj_close_stream()を呼んでいます。コールバックの中からさらにAPI叩くの気持ち悪いんですが...
しかしj_close_stream()を呼んだ後にも、on_speech_ready()とon_result()が1回ずつ呼ばれてしまいます。
録音済みのデータは破棄せずに処理されてしまうということでしょうか。
別に害はないのですが、気持ちが悪いのでj_close_stream()の直後にcallback_delete()でコールバックを削除してしまうことにしました。
static void on_result(Recog *recog, void *dummy) { /* we have only one recognition process */ RecogProcess *rp = recog->process_list; WORD_INFO *winfo; int i; if (rp->result.status < 0) { /* no results obtained */ fprintf(stderr, LOG_TAG "result error: %s\n", julius_status_str(rp->result.status)); return; } winfo = rp->lm->winfo; for (i = 0; i < rp->result.sentnum; i++) { Sentence *snt = &(rp->result.sent[i]); int word_cnt = snt->word_num; int j; /* result words */ for (j = 0; j < word_cnt; j++) { const char *wstr = winfo->woutput[snt->word[j]]; printf(" %s", wstr); } putchar('\n'); /* action */ for (j = 0; j < word_cnt; j++) { int k; const char *wstr = winfo->woutput[snt->word[j]]; for (k = 0; k < ARRAY_SIZE(cmd_table); k++) { if (!strcmp(wstr, cmd_table[k].word)) { char cmd[256]; sprintf(cmd, "lemon_corn -proxy localhost" " %s", cmd_table[k].lc_cmd); system(cmd); } } } /* score */ if (g_sysopt.verbose) printf("score%d: %f\n", i + 1, snt->score); } j_close_stream(recog); clear_callbacks(recog); }
lemon_corn のコマンドラインオプションは、serial_proxyd経由の-proxyがついていますがPC-OP-RS1を直接使う場合は削除してください。
とりあえず音声リモコンとして最低限の動作が実現できただけですが、鬱陶しいケーブルテレビのチャンネル選択がかなり楽になりました。
テレビを外部入力にして、STBの3桁のチャンネルを入力して(そもそもチャンネルが覚えられないし)という操作が一発です。
これだけでも結構実用性あります。
起動はctwmでショートカットキーを割り当てて、Shift+Ctrl+'l' で一発起動するようにしましたが、もう少し工夫する予定です。
しかしやはり音声認識をやるからには自然発話での操作をしたい。
誤認識対策とかも含め、ここやここを参考に今後進めて行きたいと思います。