Platformの開発チームに所属している新卒1年目の佐藤邦彦です。本記事では、Pythonを使って任意のSignal-to-Noise比(SN比)の音声波形を作る方法を紹介します。本記事の内容は、Clova等の弊社音声事業とは関係ありません。Learningが技術革新を起こしてから久しいですが、同様のことが音声処理の分野においても起きています。ClovaなどのAIスピーカーが市場に普及しました。コンピュータによる音声生成(Text-to-Speech)の精度も上がり、その質は人間の声と区別が付きづらいほどです。Learningによって格段に精度が上がっている音声分野があります。そのひとつが音源分離です。音源分離とは、複数の音源が混ざった入力波形を各音源の波形に分離することです。音声と雑音が混ざった入力波形を音声と雑音のそれぞれの波形に分離するのが音源分離です。音声と雑音に分離する処理は「音声強調」あるいは「雑音除去」とも言われます。入力音源にピアノとトランペットとギターの音が混ざっており、これら3つの波形を分離することも音源分離と言います。下記の図は音源分離のイメージ図です。Learningによって精度が格段に上がっています。以下はその一例の紹介です。Learningで音源分離を実現するためには学習用のデータセットを用意する必要があります。本記事の内容は、学習用の訓練データセットを作るのに役立ちます。話をわかりやすくするため、本記事では音声波形から雑音を除去し音声のみを抽出する「音声強調(雑音除去)」の音源分離について考えていきます。ニューラルネットワークの学習のイメージは以下のようになります。訓練データには音声と雑音が混ざった波形が必要です。教師データには音声のみの波形が必要です。ニューラルネットワークは雑音が混ざった音声波形から音声のみを抽出するように訓練されます。音声のみのデータセットはTIMITコーパスなど有名な音声コーパスがいくつもあります。雑音を含んだ音声データと音声のみのデータが対になっているデータセットはあまりありません。したがって、開発者自身が要件に合わせてデータセットを作る必要があります。いくつか既存のデータセットも存在しますが、開発者が各々で任意のSN比の波形を合成できるようになれば、音声と雑音だけではなく複数の楽器音を混合したデータセットなど、開発の要件に合わせてデータセットを作ることが可能になります。ratio、SN比、信号雑音比)とは、信号の大きさが雑音の大きさに比べてどのくらい大きいかを表す比率です。音声信号におけるSN比の単位はdB(デシベル)です。本記事ではSignalを音声、Noiseをそれ以外の音(ホワイトノイズ、環境音など)として説明します。SN比はその数値が高いほど音声のほうが雑音よりも大きいことを意味します。5dBよりも20dBのほうが音声が大きく聞こえ、雑音が小さく聞こえます。0dBは音声と雑音の大きさがちょうど同じことを意味します。音声よりも雑音のほうが大きい場合は、-10dBのようにマイナスの値をとります。「任意のSignal-to-Noise比の音声波形を作る」とは、例えば、5dBの割合で音声と雑音が混ざった音声波形を作るということです。Sinal-to-Noise比の計算方法SN比は以下の式で求めることができます。AsignalとAnoiseはそれぞれ音声と雑音の「大きさ」あるいは「強さ」を表します。RMS)をそれぞれの音の強さとします。振幅値の二乗平均平方根は以下の手順で求めることができます。下記の図を見てわかる通り、音声の振幅値はマイナスの値も取ることがあるので振幅値を二乗します。二乗した値を足し合わせたあと、その値の平均を計算します。最後に、平均した値の平方根を計算することで音の強さを求めることができます。波形中のすべての振幅値を利用した二乗平均平方根の値が音の強さとして使えない場合もあります。それは波形に無音区間が多かったり、ある区間だけ異常に振幅値が大きい場合です。これらの場合、振幅値の二乗平均平方根の表す値と人が知覚する音の強さに乖離が出てしまいます。よって、無音区間を取り除いたり、短時間ごとにSN比を計算したりする必要があります。ここで紹介した以外にもSN比を求める方法があると思いますのでぜひ調べてみてください。Pythonで任意のSignal-to-Noise比の音声波形を作るそれでは実際に、音声に対して任意の大きさの雑音を重畳するプログラムをPythonで実装していきます。先に完成したコードのリンクを貼っておきます。https://github.com/Sato-Kunihiko/audio-SNRまた、上記のプログラムによって生成された音声の例は以下のようになります。左上から順に、SN比が-20、-5、0、5、20dBの音声波形です。数値が大きくになるにつれて雑音が小さくなり、音声がはっきり聞こえると思います。※上記のプログラムを実行しても、動画ファイルは生成できません。音声ファイルを生成します。当ブログの仕様では音声ファイルをブログ内に埋め込めないので、YouTubeにアップロードした動画を埋め込んでいます。実行環境Python3系MacOS音声ファイルのフォーマット音声ファイルは拡張子が.wavのものを必ず使用してください。wavファイルのフォーマットを以下のように設定してください。量子化ビット数は16bit音声用のファイルと雑音用のファイルのサンプリングレートを同じにする量子化ビット数はデフォルトで16bitになっていることが多いです。サンプリングレートはファイルによって異なることが多いので注意してください。16000Hzや44100Hz、48000Hzになっていることが多いです。サンプリングレートの変更はSoXなどの音声編集ソフトウェアを使えばコマンドラインからも行うことができます。使用データセット今回の実装では音声のみのwavファイルと雑音のみのwavファイルを使用します。ARCTICコーパスを使用しました。雑音のみのファイルはDEMANDを使用しました。DEMANDは18種類の環境音を収録したデータセットです。wavファイルを読み込むまず、wavファイルを読み込む実装をします。Pythonでwavファイルを扱うためのライブラリはいくつかありますが、今回はwaveライブラリを使います。"r")上記のプログラムではPythonファイルの実行時に以下の引数を含める必要があります。音声のみのファイルの絶対パス--clean_file雑音のみのファイルの絶対パス--noise_file処理後の音声のみのファイルの絶対パス。これはオプションです。--output_clean_file処理後の雑音のみのファイルの絶対パス。これはオプションです。--output_noise_file任意のSN比の音声ファイルの絶対パス--output_noisy_file合成したいSN比の大きさ--snr実際に実行した場合のスクリプトは以下のようになります。ファイル名やフォルダのパスは任意のものに書き換えてください。0音声波形の振幅値を取得するwavファイルを読み込んだあと、そのwavファイルの振幅値を取得します。cal_amp(noise_wav)wf.readframes(n)は最大n個のオーディオフレームを読み込んで、bytesオブジェクトを返します。wf.getnframes()はオーディオフレーム数を返します。wf.readframes(wf.getnframes())でwavファイルのすべての振幅値を取得しています。dtype="int16")).astype(np.float64)でbytesオブジェクトを最終的にnp.float64にキャストしています。RMS)を求める振幅値のRMSを求める前に注意すべきことがあります。DEMANDから取得した雑音データは1ファイルあたり5分の長さがあります。CMUコーパスの音声データは1ファイルあたり2~5秒です。そのため、雑音データの波形を音声データの波形の長さに切り出す必要があります。最終的には、下記の図のように、元の雑音データから切り出した波形と音声データの波形のそれぞれのRMSを計算し、任意のSN比になるように重畳します。実際のコードでは、雑音の切り出す位置をランダムに決定し音声の長さ分だけ切り出します。cal_rms(split_noise_amp)Signal-to-Noise比の計算式を利用して任意の大きさで波形を合成するSN比の計算式は以下のようになります(再掲)。上記の式を利用して、音声に対して任意のSN比になるような雑音のRMSを求めます。求めたい雑音のRMSは上記の式を変形し以下の式で求めることができます。以下の図で示すように、上記の式で導出したRMS(Anoise)と元の雑音のRMSの比率を計算し、その比率分だけ元の雑音の振幅値を調整します。その後、調整した雑音の振幅と音声のみの振幅を足します。adjusted_noise_amp)振幅を足し合わせたあと、注意点があります。それぞれの振幅が十分に大きかった場合、足し合わせた振幅の値はwavファイルの量子化ビット数16bitの最大値(二進数で32767)を超えてしまう可能性があります。最大値を超えた波形は、いわゆる「音割れ」を引き起こします。したがって、足し合わせた値が16bitの最大値を超えた場合、足し合わせた値の最大が32767に収まるように正規化します。(32767/mixed_amp.max(axis=0))波形をwavファイルで保存する最後に波形をwavファイルとして保存します。wavファイルの書き込みもwaveライブラリを使用します。noise_wave.close()setparams()はwavファイルのフォーマットを指定するメソッドです。問題なければ、入力に使用した音声ファイルのフォーマットをそのまま使ってよいと思います。writeframes()で振幅値を指定します。Stringにキャストする必要があります。最後にコード全体を載せておきます。Gihubにもあげておきました。Learningによってさまざまな音声処理の精度が上がっている一方、音声処理の分野は前提知識が複雑であったりドキュメントが少なかったりするような気がします。本記事によってより多くの人が音声処理に興味を持つようになれば幸いです。twitterまでお願いいたします。参加レポート、そしてその後の開発」です。Developers。