音声分析ソフトウェアPraatとアノテーションフォーマットTextGrid形式について

はじめに

こんにちは、レトリバのエンジニアの岩田です。最近はリバウンドに悩みながら音声認識エンジンの製品開発をしています。

今回は、音声分析ソフトウェアのPraatと、Praatで読み込んだり保存できる音声のアノテーションフォーマットTextGrid形式について紹介したいと思います。おまけとして、TextGrid形式のファイルを読み書きするライブラリ textgrid.hpp を簡単に実装したので紹介します。

音声分析ソフトウェアPraat

Praat(プラート)は、オープンソース(GPLv2)の音声分析ソフトウェアです。 MacWindowsLinuxFreeBSDなど複数のプラットフォームでバイナリが配布されています。 Praatを使うことで、音声の録音や分析、アノテーションなどを行えます。 また、Praatのスクリプトを書くことで、指定したフォルダ内の全音声ファイルを順番に解析する、といったことが簡単に行えます。

音声の分析と言っても様々ですが、Praatで音声の波形データとスペクトログラムを見てみましょう。

f:id:eiichiroi:20191030024454p:plain
Praatで音声の波形とスペクトグラムを表示

指定した区間の音声を再生したり、拡大縮小表示することもできます。他には、ピッチ解析やフォルマント分析など、音声の様々な特徴をもちいて分析することもでき、非常に便利なソフトウェアです。

Praat以外にも音声分析のフリーソフトはたくさんあり、 WaveSurferAudacity などが有名です。 興味のある方は フリーソフトを用いた音声処理の実際 などをご覧ください。

アノテーションフォーマットTextGrid形式

Praatを使って音声データに対してアノテーションした情報は、TextGrid形式でファイルに保存したり読み込めます。

TextGrid形式で保存できる情報は、次の2種類を押さえておけば大丈夫です。

  • interval tier: 区間に対するアノテーション。開始時刻と終了時刻で指定される区間に対して文字列(例: 単語、読み、音素など)を付与する。区間はオーバーラップしない。
  • point tier: 点に対するアノテーション。特定の時刻(点)に対して文字列を付与する。

1つのTextGrid形式のファイルには複数の層(tier)を保存することができ、各層ごとにinterval tierやpoint teirといった層の種類を決めて情報を付与することができます。 また、1つの層は複数の情報(intervalやpoint)を含んでおり、時刻の昇順に並んでいます。

実際に先ほどの例の音声データにアノテーションを付けると、次のようになります。

f:id:eiichiroi:20191030024558p:plain
音声データとアノテーション情報を表示したPraatの画面

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.pyTextGridTools があります。

しかし、マイナーすぎるためか、いろんな言語でライブラリが実装されているわけでもなく、次のような特徴をもつ textgrid.hpp というライブラリを自分で簡単に実装してみました。

  • C++11
  • シングルヘッダーかつヘッダーオンリー
  • 依存ライブラリなし(標準ライブラリのみ利用)
  • 文字コードUTF-8のみサポート

実装した機能の通りです。

  • データを保持するためのクラス(構造体)
    • 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行に満たないライブラリですが、発話区間検出のアルゴリズムの評価を行うコマンドを実装するのに使っていて、いまのところうまく動いています。

足りない機能は色々ありますが、次の機能は近々必要になるかもしれないと考えています。今後の課題です。

まとめ

音声分析ソフトウェアのPraatと、アノテーションフォーマットTextGrid形式について紹介しました。

音声データを扱って何かするときに役に立つ...かもしれません。

Praatの詳しい使い方が知りたくなった方には、音声解析ソフトウェア Praat音声学(筑波大学 2010年度) 授業 の記事をおすすめしておきます。