凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Node.js で簡単に音声認識できるモジュールを作ってみた #nodefest

はじめに

この記事は東京Node学園祭2012 アドベントカレンダーの 23 日目の記事です。

現在、オープンソースの大語彙連続音声認識エンジン Julius を利用して音声による家電操作を行なっているのですが、どんな言葉を認識させるかの文法をゴリゴリ書いたり、コールバックの処理を C++ でゴリゴリ書くのが大変だったので、これらを簡単に実現してくれる Node.js のモジュールをつくってみました。
WEB 関連の話題で扱われることの多い Node.js ですが、C++ でネイティブモジュールを作成することによる Node.js の可能性を感じてもらえれば、と思います。

出来るようになること

以下のように音声認識させる言葉を登録、その文言を Julius が解釈できる形式にコンパイルして、Julius インスタンスを生成、スタートするだけで音声認識が可能になります。

var Julius  = require('julius')
  , grammar = new Julius.Grammar()
;

// 音声認識させる言葉を覚えさせる
grammar.add('お早う御座います');
grammar.add('こんにちは');
grammar.add('お休みなさい');

// 登録した言葉をコンパイルして Julius を実行する
grammar.compile(function(err, result) {
    if (err) throw err

    // Julius インスタンスの生成
    var julius = new Julius( grammar.getJconf() );

    // 認識結果のコールバックを追加
    julius.on('result', function(str) {
        console.log('認識結果:', str);
    });

    // 音声認識の開始
    julius.start();
});

文法の登録には、下記のように正規表現のような記法が可能です。 また、シンボルを定義して時間のような数字のまとまり等を一括で登録することもできます。

// 下記のような正規表現っぽい形式が使えます
grammar.add('(あ+|い*)う{2,4}');
grammar.add('(え)?お{3}');

// シンボルを定義することもできます
// 下記は「○○時」および「○○時○○分」を認識します。
var hour = [], minute = [];
for (var i = 1; i <= 24; ++i, hour.push(i)) ;
for (var i = 0; i <  60; ++i, minute.push(i)) ;
grammar.addSymbol('HOUR',   hour);
grammar.addSymbol('MINUTE', minute);
grammar.add('<HOUR>時(<MINUTE>分)?');

// 文法のファイル名を指定(省略時は tmp)
grammar.setFileName('test');

// 文法をコンパイル
grammar.compile(function(err, result) {
    if (err) throw err;
    // 文法をテストする
    grammar.test(function(err, result) {
        if (err) throw err;
        console.log(result.stdout);
        // 作成した文法ファイルを削除
        grammar.deleteFiles();
    });
});

音声認識では、各種イベントを受け取るコールバックを定義することができます。

// 発話待機
julius.on('speechReady', function() {
    console.log('onSpeechReady');
});

// 発話開始
julius.on('speechStart', function() {
    console.log('onSpeechStart');
});

// 発話終了
julius.on('speechStop', function() {
    console.log('onSpeechStop');
});

// 音声認識エンジン開始
julius.on('start', function() {
    console.log('onStart');
});

// 音声認識エンジン一時停止(stop した時に呼ばれる)
julius.on('pause', function() {
    console.log('onPause');
});

// 認識結果を str で返す
julius.on('result', function(str) {
    console.log('認識結果:', str);
});

// エラー発生
julius.on('error', function(str) {
    console.error('エラー', str);
});

動作環境/インストール方法について

(2012/12/19)アップデートしました:node-julius を更新して npm に登録した - 凹みTips

動作環境

以下動作を確認した環境です。

インストール

ソースコードgithub にあげてあります。

予め環境を整えてある状態で作ったので、下記項目で導入できなかった場合はご一報下さい(-人-)。

まず、各種ライブラリを導入します。Mac では brew で導入します。

$ brew install boost
$ brew install mecab mecab-ipadic
$ brew install icu4c

(追記)portaudio も必要でした。

$ brew install portaudio

(追記2: 2012/11/11)
ICU の include / library のリンクを自動で作ってくれなかった気もするで、その場合は /usr/local/iclude、/usr/local/lib に /usr/local/Cellar/icu4c/49.1.2/ からリンク貼ったりして下さい。

Ubuntu の場合は、aptitude で入る boost のバージョン(Ubuntu 10.04 で 1.40)が古いので、直接本家から落としてきて下さい。

$ sudo aptitude install mecab mecab-ipadic libmecab-dev
$ sudo aptitude install libicu-dev

そして、github からソースコードを落としてきてコンパイルします。コンパイルには node-gyp が必要です。

$ npm install -g node-gyp
$ cd YOUR_NODE_PROJECT_DIR
$ git clone https://github.com/hecomi/node-julius
$ mkdir node_modules
$ mv node-julius node_modules/julius
$ cd node_modules/julius
$ make

お試しで、julius ディレクトリ内で下記コマンドを実行して挨拶してみて下さい。

$ node test2
おはようございます
こんにちは
おやすみなさい

認識されれば成功です。

各種ライブラリの導入は少し面倒ですが、いずれ「npm install julius」で簡単に入るようにしようと思っています(多分)。

解説

基本的には過去の記事の寄せ集め的な感じになってます。
Julius の Node.js モジュール化に関しては以下の 2 記事がベースとなります。

基本的な流れとしては、非同期部分(各種コールバックなど)は libuv の uv_queue_work で切り出し、node::ObjectWrap を継承してイベントハンドラの呼び出しを EventEmitter を通じて行う、ということをしています。
登録する文法の解析には Gin という Boost.Spirit 風に EBNF 調で構文解析できるライブラリを使用しています。詳細は以下の記事にまとめてあります。

日本語を voca 形式に変換する部分では npm にも登録してある以下の自作 Node.js モジュールを使っています。

MeCab で日本語をカタカナにする部分を担っているので、正しい日本語ではないものは voca 形式に変換できないのが現状の課題です(「ほげ」や「ふが」は「ほ」「げ」、「ふ」「が」と分解されるが「ぴよ」はダメみたいな)。。

応用例

例えば、iRemocon との連携を行えば音声認識リモコンが簡単に出来ます。

var Julius   = require('julius')
  , grammar  = new Julius.Grammar()
  , iRemocon = require('iRemocon')
  , iremocon = new iRemocon('192.168.0.2')
;

grammar.add('(テレビ|電気)を(つけて|消して)');

grammar.compile(function(err, result) {
	if (err) throw err;

	julius = new Julius( grammar.getJconf() );
	julius.on('result', function(str) {
		if (str) console.log(str.slice(0, -1).toString() + 'ます');
		switch (str) {
			case 'テレビをつけて' : iremocon.is(1); break;
			case 'テレビを消して' : iremocon.is(2); break;
			case '電気をつけて'   : iremocon.is(3); break;
			case '電気を消して'   : iremocon.is(4); break;
		}
	});

	julius.start();
});

他にも Node.js のたくさんあるモジュールを利用して、WEB 連携してみたり、Twitter に投稿してみたりすると楽しそうです。

おわりに

WEB 関連だけでなく、Node.js 利用すると色んなものが作れるよ!という世界の紹介になっていればと思います。みんなで楽しいモジュールを作って Node.js を盛り上げて行きましょう!