センサの入力信号は、ノイズ成分が含まれる場合がほとんどです。このノイズをソフトで除去する方法がデジタルフィルタです。今回は、代表的なローパスフィルタ、Mean(平均)フィルタ、Median(中央)フィルタのプログラム方法を紹介します。
ローパスフィルタ(RCフィルタ)
ローパスフィルタは、周波数が低い信号を通して、周波数が高い信号を遮断します。また、このフィルタはRCフィルタとも呼ばれます。電気回路でローパスフィルタを作るときは、下記のような回路構成で、抵抗(R)とコンデンサ(C)を使用します。
これを式で表します。ある時刻tにおけるフィルタ出力値Voutは、係数をaとして、センサ入力値をVinとすると、下記のようになります。
Vout[t] = a * Vout[t-1] + (1-a) * Vin[t]
もう少し簡単に言葉で表すと、下記のとおりです。
フィルタ出力値 = a * 前回のフィルタ出力値 + (1-a) * センサ入力値
Arduinoのローパスフィルタ(RCフィルタ)のソースコードは以下です。
//www.stemship.com //2019.12.15 // ピンの定義 #define SENSOR_PIN 0 //照度センサ接続ピン #define a 0.6 //フィルタ係数 //フィルター後の値 int filterVal =0; void setup() { //シリアル通信設定 Serial.begin(9600); } void loop() { //センサ値を取得 int sensorVal = analogRead(SENSOR_PIN); //フィルタ後の値を計算 filterVal = a * filterVal + (1-a) * sensorVal; //シリアル出力 Serial.print(sensorVal); Serial.print(","); Serial.println(filterVal); //次のループまで50ms待つ delay(50); }
以上のプログラムを実行した結果が下記です。CSV形式でシリアル出力しているので、シリアルプロッタでグラフ表示ができます。青がセンサ入力値、赤がフィルタ後の値です。フィルタ係数aを0~1の範囲内で色々変えて試してみてください。1に近づくほどフィルタ効果が大きくなります。
Meanフィルタ(平均フィルタ)
Meanフィルタは、過去n回分の平均値を出力するフィルタです。例として、過去10回分の平均値を計算しています。
ArduinoのMeanフィルタ(平均フィルタ)のソースコードと動作確認したグラフは以下です。
//www.stemship.com //2019.12.15 // ピンの定義 #define SENSOR_PIN 0 //照度センサ接続ピン //バッファの長さ #define BUF_LEN 10 //バッファ int buf[BUF_LEN]; int index = 0; //フィルター後の値 int filterVal =0; void setup() { //シリアル通信設定 Serial.begin(9600); //バッファの初期化 for(int i=0; i<BUF_LEN; i++) { buf[i] = 0; } } void loop() { //センサ値を取得 int sensorVal = analogRead(SENSOR_PIN); //バッファに取り込んで、インデックスを更新する。 buf[index] = sensorVal; index = (index+1)%BUF_LEN; //フィルタ用の変数 long sum = 0; //フィルタ後の値を計算 for(int i=0; i<BUF_LEN; i++) { sum += buf[i]; } filterVal = sum / BUF_LEN; //シリアル出力 Serial.print(sensorVal); Serial.print(","); Serial.println(filterVal); //次のループまで50ms待つ delay(50); }
Medianフィルタ(中央フィルタ)
Medianフィルタは、過去n回分の中央値を出力するフィルタです。例として、過去10回分の中央値を計算しています。 中央値を計算するためには、データをバッファに貯めて、昇順もしくは降順で並べて、真ん中のデータを取得する必要があり、データ処理が少し複雑です。
ArduinoのMedianフィルタ(中央フィルタ)のソースコードと動作確認したグラフは以下です。
//www.stemship.com //2019.12.15 // ピンの定義 #define SENSOR_PIN 0 //照度センサ接続ピン //バッファの長さ #define BUF_LEN 10 //バッファ int buf[BUF_LEN]; int index = 0; //フィルター後の値 int filterVal =0; void setup() { //シリアル通信設定 Serial.begin(9600); //バッファの初期化 for(int i=0; i<BUF_LEN; i++) { buf[i] = 0; } } void loop() { //センサ値を取得 int sensorVal = analogRead(SENSOR_PIN); //バッファに取り込んで、インデックスを更新する。 buf[index] = sensorVal; index = (index+1)%BUF_LEN; //フィルタ後の値を計算 filterVal = medianFilter(); //シリアル出力 Serial.print(sensorVal); Serial.print(","); Serial.println(filterVal); //次のループまで50ms待つ delay(50); } //Medianフィルタ関数 int medianFilter() { //ソート用のバッファ static int sortBuf[BUF_LEN]; //ソート用バッファにデータをコピー for(int i=0; i<BUF_LEN; i++) { sortBuf[i] = buf[i]; } //クイックソートで並べ替える qsort(sortBuf, BUF_LEN, sizeof(int), quicksortFunc); return sortBuf[(int)BUF_LEN/2]; } //クイックソート関数 int quicksortFunc(const void *a, const void *b) { return *(int *)a - *(int *)b; }
まとめ
最後に、これまでのフィルタを全て比較したグラフが以下です。一応、ソースコードも載せておきます。
//www.stemship.com //2019.12.15 // ピンの定義 #define SENSOR_PIN 0 //照度センサ接続ピン #define alpha 0.6 //フィルタ係数 //バッファの長さ #define BUF_LEN 10 //バッファ int buf[BUF_LEN]; int index = 0; //フィルター後の値 int meanFilterVal =0; //Meanフィルタ int medianFilterVal =0; //Medianフィルタ int rcFilterVal = 0; //RCフィルタ void setup() { //シリアル通信設定 Serial.begin(9600); //バッファの初期化 for(int i=0; i<BUF_LEN; i++) { buf[i] = 0; } } void loop() { //センサ値を取得 int sensorVal = analogRead(SENSOR_PIN); //バッファに取り込んで、インデックスを更新する。 buf[index] = sensorVal; index = (index+1)%BUF_LEN; //フィルタ用の変数 long sum = 0; //RCフィルタ後の値を計算 rcFilterVal = alpha * rcFilterVal + (1-alpha) * sensorVal; //Meanフィルタ後の値を計算 for(int i=0; i<BUF_LEN; i++) { sum += buf[i]; } meanFilterVal = sum / BUF_LEN; //Medianフィルタ後の値を計算 medianFilterVal = medianFilter(); //シリアル出力 Serial.print(sensorVal); Serial.print(","); Serial.print(rcFilterVal); Serial.print(","); Serial.print(meanFilterVal); Serial.print(","); Serial.println(medianFilterVal); //次のループまで50ms待つ delay(50); } //Medianフィルタ関数 int medianFilter() { //ソート用のバッファ static int sortBuf[BUF_LEN]; //ソート用バッファにデータをコピー for(int i=0; i<BUF_LEN; i++) { sortBuf[i] = buf[i]; } //クイックソートで並べ替える qsort(sortBuf, BUF_LEN, sizeof(int), quicksortFunc); return sortBuf[(int)BUF_LEN/2]; } //クイックソート関数 int quicksortFunc(const void *a, const void *b) { return *(int *)a - *(int *)b; }
Meanフィルタは、急激な変化がある場合(例えば、スパイク状のノイズ)、その値も含めて平均化するため、フィルタ後の値に少し影響があります。一方、Medianフィルタは、中央値を選択するため、その影響を受けにくいという特徴があります。どのフィルタが優れているとは一概に言えません。それぞれのフィルタの特徴を理解して、状況に応じて試してみてください。
コメントを残す