M5Stackでできること 〜ノイズが多いデータを平滑化する

M5Stackなどでセンサの値を取得する際、センサによってはノイズが多い場合があります。

センサから取得した値に基づいて何らかの処理を行おうとする場合、ノイズが多いとその特異点でも処理を行なってしまうことになり、その結果、所望の処理を行えない可能性もあります。

そんな訳で、ここではデータの平滑化をしてみたいとおもいます。


直感的で分かりやすい方法がよいので、直近の10個のデータの算術平均をとることにします(単純移動平均)。

スケッチはこちらです。
今回はセンサは使わず、0〜4095の間のランダムな値を取得し、それを 3.3/4096 倍した値を使うことにします(ランダムな値なので特異点だらけということになります)。

#include <M5Stack.h>

int interval = 1;

unsigned long prev = millis();

const int elementNumber = 10;
int       elementCount  = 0;
float     subset[elementNumber];

float movingAverage(float value) {
  subset[elementCount++] = value;
  if(elementCount >= elementNumber) elementCount = 0;
  float result = 0;
  for(int i=0; i<elementNumber; i++) result += subset[i];
  result = result / elementNumber;
  return result;
}

void setup() {
  M5.begin();
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setTextDatum(4);
}

int cnt = 0;

void loop() {
  unsigned long now = millis();
  if(now - prev >= interval*1000) {
    float val0 = random(0,4096) * 3.3 / 4096.0;
    float val1 = movingAverage(val0);
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.drawFloat(val0, 2, 160,  60, 8);
    M5.Lcd.drawFloat(val1, 2, 160, 180, 8);
    Serial.printf("%d,%.5f,%.5f\n",cnt++, val0, val1);
    prev = now;
  }
}

1秒毎に値を取得し、生値と平滑化した値をLCD画面とシリアルモニタに表示します。

実行した結果をExcelでグラフ表示してみます。

生値(青の線)はガタガタでデタラメな波形ですが、平滑化した値(赤の線)は変動が滑らかになっています。

ただ、この場合、直近のデータを格納する配列の初期値が「0」となっているため、10個のデータが揃うまでは、平滑化した値が小さめになってしまいます。
これを避けるため、配列に初期値を設定することとします。

#include <M5Stack.h>

int interval = 1;

unsigned long prev = millis();

const int elementNumber = 10;
int       elementCount  = 0;
float     subset[elementNumber];

float movingAverage(float value) {
  subset[elementCount++] = value;
  if(elementCount >= elementNumber) elementCount = 0;
  float result = 0;
  for(int i=0; i<elementNumber; i++) result += subset[i];
  result = result / elementNumber;
  return result;
}

void initSubset(float value) {
  for(int i=0; i<elementNumber; i++) subset[i] = value;
}

void setup() {
  M5.begin();
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setTextDatum(4);
  initSubset(1.65);
}

int cnt = 0;

void loop() {
  unsigned long now = millis();
  if(now - prev >= interval*1000) {
    float val0 = random(0,4096) * 3.3 / 4096.0;
    float val1 = movingAverage(val0);
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.drawFloat(val0, 2, 160,  60, 8);
    M5.Lcd.drawFloat(val1, 2, 160, 180, 8);
    Serial.printf("%d,%.5f,%.5f\n",cnt++, val0, val1);
    prev = now;
  }
}

配列の初期値に「1.65」を設定したため、平滑化した値(赤の線)は最初から中間値付近で変動しています。

もちろん、初期値を「3.3」に設定すると、10個のデータが揃うまで、平滑化した値は大きめになります。

値がしきい値を下回ったときに何らかの処理を行う場合などは、このようにすることで不要な処理をしないようにすることもできそうです。

GROVEダストセンサを使って室内のホコリの濃度を測定してみました。
先ほどまでと同じく、青い線が生値、赤い線が平滑化した値です。
このダストセンサはノイズが多いのですが、平滑化の処理を行うことで、波形が滑らかになっています。

なお、私がM5Stack、M5StickCの使い方を習得するのにあたっては、以下の書籍を参考にさせていただきました。


ごく基本的なところから、かなり複雑なスケッチや、ネットワーク接続など、比較的高度なものまで、つまづかずに読み進めていけるような構成になっており、大変わかりやすい本です。


このサイトで書いている、M5Stackシリーズ(M5Stack、M5StickCなど)に関するブログ記事を、「さとやまノート」という別のブログページに、あらためて整理してまとめました。

他のM5Stackシリーズの記事にも興味のある方は「さとやまノート」をご覧ください。