Pythonでスペクトログラムからピーク値を任意数抽出する方法

  • このエントリーをはてなブックマークに追加

スペクトログラムは時間×周波数×振幅と数多くの情報を得ることが可能な便利な分析手法です。ここでは信号処理の技術でスペクトログラムのピークを自動検出するPythonコードを紹介します。

こんにちは。wat(@watlablog)です。ここではスペクトログラム(カラーマップ)から任意個数のピークを抽出する方法を紹介します

スペクトログラムの概要とピーク検出方針

スペクトログラムとは?

本記事ではスペクトログラム(Spectrogram)という信号処理分析手法を扱います。

スペクトログラムとは、横軸に時間、縦軸に周波数、奥行き(色成分)に振幅レベルをとったプロットの事で、一度に信号の時間変化、周波数変化、振幅レベル変化を読み取ることができ、特に振動騒音分野で重宝されています(以下に参考図を示します)。

スペクトログラムの説明

スペクトログラムの概要や、Pythonでスペクトログラムをプロットする方法の詳細は「Pythonで音のSTFT計算を自作!スペクトログラム表示する方法」に記載していますので、是非ご覧下さい。

ピーク自動検出のメリット

周波数分析をするとある周波数で卓越するピーク(峰と呼ぶ人も)が出てくる事が多く、このピークの変化を追う事で計測された情報から様々な現象を分析する事が可能です。

当WATLABブログでは「PythonでFFT波形から任意個数のピークを自動検出する方法」で単純な周波数波形からピークを自動検出する方法をPythonコードで書いてみました。

ピーク検出のイメージ

基本的なメリットはこちらの記事で紹介している内容と同じで、ピークを手動で読み取るよりは自動でリスト化してくれた方が生産性が向上するというものです。

しかしながらスペクトログラムは周波数波形の時間変化を表しているため、手動でピークを読み取ろうとするとかなり大変な作業になるはずです。

そこで、本記事では周波数波形だけでなく、スペクトログラムにおいてもピーク自動検出機能を実装してみようと思い書き始めました。

スペクトログラムのピーク検出方針(大きい順に抽出する方法)

ピークを検出する目的は様々ですが、ここではスペクトログラムから大きい順にピークを抽出する方法を考えていきます。

他にも、N番目のピークのみを抽出し、回転パルス無しにトラッキング解析をしたり…とか、ピーク活用は目的に応じて色々ありますね。

既存のピーク検出アルゴリズムを使う

スペクトログラムからピークを検出する…といっても基本的には先に紹介した記事にあるように、既存のピーク検出アルゴリズムをそのまま流用します。

既存のピーク検出アルゴリズム(ここではscipyのargrelmax等)はorderの指定が可能でノイズ対策がとれていたり、任意個数を指定できたり、自分で微分計算から作るとやらなければならない手間を大幅に削減できるというのが最大の理由です。

スペクトログラムは以下の図のように縦方向に時間成分でスライスすれば単一の周波数波形になるので、この形にすることができれば既存のピーク検出アルゴリズムを適用することが可能です。

スペクトログラムからピークを検出する方針

ピーク情報をストックする

単一の周波数波形のみを検出しただけではスペクトログラムのピーク検出をしたとは言えません。

そのため、下図に示すように初期時間\(t_{1}\)から順番に\(t_{2}\)…そして最終時刻\(t_{m}\)までピーク検出を行います。

検出したピークは振幅レベル(Peak), 周波数(Freq), 時間(t)の情報としてそれぞれ2次元配列に格納していきます(下図を参考のこと)。

ピーク情報をストックする

ピーク情報を平坦化する

上記方法でピーク情報をストックしていくと、非常に大量のピークが検出されてしまうことがあります。ピーク検出アルゴリズムは重要なピークのみを抽出する事が必要です。

ここでは振幅レベルでソートできる形にするため、先ほどの2次元配列を平坦化(1次元配列)にする方針をとります(以下参考図)。

ストックしたピーク情報を平坦化する

ソートして任意個数を抽出する

ここまでできれば、あとは振幅レベル(Peak)でソートし、周波数(Freq)、時間(t)情報も並べ替えした指標で抽出してくれば、それぞれの配列の先頭から任意個数を抽出するのは簡単になります。

スペクトログラムからピーク検出するPythonコード

ピーク検出の関数

今回メインとなるスペクトログラムからピークを検出するPythonコードをdef関数として以下に示します。考案したアルゴリズムは上記説明の通りですので、コード内コメントを参照して頂ければおそらく読み解けると思います。

全コード(コピペ用:単一ピークを検出)

以下にコピペしてすぐに動かせるように全コードを示します。.pyファイルのあるディレクトリに「sample.wav」(ここでは5[s]のデータ)というwavファイルを置くことでそのまま動きます。

その他の関数群は「Pythonで音のSTFT計算を自作!スペクトログラム表示する方法」で使っていたものと同じですので、もし詳細で不明な所があればこの記事を参照下さい。

上記コードを実行すると、スペクトログラム内で最も最大振幅となる点を教えてくれます。

グラフとしては以下のようなプロットがでます。

単一ピークの検出

コンソールには以下のようにピーク情報が表示されます。

複数ピークを検出する方法

先ほどは単一のピークでしたが、max_peaksの値を変更することで複数ピークをスペクトログラム全体から振幅レベルの大きい順に検出することが出来ます。

以下はmax_peaks=10, 50の比較です。数値を増やしていけば検出するピークも増えていきます。

max_peaksの比較

コンソールには検出したピーク情報が配列で表示されます。

録音データからピーク検出するコード

スペクトログラムは録音データに対して計算する需要も多くあるようです。そのため以下にPyAudioを使った録音版のスペクトログラムピーク検出コードを載せておきます。

PyAudioによる録音については「PythonのPyAudioで音声録音をする簡単な方法」という記事に詳細を載せていますので、是非ご覧下さい。

以下が例です。口笛を吹いてみました。

まとめ

本記事では過去に当WATLABブログで紹介したスペクトログラムの概要を再度説明し、スペクトログラムからピークを自動検出することの有用性を紹介しました。

記事の中盤では考案したピーク検出アルゴリズムを図解で説明しました。

最後に考案したアルゴリズムをPythonコード化し、サンプルのwavファイルを使って実際にピーク検出を行い、パラメータ違いで任意個数のピークを検出できることを確認しました。

本ピーク検出アルゴリズムは一例に過ぎません。是非各自の目的に合わせてカスタマイズしてみて下さい(特に当ブログのコードをコピペして使ったり2次利用するのに使用許諾とかは必要ありません)。

スペクトログラムという2次元データから任意個数のピークを抽出することが出来ました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!

SNSでもご購読できます。

コメント

  1. なめこ より:

    いつも参考にさせていただいております。wat様のscipy.signal.argrelmaxを使ってFFTのピーク値を検出するという試みのサイトも拝見させていただきまして、どちらにコメントさせていただこうかと迷いましたがこちらで失礼いたします。

    私もピーク値を検出するプログラムを作成しているのですが、なかなかうまくいきません。scipyのsignalのなかにはいっているspectrogram()を用いてスペクトログラムを作成し、最終的にはwat様の本ページのようなグラフ内ピーク値の描画ができないかと検討しています。
    まだPythonを初めて間もなく、あまりにも抽象的で初歩的な質問で申し訳ないのですが、そもそもspectrogram()を用いたうえでピーク値を特定することは可能でしょうか。
    ご教授いただければと思います。よろしくお願いいたします。

    1. wat より:

      いつもご訪問ありがとうございます。
      scipyのspectrogramは使った事ないのですが、試しに

      f,t,Sxx = signal.spectrogram(data, Fs, nperseg=512)
      print(Sxx)

      と書いてみますと、Sxxは2次元の結果が返ってきます。
      公式リファレンス
      signal.spectrogram()
      を確認すると、fとtはそれぞれ周波数軸と時間軸のようです。

      本ページに記載のfindpeaks_2d()関数もスペクトログラムを2次元配列の引数としてデータを渡しているので、本質的には同じデータの形をしていますので、signal.spectrogram()の結果からもピーク検出はできる事になります。
      mode=magnitudeで振幅成分を出力する事もできそうですが、おそらくこの値はdB変換等はしていない値だと思いますので、
      綺麗にグラフ上で次数を表示させるには、色レベルをログスケールにするか、dB変換を行うのが良いと考えます。

      ただ、少し公式リファレンスを良く読んでsignal.spectrogram()の動作を良く理解してから使わないと目的の値を得るのは難しいと思います。
      まずはnumpy等で理論波形を作り、意図したスペクトログラムが表示されるプログラムを作成した後で、ピーク検出を検討するのが良いと思いますが、
      現在なめこ様は既にスペクトログラム表示まではできていますでしょうか?

  2. なめこ より:

    返信ありがとうございます。
    スペクトログラム表示まではできております。
    先月から始めたPythonで知識もおぼつかず、見当違いなことをしてしまっているのだと思います。

    スペクトログラムを行う信号はpyaudioを使った録音で波形をとる形をとっております。そのうえでargrelmax()を使ってピーク値を出そうと試みたのですがうまくいかず…という感じで昨日が終わりました。

    1. wat より:

      既にTwitterでのやりとりで解決しましたが、他の方のためにこちらにも記録しておきます。
      ■scipy.signal.spectrogramについて
      以下の記事にトライ結果を追記しました。
      Pythonで音のSTFT計算を自作!スペクトログラム表示する方法
      →STFTからスペクトログラム表示までは問題なくできましたが、振幅成分に不明点あり使用には注意が必要です(記事内の自作コードであればそれらの不明点はありません)。

      ■ピーク検出の方法とエラー対処について
      エラーの原因はピーク検出時のインデックスに不備があったからでした。
      PythonでFFT波形から任意個数のピークを自動検出する方法」の記事に基本のピーク検出関数にエラー処理を入れる事で対応可能でした。
      最終的に録音データのスペクトログラムからピーク検出するコードはこちらにまとめました。
      Pythonでスペクトログラムからピーク値を任意数抽出する方法

      なめこ様のおかげでプログラムのバグ出しができ、記事を改善する事ができました。
      ありがとうございます。
      今後もよろしくお願い致します。

wat へ返信する コメントをキャンセル

*