Статьи

Высокопроизводительное изменение размера изображения с изображением :: Масштаб

Несколько лет назад я помогал в разработке медиа-сервера, предназначенного для работы на любом компьютере, от настольных ПК до недорогих NAS-устройств на базе Linux. Обычной задачей для медиасервера является создание высококачественных миниатюр всех обложек альбомов, фотографий и видео в медиатеке пользователя. При сканировании библиотеки сервер должен кэшировать все эти миниатюры, чтобы они могли отображаться быстро. Кроме того, каждое изображение должно быть кэшировано в нескольких разных размерах, чтобы оптимизировать его для отображения на разных устройствах. Например, миниатюра 200×200 может хорошо работать в веб-интерфейсе, тогда как 75×75 будет лучше вписываться в мобильное приложение.

К сожалению, некоторые низкоуровневые NAS-устройства имеют очень медленные процессоры, которые были разработаны больше для простых задач передачи файлов, чем для сложной обработки изображений с интенсивной математикой. Алгоритм передискретизации в libgd, одной из наиболее распространенных библиотек изображений с открытым исходным кодом, использует около 35 умножений с плавающей точкой на пиксель целевого изображения. Создание миниатюры размером 200×200 для типичной обложки альбома 1500×1500 требует почти 1,5 миллиона операций умножения в цикле ресэмплирования ядра libgd. Компьютеры довольно хорошо умеют умножать числа вместе, но выполнять математику с дробными числами сложнее, чем с целыми числами, и обычно это делается отдельной частью ЦП, называемой FPU, или модулем с плавающей запятой. Один из способов, с помощью которого производители процессоров могут сократить расходы, — это удаление FPU. Обычный математический блок в CPU может выполнять те же вычисления,но ценой еще многих операций. Процессоры без FPU обычно используются во встроенных устройствах, таких как брандмауэры, маршрутизаторы и, конечно, устройства NAS.

Несколько лет назад одно из самых популярных потребительских устройств хранения данных включало процессор на базе SPARC с тактовой частотой 240 МГц. Естественно, в этом маленьком парне не было места для лишних излишеств, таких как FPU. Подумайте, сколько времени понадобилось этой машине, чтобы изменить размер покрытия 1500×1500 до 200×200. 5 секунд? 10? Как звучит 34 секунды? Да, за полминуты, за один эскиз. Тот факт, что людям приходилось ждать так много часов, чтобы завершился этап оформления их музыкального сканирования, был совершенно неприемлемым.

Решение

Ранее я сталкивался с концепцией математики с фиксированной запятой при работе с различными аудиокодеками, такими как libmad (MP3), Tremor (Vorbis) и FLAC. Декодирование аудио почти всегда выполняется без использования математики с плавающей точкой. Помимо повышения производительности на младших процессорах, отказ от использования FPU экономит много энергии в портативных устройствах. Математика с фиксированной запятой казалась идеальным решением нашей проблемы с изображением; если он был достаточно хорош для высококачественного звука, он должен был работать и для обработки изображений.

До этого момента мы использовали модуль GD Perl. GD — это интерфейс библиотеки libgd C, и, хотя он создает очень высококачественные миниатюры, он имеет довольно неуклюжий API и, как известно, его очень сложно создавать, особенно в Windows. Я решил взять лучшую часть libgd, его алгоритм пересэмплирования, и перенести его на математику с фиксированной запятой в гораздо более простом и удобном в использовании модуле под названием Image :: Scale. Этот модуль будет поддерживать все распространенные форматы изображений с помощью простого Perl-ish API, а также добавит несколько функций, таких как использование предварительного масштабирования JPEG, поддержание соотношения сторон путем добавления прозрачности и автоматического поворота изображений с камеры. Я также хотел добавить некоторую защиту от случайного изменения размера очень большого изображения, которое может привести к нехватке памяти в этих встроенных системах, которые обычно имеют очень малый объем оперативной памяти.libjpeg-turbo также поддерживается и настоятельно рекомендуется; он включает в себя высоко оптимизированные процедуры сборки и может улучшить обработку JPEG в 2-4 раза.

my $img = Image::Scale->new(‘image.jpg’);
$img->resize_gd_fixed_point( { width => 150 } );
$img->save_jpeg(‘resized.jpg’); # or save_png()

Фиксированный учебник для начинающих

Фиксированная точка реализуется путем разбиения 32 битов в обычном целом числе на целую половину и дробную половину. Чем больше битов выделено дробной стороне, тем выше будет точность. Я решил использовать формат 19.12, который просто означает 19 битов для целого числа, 12 для дроби, а оставшийся бит используется для знака. 12 бит позволяют получить около 4 десятичных знаков точности, что более чем достаточно для расчетов такого типа. На самом деле, я был рад обнаружить, что качество изображения практически неразличимо по сравнению с использованием плавающей запятой. Если вы хотите узнать больше, ознакомьтесь со статьей в Википедии об арифметике с фиксированной запятой .

Полученные результаты

Я протестировал модуль на трех разных типах оборудования: высокопроизводительный Core i7 MacBook Pro, 1,2 ГГц ARM9 без FPU и вышеупомянутый 240 МГц Sparc. Каждый из трех тестов загружал 1425×1425 JPEG, изменял его размер до 200×200 и сжимал его в новый JPEG. libjpeg-turbo использовался в системах x86 и ARM.

  • Оригинальный copyResampledметод модуля GD .
  • Image :: Scale’s resize_gdmethod. Это прямой порт алгоритма с плавающей точкой.
  • Image :: Scale’s resize_gd_fixed_pointmethod.

На современном процессоре Intel можно ожидать, что версия с плавающей запятой будет такой же быстрой, если не лучше, чем версия с фиксированной запятой. Действительно, это правда, когда метод с плавающей запятой немного превосходит метод с фиксированной запятой. Image :: Scale по-прежнему примерно вдвое быстрее благодаря отсутствию накладных расходов на libgd и использованию предварительного масштабирования JPEG.

В системах без FPU производительность оказалась намного лучше, чем я ожидал. ARM9 заработал в 7,4 раза быстрее, чем GD, и Sparc удалось добиться колоссальных 66 раз быстрее! В этой системе изменение размера изображения теперь занимает полсекунды, тогда как раньше требовалось полминуты.

2,6 ГГц Core i7 MacBook Pro (модель 2012 года)

Высокопроизводительное изменение размера изображения с изображением :: Scale Benchmark 1

метод

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

GD copyResampled

1x

I :: S resize_gd_fixed_point

1.8x

I :: S resize_gd

1.9x

Marvell SheevaPlug 1,2 ГГц ARM9 (без FPU)

Высокопроизводительное изменение размера изображения с помощью Image :: Scale Benchmark 2

 

метод

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

GD copyResampled

1x

I :: S resize_gd_fixed_point

2x

I :: S resize_gd_fixed_point

7.4x

Netgear ReadyNAS Duo (240 МГц Sparc, без FPU)

Высокопроизводительное изменение размера изображения с помощью Image :: Scale Benchmark 3

метод

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

GD copyResampled

1x

I :: S resize_gd

1.1x

I :: S resize_gd_fixed_point

66x

Резюме

Хотя производительность низкоуровневых встроенных процессоров может быть не такой низкой, как это было несколько лет назад, Image :: Scale остается отличным выбором для тех, кто ищет очень простой модуль изменения размера изображений Perl. Он имеет более дружественный API, его легче устанавливать, и он может быть в два раза быстрее GD на современном оборудовании.

связи

  • Image :: Scale доступен на  CPAN , а git-репозиторий можно найти на  GitHub .
  • LibGD
  • GD Perl модуль
  • libjpeg-turbo  —  x86 / ARM-ускоренная замена вставки SIMD для libjpeg, в 2-4 раза быстрее