Обработка изображений 8. Масштабирование изображений

от
Работа с графикой   image processing, обработка изображений, javascript, js, scaling, resampling, интерполяция, interpolation, lanczos

logo.pngРассмотрим немаловажную для обработки изображений тему — масштабирование изображений. Вы наверняка сталкивались с этой задачей и может быть даже слышали об алгоритмах интерполяции при масштабировании. Вот об этом и будет статья.

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

Интерполяция методом ближайшего соседа (nearest neighbor)При масштабировании этим методом мы оперируем только пикселями, значения отдельных цветовых каналов получать не нужно. Это позволяет масштабировать картинку очень быстро, но, увы, при уменьшении множество деталей будет потеряно, а при увеличении пиксель превратится в большой квадрат или прямоугольник.

  1. const dx = width / newWidth;
  2. const dy = height / newHeight;
  3. for (let y = 0; y < newHeight; y++) {
  4.   let srcY = Math.floor(y * dy);
  5.   for (let x = 0; x < newWidth; x++) {
  6.     let srcX = Math.floor(x * dx);
  7.     dst[y * newWidth + x] = src[srcY * width + srcX];
  8.   }
  9. }

Исходное изображение размером width на height масштабируется до newWidth на newHeight. Считаем приращение dx и dy, а затем для каждого нового пикселя ищем нужную позицию в исходном изображении и записываем значение.

See the Pen Image processing 8.1. Image Scaling. Nearest Neighbor by aNNiMON (@aNNiMON) on CodePen.


Оригинал 64x64 в центре, уменьшенная копия 32x32 слева и увеличенная до 200x200 справа:
all-nearest.png


Билинейная интерполяцияДля каждой точки на картинке берутся значения трёх соседних пикселей, по формуле считается значение для каждой компоненты и новый цвет записывается в текущую позицию.

bilinear.png

  1. const xMax = (width - 1);
  2. const yMax = (height - 1);
  3. const dx = (xMax + 0.5) / newWidth;
  4. const dy = (yMax + 0.5) / newHeight;
  5. let dstOffset = 0;
  6. for (let i = 0; i < newHeight; i++) {
  7.   for (let j = 0; j < newWidth; j++) {
  8.     const x = Math.floor(dx * j);
  9.     const y = Math.floor(dy * i);
  10.     const xDiff = (dx * j) - x;
  11.     const yDiff = (dy * i) - y;
  12.     const index = y * width + x;
  13.  
  14.     const a = src[index];
  15.     const b = (x >= xMax) ? a : src[index + 1];
  16.     const c = (y >= yMax) ? a : src[index + width];
  17.     const d = (y >= yMax) ? b : ((x >= xMax) ? c : (src[index + width + 1]));
  18.  
  19.     const red = interpolate(
  20.         a & 0xff, b & 0xff,
  21.         c & 0xff, d & 0xff,
  22.         xDiff, yDiff);
  23.     const green = interpolate(
  24.         (a >> 8) & 0xff, (b >> 8) & 0xff,
  25.         (c >> 8) & 0xff, (d >> 8) & 0xff,
  26.         xDiff, yDiff);
  27.     const blue = interpolate(
  28.         (a >> 16) & 0xff, (b >> 16) & 0xff,
  29.         (c >> 16) & 0xff, (d >> 16) & 0xff,
  30.         xDiff, yDiff);
  31.     const alpha = interpolate(
  32.         (a >> 24) & 0xff, (b >> 24) & 0xff,
  33.         (c >> 24) & 0xff, (d >> 24) & 0xff,
  34.         xDiff, yDiff);
  35.  
  36.     dst[dstOffset++] = (alpha << 24) | (blue << 16) | (green << 8) | red;
  37.   }
  38. }
  39.  
  40. function interpolate(a, b, c, d, width, height) {
  41.   return a * (1 - width) * (1 - height)
  42.       + b * width * (1 - height)
  43.       + c * (1 - width) * height
  44.       + d * width * height;
  45. }

Всё так же высчитываем приращение, дистанцию между старой и новой позицией (xDiff, yDiff) и применяем функцию interpolate для каждой цветовой компоненты.

See the Pen Image processing 8.2. Image Scaling. Bilinear by aNNiMON (@aNNiMON) on CodePen.


all-bilinear.png


Интерполяция фильтром Ланцоша (Lanczos)Наконец, рассмотрим интерполяцию с применением фильтра Ланцоша. Этот способ даёт ещё большую плавность результата, но при этом время обработки значительно увеличивается.

Фильтр можно применить с различными значениями размера ядра (о ядрах свёрки будет отдельная статья). Если при билинейном интерполировании для каждого нового пикселя мы рассматривали 4 исходных пикселя, то теперь их может быть 9 (3x3), 25 (5x5), 49 (7x7) и т.д. Оптимальными по качеству и скорости являются размеры 2 и 3.

lanczos.png

See the Pen Image processing 8.3. Image Scaling. Lanczos by aNNiMON (@aNNiMON) on CodePen.


Вот результат для filterSize=2:
all-lanczos.png

Ну и напоследок, уменьшенный до 20х20, а затем увеличенный Пикачу
pika.png
+6   6   0
280