5.13. Линейные фильтры и фильтры окрестности

Поэлементные математические операции над пикселями, рассмотренные ранее в этой главе, попиксельно объединяли два изображения. Фильтры решают похожую задачу иначе: они вычисляют значение каждого выходного пикселя по небольшой окрестности входных пикселей, окружающих соответствующую позицию. Выход в точке (x, y) – это некоторая статистика (среднее, медиана, наиболее частое значение) входных пикселей в небольшом окне с центром в (x, y).

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

5.13.1. Размер ядра

Каждый фильтр окрестности принимает параметр size, который задаёт радиус окна в пикселях. Само окно квадратное и охватывает (2 * size + 1) пикселей с каждой стороны, так что size=1 означает окрестность 3 на 3, size=2 – 5 на 5, size=3 – 7 на 7 и так далее.

Небольшая пиксельная сетка изображения с выделенной подсеткой 3 на 3, представляющей окрестность фильтра. Стрелка показывает смещение окрестности на один пиксель вправо. Вторая стрелка показывает её смещение вниз к следующей строке в конце строки. Выходной пиксель для каждой позиции нарисован под окрестностью с небольшой пометкой о том, что выход -- это некоторая статистика входной окрестности.

Окрестность скользит по изображению на один пиксель за раз, от левого верхнего угла к правому нижнему. Каждый выходной пиксель – это результат применения статистики фильтра к входной окрестности с центром в нём.

Большие размеры означают большие окрестности, что означает более сильную (или более агрессивную) фильтрацию. Затраты растут пропорционально площади окна, так что фильтр с size=3 выполняет примерно в девять раз больше работы на пиксель, чем фильтр с size=1. Практическое значение по умолчанию для большинства задач очистки – size=1 или size=2; прибегайте к большим размерам только тогда, когда небольших окрестностей недостаточно для подавления того признака, который пытается подавить приложение.

5.13.2. Фильтр среднего

mean() заменяет каждый пиксель арифметическим средним его окрестности. Результат сглаживает попиксельные вариации в пределах размера окна, что делает его самым дешёвым способом подавления крапин шума датчика: высокочастотные вариации усредняются, низкочастотное содержимое сохраняется.

Компромисс в том, что границы и другие резкие признаки тоже усредняются. Яркая граница, которая до фильтра была шириной в один пиксель, после фильтра среднего с size=1 становится шириной два или три пикселя, с яркостью, плавно спадающей по краям. Для чистого шумоподавления на бедном текстурой изображении (чистая стена, внутренняя часть цветного маркера) компромисс приемлем. Для насыщенной сцены, где границы важны, один из следующих фильтров обычно подходит лучше.

img.mean(1)        # 3x3 box average -- fast, gentle smoothing
img.mean(2)        # 5x5 box average -- stronger, slower

5.13.3. Медиана, мода, средняя точка

Три других статистических фильтра окрестности заменяют простое арифметическое среднее чем-то более устойчивым к выбросам.

median() возвращает медиану окрестности – значение, оказывающееся в середине отсортированного списка пикселей окна. Один очень яркий или очень тёмный пиксель в окне не сдвигает медиану; он просто становится одним из отброшенных крайних значений. На практике это означает, что медианная фильтрация удаляет одиночные пиксельные крапины и шум типа «соль и перец», не размывая границы так, как это делает mean. Платой является больший объём вычислений на пиксель – сортировка окна медленнее его усреднения – и результат не является строго средним, что иногда важно для последующих вычислений.

Параметр percentile (по умолчанию 0.5) сдвигает выбираемое значение от строгой медианы. percentile=0.0 возвращает минимум окрестности, percentile=1.0 – максимум; промежуточные значения выбирают пропорционально между ними в отсортированном окне. Это даёт median возможность подчёркивать тёмные или яркие части окрестности, не теряя устойчивости порядковой статистики к выбросам.

mode() возвращает наиболее частое значение в окрестности. Полезно, когда модель шума такова: «большинство пикселей верны, а несколько повреждены в разной степени», где правильный ответ – то значение, которое встречается чаще всего; медиана может его упустить, когда повреждённые значения скапливаются с одной стороны отсортированного окна.

midpoint() возвращает взвешенную комбинацию минимума и максимума окрестности – bias=0.5 даёт среднюю точку между ними, bias=0.0 даёт минимум, bias=1.0 даёт максимум. Используется реже остальных, но о нём стоит знать, когда цель состоит именно в извлечении тёмных или ярких признаков.

5.13.4. Билатеральный фильтр – вариант с сохранением границ

bilateral() – это фильтр окрестности, который стоит понять лучше всего. Он даёт сглаживающий эффект mean(), но с дополнительным ограничением: чем сильнее пиксель окрестности отличается от центрального пикселя, тем меньше он учитывается в среднем. Результат сглаживает внутреннюю часть каждой однородной области, не растекаясь через границы, разделяющие их, – а это именно то, чего на самом деле хочет большинство приложений.

Два параметра управляют тем, насколько агрессивно фильтр снижает вес пикселей:

  • color_sigma определяет, как различие цвета влияет на взвешивание. Меньшие значения означают, что фильтр строже относится к снижению веса пикселей, отличающихся от центрального.

  • space_sigma определяет, как пространственное расстояние влияет на взвешивание. Меньшие значения дают больший вес пикселям, близким к центру.

Значения по умолчанию (color_sigma=0.1, space_sigma=1.0) являются разумной отправной точкой; их настройка обычно сводится к запуску фильтра на тестовом кадре и корректировке до тех пор, пока границы не станут чёткими, а внутренние области – чистыми.

Билатеральный фильтр дороже, чем median(), и значительно дороже, чем mean(), поэтому к нему стоит прибегать только тогда, когда поведение с сохранением границ – это именно то, что нужно приложению.

5.13.5. Адаптивная пороговая обработка

Фильтры среднего, медианы, моды и средней точки – все несут одну и ту же пару именованных аргументов, превращающих их выход в бинарный порог:

  • threshold=True переключает фильтр в режим пороговой обработки.

  • offset=N сдвигает локальный порог на N единиц перед сравнением.

Механизм напрямую опирается на обычное поведение фильтра. Без threshold=True фильтр вычисляет свою статистику по окрестности и записывает эту статистику в выходной пиксель. С threshold=True фильтр вычисляет ту же статистику, затем сравнивает исходный пиксель в той же позиции со статистикой плюс смещение и записывает максимальное значение формата, если исходный пиксель больше, и ноль в противном случае.

Результат – бинарное изображение, чей порог перемещается вместе с локальной яркостью по всему кадру. Яркие области получают высокий порог, тусклые области – низкий порог, и пиксель переднего плана, локально более яркий, чем его соседи, проходит порог независимо от того, находится ли он в яркой области или в тусклой, – а это именно то поведение, которое единый глобальный порог не смог бы обеспечить на неравномерно освещённом изображении.

img.mean(3, threshold=True, offset=5)

Параметр offset – это то, чем приложение управляет строгостью теста. Небольшое положительное смещение требует, чтобы исходный пиксель был ощутимо ярче своих соседей, прежде чем считаться совпадением, что подавляет ложные срабатывания от шума датчика ценой потери слабого переднего плана. Небольшое отрицательное смещение улавливает слабый передний план ценой пропуска части шума. Выбор зависит от того, что остальная часть конвейера будет делать с бинарным выходом.

Три панели изображений в ряд. Первая -- входной кадр в оттенках серого с градиентом яркости и метками переднего плана, разбросанными по нему с равномерной темнотой. Вторая панель показывает применённый к нему глобальный порог: передний план правильно классифицирован на яркой стороне, но вся тёмная сторона читается как передний план, потому что и фон, и передний план оказываются ниже порога. Третья панель показывает адаптивный порог, применённый к тому же входу: передний план правильно классифицирован по всему кадру.

При неравномерном освещении единый глобальный порог не может описать передний план в каждой позиции. Фильтр окрестности, запущенный с threshold=True, создаёт порог, который перемещается вместе с локальной яркостью и правильно классифицирует передний план по всему кадру.

Адаптивный порог выполняет всё семейство фильтров, поэтому выбор правильного фильтра важен: mean() для самого дешёвого адаптивного порога, median() – когда на входе есть шум типа «соль и перец», который фильтр должен отбросить перед вычислением локального порога.