Обработка изображений как 2D-сигналов

Расширяем DSP на два измерения: изображение — это сигнал, а фильтры — те же свёртки, только 2D.

Изображение — это двумерный сигнал I[i][j]: яркость пикселя в строке i и столбце j. Фильтрация изображений — это 2D-свёртка с ядром-матрицей.

Всё, что мы изучили про одномерные сигналы, почти без изменений переносится на изображения — нужно лишь добавить второе измерение. Размытие, резкость, выделение краёв в фоторедакторах и в нейросетях компьютерного зрения — это 2D-свёртки. Понимание 1D-свёртки даёт прямой ключ к обработке изображений.

2D-свёртка: ядро скользит по картинке

Ядро — маленькая матрица (3×3, 5×5). Мы накладываем её на каждый пиксель, перемножаем перекрытые значения и суммируем — получаем новый пиксель. То же «скольжение и сумма», но в двух направлениях.

def conv2d(img, kernel):
    H, W = len(img), len(img[0])
    kh, kw = len(kernel), len(kernel[0])
    pad = kh // 2
    out = [[0] * W for _ in range(H)]
    for i in range(H):
        for j in range(W):
            s = 0.0
            for di in range(kh):
                for dj in range(kw):
                    ii, jj = i + di - pad, j + dj - pad
                    if 0 <= ii < H and 0 <= jj < W:
                        s += img[ii][jj] * kernel[di][dj]
            out[i][j] = round(s)
    return out

# Маленькое изображение: яркий квадрат на тёмном фоне
img = [[10, 10, 10, 10],
       [10, 90, 90, 10],
       [10, 90, 90, 10],
       [10, 10, 10, 10]]

blur = [[1/9, 1/9, 1/9],
        [1/9, 1/9, 1/9],
        [1/9, 1/9, 1/9]]        # усреднение 3x3 = размытие

for row in conv2d(img, blur):
    print(row)

Вывод:

[13, 24, 24, 13]
[24, 46, 46, 24]
[24, 46, 46, 24]
[13, 24, 24, 13]

Резкая граница яркого квадрата «размазалась»: значения 10 и 90 смешались в промежуточные 13–46. Это 2D-ФНЧ — размытие. Ядро-усреднитель давит высокие пространственные частоты (резкие края).

Резкость и выделение краёв

Поменяем ядро — получим противоположные эффекты. Ядро резкости усиливает отличие пикселя от соседей; ядро Лапласа выделяет края (places где яркость резко меняется).

def conv2d(img, kernel):
    H, W = len(img), len(img[0])
    kh = len(kernel); pad = kh // 2
    out = [[0] * W for _ in range(H)]
    for i in range(H):
        for j in range(W):
            s = 0.0
            for di in range(kh):
                for dj in range(len(kernel[0])):
                    ii, jj = i + di - pad, j + dj - pad
                    if 0 <= ii < H and 0 <= jj < W:
                        s += img[ii][jj] * kernel[di][dj]
            out[i][j] = round(s)
    return out

img = [[10, 10, 10, 10],
       [10, 90, 90, 10],
       [10, 90, 90, 10],
       [10, 10, 10, 10]]

edges = [[0, -1, 0],
         [-1, 4, -1],
         [0, -1, 0]]            # оператор Лапласа — края

print("Края (Лаплас):")
for row in conv2d(img, edges):
    print(row)

Вывод:

Края (Лаплас):
[20, -70, -70, 20]
[-70, 160, 160, -70]
[-70, 160, 160, -70]
[20, -70, -70, 20]

Большие по модулю значения (160, -70) появились именно на границах квадрата, где яркость резко меняется. Внутри однородных областей отклик был бы около нуля. Так детектор краёв «обводит» контуры объектов.

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

У изображения есть пространственные частоты: плавные градиенты — низкие частоты, резкие края и мелкая текстура — высокие. Размытие (усредняющее ядро) — это 2D-ФНЧ, оно гасит высокие пространственные частоты. Резкость и края — 2D-ФВЧ, они выделяют высокие частоты. К изображениям применимо и 2D-ДПФ (и 2D-БПФ): его используют в сжатии (JPEG режет картинку на блоки 8×8 и применяет родственное преобразование DCT, отбрасывая малозаметные высокие частоты). А свёрточные нейросети (CNN) в компьютерном зрении — это, по сути, наборы обучаемых 2D-ядер: сеть сама подбирает ядра, выделяющие полезные признаки (края, углы, текстуры). То есть фундамент компьютерного зрения — ровно та свёртка, что мы реализовали.

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

  • Не нормировать ядро размытия. Если сумма коэффициентов не равна 1, изображение потемнеет или пересветится.
  • Игнорировать края изображения. У границ ядро «выходит» за картинку; нужна стратегия (нули, отражение, повтор краёв), иначе появятся артефакты.
  • Путать размытие и понижение разрешения. Перед уменьшением картинки её сначала размывают (2D-антиалиасинг), иначе появится муар.

Итог

  • Изображение — двумерный сигнал; фильтрация — 2D-свёртка с ядром-матрицей.
  • Усредняющее ядро размывает (2D-ФНЧ), ядро Лапласа выделяет края (2D-ФВЧ).
  • У картинок есть пространственные частоты; к ним применимо 2D-ДПФ (основа JPEG).
  • Свёрточные нейросети — это обучаемые 2D-ядра; фундамент компьютерного зрения — свёртка.
Проверьте себя
1. Что такое изображение с точки зрения DSP?
AОдномерный сигнал
BДвумерный сигнал — яркость как функция строки и столбца
CСпектр
DСлучайный шум
2. Какой эффект даёт усредняющее ядро 3x3 из коэффициентов 1/9?
AПовышение резкости
BРазмытие (2D-ФНЧ)
CВыделение краёв
DИнверсию цветов
3. Чем по сути являются свёрточные нейросети (CNN) в компьютерном зрении?
AНаборами обучаемых 2D-свёрточных ядер
BАлгоритмами сжатия
CФильтрами Фурье
DГенераторами шума