nyanpyou Note

主な目的は調べたり作ったりしたプログラミング備忘録(予定)

音声認識を利用したい話

なんでいきなり音声認識

唐突だが巷で流行りのVtuberというものが最近お気に入りで、自分は特に宝鐘マリンの配信をよく見ている。公式的には17歳なのだが、明らかに17歳ではない(アラサー的な)話題を連発するマシンガントークが面白い。

www.youtube.com

さて、Vtuber界隈では配信の面白かった部分を有志の視聴者が切り出して編集し、公開するという「切り抜き」という文化があるのだが、それに関してある日以下のツイートを見た。

これを見た時ふと思ったのだ。特定の話題がどの動画のどの部分で話されていたかを覚え続けるのは難しい。だったら検索して探せる仕組みが作れたりしないだろうか?と。切り抜きを作る人の作業にも使えるだろうし、「面白かったあの話をもう1回ラジオ代わりに聞きたいけど、あれどの配信のどの部分だったかなぁ~…」というような需要もありそうだ。
つまり各動画での船長の発言を文字起こしデータベース化し、 ユーザーがキーワードを入力したらそのキーワードが含まれる動画のURLとその発言のタイムスタンプを返す仕組みを作れると嬉しい。
しかし通常Vtuberの配信は最低でも1時間あり、しかもその間放送事故にならないよう常に喋り続けるため、手動で文字起こしを行うのはあまり現実的ではない。

というわけで自動での文字起こし、つまり動画を音声認識で文字起こしする方法について調査することにした。

音声認識手法を求めて

いつも通りPythonをベースに作りたいので、まずは「Python 音声認識」でヒットしたSpeechRecognitionというライブラリを試してみた。
Google Speech Recognition APIを用いた認識を実行してみたが、音声ファイルの再生時間によってエラーが出ることが分かった。試した範囲では1分は駄目で、10秒はOKだった。
正確な仕様を知りたかったため、PyPIに書かれている使用サービス(Google Speech Recognition)について検索をかけてみたが全くヒットせず、詳しいことが分からなかった。
仕方が無いのでGithubソースコード(https://github.com/Uberi/speech_recognition/blob/master/speech_recognition/__init__.py) を見たところ誰でも使えると書かれたAPIキーが書いてあったのだが、肝心の仕様についての情報を得ることは残念ながら出来なかった。
2017年が最後の更新になっているライブラリなので、Google側のサービスがもう変わっているのかもしれない。

個人で使うならまだしも、不特定多数に公開して使って貰いたいと考えている以上、使用サービスについて詳しいことが全く分からない状況 で、しかもソースコードに「このサービスはGoogleがいつでも停止するかもしれないよ」と書かれているものを使用するのは望ましくないため、大人しくGoogleが提供する現行の音声認識サービスであるSpeech-To-Textを使ってみることにした。

Speech-To-Text使ってみる

Google Cloud Platformを使うのは初めてなのでもちろん何もわからない。
ネット上の記事と公式のドキュメントを参考にしつつ、まずはアカウント作成から始めた。

www.techceed-inc.com

cloud.google.com

アカウント作成自体はすぐにできたが、ちょっと詰まりかけたのはSpeech-To-Textにアクセスするための秘密鍵を取得する部分。
まずはCloud Console プロジェクトというものを作成し、さらにそのプロジェクトに対してSpeech-To-Text APIを有効にし、さらにサービスアカウントというものを作成し、秘密鍵となるJSONファイルをダウンロードするという手順を踏まなければならない。
サービスアカウントとは何なのかよくわからなかったが、恐らく各プロジェクトに対して、どういう権限を持つかを設定するための仕組みなんじゃないかととりあえず認識することにした。
今回は自分一人で使うものなので、オーナーで設定しておいた。

改めて調べるとサービスアカウントとはユーザーじゃなくアプリケーションに属するアカウントであるらしい。う~ん…正直まだすっきりはしないが概ねイメージは間違っていなさそう。

qiita.com

そうこうして無事JSONファイルの入手に成功したので、

pip install --upgrade google-cloud-speech

でクライアントライブラリをインストールし、公式のサンプルコードを元に以下のコードを実行してテストを行った。

今回素材として使用したのはこちらの動画の音声(諸事情により7/31現在一時非公開になっていますがじきに戻るはず)

www.youtube.com

開始0~10秒と開始0~30秒を切り出してSpeech-to-Text APIへ放り投げた。

from google.cloud import speech_v1
from google.cloud.speech_v1 import enums
import io
import os

def sample_recognize(local_file_path):
    """
    Transcribe a short audio file using synchronous speech recognition

    Args:
      local_file_path Path to local audio file, e.g. /path/audio.wav
    """

    client = speech_v1.SpeechClient()

    # local_file_path = 'resources/brooklyn_bridge.raw'

    # The language of the supplied audio
    language_code = "ja-JP"

    # Sample rate in Hertz of the audio data sent
    sample_rate_hertz = 16000

    # Encoding of audio data sent. This sample sets this explicitly.
    # This field is optional for FLAC and WAV audio formats.
    encoding = enums.RecognitionConfig.AudioEncoding.LINEAR16

    config = {
        "language_code": language_code,
        "sample_rate_hertz": sample_rate_hertz,
        "encoding": encoding,
    }

    with io.open(local_file_path, "rb") as f:
        content = f.read()
    audio = {"content": content}

    response = client.recognize(config, audio)
    for result in response.results:
        # First alternative is the most probable result
        alternative = result.alternatives[0]
        print(u"Transcript: {}".format(alternative.transcript))

if __name__ == "__main__":
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "秘密鍵のパス"
    sample_recognize("【切り抜き】おもしろマリン動画【ホロライブ宝鐘マリン】1.wav")

遭遇したエラー

google.api_core.exceptions.InvalidArgument: 400 sample_rate_hertz (16000) in RecognitionConfig must either be omitted or match the value in the WAV header ( 44100)

configのsample_rate_hertzは実際のファイルの中身と一致していないと駄目らしい。省略してもいいと言われているのでひとまずconfigの中身はlanguage_codeのみにして省略することにした(いちいちサンプリングレートを調べるのがめんどうなので)。

google.api_core.exceptions.InvalidArgument: 400 Must use single channel (mono) audio, but WAV header indicates 2 channels.

読み込ませるファイルはモノラルの必要があると言われた。これは仕方がないのでそのように音声ファイルを作り直し。

結果1

  • 0~10秒の音声

[正解の音声(実際に聞き取った音声)]
ただいま 寂しかったっちゃか ダーリン 浮気してなかったっちゃ 浮気は



[Speech-to-text]
ただいま寂しかったちょこっとりん浮気しなかった理由


  • 0~30秒の音声

[正解の音声]
ただいま 寂しかったっちゃか ダーリン 浮気してなかったっちゃ 浮気は許さないっちゃ びりびりびりびりびりびりびりびりびり (笑い声) ドクロ君 は ドク ドクロ君 船長裏切ったな 寝取りやがって 船長のリスナーさんを返せ うわー びりびりびり うわー



[Speech-to-text]
ただいま寂しかったちょこっとりん浮気しなかった理由を言っちゃびりびりびりびりびりびりびり黒瀬町裏切ったら寝とるやだって船長のリストを再生


予想はしていたが思ったほどの精度は出ない。認識できなかった部分は飛ばしてしまうようだ。精度の出ない理由は話者の発声以外の音(BGMとか)も入っていることに加え、通常の会話では遭遇しないような喋り方や言葉を使っているせいだろう。限定的な言葉について学習させたりすれば精度はもちろん上がるのだろうが、残念ながらSpeech-to-textにはユーザーが自由に学習させる機能は提供されていないようだ。
その分他の音声認識サービスよりデフォルトの状態でも非常に精度がいいようなのだが、

www.slideshare.net

流石にハードルが高かったようだ。
(手動で行った文字起こしを冷静に眺めてみると、これは流石にきついだろとも思うが…w)
公式のベストプラクティスによると、学習の代わりに語句のヒントを使用して名前や用語を語彙に追加して精度を上げることもできるそうだが、

cloud.google.com

今回の結果を見る限りはそれでも限界がありそうに思える。ただし、「船長」や「ドクロくん」といった単語は認識して貰えるかもしれない。

どの程度までなら認識する?

今回の目的に使うにはどうやらまだ無理がありそうなことが分かったが、では果たしてどの程度までならデフォルトの状態でもきっちり認識してもらえるのか、ついでなので調べてみることにした。
比較に使うのは以下の5パターン

1.プロのナレーション(BGMなし)
2.プロのナレーション(BGMあり)
3.素人(自分読み上げて録音)のナレーション(BGMなし)
4.素人(自分読み上げて録音)のナレーション(BGMあり)
5.プロの雑談(BGMあり)

プロのナレーションについてはBGMあり・なしで同じ内容のものは用意されていなかったので、BGMをAudacityで合成したものを自分で作成して試すことにした。
合成に使用したBGMはMusMus掲載の「柔らかな嘘」。そのままだとBGMにしては音量が大きすぎるので、ゲインを-9.0dBして合成した。

musmus.main.jp

プロのナレーション素材としては大塚明夫さんのサンプルボイスを使わせて頂くことにした。(スネークとかガトーとか何度聞いてもカッコいいんだよなぁ)

mausu.net

自分が読み上げる内容は大塚明夫さんの読み上げる原稿と同じものを使う。
プロの雑談についてはどの音声を使うか悩ましいところだったが、大塚明夫さんのいい感じの雑談音声が見つからなかったため、たまたま目に入った同じく低音ボイスの杉田智和さんの音声を使うことにした。使用した音声はこちらの動画

www.youtube.com

の17秒~72秒を切り出した。
また、認識させるファイル形式はいずれもモノラルのWAVとした。

結果2

差異が判別しやすいように適宜空白を追加しているが、Speech-to-textが返す文章に本来空白は挿入されていない。

1.プロのナレーション(BGMなし)


[正解の音声]
大塚明夫です ご存知大塚明夫です アルファスパイダー それは上質のコンフォートを備えた自由への確かな意志である 柔らかで官能的なラインに秘めたみなぎるパワーと躍動感 サイドを深く抉るグルーヴ あたかも宝石のように輝く円形のヘッドライト オープンエアを駆け抜けるドライビングの歓びを まさに極めた存在であることを この車は全身で訴えているのだ 伝統のエンブレムを抱いたフロントに端を発するラインの美しさは たとえフードを閉じても 変わらない



[Speech-to-text]
大塚明夫です ご存知大塚明夫です アルファスパイダー それは上質のコンポートを備えた自由への確かな意志である 柔らかで官能的な LINE に秘めたみなぎるパワーと躍動感 サイドを深くえぐるグルーヴ あたかも宝石のように輝く円形のヘッドライト オープンエアを駆け抜けるドライビングの歓びを マーシャに極めた存在であることを この車は全身で訴えているのだ 伝統のエンブレムを抱いたフロントに端を発するラインの美しさは たとえフードを閉じても 変わらない


2.プロのナレーション(BGMあり)


[Speech-to-text]
大塚明夫です ご存知大塚明夫です アルファスパイダー それは上質のコンポートを備えた自由への確かな意志である 柔らかで官能的な LINE に秘めたみなぎるパワーと躍動感 サイドを深くえぐるグルーヴ あたかも宝石のように輝く円形のヘッドライト オープンエアを駆け抜けるドライビングの歓びを マーシャに極めた存在であることを この車は全身で訴えているのだ 伝統のエンブレムを開いた風呂に端を発するラインの美しさは たとえフードを閉じても 変わらない


3.素人のナレーション(BGMなし)


[Speech-to-text]
大塚明夫です ご存知大塚明夫です アルファスパイダー それは上質のコンフォートを備えた自由への確かな意志である 柔らかで官能的な LINE に秘めたみなぎるパワーと躍動感 サイドを深くえぐるグルーヴ あたかも宝石のように輝く円形のヘッドライト 黄ばみを駆け抜けるドライビングの歓びを まさに極めた存在であることを この車は全身で訴えているのだ 伝統のエンブレムを抱いたフロントに端を発するラインの美しさは たとえフードを閉じても 変わらない


4.素人のナレーション(BGMあり)


[Speech-to-text]
大塚明夫ですと ご存知大塚明夫です アルファスパイダー それは上質な梱包に備えた自由への確かな 柔らかで官能的な LINE に秘めたみなぎるパワーと送ろうか サイドテーブルグループ 化も宝石のように輝く型のヘッドライト 黄組を駆け抜けるドライビング美容 鋏止めた存在であること この車を全身に訴えているのか 現行のエンブレムを抱いたことに端を発する LINE の 例え楓どうしても終わらない


5.プロの雑談(BGMあり)


[正解の音声]
少年エースの創刊の目玉としてガンダムやらマクロスやらがある中 キングオブファイターズの漫画があったんですね やっぱり格ゲーも当時一番ハマってたんで KOF94の漫画まで始まんのかやったねって言って見たらですね まぁーそのカオスな展開ぶりにですね 私は感動いたしましてですね 主人公がまず草薙京じゃないんですよ テリーボガードなんです えなんで餓狼伝説の人が主人公なのって それも真行寺先生あの単行本の終わりかなんかに書いてて 新規主人公では作品が立たないからみたいな理由で経験値とその知名度のあるテリーを主人公にしましたって編集さんと相談して決めたみたいなこと書いてあって でも脇役じゃないのよいわゆるダークヒーローみたいな形でライバルっていうポジのなんかこうキングオブファイターズに際して新進気鋭のやばいやつらが出てきたっていう 招待状もボッて握りつぶすし相手をこうクールに倒した



[Speech-to-text]
少年エースの相関の目玉としてガンダムやらマクロスやらがある中 キングオブファイターズの漫画買ったんですね やっぱり格ゲーも頭で一番はまってたんで 594のままで始まるのかよってって言ってみたんですね そのカオスな展開ぶりですね 私は感動いたしましてですね 主人公がまずく実に器用じゃないんですよ テリーボガード何です 何で餓狼伝説の人が主人公なので それも真行寺先生の単行本の終わりがなんかに書いてて 新規主人公では作品が立たないからみたいな理由で経験とその知名度のあるテリーを主人公にしましたって編集さんと相談して決めたみたいのを書いてあって でも脇役じゃないのよ今言った区広尾みたいな風ライバルっていう文字に何かこうキングオブファイターズに際して新進気鋭のヤバイ奴らが出てきたっていう 招待状もらって握りつぶし開いておこうクールに倒した上


結果2を見て

まず、ナレーションを読み込ませた時の精度の高さに驚いた。コンフォート→コンポート と まさに→マーシャに の2か所以外正確に認識している。それどころか素人の読み上げもほぼ同等の精度で認識できている。大塚さんの音声に関してはBGMが付いていても影響なしだ。ただし、素人読み上げについてはBGMが付いた途端明らかに精度が落ちている。
BGMなしでの精度がプロと素人に差が無いことを見ると、恐らくBGMとの音量バランスが問題だと考えられる。以下の画像のように、大塚さんの音声と素人の音声には大きな音量差がある(素人が小さい)。BGMの音量はどちらも同じ大きさで合成したため、素人の音声がBGMに隠れてしまっているのだろう。

f:id:nyanpyou106:20200731093535p:plain
(上から大塚さん、BGM、素人の波形)ベテランプロ流石の音圧

雑談についても想像以上の精度だった。KOF(SNK製の格闘ゲーム)について語る動画だったため専門用語の嵐かつ台本の無い喋りにも関わらず、Speech-to-textが返してきた文章は大体何を言っているか理解できる文章になっている。ただし、力を抜いて話された語尾などは拾いきれていないところもあるようだ。
他にも草薙京は認識できなくてテリーボガードは認識できているなど色々面白い部分はあったが、1~5全ての結果と先に試した船長の結果を見ると、精度の良い結果を得るには次の条件が必要だと考えられる。

  • 日常会話で使わないような喋り方をしていないこと
  • はっきりとした発音がなされていること
  • 十分な音量があること
  • BGMと音声の音量比が一定以下であること

いずれも公式のベストプラクティスに書かれていることではあるが、とにもかくにも認識させたい音声を、他の音声より十分大きな音量でSpeech-to-textへ渡すことが重要なようだ。

終わりに

残念ながら全ての動画を自動で文字起こしさせる目論見は実現できなさそうだということが分かった。しかしうまく動画中のBGMのみを消す(低減する)方法があれば、ある程度までは自動で文字入れし、それを人力で修正する方式が可能かもしれない。チェックに膨大な時間がかかるけど…
一味の有識者求む。