본문 바로가기

Android/통신

[안드로이드 Java] Bandpass Filter(BPF) 구현하기 ― BPF IIR library, FIR 코드

728x90
반응형

Band-pass Filter(BPF) 


Band-pass 필터는 특정 주파수 사이의 신호만 통과시키는 필터입니다.

Low-pass 필터와 High-pass 필터의 조합으로도 만들어 질 수 있습니다.

밴드 패스 필터가 통과시키는 주파수 사이 범위를 통과대역(passband)이라고 합니다.

특정 주파수 사이에서 신호를 통과 시키고 그 외의 주파수 대역에서는 신호를 차단해야 하지만, 완벽하게 차단하는 이상적인 밴드 패스 필터는 없습니다. 이러한 형상을 롤오프(roll-off)라 하고, 롤오프를 최대한 줄여가는 쪽으로 필터를 설계합니다.

중심 주파수(center frequency), 공진주파수(resonant frequency)f0는 필터의 전달함수가 순수히 실수일때 주파수 입니다. 

차단주파수(cutoff frequency)는 신호가 통과하는 주파수 대역과 통과하지 못하는 주파수 대역의 경계점입니다. 밴드패스필터는 두 차단 주파수 사이가 통과 대역이 되며 신호가 통과합니다.

실제 필터에서는 차단주파수는 전달함수의 크기가 1/root(7) ≒ 0.707이 되는 곳으로 정합니다.

밴드 패스 필터에서 주파수가 높은 차단주파수를 고역 차단주파수(higher cutoff frequency)fH, 파수가 낮은 차단주파수를 저역 차단주파수(lower cutoff frequency)fL라 합니다.

대역폭(bandwidth)B는 두 차단주파수의 차이입니다.

Q인자(Q-factor)는 필터의 성능을 나타내는 변수중 하나입니다. Q인자는 중심주파수와 대역폭의 비로 정의됩니다. Q인자가 높을수록 좁은 통과 대역을 갖게되고 Q인자가 낮을수록 넓은 통과 대역을 갖게됩니다.

 


FIR Code


 /**
     * TODO: BandPass Filter
     * @See - https://stackoverflow.com/questions/16389205/simple-bandpass-filter-in-java
     * @Matlab
     * Fs = 200;           % Sampling Frequency
     * N    = 10;          % Order
     * Fc1  = 1.5;         % First Cutoff Frequency
     * Fc2  = 7.5;         % Second Cutoff Frequency
     * flag = 'scale';     % Sampling Flag
     *
     * % Create the window vector for the design algorithm.
     * win = blackman(N+1);
     *
     * % Calculate the coefficients using the FIR1 function.
     * b  = fir1(N, [Fc1 Fc2]/(Fs/2), 'bandpass', win, flag);
     * Hd = dfilt.dffir(b);
     * res = filter(Hd, data);
     *
     * @useInJava
     * double[] kernel = bandPassKernel(10, 1.5d / (200/2), 7.5d / (200/2));
     * double[] res = filter(data, kernel);
     * */
    private static double[] blackmanWindow(int length) {

        double[] window = new double[length];
        double factor = Math.PI / (length - 1);

        for (int i = 0; i < window.length; ++i) {
            window[i] = 0.42d - (0.5d * Math.cos(2 * factor * i)) + (0.08d * Math.cos(4 * factor * i));
        }

        return window;
    }

    private static double[] lowPassKernel(int length, double cutoffFreq, double[] window) {

        double[] ker = new double[length + 1];
        double factor = Math.PI * cutoffFreq * 2;
        double sum = 0;

        for (int i = 0; i < ker.length; i++) {
            double d = i - length/2;
            if (d == 0) ker[i] = factor;
            else ker[i] =  Math.sin(factor * d) / d;
            ker[i] *= window[i];
            sum += ker[i];
        }

        // Normalize the kernel
        for (int i = 0; i < ker.length; ++i) {
            ker[i] /= sum;
        }

        return ker;
    }

    private static double[] bandPassKernel(int length, double lowFreq, double highFreq) {

        double[] ker = new double[length + 1];
        double[] window = blackmanWindow(length + 1);

        // Create a band reject filter kernel using a high pass and a low pass filter kernel
        double[] lowPass = lowPassKernel(length, lowFreq, window);

        // Create a high pass kernel for the high frequency
        // by inverting a low pass kernel
        double[] highPass = lowPassKernel(length, highFreq, window);
        for (int i = 0; i < highPass.length; ++i) highPass[i] = -highPass[i];
        highPass[length / 2] += 1;

        // Combine the filters and invert to create a bandpass filter kernel
        for (int i = 0; i < ker.length; ++i) ker[i] = -(lowPass[i] + highPass[i]);
        ker[length / 2] += 1;

        return ker;
    }

    private static double[] filter(double[] signal, double[] kernel) {

        double[] res = new double[signal.length];

        for (int r = 0; r < res.length; ++r) {

            int M = Math.min(kernel.length, r + 1);
            for (int k = 0; k < M; ++k) {
                res[r] += kernel[k] * signal[r - k];
            }
        }

        return res;
    }

 위의 코드는 blackman window를 사용하였고, lowPass 커널을 이용하여 lowPass, HighPass 커널을 만들고 둘을 사용한 bandPass 커널을 만들었습니다.

데이터는 double 형의 array로 들어오고, bandPass Filter를 걸쳐 double 형의 array를 return합니다.

 

만약 Sampling Frequency가 200, N=10, 차단주파수가 1.5, 7.5인 경우

double[] kernel = bandPassKernel(10, 1.5d / (200/2), 7.5d / (200/2));
double[] result = filter(data, kernel);

이렇게 사용하면 됩니다.

참고. 숫자 뒤의 d는 double형을 뜻합니다.

 


 

IIR code 


java의 IIR filter library를 찾았습니다.

Highpass, Lowpass, Bandpass와 BandStop 모두 가능한 Butterworth 라이브러리 입니다.

import uk.me.berndporr.iirj.Butterworth;

 

dependencies {
    ...
    implementation group: 'uk.me.berndporr', name:'iirj', version: '1.3'
    ...
}

위의 코드를 dependecies (buld.gradle)에 추가하고, import를 하여 쓰면 됩니다.

 

Butterworth butterworth = new Butterworth();

위와 같이 생성자 생성후,

BandStop, Bandpass, Lowpass, Hightpass중 원하는 필터에 맞게 초기화 해줍니다.

//Bandstop
butterworth.bandStop(order,Samplingfreq,Center freq,Width in frequ);

//Bandpass
butterworth.bandPass(order,Samplingfreq,Center freq,Width in frequ);

//Lowpass
butterworth.lowPass(order,Samplingfreq,Cutoff frequ);

//Highpass
butterworth.highPass(order,Samplingfreq,Cutoff frequ);

그리고, 원하는 data를 realtime으로 처리할 수 있습니다.

v = butterworth.filter(v)

 

Bandpass나 Lowpass하기 위한 사용 예는 다음과 같습니다.

//Band-Pass Filter
//10~500Hz, Sampling rate : 250Hz, order : 4.
bpf_data = raw_data; 
Butterworth butterworthBP = new Butterworth();
butterworthBP.bandPass(4,250,255,490);
bpf_data = butterworthBP.filter(bpf_data);

//12Hz Low-Pass Filter
lpf_data = raw_data;
Butterworth butterworthLP = new Butterworth();
butterworthLP.lowPass(4,250,12);
lpf_data = butterworthLP.filter(lpf_data);

 


 

 

 

참고 문서

 

 

 

728x90
반응형