Savitzky–Golay filter を Processing on Eclipse で使う

やりたいこと

ある時系列データ(下図のグレー)が与えられた時、それにフィルターをかけることで青線のようなスムージングされたデータにしたい。

sgfilter

やりかた

Savitzky-Golay フィルターという多項式近似フィルターがあり、性能が良いらしいのでこれを使う。
java ライブラリがあるので有難く使わせてもらいます。

このページから sgfilter_v1_2r25.jar と SGFilterVis2.zip をダウンロードする。
SGFilterVis2.zip を解凍します。
– sgfilter_v1_2r25.jar
– appframework-1.0.3.jar
– beansbinding-1.2.1.jar
– commons-math.jarとliquidlnf.jar
の4つを Eclipse にインポートしましょう。

まず Eclipse でプロジェクトを選択して Properties > Java Build Path で Libraries タブを選択し、Add Library ボタンを押します。
User Library を選んで Next ボタンを押します。
パネル内の右の方にある User Libraries ボタンを押します。
新規ウィンドウが出るので New を押して SGFilter などと名前を付けて OK ボタンを押します。
SGFilter が選択状態になっているので右の Add External Jars ボタンを押し、上記の4つの jar ファイルを選択し追加します。
SGFilter に jar ファイルたちがインポートできたので OK ボタンを押します。
Java Build Path の Libraries タブ内に SGFilter が表示されていたら OK。

動かしてみる

コードは下記のよう。
import mr.go.sgfilter.*; でライブラリをインポートします。
オリジナルのデータが input 変数に、フィルター後のデータを output 変数に入れています。
フィルタリングの実行には
SGFilter.computeSGCoefficients( kernelSizeBack, kernelSizeFront, polynomialOrder ); と
SGFilter( kernelSizeBack, kernelSizeFront );
の部分でパラメータを設定します。
kernelSizeBack というのは フィルターに使う範囲の過去方向のデータ点数、kernelSizeFront は未来方向のデータ点数です。
polynomialOrder は何次式で近似するかです。ここでは2次式でやることにします。
下記コードの設定の場合、当該データ点をその前後3点ずつ、計6点を用いて2次式で近似することになります。

scaling というのは結果をグラフで可視化する時の都合用の変数なのでフィルタリングには無関係です。
ちなみに、Processing ライブラリの導入は前回の記事などを参考。

動作させた結果は冒頭の図の通りです。

参考にしたページ

Savitzky-Golay フィルター
http://memorandums.hatenablog.com/entry/2015/06/29/203907

package パッケージ名;

import processing.core.*;
import mr.go.sgfilter.*;

public class このファイルのクラス名 extends PApplet{
	
	float[] input; 
	float[] output;
	int kernelSizeBack = 3;
	int kernelSizeFront = 3;
	int polynomialOrder = 2;
	int scaling = 6;
	
	public void settings(){
        size( 1200, 400, P2D );
    }

	public void setup() {
		strokeWeight( 1.6f );
		textFont( createFont( "sans-serif", 18) );
		mouseClicked();
	}

	public void draw() {
		background( 250 );
		
		stroke( 180 );
		drawGraph( input );
		
		stroke( color( 50, 200, 200 ) );
		drawGraph( output );
		
		fill( 120 );
		text( "–– original", 20, 40 );
		fill( color( 20, 180, 180 ) );
		text( "–– filtered", 20, 70 );
	}
	
	private float[] createPerlinNoise( int length ){
		float[] n = new float[ length ];
		float d = 0.4f;
		int offset = frameCount;
		for( int i = 0; i < length; i++ ){
			n[ i ] = noise( offset + i * d ) * height;
		}
		return n;
	}
	
	private float[] doSGFilter(){
		double[] coeffs = SGFilter.computeSGCoefficients( kernelSizeBack, kernelSizeFront, polynomialOrder );
		ContinuousPadder padder1 = new ContinuousPadder();
		SGFilter sgFilter = new SGFilter( kernelSizeBack, kernelSizeFront );
		sgFilter.appendPreprocessor( padder1 );
		return sgFilter.smooth( input, coeffs );
	}
	
	private void drawGraph( float[] data ){
		noFill();
		beginShape();
		for( int i = 0; i < data.length; i++ ) vertex( i * scaling, data[ i ] );
		endShape();
	}
	
	public void mouseClicked(){
		input = createPerlinNoise( width / scaling );
		output = doSGFilter();
	}
	
	public static void main(String[] args){
    	PApplet.main( このファイルのクラス名.class.getName() );
    }
	
}

コメント