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

Содержание:
1. Введение
2. Изображения. Простая трансформация
3. Негатив, извлечение и инверсия каналов
4. Обесцвечивание
5. Цветовые модели
6. Яркость, насыщенность, контрастность, гамма-коррекция
7. Гистограмма
8. Масштабирование изображения
9. Размытие
10. Свёртка
В прошлый раз при масштабировании изображений билинейной интерполяцией для каждого нового пикселя мы рассматривали 4 пикселя в исходном изображении, чтобы смешать компоненты с нужными коэффициентами и получить сглаженный результат. Для фильтра Ланцоша это количество пикселей возрастало до 9, 25 и т.д.
По такому же принципу работает и размытие: необходимо рассмотреть несколько пикселей вокруг текущего просматриваемого пикселя, а затем записать среднее арифметическое каждой цветовой компоненты.
- for (let y = 0; y < height; y++) {
- for (let x = 0; x < width; x++) {
- let a = 0, r = 0, g = 0, b = 0, count = 0;
- for (let sy = y - size; sy <= y + size; sy++) {
- const yy = Math.min(height - 1, Math.max(0, sy));
- for (let sx = x - size; sx <= x + size; sx++) {
- const xx = Math.min(width - 1, Math.max(0, sx));
- let pix = src[yy * width + xx];
- r += pix & 0xFF;
- g += (pix >> 8) & 0xFF;
- b += (pix >> 16) & 0xFF;
- a += (pix >> 24) & 0xFF;
- count++;
- }
- }
- a = (a / count) & 0xFF;
- r = (r / count) & 0xFF;
- g = (g / count) & 0xFF;
- b = (b / count) & 0xFF;
- dst[dstIndex++] = (a << 24) | (b << 16) | (g << 8) | r;
- }
- }
See the Pen Image processing 9.1. Blur by aNNiMON (@aNNiMON) on CodePen.

Отвлечёмся на небольшой расчёт. Дана картинка 400x292. При обычном обходе мы
400*292=116800 раз производим чтение пикселя.
При размытии с радиусом 1 (3x3) мы делаем 400*292*9=1051200 чтений.
С радиусом 2 (5x5) – 400*292*25=2920000
С радиусом 3 (7x7) – 400*292*49=5723200
Много. А ведь это ещё не размытие по Гауссу, где можно рассматривать и 25x25 пикселей!
Размытие с фильтром Гаусса отличается от вышеприведённого тем, что при суммировании значений учитываются коэффициенты, которые уменьшаются в зависимости от расстояния до центра. Если хотите, можете модифицировать код выше так, чтобы учитывались коэффициенты. К примеру для радиуса 1:
- 0.2 0.6 0.2
- 0.6 1 0.6
- 0.2 0.6 0.2
ОптимизацияСуществуют алгоритмы приближения к результатам размытия Гаусса. Они размывают не так качественно, но при этом работают гораздо быстрее.
Например, можно сначала пройтись по картинке и размыть только в горизонтальном направлении, затем полученный результат размыть по-вертикали.

Также можно снизить количество повторных чтений пикселя. К примеру, мы делаем горизонтальное размытие и сейчас находимся в точке (0,3). Радиус просмотра равен двум пикселям (два влево, два вправо и один центральный).

Мы посчитали сумму, поделили на 5, записали в результирующий массив по адресу (0,3) и теперь переходим к обработке (0,4).

В прошлый раз мы уже читали (0,2), (0,3), (0,4) и (0,5). И эти значения уже есть в сумме, поэтому вместо перечитывания, мы удаляем из суммы (0,1) и прибавляем новый элемент (0,6).

Количество чтений сократилось до двух (и 2r+1 для первого пикселя в строке).
Можно пойти дальше и заменить деление на чтение уже заранее подготовленного среднего значения.
See the Pen Image processing 9.2. Box blur by aNNiMON (@aNNiMON) on CodePen.
Один проход даёт не очень качественный результат, поэтому обычно этот фильтр применяют два или больше раз. Вот сравнение результатов алгоритма, приведённого выше при радиусе 10 и размытия по Гауссу в фотошопе с тем же радиусом.

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