Обработка изображений 9. Размытие
от aNNiMON
Снова возвращаемся к обработке пикселей и на этот раз рассмотрим алгоритм размытия изображений.
Содержание:
1. Введение
2. Изображения. Простая трансформация
3. Негатив, извлечение и инверсия каналов
4. Обесцвечивание
5. Цветовые модели
6. Яркость, насыщенность, контрастность, гамма-коррекция
7. Гистограмма
8. Масштабирование изображения
9. Размытие
10. Свёртка
В прошлый раз при масштабировании изображений билинейной интерполяцией для каждого нового пикселя мы рассматривали 4 пикселя в исходном изображении, чтобы смешать компоненты с нужными коэффициентами и получить сглаженный результат. Для фильтра Ланцоша это количество пикселей возрастало до 9, 25 и т.д.
По такому же принципу работает и размытие: необходимо рассмотреть несколько пикселей вокруг текущего просматриваемого пикселя, а затем записать среднее арифметическое каждой цветовой компоненты.
Отвлечёмся на небольшой расчёт. Дана картинка 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,3). Радиус просмотра равен двум пикселям (два влево, два вправо и один центральный).
Мы посчитали сумму, поделили на 5, записали в результирующий массив по адресу (0,3) и теперь переходим к обработке (0,4).
В прошлый раз мы уже читали (0,2), (0,3), (0,4) и (0,5). И эти значения уже есть в сумме, поэтому вместо перечитывания, мы удаляем из суммы (0,1) и прибавляем новый элемент (0,6).
Количество чтений сократилось до двух (и 2r+1 для первого пикселя в строке).
Можно пойти дальше и заменить деление на чтение уже заранее подготовленного среднего значения.
Один проход даёт не очень качественный результат, поэтому обычно этот фильтр применяют два или больше раз. Вот сравнение результатов алгоритма, приведённого выше при радиусе 10 и размытия по Гауссу в фотошопе с тем же радиусом.
Напоследок, о математике.
Один проход оптимизированного алгоритма всё с тем же изображением 400x292 при радиусе 1 даёт 469276 чтений. При радиусе 2 — 470660, 3 — 472044, 5 – 474812, 10 – 481732, 20 – 495572.
Для радиуса 3 получили почти в 12 раз меньше чтений пикселей, чем при первом алгоритме без оптимизаций. Даже если выполнить размытие два или три раза, всё равно получится быстрее.
Следующая статья →Содержание:
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. Свёртка