Обработка изображений 9. Размытие

от
Работа с графикой   image processing, обработка изображений, javascript, js, blur, размытие, box blur

logo.pngСнова возвращаемся к обработке пикселей и на этот раз рассмотрим алгоритм размытия изображений.

Содержание:
  1. Введение
  2. Изображения. Простая трансформация
  3. Негатив, извлечение и инверсия каналов
  4. Обесцвечивание
  5. Цветовые модели
  6. Яркость, насыщенность, контрастность, гамма-коррекция
  7. Гистограмма
  8. Масштабирование изображения
  9. Размытие
  10. Свёртка

В прошлый раз при масштабировании изображений билинейной интерполяцией для каждого нового пикселя мы рассматривали 4 пикселя в исходном изображении, чтобы смешать компоненты с нужными коэффициентами и получить сглаженный результат. Для фильтра Ланцоша это количество пикселей возрастало до 9, 25 и т.д.
По такому же принципу работает и размытие: необходимо рассмотреть несколько пикселей вокруг текущего просматриваемого пикселя, а затем записать среднее арифметическое каждой цветовой компоненты.

  1. for (let y = 0; y < height; y++) {
  2.   for (let x = 0; x < width; x++) {
  3.     let a = 0, r = 0, g = 0, b = 0, count = 0;
  4.     for (let sy = y - size; sy <= y + size; sy++) {
  5.       const yy = Math.min(height - 1, Math.max(0, sy));
  6.       for (let sx = x - size; sx <= x + size; sx++) {
  7.         const xx = Math.min(width - 1, Math.max(0, sx));
  8.         let pix = src[yy * width + xx];
  9.         r += pix & 0xFF;
  10.         g += (pix >> 8) & 0xFF;
  11.         b += (pix >> 16) & 0xFF;
  12.         a += (pix >> 24) & 0xFF;
  13.         count++;
  14.       }
  15.     }
  16.  
  17.     a = (a / count) & 0xFF;
  18.     r = (r / count) & 0xFF;
  19.     g = (g / count) & 0xFF;
  20.     b = (b / count) & 0xFF;
  21.  
  22.     dst[dstIndex++] = (a << 24) | (b << 16) | (g << 8) | r;
  23.   }
  24. }

See the Pen Image processing 9.1. Blur by aNNiMON (@aNNiMON) on CodePen.


blur1.jpg

Отвлечёмся на небольшой расчёт. Дана картинка 400x292. При обычном обходе мы
400*292=116800 раз производим чтение пикселя.
При размытии с радиусом 1 (3x3) мы делаем 400*292*9=1051200 чтений.
С радиусом 2 (5x5) – 400*292*25=2920000
С радиусом 3 (7x7) – 400*292*49=5723200

Много. А ведь это ещё не размытие по Гауссу, где можно рассматривать и 25x25 пикселей!
Размытие с фильтром Гаусса отличается от вышеприведённого тем, что при суммировании значений учитываются коэффициенты, которые уменьшаются в зависимости от расстояния до центра. Если хотите, можете модифицировать код выше так, чтобы учитывались коэффициенты. К примеру для радиуса 1:
  1. 0.2 0.6 0.2
  2. 0.6  1  0.6
  3. 0.2 0.6 0.2
Не забудьте в комментариях поделиться вашей реализацией.


ОптимизацияСуществуют алгоритмы приближения к результатам размытия Гаусса. Они размывают не так качественно, но при этом работают гораздо быстрее.

Например, можно сначала пройтись по картинке и размыть только в горизонтальном направлении, затем полученный результат размыть по-вертикали.

boxblur.jpg

Также можно снизить количество повторных чтений пикселя. К примеру, мы делаем горизонтальное размытие и сейчас находимся в точке (0,3). Радиус просмотра равен двум пикселям (два влево, два вправо и один центральный).
boxblur_opt1.png
Мы посчитали сумму, поделили на 5, записали в результирующий массив по адресу (0,3) и теперь переходим к обработке (0,4).
boxblur_opt2.png
В прошлый раз мы уже читали (0,2), (0,3), (0,4) и (0,5). И эти значения уже есть в сумме, поэтому вместо перечитывания, мы удаляем из суммы (0,1) и прибавляем новый элемент (0,6).
boxblur_opt3.png

Количество чтений сократилось до двух (и 2r+1 для первого пикселя в строке).
Можно пойти дальше и заменить деление на чтение уже заранее подготовленного среднего значения.

See the Pen Image processing 9.2. Box blur by aNNiMON (@aNNiMON) on CodePen.


Один проход даёт не очень качественный результат, поэтому обычно этот фильтр применяют два или больше раз. Вот сравнение результатов алгоритма, приведённого выше при радиусе 10 и размытия по Гауссу в фотошопе с тем же радиусом.
blur2.jpg

Напоследок, о математике.
Один проход оптимизированного алгоритма всё с тем же изображением 400x292 при радиусе 1 даёт 469276 чтений. При радиусе 2 — 470660, 3 — 472044, 5 – 474812, 10 – 481732, 20 – 495572.

Для радиуса 3 получили почти в 12 раз меньше чтений пикселей, чем при первом алгоритме без оптимизаций. Даже если выполнить размытие два или три раза, всё равно получится быстрее.
+6   6   0
265