Pull to refresh

Быстрый ресайз джипегов на видеокарте

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



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

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

Использование других форматов, отличных от JPEG, в качестве базовых для организации такого хранилища изображений не кажется оправданным. Конечно, есть стандартные, широко используемые форматы, которые дают лучшее сжатие при аналогичном качестве (JPEG2000, WebP), но скорость кодирования и декодирования таких изображений очень мала по сравнению с JPEG, поэтому имеет смысл выбрать именно JPEG качестве базового формата для хранения оригинальных фотографий, которые при необходимости будут отмасштабированы в реальном времени после получения запроса от пользователя.

Конечно, кроме джипегов на каждом сайте чаще всего есть изображения в форматах PNG и GIF, но обычно их относительное количество невелико, а фотографии в этих форматах хранятся крайне редко. Поэтому заметного влияния на рассматриваемую задачу эти форматы не внесут в большинстве случаев.

Описание алгоритма ресайза на лету


Итак, входными данными являются файлы формата JPEG, причём для достижения быстрого декодирования (это верно как для CPU, так и для GPU), сжатые изображения должны иметь встроенные рестарт-маркеры. Эти маркеры описаны в стандарте JPEG и часть кодеков умеет с ними работать, остальные умеют их не замечать. Если таких маркеров у джипегов нет, их можно заранее добавить с помощью утилиты jpegtran. При добавлении маркеров изображение не изменяется, но размер файла становится чуть больше. В итоге получаем следующую схему работы:

  1. Получаем данные изображения из памяти CPU
  2. Если есть цветовой профиль, получаем его из EXIF секции и сохраняем
  3. Копируем картинку на видеокарту
  4. Декодируем JPEG
  5. Делаем ресайз по алгоритму Ланцоша (уменьшение)
  6. Накладываем резкость
  7. Кодируем изображение по алгоритму JPEG
  8. Копируем изображение на хост
  9. Добавляем в полученный файл исходный цветовой профиль

Можно сделать и более точное решение, когда перед ресайзом накладывается обратная гамма на каждую компоненту пиксела, чтобы ресайз был в линейном пространстве, а потом гамму применить снова, но уже после шарпа. Фактическая разница для пользователя невелика, но она существует, а вычислительные затраты для такой модификации минимальны. Нужно лишь вставить наложение обратной и прямой гаммы в общую схему обработки.

Также возможен вариант решения, когда декодирование джипегов выполняется на многоядерном CPU с помощью библиотеки libjpeg-turbo. В этом случае каждая картинка декодируется в отдельном потоке CPU, а все остальные действия выполняются на видеокарте. При наличии большого количества ядер CPU так может получиться даже быстрее, но будет серьёзный проигрыш в латентности. Если латентность при декодировании джипега на одном ядре CPU является допустимой, то этот вариант может оказаться очень быстрым, особенно для случая, когда исходные джипеги имеют небольшое разрешение. При увеличении разрешения исходного изображения, время декодирования джипега в одном потоке CPU будет увеличиваться, поэтому этот вариант может подойти только для небольших разрешений.

Базовые требования к задаче ресайза для веба


  • Желательно не хранить на сервере десятки копий каждого изображения в разных разрешениях, а быстро создавать нужную картинку с правильным разрешением сразу при получении запроса. Это важно для уменьшения размера хранилища, так как в противном случае придётся хранить много разных копий каждого изображения.
  • Задачу нужно решить максимально быстро. Это вопрос о качестве предоставляемой услуги с точки зрения уменьшения времени отклика на запрос пользователя.
  • Качество отправленного изображения должно быть высоким.
  • Размер файла для отправленного изображения должен быть как можно меньше, а его разрешение в точности должно соответствовать размеру окна, в котором оно появится. Тут важны следующие моменты:

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

б) Экранный ресайз дополнительно потребляет энергию устройства.

в) Если для решения задачи использовать серию заранее отмасштабированных изображений, то не всегда получится точно попасть в нужный размер, а значит, придётся посылать картинку большего разрешения. Увеличенный размер изображения приводит к большему трафику, чего тоже хотелось бы избежать.

Описание общей схемы работы


  1. Получаем от пользователей изображения в любых форматах и в любых разрешениях. Оригиналы храним в отдельной базе данных (если нужно).
  2. В оффлайне с помощью ImageMagick или аналогичного софта, сохраняем цветовой профиль, преобразуем исходные оригинальные изображения к стандартному формату BMP или PPM, после чего делаем ресайз до разрешения 1К или 2К и сжимаем в JPEG, затем утилитой jpegtran добавляем рестарт-маркеры с заданным фиксированным интервалом.
  3. Составляем базу данных из таких 1К или 2К изображений.
  4. При получении запроса от пользователя получаем информацию о картинке и размере окна, где должно быть показано это изображение.
  5. Изображение находим в базе данных и отправляем ресайзеру.
  6. Ресайзер получает файл изображения, декодирует, делает ресайз, шарп, кодирует и вставляет исходный цветовой профиль в полученный джипег. После этого отдаёт картинку внешней программе.
  7. На каждой видеокарте можно запустить несколько потоков, а в компьютере можно установить несколько видеокарт — таким образом достигается масштабирование производительности.
  8. Всё это можно сделать на базе видеокарт NVIDIA Tesla (например, Р40 или V100), поскольку видеокарты NVIDIA GeForce не предназначены для непрерывной длительной работы, а у NVIDIA Quadro есть много видео выходов, которые в данном случае не нужны. Для решения этой задачи требования к размеру памяти GPU минимальные.
  9. Также из базы с приготовленными изображениями можно динамически выделить кеш для часто используемых файлов. Там имеет смысл хранить часто используемые изображения по статистике предыдущего периода.



Параметры программы


  1. Ширина и высота нового изображения. Они могут быть любыми и их лучше задавать в явном виде.
  2. Режим прореживания JPEG (subsampling). Есть три варианта: 4:2:0, 4:2:2 и 4:4:4, но обычно используют 4:4:4 или 4:2:0. Максимальное качество у 4:4:4, минимальный размер кадра у 4:2:0. Прореживание делается для цветоразностных компонент, которые зрение человека воспринимает не так хорошо, как яркостную. Для каждого режима прореживания есть свой оптимальный интервал для рестарт-маркеров для достижения максимальной скорости кодирования или декодирования.
  3. Качество сжатия JPEG и режим прореживания при создании базы данных изображений.
  4. Шарп делается в окне 3х3, сигмой (радиусом) можно управлять.
  5. Качество сжатия JPEG и режим прореживания при кодировании финальной картинки. Обычно качество не менее 90% означает, что это сжатие «визуально без потерь», т.е. неподготовленный пользователь не должен увидеть артефакты алгоритма JPEG при стандартных условиях просмотра. Считается, что для подготовленного пользователя нужно 93-95%. Чем больше это значение, тем больше размер кадра, отправляемого пользователю, и тем больше время декодирования и кодирования.

Важные ограничения


Рестарт-маркеры. Мы можем быстро декодировать на видеокарте изображения в формате JPEG только в том случае, если внутри него находятся рестарт-маркеры. В официальном стандарте JPEG эти маркеры описаны, это стандартный параметр. Если рестарт-маркеров нет, то распараллелить декодирование картинки на видеокарте нельзя, что приведёт к очень малой скорости декодера. Поэтому нужна база приготовленных изображений, в которых есть эти маркеры.

Фиксированный алгоритм для кодека изображений. Декодирование и кодирование изображений по алгоритму JPEG является на сегодняшний день самым быстрым вариантом.

Разрешение изображений в приготовленной базе данных может быть любым, а в качестве вариантов мы рассмотрим 1К и 2К (можно взять и 4К). Также можно сделать не только уменьшение, но и увеличение изображений при ресайзе.

Производительность быстрого ресайза


Мы протестировали приложение для быстрого ресайза из комплекта Fastvideo SDK на видеокарте NVIDIA Tesla V100 (OS Windows Server 2016, 64-bit, драйвер 24.21.13.9826) на 24-битных изображениях 1k_wild.ppm и 2k_wild.ppm с разрешением 1К и 2К (1280х720 и 1920х1080). Тесты проведены для разного количества потоков, запущенных на одной видеокарте. При этом требуется не более 110 МБайт памяти на видеокарте на один поток. На 4 потока нужно не более 440 МБайт.

Сначала сжимаем исходные изображения в JPEG c качеством 90%, с прореживанием 4:2:0 или 4:4:4. Затем декодируем и делаем ресайз в 2 раза по ширине и высоте, делаем шарп, потом опять кодируем c качеством 90% в 4:2:0 или в 4:4:4. Исходные данные лежат в оперативной памяти, финальная картинка размещается там же.

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

Пример командной строки для 24-битного изображения 1К
PhotoHostingSample.exe -i 1k_wild.90.444.jpg -o 1k_wild.640.jpg -outputWidth 640 -q 90 -s 444 -sharp_after 0.95 -repeat 200

Бенчмарк для обработки одного изображения 1K в одном потоке


Декодирование (включая пересылку данных на видеокарту): 0,70 мс
Ресайз в два раза (по ширине и по высоте): 0,27 мс
Шарп: 0,02 мс
Кодирование JPEG (включая пересылку данных с видеокарты): 0,20 мс
Суммарное время на один кадр: 1,2 мс

Производительность для 1К


Качество Прореживание Ресайз Потоки Частота кадров (Гц)
1 90% 4:4:4 / 4:2:0 2 раза 1 868 / 682
2 90% 4:4:4 / 4:2:0 2 раза 2 1039 / 790
3 90% 4:4:4 / 4:2:0 2 раза 3 993 / 831
4 90% 4:4:4 / 4:2:0 2 раза 4 1003 / 740


Производительность для 2К


Качество Прореживание Ресайз Потоки Частота кадров (Гц)
1 90% 4:4:4 / 4:2:0 2 раза 1 732 / 643
2 90% 4:4:4 / 4:2:0 2 раза 2 913 / 762
3 90% 4:4:4 / 4:2:0 2 раза 3 891 / 742
4 90% 4:4:4 / 4:2:0 2 раза 4 923 / 763


Прореживание 4:2:0 для исходного изображения уменьшает скорость работы, зато размеры исходного и конечного файлов становятся меньше. При переходе на 4:2:0 степень параллелизма падает в 4 раза, так как теперь блок 16х16 рассматривается как одно целое, поэтому в этом режиме скорость работы ниже, чем для 4:4:4.

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

Резюме


Результаты тестов показали, что для видеокарты NVIDIA Tesla V100 скорость обработки 1К и 2К изображений максимальна при запуске 2-4 потоков одновременно, и составляет от 800 до 1000 кадров в секунду на одну видеокарту. Обработка 1К картинок выполняется быстрее, чем 2К, а работа с изображениями 4:2:0 всегда выполняется медленнее, чем с 4:4:4. Для получения окончательного результата по производительности, нужно точно определить все параметры программы и оптимизировать её под конкретную модель видеокарты.

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

Для обработки одного миллиарда джипегов в сутки с разрешениями 1K или 2K, может потребоваться до 16 видеокарт NVIDIA Tesla V100. Некоторые из наших заказчиков уже используют это решение, другие тестируют его в своих задачах.

Ресайз джипегов на видеокарте может быть очень полезен не только для веб-сервисов. Есть огромное количество высокопроизводительных приложений по обработке изображений, где такой функционал может быть востребован. Например, быстрый ресайз очень часто необходим практически для любой схемы обработки изображений, полученных от камер, перед выводом картинки на монитор. Такое решение может работать для Windows/Linux на любых видеокартах NVIDIA: Tegra K1/X1/X2/Xavier, GeForce GT/GTX/RTX, Quadro, Tesla.

Преимущества решения с быстрым ресайзом на видеокарте


  • Значительное уменьшение размера хранилища для исходных изображений
  • Сокращение первичных затрат на стоимость инфраструктуры (железо и софт)
  • Улучшение качества сервиса благодаря малому времени отклика
  • Сокращение исходящего трафика
  • Меньшее потребление энергии на устройствах пользователей
  • Надёжность и скорость представленного решения, которое уже было протестировано на огромных наборах данных
  • Уменьшение времени разработки для вывода на рынок таких приложений для ОС Linux и Windows
  • Масштабируемость решения, которое может работать как на одиночной видеокарте, так и в составе кластера
  • Быстрый возврат инвестиций для таких проектов

Кому это может быть интересно


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

Разработчики ПО могут использовать эту библиотеку, которая обеспечивает латентность порядка нескольких миллисекунд для ресайза джипегов с разрешением 1К, 2К и 4К на видеокарте.

По всей видимости, этот подход может оказаться более быстрым, чем решение NVIDIA DALI для быстрого декодирования джипегов, ресайза и подготовки изображений на этапе тренировки нейросетей для Deep Learning.

Что ещё можно сделать


  • Кроме ресайза и шарпа можно в существующий алгоритм добавить кроп, повороты на 90/180/270, наложение ватермарка, управление яркостью и контрастом.
  • Оптимизация решения для видеокарт NVIDIA Tesla P40 и V100.
  • Дополнительная оптимизация производительности декодера JPEG.
  • Режим пачки для декодирования джипегов на видеокарте.
Tags:
Hubs:
+23
Comments31

Articles

Change theme settings