はじめに
音信号処理では音を加工する1つの方法としてFIRフィルタがよく使われます。
具体的な計算としては、元の信号とFIRフィルタ信号の畳み込み演算を行います。
この畳み込み演算を時間領域で素直に行うと、元の信号長×FIRフィルタ長の計算が必要です。
従って、例えばコンボリューションリバーブのようなFIRフィルタ長が比較的大きい場合では、計算量が多くなってしまう問題があります。
そこで、この畳み込みを効率的に行う方法としてオーバーラップ加算法(オーバーラップアド、重畳加算法)が知られています。
この手法は SciPyで scipy.signal.oaconvolve
として実装されているため、すぐに利用することができます。
そこで、この記事では scipy.signal.oaconvolve
の使い方を紹介します。
また、時間領域での畳み込み演算と処理速度を比較します。
使い方
scipy.signal.oaconvolve — SciPy v1.10.1 Manual
SciPy のバージョン 0.14 以上で利用できます。
基本的に通常の畳み込み演算 scipy.signal.convolve
と同じ引数でOKです。
すなわち、元信号とFIRフィルタを in1
, in2
に引数として与えます。
scipy.signal.oaconvolve(in1, in2, mode='full', axes=None)
処理速度の比較
FIRフィルタ長をいくつか変えて実験してみました。
元の信号長は 1024**2
サンプルで固定しています。
この結果から、FIRフィルタ長が小さいときは通常の畳み込み scipy.signal.convolve
のほうが速いですが、FIRフィルタ長が大きくなるとscipy.signal.oaconvolve
のほうが効率的であることが分かります。
import time import numpy as np from scipy import signal from functools import partial def measure_time(func, n_trial=10): elapsed_time = 0.0 for k in range(0, n_trial): start = time.time() func() elapsed_time += time.time() - start elapsed_time /= n_trial return elapsed_time for x_len in [1024**2]: for h_len in [16, 64, 256, 2048, 8192]: x = np.random.normal(0.0, 0.1, x_len) h = np.random.normal(0.0, 0.1, h_len) * np.linspace(1.0, 0.0, h_len)**2 print(f"x: {x_len}, h: {h_len}") elapsed_time = measure_time(partial(signal.convolve, x, h)) print ("convolve\t", f"\t{elapsed_time:.3f} sec") elapsed_time = measure_time(partial(signal.oaconvolve, x, h)) print ("oaconvolve\t", f"\t{elapsed_time:.3f} sec")
まとめ
畳み込み演算を効率的に行うオーバーラップ加算法の Scipy 実装である scipy.signal.oaconvolve
の使い方と計算速度の検証を行いました。
オーバーラップ加算法はバッチ処理だけでなくリアルタイム処理でも利用できる手法なので、FIRフィルタを使うプラグインを自作するときにも役立ちます。