【音声認識】Golang で Julius-Client の実装を試みる

最近, Sirius: An Open Intelligent Personal Assistantが話題を集めていますが, 手軽に音声認識を行えるJuliusに関する話。

JuliusをGolangで扱う場合, 他の言語と同様2つのアプローチがあります。
・JuliusLibを組み込む。Golangの場合cgoパッケージを用いる。
・サーバモードで起動しTCP/IPで結果を取得する。

結論を言うと, JuliusLibを組み込む方は上手くいっていません。

環境はOSX 10.10.2です。

Golangでlibjuliusを組み込む

julius-4.3.1.tar.gzをダウンロードし展開します。
C言語クライアントのjulius-simple/julius-simple.cを参考にアプリケーションを書いてみます。

Golangではcgoパッケージを使って, JuliusLibをLinkします。
最小だと以下のようになり, buildが通ります。

package main

/*
#include 
#cgo CPPFLAGS: -I/usr/local/include -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -ljulius -lpthread -L/usr/local/lib -lsent -Wl,-framework -Wl,CoreServices -Wl,-framework -Wl,CoreAudio -Wl,-framework -Wl,AudioUnit -Wl,-framework -Wl,AudioToolbox -lz -liconv -lm
*/
import "C"
import "fmt"

func main() {
	fmt.Println("hoge")
}

julius-simple.cをGolangに移植してみようとしましたが, callbackを登録する関数でコケました。

C.callback_add(recog, C.CALLBACK_EVENT_SPEECH_READY, status_recready, nil);
$ go build julius-simple.go 
./julius-go.go:86: cannot use status_recready (type func(*C.struct___Recog__, *C.void)) as type *[0]byte in argument to _Cfunc_callback_add

*[0]byteでcastとか試したけどなんとかならなかったので, 組み込む作戦は一旦断念。
調べている中で見つけた情報は以下。

[1] CGo error : Cannot use GoCallback (type func(_Ctype_int, unsafe.Pointer)) as type *[0]byte in function argument
[2] Passing function arguments to C
[3] golang で string を []byte にキャストしてもメモリコピーが走らない方法を考えてみる

JuliusサーバモードにGolang-Clientで認識結果を取得する

portaudioのインストール。

$ brew install portaudio

dictation-kit-v4.3.1-osxをダウンロードして展開します。
バイナリも付属してきます。早速juliusをサーバモードで起動します。

$ cd dictation-kit-v4.3.1-osx
$ ./bin/julius -C main.jconf -C am-gmm.jconf -module

以下ではGoroutineで処理を切り離していますが, 認識結果を取得するだけなら全く必要ありません。

package main

import (
	"bufio"
	"fmt"
	"net"
	"strings"
	"time"
)

func rcvJuliusResp(_ch chan string) {
	for word := range _ch {
		fmt.Println(time.Now(), " : ", word)
	}
}

func main() {
	conn, _ := net.Dial("tcp", "localhost:10500")

	ch := make(chan string)

	go rcvJuliusResp(ch)

	for {
		message, _ := bufio.NewReader(conn).ReadString('\n')
		isContain := strings.Contains(message, "WORD")

		if isContain == true {
			word := strings.Split(message, "\"")
			ch <- word[1]
		}
	}
}

結果を見る限り, 音響モデルのチューニングが必要。

$ go run julius-client.go 
2015-03-28 13:18:59.866475432 +0900 JST  :  本
2015-03-28 13:19:28.449968828 +0900 JST  :  出
2015-03-28 13:19:31.455237858 +0900 JST  :  て
2015-03-28 13:19:35.514823058 +0900 JST  :  ヘレン
...

[4] 第10章 モジュールモード - Julius

言語モデルの作成

おはよう, こんにちは, さようならの3単語を含むjulius言語モデルを作ってみます。
greeting.grammarとgreeting.vocaを作成します。構文は[5]第7章 言語モデルを参考に。

$ mkdir greeting
$ cd greeting
$ vim greeting.grammar
S       :  NS_B GREETING NS_E
# おはよう/こんにちは/さようなら
GREETING  :  OHAYOU
GREETING  :  KONNITIWA
GREETING  :  SAYOUNARA

$ vim greeting.voca
% OHAYOU
おはよう       o h a y o u
% KONNITIWA
こんにちは     k o n n i t i w a
% SAYOUNARA
さようなら     s a y o u n a r a
% NS_B
            silB
% NS_E
           silE

mkdfa.plでgreeting.dfa, greeting.term, greeting.dictを生成します。

$ ../dictation-kit-v4.3.1-osx/bin/mkdfa.pl greeting
greeting.grammar has 4 rules
greeting.voca    has 5 categories and 5 words
---
Now parsing grammar file
Now modifying grammar to minimize states[-1]
Now parsing vocabulary file
Now making nondeterministic finite automaton[4/4]
Now making deterministic finite automaton[4/4] 
Now making triplet list[4/4]
5 categories, 4 nodes, 5 arcs
-> minimized: 4 nodes, 5 arcs
---
generated: greeting.dfa greeting.term greeting.dict

generateコマンドで確認します。

$ ../../dictation-kit-v4.3.1-osx/bin/generate greeting
Stat: init_voca: read 5 words
Reading in term file (optional)...done
5 categories, 5 words
DFA has 4 nodes and 5 arcs
----- 
  さようなら 
  おはよう 
  こんにちは 
no further sentence in the last 300 trial

作成した言語モデルを試してみます。認識結果はそこそこな印象です。

$ ../dictation-kit-v4.3.1-osx/bin/julius -gram greeting -h ../dictation-kit-v4.3.1-osx/model/phone_m/jnas-mono-16mix-gid.binhmm -input mic -rejectshort 800

[5] 第7章 言語モデル - Julius

ちなみに, Siriusの論文はこちら

* この記事はkarota-projectの活動に関する記事です。