При обработке экспериментальных данных часто возникает необходимость их усреднения. С этой целью часто используют алгоритм арифметического скользящего среднего.
Скользящее среднее — общее название для семейства функций, значения которых в каждой точке определения равны среднему значению исходной функции за предыдущий период. Скользящие средние обычно используются с данными временных рядов для сглаживания краткосрочных колебаний и выделения основных тенденций или циклов. Математически скользящее среднее является одним из видов свертки и поэтому его можно рассматривать как фильтр низких частот, используемых в обработке сигналов.
Простое (арифметическое) скользящее среднее численно равно среднему арифметическому значений исходной функции за установленный период и вычисляется по формуле:
где fi – исходные значения рассматриваемой функции; h=k-l – сглаживающий интервал – количество значений исходной функции для расчета скользящего среднего.
На рисунке представлены функции скользящего среднего для исходной последовательности значений с разной величиной сглаживающего интервала.
Чем шире сглаживающий интервал, тем более плавным будет график результирующей функции.
Однако с другой стороны, увеличение сглаживающего интервала приводит к временному сдвигу усредненной функции относительно исходной.
Из предыдущего своего значения простое скользящее среднее может быть получено по следующей рекуррентной формуле:
где fk-h — значение исходной функции в точке k-hfk — значение исследуемой функции точке k (последнее значение).
При реализации рекуррентной формулы удобно использовать массив для хранения значений сглаживающего интервала, а также переменную, содержащую сумму этих значений.
Реализация на C++
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdlib.h>
using namespace std;
// Задание начального набора значений
double ** getData(int n) {
double **f;
f = new double*[3];
f[0] = new double[n];
f[1] = new double[n];
f[2] = new double[n];
for (int i = 0; i<n; i++) {
f[0][i] = (double)i;
f[1][i] = 8 * (double)i - 3;
// Добавление случайной составляющей
//f[1][i] = 8*(double)i - 3 + ((rand()%100)-50)*0.05;
}
return f;
}
// Вычисление результата скользящего среднего
void getMA(double **x, int n, int size) {
// size - количество отсчетов интервала усреднения
double sumx = 0; // сумма отсчетов на интервале
double *mas; // массив для хранения size отсчетов
int index = 0; // индекс элемента массива
mas = new double[size];
for (int i = 0; i<size; i++) mas[i] = 0;
for (int i = 0; i<n; i++) {
sumx -= mas[index];
mas[index] = x[1][i];
sumx += mas[index];
index++;
if (index >= size)
index = 0; // возврат к началу "окна"
x[2][i] = sumx / size;
}
return;
}
int main() {
double **x;
int n, h;
system("chcp 1251");
system("cls");
cout << "Введите количество точек: ";
cin >> n;
cout << "Введите сглаживающий интервал: ";
cin >> h;
x = getData(n);
getMA(x, n, h);
for (int i = 0; i<n; i++)
cout << x[0][i] << " - " << x[1][i] << " - " << x[2][i] << endl;
getchar(); getchar();
return 0;
}
Результат выполнения
График функции скользящего среднего с интервалом усреднения h=4 для данных, приведенных здесь, показан на рисунке.
Назад: Алгоритмизация
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
uint16_t tmp_rez = 0; // для хранения текущего результата
uint32_t sum = 0; // здесь хранится сумма всех элементов массива
uint32_t number = 0; // а сюда запишем результат вычисления среднего скользящего
void TIM1_UP_IRQHandler(void) // обработчик прерывания таймера (интервал через который будем делать замер запуская АЦП)
{
HAL_ADC_Start_IT(&hadc1);
HAL_TIM_IRQHandler(&htim1);
}
void ADC1_2_IRQHandler(void) // обработчик прерывания по окончанию преобразования
{
static uint8_t cnt = 0; // статическая переменная которая будет переполняться, специально делаю так чтобы не использовать условия if и т.д. , но размер окна получается в 256 значений
sum -= tmp_arr[cnt]; // отнимаем от общ. сум. знач. ячейки массива которую в дальнейшем перезапишем новым знач (т.е. самое старое значение)
tmp_res = HAL_ADC_GetValue(&hadc1); // по оконч. преобраз, присваиваем результ.временной перем.
sum += tmp_res; // прибавляем результат в общую сумму
tmp_arr[cnt] = tmp_res; // перезаписываем значение ячейки с которой работали
++cnt; // увеличиваем счетчик
number = sum >> 8; // делим на 256, т.е. количество элементов массива
HAL_ADC_IRQHandler(&hadc1);
}
2
3
4
5
6
7
8
9
10
…
static uint8_t cnt = 0;
sum -= tmp_arr[cnt];
tmp_res = HAL_ADC_GetValue(&hadc1);
sum += tmp_res;
tmp_arr[cnt] = tmp_res;
++cnt;
cnt = cnt % 16; // находим остаток от деления на 16 **
number = sum >> 4;