[Перевод] Pillow 2.7 — Существенные улучшения качества и производительности
Первого января 2015 года, по расписанию, вышла новая версия библиотеки для работы с изображениями Pillow 2.7. Так как многие изменения в ней были сделаны командой Uploadcare, мы рады представить вам расширенную версию заметок о релизе этой версии.Для начала вспомним, с чего все началось. Pillow — дружественный форк (как называют его авторы) популярной библиотеки PIL, Python Imaging Library. Последняя версия PIL 1.1.7 вышла в 2009 году и в основном содержала исправления ошибок. Изначально Pillow задумывался как проект только по приведению в порядок сборки PIL, и разработчики рекомендовал отправлять все баги, не связанные со сборкой, в оригинальный PIL. Но время шло, PIL стремительно устаревала, багов не уменьшалось, тут еще Python 3 маячил на горизонте. Поэтому с версией Pillow 2.0 все изменилось. «Pillow 2.0.0 добавляет поддержку Python 3 и включает много багфиксов со всего интернета» гласит описание проекта на PyPI. И с тех пор понеслось. Каждые три месяца выходили версии с огромных количеством багфиксов и другими улучшениями от различных разработчиков. Самое значительное нововведение за это время было, пожалуй, поддержка форматов WebP и JPEG2000. Теперь пришло время следующего большого шага.
Фильтры ресайза изображенийФункции ресайза изображений Image.resize () и Image.thumbnail () в качестве одного из аргументов принимают resample — фильтр, использующийся для ресайза. Его возможные значения: NEAREST, BILINEAR, BICUBIC и ANTIALIAS. Поведение практически каждого из них изменилось в новой версии.Уменьшение изображения с билинейным и бикубическим фильтрами Одной из проблем в PIL, а потом и в Pillow, было то, что для ресайза с помощью билинейного и бикубического фильтра использовался метод аффинных преобразований, который использует одно и то же количество пикселей исходного изображения для формирования одного пикселя конечного (2×2 пикселя для билинейного, 4×4 для бикубического) и фиксированный размер фильтра. Это приводило к неудовлетворительным результатам для уменьшения изображения, практически не отличавшимся от метода ближайшего соседа.
Слева метод ближайшего соседа, справа бикубический фильтр аффинных преобразований. Первый образец — уменьшение в 5,8 раз, различий практически нет. Второй — в 1,8 раз, отличия минимальные, на резких диагональных линиях видна лесенка.
В тоже время, для фильтра ANTIALIAS использовался высококачественный алгоритм на основе сверток, что давало одинаково хороший результат как для уменьшения, так и для увеличения.
Начиная с Pillow 2.7.0, высококачественный алгоритм на основе сверток используется для всех трех фильтров.
Слева бикубический фильтр на основе аффинных преобразований, справа сверток. Свертки определенно выигрывают.
Если до того вы использовали какие-то ухищрения для улучшения качества при использовании билинейного или бикубического фильтра (например, уменьшение изображения за несколько шагов или предварительное размытие), теперь в них нет необходимости.
Antialias переименован в Lanczos Новая константа Image.LANCZOS была добавлена взамен Image.ANTIALIAS.Когда метод ANTIALIAS был впервые представлен, он был единственным высококачественным методом, основаны на свертках. И его имя отражало этот факт. Теперь, когда все методы основаны на свертках, они все стали «сглаживающими». А настоящее название фильтра, которое использовалось раньше для этой константы — фильтр Ланцоша.
Само собой, старая константа оставлена для обратной совместимости и является псевдонимом для новой. Юмор для лингвистов: Antialias is alias now.
Качество фильтра Ланцоша при увеличении Как ни странно, с качеством сверок тоже было не все в порядке. В предыдущих версиях был баг, из-за которого качество фильтра Ланцоша при увеличении было практически таким же, как у фильтра BILINEAR. Этот баг был исправлен.
Слева результат увеличения в 4,3 раза предыдущей версии, справа — Pillow 2.7.0. Картинки слева одновременно более размытые и пикселизированные.
Качество бикубического фильтра при увеличении Бикубический фильтр, реализованный для аффинных преобразований, давал резкую, слегка пикселизованную картинку при увеличении. Бикубический фильтр, реализованный для сверток немного мягче.
Слева результат увеличения в 4,3 раза предыдущей версии, справа — Pillow 2.7.0. Картинки слева более пикселизированные (имеют более ощутимые границы пикселей). В то же время, диагональные линии на первом примере более четкие и менее подвержены эффекту лесенки. И то и другое — влияние параметра «a» в бикубическом уравнении. Избежать обоих эффектов можно только с помощью более качественного фильтра Ланцоша.
Производительность ресайза В общем случае свертки — более затратный алгоритм для уменьшения, потому что в отличии от аффинных преобразований, он учитывает все пиксели исходного изображения. Из-за этого чистая производительность билинейного и бикубического фильтров может быть ниже, чем раньше. С другой стороны, если вы до этого были удовлетворены билинейным и бикубическим фильтрами, которые дают качество для уменьшения, схожее с ближайшим соседом, возможно вам стоит подумать над использованием самого NEAREST фильтра. Это существенно увеличит производительность.В то же время, одно из существенных улучшений Pillow 2.7.0 в том, что производительность сверток для уменьшения была увеличена в среднем в 2 раза по сравнению с предыдущей версией и даже по сравнению с ImageMagick. Производительность при увеличении реализации на свертках для фильтра BILINEAR оказалась быстрее в полтора раза, для BICUBIC в четыре, а для LANCZOS осталась на том же уровне.
Т.к. скорее всего вы не использовали в своем приложении ничего, кроме LANCZOS (бывший ANTIALIAS), то производительность при уменьшении для вас должна увеличиться в среднем в два раза. Если, например, использование Ланцоша для вас было вынужденной мерой из-за низкого качества остальных фильтров, то теперь вы можете перейти, например, на билинейный фильтр. Это увеличит производительность еще примерно в 2 раза для уменьшения и примерно на 30% для увеличения.
Фильтр по умолчанию для Image.thumbnail () В Pillow 2.5 фильтр по умолчанию для Image.thumbnail () был изменен с NEAREST на ANTIALIAS. Этот фильтр был выбран по причинам неоднократно озвученным выше — низкое качество остальных фильтров. В Pillow 2.7.0 фильтр по умолчанию вновь изменен, в этот раз на BICUBIC, потому что он немного быстрее. На самом деле Ланцош не дает каких-либо преимуществ после использования метода Image.draft () внутри Image.thumbnail (), который уменьшает изображение с помощью библиотеки libjpeg и использует для этого суперсемплинг, а не свертки.Транспонирование изображений Новый метод Image.TRANSPOSE был добавлен для функции Image.transpose () в дополнение к уже существующим FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, ROTATE_270. TRANSPOSE — это алгебраическое транспонирование, т.е. отражение изображение относительно её основной диагонали.Производительность методов ROTATE_90, ROTATE_270 и TRANSPOSE была существенно увеличена для больших изображений, не помещающихся в кэш процессора.
Эти три метода объединяет то, что в них пиксели берутся из строк, а помещаются в столбцы. Такой шаблон доступа к памяти оказывается очень не эффективным для больших изображений, потому что данные успевают вытесниться из кэша процессора за один проход и их приходится заново загружать из памяти для следующего прохода.
В новой версии изображение разбивается на логические квадраты размером в 128×128 пикселей и операции над пикселями производится последовательно внутри каждого квадрата. Это позволяет существенно сократить дистанцию, которую проходит процессор на каждой строке, в результате чего данные не успевают вытесниться из кэша (память, необходимая для одного квадрата равна 64Кб).
Гауссово размытие и контурная резкость Реализация ImageFilter.GaussianBlur была заменена на последовательное применение бокс-фильтров. Новая реализация основана на работе Theoretical foundations of Gaussian convolution by extended box filtering от Mathematical Image Analysis Group. Так как реализация ImageFilter.UnsharpMask базируется на Гауссовом размытии, все что описано в этой секции, также применимо и к ней.Радиус размытия В предыдущих версиях Pillow была ошибка, из-за которой радиус размытия (стандартное отклонение Гауссианы) на самом деле задавал его диаметр. Поэтому, например, чтобы размыть изображение на радиус 5, нужно было указывать значение 10. Ошибка была исправлена, и теперь значение радиуса интерпретируется так же, как во всем остальном программном обеспечении.Если до этого вы использовали Гауссово размытие с определенным радиусом, вам нужно поделить его значение на два.
Производительность размытия Время вычисления бокс-фильтра постоянно относительно его радиуса и зависит только от размеров входного изображения. Т.к. новая реализация Гауссового размытия основана на бокс-фильтре, её вычисление также не зависит от радиуса размытия.Для радиуса в 1 пиксель новая реализация работает 5 раз быстрее, для радиуса 10 — в 18 раз, для радиуса 50 уже в 85 раз. Ваш дизайнер, рисующий интерфейсы в стиле iOS 8, должен быть доволен.
Качество размытия Теоретически при Гауссовом размытии в вычислении каждой точки конечного изображения должны участвовать все точки исходного с определенными коэффициентами. На практике коэффициенты точек дальше 3×стандартное отклонение настолько малы, что учитывать их нет смысла.Предыдущая реализация учитывала только пиксели в радиусе 2×стандартное отклонение для каждого конечного пикселя. Это было недостаточно, поэтому качество было хуже в сравнении с другими реализациями Гауссова размытия.
Несмотря на то, что новая реализация является лишь математической аппроксимацией, она не содержит такого бага.
Слева результат размытия с радиусом 5 в предыдущей версии (с учетом бага с удвоением радиуса), справа в новой. Слева видны резкие границы объектов.
Все эти изменения уже работают на наших серверах. Благодаря им мы повысили качество и скорость API для обработки картинок на лету. Также мы реализовали операцию быстрого блюра. Но это еще не все. Мы готовим следующий большой шаг для Pillow, о котором мы объявим чуть позже.