センサの入力信号は、ノイズ成分が含まれる場合がほとんどです。このノイズをソフトで除去する方法がデジタルフィルタです。今回は、代表的なローパスフィルタ、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フィルタは、中央値を選択するため、その影響を受けにくいという特徴があります。どのフィルタが優れているとは一概に言えません。それぞれのフィルタの特徴を理解して、状況に応じて試してみてください。
コメントを残す