はじめに
こんにちは、レトリバのエンジニアの岩田です。最近はリバウンドに悩みながら音声認識エンジンの製品開発をしています。
今回は、音声分析ソフトウェアのPraatと、Praatで読み込んだり保存できる音声のアノテーションフォーマットTextGrid形式について紹介したいと思います。おまけとして、TextGrid形式のファイルを読み書きするライブラリ textgrid.hpp を簡単に実装したので紹介します。
音声分析ソフトウェアPraat
Praat(プラート)は、オープンソース(GPLv2)の音声分析ソフトウェアです。 MacやWindows、LinuxやFreeBSDなど複数のプラットフォームでバイナリが配布されています。 Praatを使うことで、音声の録音や分析、アノテーションなどを行えます。 また、Praatのスクリプトを書くことで、指定したフォルダ内の全音声ファイルを順番に解析する、といったことが簡単に行えます。
音声の分析と言っても様々ですが、Praatで音声の波形データとスペクトログラムを見てみましょう。
指定した区間の音声を再生したり、拡大縮小表示することもできます。他には、ピッチ解析やフォルマント分析など、音声の様々な特徴をもちいて分析することもでき、非常に便利なソフトウェアです。
Praat以外にも音声分析のフリーソフトはたくさんあり、 WaveSurfer や Audacity などが有名です。 興味のある方は フリーソフトを用いた音声処理の実際 などをご覧ください。
アノテーションフォーマットTextGrid形式
Praatを使って音声データに対してアノテーションした情報は、TextGrid形式でファイルに保存したり読み込めます。
TextGrid形式で保存できる情報は、次の2種類を押さえておけば大丈夫です。
- interval tier: 区間に対するアノテーション。開始時刻と終了時刻で指定される区間に対して文字列(例: 単語、読み、音素など)を付与する。区間はオーバーラップしない。
- point tier: 点に対するアノテーション。特定の時刻(点)に対して文字列を付与する。
1つのTextGrid形式のファイルには複数の層(tier)を保存することができ、各層ごとにinterval tierやpoint teirといった層の種類を決めて情報を付与することができます。 また、1つの層は複数の情報(intervalやpoint)を含んでおり、時刻の昇順に並んでいます。
実際に先ほどの例の音声データにアノテーションを付けると、次のようになります。
TextGrid形式の実際のデータを見てみましょう。
File type = "ooTextFile" Object class = "TextGrid" xmin = 0 xmax = 3.9893650793650792 tiers? <exists> size = 1 item []: item [1]: class = "IntervalTier" name = "単語" xmin = 0 xmax = 3.9893650793650792 intervals: size = 23 intervals [1]: xmin = 0 xmax = 0.04164132431058888 text = "" intervals [2]: xmin = 0.04164132431058888 xmax = 0.5574000052695588 text = "あらゆる" intervals [3]: xmin = 0.5574000052695588 xmax = 0.575465489542943 text = "" intervals [4]: xmin = 0.575465489542943 xmax = 0.9958441404615553 text = "現実" intervals [5]: xmin = 0.9958441404615553 xmax = 1.0003172632539812 text = "" intervals [6]: xmin = 1.0003172632539812 xmax = 1.2113897371395872 text = "を" intervals [7]: xmin = 1.2113897371395872 xmax = 1.4156967091301467 text = "" intervals [8]: xmin = 1.4156967091301467 xmax = 1.9208576089735214 text = "すべて" intervals [9]: xmin = 1.9208576089735214 xmax = 2.025607136616711 text = "" intervals [10]: xmin = 2.025607136616711 xmax = 2.4053609599255426 text = "自分" intervals [11]: xmin = 2.4053609599255426 xmax = 2.406244108351842 text = "" intervals [12]: xmin = 2.406244108351842 xmax = 2.5272354427548884 text = "の" intervals [13]: xmin = 2.5272354427548884 xmax = 2.571852760478189 text = "" intervals [14]: xmin = 2.571852760478189 xmax = 2.7557684202550825 text = "ほう" intervals [15]: xmin = 2.7557684202550825 xmax = 2.7565057092069747 text = "" intervals [16]: xmin = 2.7565057092069747 xmax = 2.9843580031922734 text = "へ" intervals [17]: xmin = 2.9843580031922734 xmax = 3.0746639210234625 text = "" intervals [18]: xmin = 3.0746639210234625 xmax = 3.5533303680778494 text = "ねじ曲げ" intervals [19]: xmin = 3.5533303680778494 xmax = 3.663416635923271 text = "た" intervals [20]: xmin = 3.663416635923271 xmax = 3.663858210136421 text = "" intervals [21]: xmin = 3.663858210136421 xmax = 3.785732692965767 text = "の" intervals [22]: xmin = 3.785732692965767 xmax = 3.9588297845204896 text = "だ" intervals [23]: xmin = 3.9588297845204896 xmax = 3.9893650793650792 text = ""
最初にヘッダ情報(ファイル形式や層の数など)があり、そのあとに層ごとのアノテーションの情報が続く構造になっています。 詳細な構文や制約に関しては、本家のドキュメントの TextGrid file formats を参照してください。
Praatはアノテーション(音声コーパスの構築作業)にも使われており、 日本語話し言葉コーパス(CSJ) や理研母子会話コーパス(R-JMICC)のコーパスの構築に実際に使われているそうです。 また、TextGridの利用方法 で説明されているように、日本語話し言葉コーパスは複数の形式でアノテーション情報を提供しており、その中にTextGrid形式もあります。
TextGrid形式の他の活用方法としては、音声認識や発話区間検出の結果をTextGrid形式で出力して評価に利用することも考えられます。
TextGrid形式を読み書きするライブラリ
TextGrid形式をPraat以外のプログラムでも扱えると音声コーパスの活用や音声認識システムの評価に利用できるので、とても便利です。
TextGrid形式の読み込みや書き込みを行えるライブラリはいくつかあり、pythonだと textgrid.py や TextGridTools があります。
しかし、マイナーすぎるためか、いろんな言語でライブラリが実装されているわけでもなく、次のような特徴をもつ textgrid.hpp というライブラリを自分で簡単に実装してみました。
実装した機能の通りです。
- データを保持するためのクラス(構造体)
- TextGrid、Tier、IntervalTier、PointTier、Interval、Pointなど
- 入力ストリームからTextGrid形式のデータを読み込む機能
- TextGrid形式のParser(+Lexer)
- 出力ストリームへTextGrid形式でデータを書き出す機能
- TextGridオブジェクトのデータにアクセスするためのTextGridVisitor
一番面倒だったのはTextGrid形式の解析を行うParserでした。 TextGrid file formats のドキュメントに詳細なファイル形式の説明がありますが、人間が読みやすいゆるい形式と機械可読な短い形式があります。 最終的には両方読み込めるようにしたのですが、実装している最中は、ファイルフォーマットの仕様がもっと簡素だったら構文の定義と構文解析の実装がもっと楽だったよね、という気持ちと、たまには構文解析を手書きで実装するのも楽しいよね、という気持ちがせめぎ合いました。
標準入力からTextGrid形式のデータを読み込んで、そのまま標準出力に出力する簡単なサンプルプログラムは次のように簡単に書くことができます。
#include <iostream> #include "textgrid.hpp" int ReadAndWrite(bool in_short = false) { try { textgrid::Parser parser(std::cin); textgrid::TextGrid text_grid = parser.Parse(); if (in_short) { textgrid::ShortWriter writer(std::cout); text_grid.Accept(writer); } else { textgrid::Writer writer(std::cout); text_grid.Accept(writer); } return 0; } catch (const textgrid::Exception& e) { std::cerr << e.what() << std::endl; return -1; } } int main() { return ReadAndWrite(); }
1000行に満たないライブラリですが、発話区間検出のアルゴリズムの評価を行うコマンドを実装するのに使っていて、いまのところうまく動いています。
足りない機能は色々ありますが、次の機能は近々必要になるかもしれないと考えています。今後の課題です。
- TextGridオブジェクトを構築しやすいインタフェース
- アノテーション情報の編集を行うインタフェース
- アノテーション情報の不変性(区間はオーバーラップしない、など)のチェック
- Pitch Tierのサポート
まとめ
音声分析ソフトウェアのPraatと、アノテーションフォーマットTextGrid形式について紹介しました。
音声データを扱って何かするときに役に立つ...かもしれません。
Praatの詳しい使い方が知りたくなった方には、音声解析ソフトウェア Praat と 音声学(筑波大学 2010年度) 授業 の記事をおすすめしておきます。