matplotlibでかっこいいスペクトログラム表示
スペクトログラム表示
短時間フーリエ変換を行うことで、スペクトルの時間変化を表したスペクトログラムを得ることができます。Pythonで短時間フーリエ変換(STFT)と逆変換 - 音楽プログラミングの超入門(仮)
この記事で、スペクトログラムを matplotlib.pyplot.imshow でそのまま表示していますが、すごい見にくいです。こんな感じ↓
高周波成分とか全部見えないし、そもそも色合い的にも見づらいという感じで、図として使い物になりません。
sox とかいう有能な
sox という CUI ベースの音声編集ソフトウェアがあります。
SoX - Sound eXchange | HomePage
これは、Linux、Windows、Mac など様々なプラットフォームに対応しており、例えば Ubuntu の場合でしたら、以下のように簡単にインストールができます。
sudo apt-get install sox
普段は、音声ファイルの形式を変換したりするのに使っているのですが、sox にもスペクトログラムを作成する機能があります。例えば、サンプリング周波数 16kHz のWAVファイルですと、以下のコマンドでスペクトログラムを作成できます。
sox aiueo.wav -n rate 16k spectrogram
カレントディレクトリに spectrogram.png というファイルが作成されると思います。作成された画像がこんな感じ。
もう何というか、圧倒的見やすさ、美しさですね。ちゃんと高周波も表示されているし、フォルマントもくっきり見えます。
今回は、matplotlib でこんな感じの美しいスペクトログラム表示を目指します。
美しく表示する
sox のようにスペクトログラムを美しく表示するには、主に以下の点に注意する必要があります。- 対数(dBFS)スケールにする
- カラーマップの上限と下限をうまく決定する
- 見やすい配色にする
対数(dBFS)スケールにする
sox の画像のカラーバー(右端の棒)の下に小さく"dBFS"と書いてあります。これは、スペクトログラム行列中の最大値を 0 db(デシベル) とした対数スケールにするという意味です。ある要素の元の値をV、行列中の最大値をMとすると、その要素のデシベル値は次の式で計算できます。
つまり最大値以外の要素の値は負になります。このように、対数スケールにして表示することで、小さな値を際立たせることができます。多分、基本的なテクニックだと思います。
カラーマップの上限と下限をうまく決定する
カラーマップは、sox のカラーバーのように、どの値からどの値までを、どのような色でモーフィングするかを示したものです。この上限と下限をうまく決めてやらないと、色が濃すぎたり薄すぎたりして、見づらくなってしまいます。
スペクトログラム上の要素の値は、どのような割合で存在しているでしょうか?実は、一番小さな値などは 1 要素しか無い、などという場合が結構あります。なので、この値を下限としてしまうと、ほとんどの値はその値よりもとても大きな値なので、全体的に明るい色となってしまい、メリハリがなく目立つべき部分が目立たないような表示になってしまいます。なので、そのように、ほとんど存在しないような要素を除外した下限を設定する必要があります。
見やすい配色にする
これが一番大事かもしれません。上で示した、matplotlib.pyplot.imshow によるスペクトログラムでは、青色の上に赤色がのっているため非常に見づらいですが、sox のものは黒色の上に赤色がのっているため、かなり見やすいです。
この辺の配色はある程度個人の趣向によると思います。自分で作ることもできますが、matplotlib 側でもいくつかカラーマップのパターンを用意してくれています。
color example code: colormaps_reference.py — Matplotlib 2.0.2 documentation
今回は sox のものと一番近い(と自分が思った)"hot" のカラーマップを使っていこうと思います。
実装
かなり適当な実装なので、適当に参考にしてください。# -*- coding: utf-8 -*- """ sox な感じにスペクトログラムを表示する。 """ from scipy import hanning, hamming, histogram, log10 from scikits.audiolab import wavread from matplotlib import pylab as pl from stft import stft # ================================= # sox な感じにスペクトログラム表示 # ================================= def imshow_sox(spectrogram, rm_low = 0.1): max_value = spectrogram.max() ### amp to dbFS db_spec = log10(spectrogram / float(max_value)) * 20 ### カラーマップの上限と下限を計算 hist, bin_edges = histogram(db_spec.flatten(), bins = 1000, normed = True) hist /= float(hist.sum()) pl.hist(hist) pl.show() S = 0 ii = 0 while S < rm_low: S += hist[ii] ii += 1 vmin = bin_edges[ii] vmax = db_spec.max() pl.imshow(db_spec, origin = "lower", aspect = "auto", cmap = "hot", vmax = vmax, vmin = vmin) def test(): wavfile = "../wav/aiueo.wav" data, fs, enc = wavread(wavfile) ### STFT fftLen = 1024 win = hanning(fftLen) step = fftLen / 8 spectrogram = abs(stft(data, win, step)[:, : fftLen / 2 + 1]).T ### 表示 fig = pl.figure() fig.patch.set_alpha(0.) imshow_sox(spectrogram) pl.tight_layout() pl.show() if __name__ == "__main__": test()