音楽プログラミングの超入門(仮)

Python / 音楽情報処理 初心者が、初心者にも分かるような記事を書きたい。

matplotlibでかっこいいスペクトログラム表示

スペクトログラム表示

短時間フーリエ変換を行うことで、スペクトルの時間変化を表したスペクトログラムを得ることができます。

Pythonで短時間フーリエ変換(STFT)と逆変換 - 音楽プログラミングの超入門(仮)

この記事で、スペクトログラムを matplotlib.pyplot.imshow でそのまま表示していますが、すごい見にくいです。こんな感じ↓

imshow

高周波成分とか全部見えないし、そもそも色合い的にも見づらいという感じで、図として使い物になりません。

sox とかいう有能な

sox という CUI ベースの音声編集ソフトウェアがあります。

SoX - Sound eXchange | HomePage

これは、LinuxWindowsMac など様々なプラットフォームに対応しており、例えば Ubuntu の場合でしたら、以下のように簡単にインストールができます。

sudo apt-get install sox

普段は、音声ファイルの形式を変換したりするのに使っているのですが、sox にもスペクトログラムを作成する機能があります。例えば、サンプリング周波数 16kHz のWAVファイルですと、以下のコマンドでスペクトログラムを作成できます。

sox aiueo.wav -n rate 16k spectrogram

カレントディレクトリに spectrogram.png というファイルが作成されると思います。作成された画像がこんな感じ。

sox

もう何というか、圧倒的見やすさ、美しさですね。ちゃんと高周波も表示されているし、フォルマントもくっきり見えます。

今回は、matplotlib でこんな感じの美しいスペクトログラム表示を目指します。

美しく表示する

sox のようにスペクトログラムを美しく表示するには、主に以下の点に注意する必要があります。

  1. 対数(dBFS)スケールにする
  2. カラーマップの上限と下限をうまく決定する
  3. 見やすい配色にする

対数(dBFS)スケールにする

sox の画像のカラーバー(右端の棒)の下に小さく"dBFS"と書いてあります。これは、スペクトログラム行列中の最大値を 0 db(デシベル) とした対数スケールにするという意味です。ある要素の元の値をV、行列中の最大値をMとすると、その要素のデシベル値は次の式で計算できます。



\displaystyle
\begin{align}
db = 20 \; \log_{10} \frac{V}{M}
\end{align}

つまり最大値以外の要素の値は負になります。このように、対数スケールにして表示することで、小さな値を際立たせることができます。多分、基本的なテクニックだと思います。

カラーマップの上限と下限をうまく決定する

カラーマップは、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()

スペクトログラム

こんな感じに表示されます。

imshow like sox

だいぶ sox っぽくなりましたね。色の濃さとかは、imshow_sox の引数にある rm_low とかをいじると適当に変えられます。