Изменение размеров изображения с учетом его содержимого в JavaScript

Изменение размера изображения с учетом содержимого (content-aware image resizing) может быть применено, когда дело доходит до изменения пропорций изображения (например, уменьшения ширины при сохранении высоты), а также когда потеря некоторых частей изображения нежелательна. Простое масштабирование изображения в этом случае исказит находящиеся в нем объекты. Для сохранения пропорций объектов при изменении пропорций изображения можно использовать алгоритм Seam Carving, который был описан Shai Avidan и Ariel Shamir.

В приведенном выше примере показано, как ширина исходного изображения была уменьшена на 50% с учетом содержимого изображения (слева) и без учета содержимого изображения (справа, простой скейлинг). В данном случае левое изображение выглядит более естественным, так как пропорции воздушных шаров в нем были сохранены.

Горы на заднем плане плавно сжимаются, без видимых швов.

Идея алгоритма Seam Carving заключается в том, чтобы найти шов (seam, непрерывную последовательность пикселей) с наименьшим влиянием на содержание изображения, а затем его вырезать (carve). Этот процесс повторяется снова и снова, пока мы не получим требуемую ширину или высоту изображения. В примере ниже интуитивно видно, что пиксели воздушных шаров вносят больший «вклад» в содержание и смысл изображения, чем пиксели неба. Таким образом, сначала удаляются пиксели неба.

демонстрация алгоритма Seam Carving для изменения размеров изображения

Чтобы избежать изменения важных частей изображения можно найти непрерывную последовательность пикселей (шов), которая будет идти от верхней границы к нижней и иметь наименьший вклад в содержимое изображения (шов, который не проходит через важные части изображения), а затем удалить его. Удаление шва сожмет изображение на один пиксель. Далее надо повторять этот шаг до тех пор, пока изображение не станет нужной ширины.

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

вычисление важности пикселя

Предполагая, что цвет пикселя представлен 4-мя числами (R — красный, G — зеленый, B — синий, A — альфа, прозрачность), мы можем использовать следующую формулу для вычисления разницы в цвете (энергии пикселя):

формула для вычисления разницы в цвете (энергии пикселя)

Где:

  • mEnergy — Энергия (важность) среднего пикселя ([0..626] если округлить)
  • lR — Красный цветовой канал левого пикселя ([0..255])
  • mR — Красный цветовой канал среднего пикселя ([0..255])
  • rR — Красный цветовой канал правого пикселя ([0..255])
  • lG — Зеленый цветовой канал левого пикселя ([0..255])
  • и так далее…
пример вычисления энергии пикселя

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

Например, на 1-м шаге у нас будет изображение размером 1000 x 500 и энергетическая карта размером 1000 x 500. На 2-м шаге изменения размера мы удалим шов с изображения и пересчитаем карту энергий на основе нового уменьшенного изображения. Таким образом, мы получим изображение размером 999 x 500 и карту энергий размером 999 x 500.

Чем выше энергия пикселя, тем больше вероятность того, что он является частью ребра, важен для содержимого изображения и тем меньше вероятность того, что нам потребуется его удалить.

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

Вот реальный пример энергетической карты для изображения, которое вы видели выше (с воздушными шарами).

энергетическая карта изображения

Вот пример, как это работает. Можете загрузить своё и попробовать изменить его размеры с помощью алгоритма Seam Carving (вырезания швов):

Исходный код и функции можно найти в репозитории js-image-carver.
Полный текст на русском языке с описанием работы алгоритма и примерами кода для изменения размеров изображения с учетом его содержимого в JavaScript.