Давайте подробно рассмотрим реализацию размытия по Гауссу. Алгоритм обработки изображений позволяет выполнять такие манипуляции с изображениями:

Мы начнем с обзора гауссовых распределений и свертки изображений - движущих сил размытия по Гауссу. Затем мы реализуем наш собственный алгоритм размытия по Гауссу с нуля с помощью Swift.

Если вы еще не читали мою статью об обнаружении краев на изображениях, я рекомендую вам сначала прочитать ее. Это поможет заложить основы свертки и основ обработки изображений.

Свертка

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

Изменяя значения в ядре, мы можем изменить эффект на изображение - размытие, резкость, обнаружение краев, уменьшение шума и т. Д.

Свертка станет понятнее, когда мы увидим пример.

Гауссовские распределения

Теперь обратимся к гауссовской части размытия по Гауссу. Размытие по Гауссу - это просто метод размытия изображения с помощью функции Гаусса.

Вы, возможно, слышали термин Гауссово раньше в отношении Гауссовского распределения (также известного как нормальное распределение).

Ниже вы увидите двухмерное распределение Гаусса. Обратите внимание, что в центре есть пик, а кривая сглаживается по мере продвижения к краям.

Представьте, что это распределение накладывается на группу пикселей изображения. Глядя на этот график, должно быть очевидно, что если мы возьмем средневзвешенное значение значений пикселей и высоту кривой в этой точке, пиксели в центре группы внесут наибольший вклад в итоговое значение. По сути, так работает размытие по Гауссу.

TL; DR. Размытие по Гауссу применяется путем свертки изображения с помощью функции Гаусса.

На английском это означает, что мы возьмем функцию Гаусса и сгенерируем матрицу n x m. Используя эту матрицу и высоту распределения Гаусса в этом местоположении пикселя, мы вычислим новые значения RGB для размытого изображения.

Обзор

Для начала нам понадобится функция Гаусса в двух измерениях:

Значения этой функции создадут матрицу / ядро ​​свертки, которые мы будем применять к каждому пикселю исходного изображения. Ядро, как правило, довольно маленькое - чем оно больше, тем больше вычислений мы должны выполнять для каждого пикселя.

x и y определяют дельту от центрального пикселя (0, 0). Например, если для ядра выбран радиус 3, x и y будут в диапазоне от -3 до 3 (включительно).

σ - стандартное отклонение - влияет на то, насколько сильно соседние пиксели центрального пикселя влияют на результат вычислений.

Технически, в функции Гаусса, поскольку она расширяется бесконечно, вы можете возразить, что вам нужно учитывать каждый пиксель в изображении, чтобы получить «правильный» эффект размытия, но на практике пиксели больше 3 σ практически не влияют на итоговые значения.

Реализация

Мы почти готовы приступить к реализации.

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

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

В нашем случае для простоты реализации мы проигнорируем пиксели по краям.

Начнем с реализации функции Гаусса. Первая задача - определить разумные значения для x, y и σ.

Хотя технически ядро ​​может иметь произвольный размер, мы должны масштабировать σ пропорционально размеру ядра. Если у нас большой радиус ядра, но маленькая сигма, то все новые пиксели, которые мы вводим с нашим большим радиусом, на самом деле не влияют на вычисления.

Вот пример большого радиуса ядра, но маленькой сигмы:

В отличие от Sigma 5, ядро ​​111:

Код

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

Приведенный ниже код отнюдь не самый быстрый и отдает предпочтение ясности, а не краткости:

Вот полная реализация в Swift:

Вот некоторые результаты на фотографии, которую я сделал на прошлой неделе среди пожаров Йосемити:

Исходный

Радиус: 3

Радиус: 5

Радиус: 11

Если вам понравился этот пост, подпишитесь на меня в Twitter или загляните на мой личный сайт, чтобы узнать больше.