КИХ-фильтры (FIR): свёртка с ядром
Разбираем FIR — самый понятный и устойчивый класс фильтров: просто свёртка с подобранным ядром.
FIR-фильтр (КИХ, с конечной импульсной характеристикой) — фильтр, выход которого зависит только от конечного числа входных отсчётов:
y[n] = sum(h[k]*x[n-k]). Это прямая свёртка сигнала с ядромh.
FIR — самый дружелюбный тип фильтра. Его работа — это в точности свёртка, которую мы уже освоили. У него нет обратной связи, поэтому он всегда устойчив и может иметь идеально линейную фазу (не искажает форму). Цена — для крутых фильтров нужно длинное ядро. Научимся проектировать FIR двумя способами.
FIR — это свёртка
Весь FIR-фильтр задаётся ядром h (его коэффициенты называют отводами, taps). Применение — свёртка. Самый простой FIR — скользящее среднее: ядро из одинаковых весов.
def fir(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(round(s, 2))
return out
def moving_average(k):
return [1 / k] * k
sig = [2, 8, 4, 6, 10, 2, 4, 8]
print("MA-3 ядро:", [round(v, 3) for v in moving_average(3)])
print("сглажено: ", fir(sig, moving_average(3)))
Вывод:
MA-3 ядро: [0.333, 0.333, 0.333] сглажено: [3.33, 4.67, 6.0, 6.67, 6.0, 5.33, 4.67, 4.0]
Скользящее среднее — это FIR-ФНЧ. Но у него грубая АЧХ. Для качественного фильтра ядро проектируют умнее — методом окна.
Проектирование методом окна
Идеальный ФНЧ имеет ядро в виде функции sinc (бесконечной). Мы обрезаем её до конечной длины и умножаем на оконную функцию (чтобы убрать «звон»). Получается практичный FIR-ФНЧ.
import math
def fir_lowpass(numtaps, fc):
# fc — частота среза в долях fs (0..0.5)
M = numtaps - 1
h = []
for n in range(numtaps):
k = n - M / 2
ideal = 2 * fc if k == 0 else math.sin(2 * math.pi * fc * k) / (math.pi * k)
window = 0.54 - 0.46 * math.cos(2 * math.pi * n / M) # окно Хэмминга
h.append(ideal * window)
s = sum(h)
return [round(c / s, 4) for c in h] # нормируем усиление на 0 Гц к 1
kernel = fir_lowpass(7, 0.1)
print("Ядро ФНЧ (7 отводов):", kernel)
print("Сумма ~ 1 (усиление DC):", round(sum(kernel), 3))
Вывод:
Ядро ФНЧ (7 отводов): [0.0135, 0.0785, 0.2409, 0.3344, 0.2409, 0.0785, 0.0135] Сумма ~ 1 (усиление DC): 1.0
Ядро симметрично (это и даёт линейную фазу) и колоколообразно: центральный отвод самый большой, края малы. Сумма коэффициентов равна единице — значит, постоянный сигнal проходит без изменения уровня.
FIR реально чистит шум
Проверим фильтр в деле: добавим шум к синусоиде и пропустим через наш FIR-ФНЧ.
import math, random
random.seed(42)
def fir(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
h = [0.0135, 0.0785, 0.2409, 0.3344, 0.2409, 0.0785, 0.0135]
clean = [math.sin(2 * math.pi * n / 32) for n in range(32)]
noisy = [s + random.uniform(-0.4, 0.4) for s in clean]
filtered = fir(noisy, h)
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(noisy, clean))
print("RMS шума после: ", rms(filtered, clean))
Вывод:
RMS шума до фильтра: 0.244 RMS шума после: 0.122
Фильтр вдвое снизил отклонение от чистого сигнала: высокочастотный шум подавлен, синусоида осталась. FIR работает.
Как работает под капотом
Ключевое достоинство FIR — линейная фаза при симметричном ядре. Линейная фаза означает, что все частоты задерживаются на одинаковое время, поэтому форма сигнала не искажается — только сдвигается. Это критично для аудио и связи, где важна сохранность формы (например, при передаче данных). У IIR-фильтров (следующий урок) фаза нелинейна. Обратная сторона: чтобы получить крутой срез, FIR требует много отводов (десятки-сотни), а значит, много умножений на отсчёт. Длинные FIR на практике считают через БПФ (быстрая свёртка): перейти в спектр, перемножить, вернуться — это O(N log N) вместо O(N*M).
Частые ошибки
- Забыть нормировать ядро. Если сумма коэффициентов не равна 1, фильтр изменит общий уровень сигнала.
- Обрезать sinc без окна. Резкое обрезание даёт «звон» в АЧХ (эффект Гиббса); окно сглаживает.
- Брать слишком короткое ядро для крутого среза. Чем круче нужен переход, тем больше отводов — это закон.
Итог
- FIR-фильтр — это свёртка сигнала с конечным ядром
h(отводами). - Простейший FIR — скользящее среднее; качественный проектируют методом окна.
- Симметричное ядро даёт линейную фазу — форма сигнала не искажается.
- FIR всегда устойчив, но для крутого среза требует много отводов.