Убираем сетевую наводку 50 Гц из ЭКГ

Решаем классическую медицинскую задачу: чистим кардиограмму от помехи электросети.

Сетевая наводка — паразитный сигнал частотой 50 Гц (в РФ и Европе; 60 Гц в США/Японии), наводимый проводкой на чувствительные датчики вроде электродов ЭКГ.

Это, возможно, самая частая практическая задача DSP в медицине и измерениях. Электроды ЭКГ снимают милливольты, а вокруг — провода под напряжением 220 В, 50 Гц. Наводка «подмешивается» в сигнал и мешает врачу видеть зубцы кардиограммы. Полезный сигнал сердца — это в основном частоты до ~40 Гц, а помеха — ровно 50 Гц. Их можно разделить фильтром.

Видим проблему

Смоделируем ЭКГ как медленный полезный тон 2 Гц плюс сильную наводку 50 Гц. Сравним сигнал с эталоном (чистым 2 Гц) по RMS-отклонению.

import math

fs, N = 200.0, 200
ecg = [math.sin(2 * math.pi * 2 * n / fs) + 0.7 * math.sin(2 * math.pi * 50 * n / fs)
       for n in range(N)]
clean = [math.sin(2 * math.pi * 2 * n / fs) for n in range(N)]

def rms(a, b):
    return round(math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)) / len(a)), 3)

print("RMS наводки в сыром сигнале:", rms(ecg, clean))

Вывод:

RMS наводки в сыром сигнале: 0.495

Отклонение 0.495 — наводка сравнима по силе с полезным сигналом. Без фильтрации зубцы ЭКГ тонут в «сетевой ряби».

Трюк: скользящее среднее, гасящее ровно 50 Гц

Есть элегантное решение. Скользящее среднее длиной L отсчётов имеет в АЧХ нули на частотах fs/L, 2*fs/L, .... Если выбрать L = fs/50, один из нулей попадёт ровно на 50 Гц — и наводка исчезнет. При fs = 200 нужно L = 200/50 = 4.

import math

fs, N = 200.0, 200
ecg = [math.sin(2 * math.pi * 2 * n / fs) + 0.7 * math.sin(2 * math.pi * 50 * n / fs)
       for n in range(N)]
clean = [math.sin(2 * math.pi * 2 * n / fs) for n in range(N)]

def convolve_same(x, h):
    M = len(h); pad = M // 2; out = []
    for i in range(len(x)):
        s = 0.0
        for j in range(M):
            idx = i - pad + j
            if 0 <= idx < len(x):
                s += x[idx] * h[j]
        out.append(s)
    return out

def rms(a, b):
    return round(math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)) / len(a)), 3)

L = int(fs / 50)          # = 4: нуль АЧХ ровно на 50 Гц
ma = [1 / L] * L
filtered = convolve_same(ecg, ma)
print("Длина фильтра L =", L)
print("RMS наводки после фильтра:", rms(filtered, clean))

Вывод:

Длина фильтра L = 4
RMS наводки после фильтра: 0.028

Наводка упала с 0.495 до 0.028 — почти в 18 раз. Простое 4-точечное среднее, настроенное на нуль 50 Гц, вычистило помеху, почти не задев полезный сигнал 2 Гц. Кардиограмма читается.

Почему это работает

АЧХ скользящего среднего длины L — это функция вида |sin(pi*f*L/fs) / (L*sin(pi*f/fs))|. Её нули — там, где числитель равен нулю, то есть на частотах, кратных fs/L. При L=4 и fs=200 нули стоят на 50, 100, 150 Гц. Наша помеха 50 Гц попадает точно в первый нуль и подавляется почти полностью. А полезные 2 Гц лежат далеко в полосе пропускания и проходят. Это дешёвый аналог режекторного (notch) фильтра, идеально подходящий, когда мешающая частота кратна fs/L.

Как работает под капотом

В реальной аппаратуре ЭКГ применяют специальный notch-фильтр: IIR второго порядка с парой нулей точно на 50 Гц и парой полюсов чуть внутри окружности рядом с ними. Полюса делают провал АЧХ узким, чтобы вырезать только 50 Гц, не трогая соседние полезные частоты (наш MA-трюк вырезает шире). Иногда вместо фильтра используют адаптивное вычитание: берут опорный сигнал сети, подстраивают его амплитуду и фазу и вычитают из ЭКГ — так убирают наводку, вообще не трогая спектр сигнала. Выбор метода зависит от того, насколько стабильна частота сети и важны ли частоты возле 50 Гц.

Частые ошибки

  • Резать слишком широко. Грубый фильтр уберёт вместе с 50 Гц и полезные близкие частоты (для ЭКГ это детали зубцов).
  • Перепутать 50 и 60 Гц. В США сеть 60 Гц; фильтр под 50 Гц там не сработает.
  • Фильтровать вместо экранирования. Лучше сначала уменьшить наводку аппаратно (экран, заземление), а фильтром добивать остаток.

Итог

  • Сетевая наводка — паразитный тон 50 Гц (60 в США) на чувствительных датчиках.
  • Скользящее среднее длины L = fs/50 имеет нуль АЧХ ровно на 50 Гц.
  • Такой фильтр дёшево гасит помеху, почти не трогая полезный низкочастотный сигнал.
  • В аппаратуре применяют узкий notch-фильтр или адаптивное вычитание сети.
Проверьте себя
1. Почему скользящее среднее длины L = fs/50 убирает наводку 50 Гц?
AОно усиливает 50 Гц
BОдин из нулей его АЧХ попадает ровно на 50 Гц
CОно случайно ослабляет все частоты
DОно работает как ФВЧ
2. Какой специализированный фильтр применяют для удаления узкой помехи?
AФНЧ
BФВЧ
CРежекторный (notch)
DПолосовой
3. Почему важно резать наводку как можно у́же?
AЧтобы фильтр был быстрее
BЧтобы не повредить полезные частоты, близкие к 50 Гц
CЧтобы сэкономить память
DУзость не важна