Документация Engee
Notebook

Применение системных объектов в обработке сигналов

Библиотека EngeeDSP содержит мощные инструменты для обработки сигналов с использованием системных объектов, которые представляют собой специализированные компоненты для потоковой обработки данных. В представленном ниже коде демонстрируется комплексный подход к обработке сигналов, сочетающий системные объекты EngeeDSP с традиционными функциями обработки сигналов.

Перечислим ключевые особенности реализации.

  1. Использование системного объекта SineWaveDSP для генерации сигнала с конфигурируемыми параметрами (амплитуда, частота, метод вычисления).
  2. Применение DescretFIRFilter для фильтрации сигнала в потоковом режиме.
  3. Интеграция с обычными функциями обработки (скользящее среднее через свертку).
  4. Сочетание объектно-ориентированного подхода EngeeDSP с процедурными методами Julia.

Начнём с подключения библиотек, применяемых в данном проекте.

In [ ]:
# Pkg.add("DSP")
# Pkg.add("Statistics")

using DSP
using EngeeDSP
using Statistics

Теперь выполним инициализацию. Код, представленный ниже, создает системный объект SineWaveDSP из библиотеки EngeeDSP для генерации синусоидального сигнала с заданными параметрами: амплитудой A = 0.8, частотой f = 100 Гц, частотой дискретизации fs = 1000 Гц, размером кадра framesize = 796 отсчетов.

Объект настраивается на табличный метод вычисления (Table lookup), вещественный выход (Real) и непрерывный режим генерации (Continuous).

In [ ]:
A = 0.8
f = 100
fs = 1000
framesize = 796

H = EngeeDSP.SineWaveDSP(
    Amplitude = A,
    Frequency = f,
    SampleTime = 1//fs,
    SamplesPerFrame = framesize,
    ComputationMethod = "Table lookup",
    OutputComplexity = "Real",
    SampleMode = "Continuous"
)
Out[0]:
SineWaveDSP:
    Amplitude=0.8
    Frequency=100
    PhaseOffset=0
    SampleMode=Continuous
    OutputComplexity=Real
    ComputationMethod=Table lookup
    SampleTime=1152921504606847//1152921504606846976
    SamplesPerFrame=796
    ResettingStatesWhenReEnabled=Restart at time zero

Код ниже создает системный объект DescretFIRFilter, представляющий собой КИХ-фильтр с базовой конфигурацией по умолчанию, и имеет следующие характеристики:

  • CoefSource=Dialog parameters — коэффициенты, в данном случае это массив [0.5 0.5], что соответствует простейшему усредняющему фильтру.
  • FilterStructure=Direct form — используется стандартная прямая структура реализации фильтра.
  • InputProcessing=Elements as channels — каждый входной отсчет обрабатывается как отдельный канал.
  • InitialStates=0 — начальное состояние фильтра нулевое.
  • ShowEnablePort=false и ExternalReset=None — фильтр работает в непрерывном режиме без внешнего управления сбросом.

Этот объект предназначен для потоковой обработки сигналов и сохраняет внутреннее состояние (PreEmphasis_states=[0.0]) между вызовами.

In [ ]:
fir_filter = EngeeDSP.DescretFIRFilter() 
Out[0]:
DescretFIRFilter:
    CoefSource=Dialog parameters
    FilterStructure=Direct form
    Coefficients=[0.5 0.5]
    InputProcessing=Elemets as channels
    InitialStates=0
    ShowEnablePort=false
    ExternalReset=None
    PreEmphasis_states=[0.0]

Ниже показано создание конфидентов усредняющего фильтра с окном из 10 отсчетов. Массив averaging_filter заполняется значениями 1/window_size (т.е. 0.1), что означает равномерное взвешивание всех 10 предыдущих значений сигнала. Такой фильтр подавляет высокочастотные шумы, сглаживая сигнал за счет усреднения по заданному окну. В отличие от системных объектов EngeeDSP, это статическая свёртка, которая не сохраняет состояние между вызовами.

In [ ]:
window_size = 10
averaging_filter = ones(window_size) / window_size
Out[0]:
10-element Vector{Float64}:
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1
 0.1

Теперь соберём модель из наших объектов. Код ниже выполняет циклическую генерацию и обработку сигнала в 100 шагов:

  1. Генерация сигнала:
    • На каждом шаге H() генерирует новый фрейм синусоиды (длиной framesize=796 отсчетов) через системный объект SineWaveDSP.
  2. Фильтрация:
    • y_fir = fir_filter(x) — применяет потоковый FIR-фильтр (с коэффициентами [0.5, 0.5]) к сигналу, сохраняя состояние между вызовами.
    • y_avg = conv(y_fir, averaging_filter)[1:length(y_fir)] — дополнительно сглаживает сигнал скользящим средним (окно 10 отсчетов), обрезая результат свертки до исходной длины.
  3. Сохранение результатов:
    • Исходный (s), отфильтрованный (fir_filtered) и сглаженный (avg_filtered) сигналы накапливаются в массивах для последующего анализа.
In [ ]:
# Генерируем и обрабатываем сигнал
s = Float64[]          # Исходный сигнал
fir_filtered = Float64[] # После FIR фильтра
avg_filtered = Float64[] # После усреднения

for n in 1:100
    x = H()
    y_fir = fir_filter(x)
    y_avg = conv(y_fir, averaging_filter)[1:length(y_fir)] # Усекаем до исходной длины
    
    append!(s, x)
    append!(fir_filtered, y_fir)
    append!(avg_filtered, y_avg)
end

Теперь по сохранённым данным выполним построение графиков.

In [ ]:
gr()
n_show = 100
t = (0:n_show-1)/fs

p_signals = plot(t, s[1:n_show], label="Исходный сигнал", linewidth=2)
plot!(t, fir_filtered[1:n_show], label="После FIR", linewidth=2, linestyle=:dash)
plot!(t, avg_filtered[1:n_show], label="После усреднения", linewidth=2, linestyle=:dot)
title!("Сигналы на разных этапах обработки")
xlabel!("Время (с)")
ylabel!("Амплитуда")
Out[0]:

Результаты графиков:

  • Исходный сигнал (сплошная линия) — чистый синус с амплитудой 0.8.
  • После FIR (пунктир) — сглаженный сигнал с уменьшенными резкими перепадами (эффект окна 2 отсчёта).
  • После усреднения (точки) — первые 10 точек демонстрируют нарастание averaged signal (фильтр «наполняется»), а далее обнуление, так как conv обрезается до длины входного сигнала без заполнения краёв.

Почему нули?
Скользящее среднее требует window_size-1 дополнительных отсчётов для полного перекрытия. В коде conv обрезается строго до длины y_fir, отбрасывая «неполные» участки свертки — отсюда нули после 10-й точки, нулевые значения после усреднения — артефакт обработки, а не реальное поведение системы. Для потокового усреднения лучше применить DSP.filt(averaging_filter, y_fir) с сохранением состояния.

In [ ]:
s = Float64[]         
fir_filtered = Float64[] 
avg_filtered = Float64[] 

for n in 1:100
    x = H() 
    y_fir = fir_filter(x) 
    y_avg, filt_state = DSP.filt(averaging_filter, y_fir)
    
    append!(s, x)
    append!(fir_filtered, y_fir)
    append!(avg_filtered, y_avg)
end

p_signals = plot(t, s[1:n_show], label="Исходный сигнал", linewidth=2)
plot!(t, fir_filtered[1:n_show], label="После FIR", linewidth=2, linestyle=:dash)
plot!(t, avg_filtered[1:n_show], label="После усреднения", linewidth=2, linestyle=:dot)
title!("Сигналы на разных этапах обработки")
xlabel!("Время (с)")
ylabel!("Амплитуда")
Out[0]:

Переход на filt устранил артефакты обрезания, и график теперь отражает истинное поведение системы, а также это позволило создать полностью потоковую систему обработки, где:

  1. FIR-фильтр (окно 2 отсчёта) плавно сглаживает сигнал (пунктирная линия);
  2. скользящее среднее (окно 10 отсчётов) теперь корректно обрабатывает весь сигнал без нулей;
  3. фазовый сдвиг между фильтрами стал предсказуемым, так как оба фильтра работают в потоковом режиме с сохранением состояния.

Вывод

Пример наглядно демонстрирует преимущества комбинированного подхода, где системные объекты EngeeDSP используются для потоковой обработки, а традиционные функции – для дополнительных преобразований. Особенно показателен итоговый график, который позволяет отследить изменения сигнала на каждом из этапов модели. Использование системных объектов Engee в сочетании с традиционными методами Julia открывает широкие возможности для создания эффективных и гибких систем обработки сигналов.