Javaでローパスフィルターを実装しようとしています。私の要件は非常に単純です。特定の周波数(単一次元)を超える信号を排除する必要があります。バターワースフィルターが私のニーズに合うようです。
ここで重要なことは、CPU時間を可能な限り低くすることです。フィルターが処理しなければならないサンプルは100万近くになり、ユーザーはあまりにも長く待つのが嫌いです。フィルタリングに最適なアルゴリズムを持つバターワースフィルターの既製の実装はありますか?.
フレームレートに依存しないこともできる非常にシンプルで非常にローCPUのローパスフィルターについて説明しているページがあります。ユーザー入力の平滑化やフレームレートのグラフ化によく使用します。
http://phrogz.net/js/framerate-independent-low-pass-filter.html
つまり、更新ループでは:
// If you have a fixed frame rate
smoothedValue += (newValue - smoothedValue) / smoothing
// If you have a varying frame rate
smoothedValue += timeSinceLastUpdate * (newValue - smoothedValue) / smoothing
smoothing
値1
を指定すると平滑化は行われませんが、値を大きくすると結果が徐々に平滑化されます。
ページにはJavaScriptで記述されたいくつかの関数がありますが、式は言語に依存しません。
私はこれを http://www.dspguide.com/ から採用しました。私はJavaにかなり慣れていないので、きれいではありませんが、動作します
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package SoundCruncher;
import Java.util.ArrayList;
/**
*
* @author 2sloth
* filter routine from "The scientist and engineer's guide to DSP" Chapter 20
* filterOrder can be any even number between 2 & 20
* cutoffFreq must be smaller than half the samplerate
* filterType: 0=lowPass 1=highPass
* ripplePercent is amount of ripple in Chebyshev filter (0-29) (0=butterworth)
*/
public class Filtering {
double[] filterSignal(ArrayList<Float> signal, double sampleRate ,double cutoffFreq, double filterOrder, int filterType, double ripplePercent) {
double[][] recursionCoefficients = new double[22][2];
// Generate double array for ease of coding
double[] unfilteredSignal = new double[signal.size()];
for (int i=0; i<signal.size(); i++) {
unfilteredSignal[i] = signal.get(i);
}
double cutoffFraction = cutoffFreq/sampleRate; // convert cut-off frequency to fraction of sample rate
System.out.println("Filtering: cutoffFraction: " + cutoffFraction);
//ButterworthFilter(0.4,6,ButterworthFilter.Type highPass);
double[] coeffA = new double[22]; //a coeffs
double[] coeffB = new double[22]; //b coeffs
double[] tA = new double[22];
double[] tB = new double[22];
coeffA[2] = 1;
coeffB[2] = 1;
// calling subroutine
for (int i=1; i<filterOrder/2; i++) {
double[] filterParameters = MakeFilterParameters(cutoffFraction, filterType, ripplePercent, filterOrder, i);
for (int j=0; j<coeffA.length; j++){
tA[j] = coeffA[j];
tB[j] = coeffB[j];
}
for (int j=2; j<coeffA.length; j++){
coeffA[j] = filterParameters[0]*tA[j]+filterParameters[1]*tA[j-1]+filterParameters[2]*tA[j-2];
coeffB[j] = tB[j]-filterParameters[3]*tB[j-1]-filterParameters[4]*tB[j-2];
}
}
coeffB[2] = 0;
for (int i=0; i<20; i++){
coeffA[i] = coeffA[i+2];
coeffB[i] = -coeffB[i+2];
}
// adjusting coeffA and coeffB for high/low pass filter
double sA = 0;
double sB = 0;
for (int i=0; i<20; i++){
if (filterType==0) sA = sA+coeffA[i];
if (filterType==0) sB = sB+coeffB[i];
if (filterType==1) sA = sA+coeffA[i]*Math.pow(-1,i);
if (filterType==1) sB = sB+coeffA[i]*Math.pow(-1,i);
}
// applying gain
double gain = sA/(1-sB);
for (int i=0; i<20; i++){
coeffA[i] = coeffA[i]/gain;
}
for (int i=0; i<22; i++){
recursionCoefficients[i][0] = coeffA[i];
recursionCoefficients[i][1] = coeffB[i];
}
double[] filteredSignal = new double[signal.size()];
double filterSampleA = 0;
double filterSampleB = 0;
// loop for applying recursive filter
for (int i= (int) Math.round(filterOrder); i<signal.size(); i++){
for(int j=0; j<filterOrder+1; j++) {
filterSampleA = filterSampleA+coeffA[j]*unfilteredSignal[i-j];
}
for(int j=1; j<filterOrder+1; j++) {
filterSampleB = filterSampleB+coeffB[j]*filteredSignal[i-j];
}
filteredSignal[i] = filterSampleA+filterSampleB;
filterSampleA = 0;
filterSampleB = 0;
}
return filteredSignal;
}
/* pi=3.14...
cutoffFreq=fraction of samplerate, default 0.4 FC
filterType: 0=LowPass 1=HighPass LH
rippleP=ripple procent 0-29 PR
iterateOver=1 to poles/2 P%
*/
// subroutine called from "filterSignal" method
double[] MakeFilterParameters(double cutoffFraction, int filterType, double rippleP, double numberOfPoles, int iteration) {
double rp = -Math.cos(Math.PI/(numberOfPoles*2)+(iteration-1)*(Math.PI/numberOfPoles));
double ip = Math.sin(Math.PI/(numberOfPoles*2)+(iteration-1)*Math.PI/numberOfPoles);
System.out.println("MakeFilterParameters: ripplP:");
System.out.println("cutoffFraction filterType rippleP numberOfPoles iteration");
System.out.println(cutoffFraction + " " + filterType + " " + rippleP + " " + numberOfPoles + " " + iteration);
if (rippleP != 0){
double es = Math.sqrt(Math.pow(100/(100-rippleP),2)-1);
// double vx1 = 1/numberOfPoles;
// double vx2 = 1/Math.pow(es,2)+1;
// double vx3 = (1/es)+Math.sqrt(vx2);
// System.out.println("VX's: ");
// System.out.println(vx1 + " " + vx2 + " " + vx3);
// double vx = vx1*Math.log(vx3);
double vx = (1/numberOfPoles)*Math.log((1/es)+Math.sqrt((1/Math.pow(es,2))+1));
double kx = (1/numberOfPoles)*Math.log((1/es)+Math.sqrt((1/Math.pow(es,2))-1));
kx = (Math.exp(kx)+Math.exp(-kx))/2;
rp = rp*((Math.exp(vx)-Math.exp(-vx))/2)/kx;
ip = ip*((Math.exp(vx)+Math.exp(-vx))/2)/kx;
System.out.println("MakeFilterParameters (rippleP!=0):");
System.out.println("es vx kx rp ip");
System.out.println(es + " " + vx*100 + " " + kx + " " + rp + " " + ip);
}
double t = 2*Math.tan(0.5);
double w = 2*Math.PI*cutoffFraction;
double m = Math.pow(rp, 2)+Math.pow(ip,2);
double d = 4-4*rp*t+m*Math.pow(t,2);
double x0 = Math.pow(t,2)/d;
double x1 = 2*Math.pow(t,2)/d;
double x2 = Math.pow(t,2)/d;
double y1 = (8-2*m*Math.pow(t,2))/d;
double y2 = (-4-4*rp*t-m*Math.pow(t,2))/d;
double k = 0;
if (filterType==1) {
k = -Math.cos(w/2+0.5)/Math.cos(w/2-0.5);
}
if (filterType==0) {
k = -Math.sin(0.5-w/2)/Math.sin(w/2+0.5);
}
d = 1+y1*k-y2*Math.pow(k,2);
double[] filterParameters = new double[5];
filterParameters[0] = (x0-x1*k+x2*Math.pow(k,2))/d; //a0
filterParameters[1] = (-2*x0*k+x1+x1*Math.pow(k,2)-2*x2*k)/d; //a1
filterParameters[2] = (x0*Math.pow(k,2)-x1*k+x2)/d; //a2
filterParameters[3] = (2*k+y1+y1*Math.pow(k,2)-2*y2*k)/d; //b1
filterParameters[4] = (-(Math.pow(k,2))-y1*k+y2)/d; //b2
if (filterType==1) {
filterParameters[1] = -filterParameters[1];
filterParameters[3] = -filterParameters[3];
}
// for (double number: filterParameters){
// System.out.println("MakeFilterParameters: " + number);
// }
return filterParameters;
}
}
Apache数学ライブラリでフーリエ変換を使用するローパスフィルターを次に示します。
public double[] fourierLowPassFilter(double[] data, double lowPass, double frequency){
//data: input data, must be spaced equally in time.
//lowPass: The cutoff frequency at which
//frequency: The frequency of the input data.
//The Apache Fft (Fast Fourier Transform) accepts arrays that are powers of 2.
int minPowerOf2 = 1;
while(minPowerOf2 < data.length)
minPowerOf2 = 2 * minPowerOf2;
//pad with zeros
double[] padded = new double[minPowerOf2];
for(int i = 0; i < data.length; i++)
padded[i] = data[i];
FastFourierTransformer transformer = new FastFourierTransformer(DftNormalization.STANDARD);
Complex[] fourierTransform = transformer.transform(padded, TransformType.FORWARD);
//build the frequency domain array
double[] frequencyDomain = new double[fourierTransform.length];
for(int i = 0; i < frequencyDomain.length; i++)
frequencyDomain[i] = frequency * i / (double)fourierTransform.length;
//build the classifier array, 2s are kept and 0s do not pass the filter
double[] keepPoints = new double[frequencyDomain.length];
keepPoints[0] = 1;
for(int i = 1; i < frequencyDomain.length; i++){
if(frequencyDomain[i] < lowPass)
keepPoints[i] = 2;
else
keepPoints[i] = 0;
}
//filter the fft
for(int i = 0; i < fourierTransform.length; i++)
fourierTransform[i] = fourierTransform[i].multiply((double)keepPoints[i]);
//invert back to time domain
Complex[] reverseFourier = transformer.transform(fourierTransform, TransformType.INVERSE);
//get the real part of the reverse
double[] result = new double[data.length];
for(int i = 0; i< result.length; i++){
result[i] = reverseFourier[i].getReal();
}
return result;
}
フィルター設計はトレードオフの芸術であり、それをうまく行うには、いくつかの詳細を考慮する必要があります。
「あまり」の注意を払わなければならない最大周波数は何ですか?「あまり」の最大値は何ですか?
「たくさん」を減衰しなければならない最小周波数は何ですか?「たくさん」の最小値は何ですか?
フィルターが通過するはずの周波数内で、どの程度のリップル(つまり、減衰の変動)を許容できますか?
幅広い選択肢があるため、さまざまな量の計算が必要になります。 matlabやscilabなどのプログラムは、トレードオフを比較するのに役立ちます。周波数をサンプルレートの小数として表現する、減衰の線形測定とログ(dB)測定を交換するなどの概念に慣れる必要があります。
たとえば、「完全な」ローパスフィルターは、周波数領域では長方形です。時間領域でインパルス応答として表現すると、正と負の両方の無限大に到達する尾を持つsinc関数(sin x/x)になります。もちろん、それを計算することはできないので、sinc関数を計算できる有限の期間に近似すると、フィルターがどの程度劣化するのでしょうか。
あるいは、計算が非常に安価な有限インパルス応答フィルターが必要な場合は、すべての係数が1である「ボックスカー」または長方形フィルターを使用できます(これをCICフィルターとして実装すると、さらに安価にできます)バイナリオーバーフローを利用して「循環」アキュムレータを実行します。これは、とにかく後で派生物を使用するためです)。しかし、時間的に長方形のフィルターは、周波数ではsinc関数のように見えます-通過帯域にsin x/xロールオフがあり(通常、多段バージョンになるため、ある程度のパワーに引き上げられます)、いくつかの "バウンスバック"があります。ストップバンドで。それでも場合によっては、それ自体で、または別のタイプのフィルターが後に続くときに、それは便利です。
最近、単純なバターワース関数を設計しました( http://baumdevblog.blogspot.com/2010/11/butterworth-lowpass-filter-coefficients.html )。これらは、Javaでコーディングするのは簡単であり、私に尋ねれば十分高速であるはずです(filter(double *サンプル、intカウント)をfilter(double []サンプルに変更する必要があるだけです)。 int count)、私は推測します)。
JNIの問題は、プラットフォームに依存せず、ホットスポットコンパイラーを混乱させ、コード内のJNIメソッド呼び出しがまだ遅くなる可能性があることです。したがって、Javaを試して、それが十分に高速かどうかを確認することをお勧めします。
場合によっては、最初に高速フーリエ変換を使用して、周波数領域でフィルタリングを適用することが有益な場合がありますが、単純なローパスフィルターの場合、これは約6乗算およびサンプルあたりの数回の加算よりも速いとは思えません。
Mark Petersが彼のコメントで言ったように:たくさんフィルターする必要があるフィルターはCまたはC++で書かれるべきです。ただし、Javaは引き続き使用できます。 Java Native Interface(JNI) を見てください。 C/C++はネイティブマシンコードにコンパイルされるため、バイトコードをJava仮想マシン(JVM)で実行するよりもはるかに高速に実行されます。これは、実際にはバイトコードを変換する仮想プロセッサです。ローカルマシンへのネイティブコード(x86、x64などのCPU命令セットによって異なります [〜#〜] arm [〜#〜] 、....)