Обработка изображений 10. Свёртка

от
Работа с графикой    image processing, обработка изображений, javascript, свёртка, ядро свёртки, convolution, convolution kernel

logo.pngБлагодаря свёртке можно реализовать множество интересных эффектов, таких как размытие в движении, резкость, подсветка границ, выдавливание и т.д. Эти эффекты мы и рассмотрим в статье.

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

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

А чтобы было универсальнее, помимо массива коэффициентов 3x3, именуемого ядром свёртки, и делителя div, также будет задаваться некоторое смещение offset. offset будет суммироваться с каждым цветовым компонентом, позволяя увеличить или уменьшить яркость.

Разумеется, можно не ограничиваться ядром 3x3, а сделать его 5x5 или даже больше.
В фотошопе в Фильтр - Другое - Заказная реализовано ядро 5x5.
shot-20190330T161702.jpg

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

See the Pen Image processing 10.1. Convolution matrix by aNNiMON (@aNNiMON) on CodePen.


По умолчанию ядро задано в таком виде:
  1. 0 0 0
  2. 0 1 0
  3. 0 0 0
  4. div: 1, offset: 0
Это значит, что учитывается только текущий просматриваемый пиксель, а все окружающие домножаются на ноль. В качестве результата получаем изображение без изменений.
identity.jpg

  1. 1 1 1
  2. 1 1 1
  3. 1 1 1
  4. div: 9, offset: 0
Считается сумма из всех 9 пикселей, а затем делится на 9. Это размытие из предыдущей статьи, только реализовано через свёртку.
blur.jpg

  1. 1 2 1
  2. 2 4 2
  3. 1 2 1
  4. div: 16, offset: 0
Всё то же размытие, только теперь коэффициенты расставлены с учётом расстояния от центра. Это приближенный алгоритм размытия по Гауссу.
gaussian_blur.jpg

  1.  0 -1  0
  2. -1  5 -1
  3.  0 -1  0
  4. div: 1, offset: 0
Если в ядре задан отрицательный коэффициент, значит он будет вычтен из суммы. Если вычесть значения четырёх окружающия пикселей, но среднему задать пятикратную силу, то получится эффект Резкость.
sharpen.jpg

Коэффициенты можно уменьшить для ослабления эффекта резкости.
  1.   0   -0.2    0
  2. -0.2   1.8  -0.2
  3.   0   -0.2    0
  4. div: 1, offset: 0
sharpen_soft.jpg

  1. -2 -1  0
  2. -1  1  1
  3.  0  1  2
  4. div: 1, offset: 0
Диагональный эффект Выдавливание. Ядро можно транспонировать, либо задать вертикальный или горизонтальный проход.
emboss.jpg

  1. -2 -1  0
  2. -1  0  1
  3.  0  1  2
  4. div: 1, offset: 0
Если не учитывать значение центрального пикселя, то получится эффект подсветки границ.
edgedetection.jpg

А если задать offset=128, то получится эффект Рельеф
  1. -2 -1  0
  2. -1  0  1
  3.  0  1  2
  4. div: 1, offset: 128
relief.jpg

А ещё при помощи свёртки можно сделать инверсию изображения.
  1. 0  0  0
  2. 0 -1  0
  3. 0  0  0
  4. div: 1, offset: 255
negate.jpg

Более наглядную демонстрацию работы свёртки с просмотром коэффициентов и их сумм, но на одноканальном изображении, можно посмотреть здесь http://setosa.io/ev/image-kernels/
  • +10
  • views 9429