Реализация фильтра скользящего среднего с помощью блока «Java-функция»#
Примечание
Модель, построенная в данном уроке, доступна в демо-проектах: Фильтр “Скользящее среднее” – Java функция.
В реальном мире данные редко бывают идеальными. Контролируемые параметры зачастую имеют случайные помехи, которые нужно «фильтровать» и игнорировать. В данном уроке приведено описание шагов по разработке фильтра по типу «Скользящее среднее», выполненного при помощи блока Java-функция.
Во многих системах управления фильтрация измерений помогает повысить устойчивость и снизить влияние шумов. В системах автоматического управления «сырой» зашумленный сигнал может привести к ряду негативных последствий:
Износ исполнительных механизмов: постоянные высокочастотные колебания заставляют приводы (моторы, клапаны, реле) совершать микро-движения, что ведет к их преждевременному выходу из строя.
Ложные срабатывания защиты: случайный выброс (пик) шума может быть ошибочно принят системой за аварийную ситуацию, что приведет к необоснованной остановке оборудования.
Нестабильность контура управления: шум вносит хаос в работу регуляторов (например, ПИД регулятор), вызывая автоколебания и снижая точность поддержания заданных параметров.
В ходе урока рассматриваются:
Логика работы с массивами: создание буфера данных и реализация алгоритма “кольцевой очереди” для хранения истории сигнала.
Математическая обработка: вычисление среднего арифметического в реальном времени для подавления высокочастотных шумов.
Математическая обработка: вычисление среднего арифметического в реальном времени для подавления высокочастотных шумов.
Подход с применением пакета org.apache.commons: оптимизация кода с использованием стандартной библиотеки Apache Commons Math для повышения надежности и упрощения написания кода.
Преимущества использования Java-функции для данной задачи:
Расширяемость алгоритмов: Использование языка Java позволяет дополнять базовую фильтрацию произвольной логикой, такой как валидация входных данных, обработка исключений или реализация нелинейных алгоритмов обработки.
Интеграция внешних решений: Возможность импорта сторонних Java-пакетов открывает доступ к верифицированным математическим библиотекам, что сокращает время разработки и минимизирует вероятность программных ошибок.
Лаконичность и читаемость модели: Реализация сложных математических алгоритмов или многоуровневых логических условий внутри Java-функции позволяет избежать создания громоздких и трудночитаемых схем из сотен стандартных блоков. Один компактный текстовый блок заменяет собой целые подсистемы, упрощая визуальную структуру модели и облегчая её дальнейшую отладку и сопровождение.
1. Постановка задачи#
В процессе моделирования на вход блока Java-функция поступает сигнал с Гауссовским шумом.
Целью данного урока является разработка пользовательской функции, которая осуществляет сглаживание входного сигнала путем вычисления его среднего арифметического значения внутри скользящего временного окна фиксированной длины.
Для хранения предыстории сигнала формируется массив (буфер) размерностью n элементов, где n - глубина фильтрации (размер окна). На каждом шаге расчета k алгоритм выполняет следующие операции:
Обновление массива данных: добавление текущего значения \(x_k\) и удаление самого старого значения \(x_{k-n}\).
Расчет выходного сглаженного значения \(y_k\) по формуле простого скользящего среднего (1) :
где:
\(y_k\) - отфильтрованное значение сигнала на текущем шаге;
\(x_{k-i}\) - текущее и предыдущие значения входного сигнала, сохраненные в массиве;
\(n\) - количество расчетных точек в окне сглаживания (размер массива).
2. Создание и настройка проекта#
Сначала требуется создать новый проект и присвоить ему название, например, Скользящее среднее - Java функция.
Затем необходимо внести следующие настройки проекта (см. рис. 1 ниже):
Отключить параметр
Синхронизация с реальным временем;Присвоить параметру
Время моделированиязначение 10 [с];Присвоить параметру
Шаг интегрированиязначение 10 [мс];Присвоить параметру
Шаг дискретизациизначение 10 [мс].
Рис. 1 Настройка проекта#
3. Создание источника сигнала#
В первую очередь необходимо разработать источник сигнал с шумом для этого необходимо добавить на холст следующие блоки:
Нормальный шум, расположенный в библиотеке Автоматика, разделе Источники, установив ему следующие значения свойств (см. рис. 2 ниже):
Свойство |
Значение |
|---|---|
Метка блока |
Сигнал с шумом |
Математическое ожидание |
45 |
Среднеквадратическое отклонение |
5 |
Период дискретизации, с |
0.01 |
Постоянно-кусочная функция, расположенный в библиотеке Автоматика, разделе Источники, установив ему следующие значения свойств (см. рис. 2 ниже):
Свойство |
Значение |
|---|---|
Метка блока |
Кусочное изменение сигнала в момент t = 5 сек |
Вектор аргументов времени, с |
0;5 |
Вектор значений функции |
0;15 |
Периодичность |
выкл |
Рис. 2 Задание свойств блокам источников сигналов#
Сумматор, расположенный в библиотеке Автоматика, разделе Операции.
Примечание
Свойства блока остаются по умолчанию, необходимо лишь добавить Метку блока - Общий сигнал
Данные блоки необходимо соединить между собой согласно рис. 3:
Рис. 3 Соединение блоков#
В результате имеется источник сигнала с шумом, который в момент t = 5 [c] динамически меняет амплитуду. Далее для проведения тестового расчета модели необходимо (см. рис. 4 ниже):
Открыть вкладку
Визуализация, расположенную в нижней панели слева, и нажать ЛКМ кнопку Добавить;В открывшемся окне выберать выходное значение блока Сумматор;
Рис. 4 Подготовка к расчёту#
После завершения расчета будет получен сигнал с шумом (см. рис. 5 ниже), который необходимо сгладить при помощи алгоритма “Скользящее среднее”, выполненное в Java-функции.
Рис. 5 График сигнала с шумом#
4. Java функция для реализации фильтра “Скользящее среднее”#
Блок Java-функция (см. рис. 6 ниже) из раздела Внешние модели — это программируемый компонент, позволяющий встраивать пользовательский Java-код в общую схему моделирования. Логика блока описывается во встроенном редакторе, который открывается двойным кликом ЛКМ (см. рис. 7 ниже), что дает возможность реализовать алгоритмы любой сложности.
Рис. 6 Блок Java-функция#
Рис. 7 Редактор Java блока#
Блок содержит четыре подсекции с кодом:
Импорт – для импорта Java-библиотек (например: import java.lang.Math;);
Инициализация – для объявления и инициализации глобальных переменных;
Код расчета – основной код расчета, в котором происходит вызов расчетных методов;
Методы – для написания пользовательских методов.
Также блок имеет настраиваемые входные и выходные порты: in_1 (вход) и out_1, out_2, out_3 (выход) по умолчанию.
Перед тем как реализовать собственный код для применения сглаживающего фильтра необходимо:
Рис. 8 Пустые вкладки редактора#
Изменить количество выходных портов до одного путём нажатия на красную кнопку -, расположенную в правой панели рядом с каждым названием порта и появляющуюся при наведении курсора на нужный параметр, рис. 9:
Рис. 9 Удаление выходного порта блока#
В итоге, перед написанием кода сглаживающего фильтра, пользовательская Java-функция будет иметь один входной и один выходной порт и пустые подсекции.
Подключим блок согласно общей схеме модели, рис. 10:
Рис. 10 Общая схема модели#
Далее приведен код, используемый для реализации сглаживающего фильтра согласно подсекциям. Комментарии выделяются символами //.
4.1. Импорты#
В данном разделе происходит импорт пакетов и классов, используемых в вычислениях, например, класс java.lang.Math, содержащий математические функции.
1import java.lang.Math;
2import java.util.*;
3import java.util.Arrays;
Строка 3. Импорт классов, необходимых для работы с массивами
4.2. Инициализация#
1int windowSize = 20;
2double[] buffer = new double[windowSize];
3int currentIndex = 0;
4boolean bufferFull = false;
Строка 1. Для выделения памяти под массив (буфер) необходимо создать целочисленную переменную windowSize. Значение равно 20 означает, что фильтр хранит 20 последних отсчетов.
Строка 2. По умолчанию в Java все элементы типа double инициализируются значением 0.0.
Строка 3. Указатель на текущую ячейку массива, куда будет записано новое входящее значение. Индексация в Java начинается с 0, поэтому стартовая позиция — 0.
Строка 4. Флаг-маркер. Становится true, когда массив полностью заполнится первыми 20 значениями. Нужен для корректного расчета среднего значения на начальном этапе симуляции.
4.3. Код расчета#
Происходит на каждом шаге модели. Здесь вызывается метод processSignal(in_1), передается значение сигнала с шумом in_1, и результат записывается в выходной порт out_1.
1out_1 = processSignal(in_1);
Строка 1. В «Коде расчета» пишется код, выполняемый на каждом шаге расчета модели, в нашем случае - вызов метода сглаживающего фильтра. out_1 - выходной порт блока (значение сглаженного сигнала). in_1 - входной порт (значение сигнала с шумом).
4.4. Методы#
Здесь находится сама логика фильтра. Написан метод processSignal(), который обновляет кольцевой буфер и возвращает скользящее среднее.
1public double processSignal(double rawSignal) {
2 buffer[currentIndex] = rawSignal;
3 currentIndex++;
4 if (currentIndex >= buffer.length) {
5 currentIndex = 0;
6 bufferFull = true;
7 }
8 double sum = 0.0;
9 int count = bufferFull ? buffer.length : currentIndex;
10 for (int i = 0; i < count; i++) {
11 sum += buffer[i];
12 }
13 return sum / count;
14 }
Строка 2. ШАГ 1: Запись новых данных в циклическую очередь (кольцевой буфер). Помещаем текущее зашумленное значение датчика в ячейку под индексом currentIndex.
Строка 3. ШАГ 2: Управление индексами и контроль переполнения буфера. Сдвигаем указатель на следующую ячейку для будущего шага симуляции.
Строка 4. Проверяем, не вышли ли мы за границы массива (выход за пределы вызовет ошибку). Свойство buffer.length динамически возвращает размер массива (в данном случае 20).
Строка 5. Сбрасываем индекс в 0. Новые данные начнут перезаписывать самые старые.
Строка 6. Выставляем флаг: буфер заполнен, теперь все 20 ячеек содержат реальные данные.
Строка 8. ШАГ 3: Расчет скользящего среднего значения. Локальная переменная для накопления суммы. Сбрасывается в 0 на каждом шаге.
Строка 9. Задаем количество элементов для расчета среднего. Если буфер заполнен (bufferFull == true) — делим на длину массива (20). Если буфер еще заполняется — делим только на количество фактически записанных элементов (currentIndex).
Строка 10. Запускаем цикл для обхода элементов массива и их суммирования. Переменная „i“ — это счетчик цикла, который увеличивается на 1 (i++) на каждой итерации. Цикл выполняется до тех пор, пока „i“ строго меньше значения „count“.
Строка 11. Накапливаем сумму: sum = sum + buffer[i].
Строка 13. Возвращаем результат вычисления. Операция деления суммы на количество элементов дает итоговое отфильтрованное значение.
Код для реализации фильтра “Скользящее среднее” в Java-функции написан.
4.5. Анализ результатов расчёта#
Для оценки работы фильтра и сравнительного анализа отфильтрованного сигнала добавим в график второй параметр: “Java-функция – out_1”, рис. 11:
Рис. 11 Добавление графика выходного порта Java блока#
Согласно полученным графикам (см. рис. 12 ниже), можно сделать вывод, что скользящее среднее подавляет высокочастотный шум, поскольку случайные положительные и отрицательные отклонения при усреднении частично компенсируют друг друга. При этом медленно изменяющаяся полезная составляющая сигнала сохраняется.
Рис. 12 Сигнал с шумом и отфильтрованный сигнал#
Далее проводится расчет с окном отсчетов равным 100. Для этого значение переменной windowSize устанавливают 100.
1int windowSize = 100;
2double[] buffer = new double[windowSize];
Строка 2. По умолчанию в Java все элементы типа double инициализируются значением 0.0.
При увеличении размера окна фильтра заметно:
Существенное улучшение сглаживания сигнала и подавления шумов;
Появление временной задержки выходного сигнала относительно входного.
Задержка возникает из-за того, что выходное значение фильтра вычисляется как среднее нескольких последних отсчетов сигнала. Чем больше размер окна, тем сильнее подавляется шум, однако тем медленнее фильтр реагирует на быстрые изменения входного сигнала.
При шаге дискретизации 10 [мс] и размере окна 100 отсчетов фильтр усредняет значения за временной интервал около 1 [с], поэтому изменение сигнала на выходе происходит заметно медленнее по сравнению с исходным сигналом, рис. 13:
Рис. 13 Результаты работы фильтра с увеличенным окном#
5. Реализация фильтра при помощи Apache Commons Math#
Реализованный вручную фильтр скользящего среднего полностью рабочий, но в реальных проектах часто выгоднее использовать стандартные математические библиотеки. Это сокращает время разработки, уменьшает вероятность ошибок и даёт доступ к дополнительным статистическим методам.
Одной из самых популярных Java‑библиотек для численных расчётов является Apache Commons Math. В нашем случае класс DescriptiveStatistics автоматически реализует кольцевой буфер, вычисление среднего, дисперсии, медианы и других статистик.
Ниже приведён полный код для каждой секции блока Java-функция с использованием класса DescriptiveStatistics пакета Apache Commons Math.
5.1. Импорт#
В данной подсекции добавляется импорт класса DescriptiveStatistics.
В данном разделе происходит импорт пакетов и классов, используемых в вычислениях, например, класс java.lang.Math, содержащий математические функции и константы
1import java.lang.Math;
2import java.util.*;
3import java.util.Arrays;
4import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
Строка 3. Импорт классов, необходимых для работы с объектами типа String.
Строка 4. Импорт класса DescriptiveStatistics из пакета org.apache.commons.math3.stat.descriptive.
5.2. Инициализация#
Создаётся один объект DescriptiveStatistics с заданным размером окна.
1int windowSize = 100;
2DescriptiveStatistics stats = new DescriptiveStatistics(windowSize);
Строки 1-2. Создаем объект статистики с окном windowSize в 100 элементов.
5.3. Код расчета#
По‑прежнему вызывается processSignal(in_1).
1out_1 = processSignal(in_1);
Строка 1. В «Коде расчета» пишется код, выполняемый на каждом шаге расчета модели, в нашем случае - вызов метода сглаживающего фильтра. out_1 - выходной порт блока (значение сглаженного сигнала). in_1 - входной порт (значение сигнала с шумом).
5.4. Методы#
Метод processSignal() содержит всего две основные строки: добавить значение и вернуть среднее. Библиотека сама управляет буфером.
1public double processSignal(double rawSignal) {
2 stats.addValue(rawSignal);
3 double mean = stats.getMean();
4 return mean;
5 }
Строка 2. Добавляем новое значение. Библиотека сама удалит самое старое значение, если окно заполнено.
Строка 3. Получаем среднее арифметическое.
Строка 4. Дополнительный бонус: можем легко получить стандартное отклонение (шумность). double std = stats.getStandardDeviation();
Далее проводится сравнение фильтров, выполненных собственноручно, и при помощи класса DescriptiveStatistics.
6. Сравнение реализаций: ручной фильтр и Apache Commons Math#
Для проверки идентичности работы фильтров двух разных исполнений нам необходимо продублировать источник сигнала с шумом – для этого нам понадобится блок Разветвитель (библиотека Автоматика, раздел Переключатели и узлы) у которого необходимо поменять значение свойства Количество выходных портов на 2. Схема соединения приведена на рис. 14:
Рис. 14 Добавление разветвителя для дублирования сигнала#
Построим на одном графике три сигнала (см. рис. 15 ниже):
Сигнал с шумом;
Отфильтрованный сигнал с фильтра, написанного вручную;
Отфильтрованный сигнал с фильтра, реализованного при помощи класса DescriptiveStatistics.
Рис. 15 Параметры для построения графика#
Результаты значений сглаженных сигналов совпадают с высокой точностью, рис. 16:
Рис. 16 Результаты работы двух фильтров
Класс DescriptiveStatistics - не единственный полезный компонент библиотеки Apache Commons Math. В её состав входят модули для решения других математических задач:
linear - операции с матрицами и векторами, решение систем линейных уравнений;
analysis - численное решение уравнений, интерполяция, интегрирование;
random - генерация случайных чисел с различными распределениями;
stat - дополнительные статистические методы (регрессия, тесты).
Примечание
Подробное описание всех модулей доступно на официальном сайте
7. Заключение#
В данном уроке рассмотрены:
Реализация фильтра скользящего среднего с использованием кольцевого буфера и ручного вычисления среднего арифметического при помощи блока Java-функция;
Использование внешней библиотеки Apache Commons Math в блоке Java-функция для импорта класса DescriptiveStatistics, который автоматически управляет окном данных и возвращает среднее значение;
Влияние размера окна фильтрации на качество сглаживания и задержку сигнала: увеличение окна улучшает подавление шума, но увеличивает временную задержку;
Сравнение двух реализаций – их результаты идентичны, что подтверждает корректность написанного алгоритма.

