Pillow 2.7 - Істотні поліпшення якості і продуктивності

Першого січня 2015 року, за розкладом, вийшла нова версія бібліотеки для роботи із зображеннями Pillow 2.7. І так як багато зміни в ній були зроблені командою Uploadcare, ми раді представити вам розширену версію нотаток про релізі цієї версії.

Для початку згадаємо, з чого все почалося. Pillow — дружній форк (як називають його автори) популярної бібліотеки PIL, Python Imaging Library. Остання версія PIL 1.1.7 вийшла в 2009 році і в основному містила виправлення помилок. Спочатку Pillow замислювався як проект тільки з приведення в порядок складання ПІЛЬ, і розробники рекомендував відправляти всі баги, не пов'язані зі складанням, оригінальний 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, було те, що для ресайза з допомогою билинейного і бикубического фільтра використовувався метод афінних перетворень, який використовує одне і те ж кількість пікселів вихідного зображення для формування одного пікселя кінцевого (2x2 пікселя для билинейного, 4x4 для бикубического) і фіксований розмір фільтра. Це призводило до незадовільних результатів для зменшення зображення, практично не відрізнявся від методу найближчого сусіда.

nearestафінна
nearestафінна

Зліва метод найближчого сусіда, праворуч бікубічний фільтр афінних перетворень. Перший зразок — зменшення в 5,8 разів, відмінностей практично немає. Другий — в 1,8 раз, відмінності мінімальні, на різких діагональних лініях видно драбинка.

У теж час, для фільтра
ANTIALIAS
використовувався високоякісний алгоритм на основі згорток, що давало однаково гарний результат як для зменшення, так і збільшення.

Починаючи з Pillow 2.7.0, високоякісний алгоритм на основі пакунок використовується для всіх трьох фільтрів.

афіннаconvolution
афіннаconvolution

Зліва бікубічний фільтр на основі афінних перетворень, праворуч згорток. Згортки безумовно виграють.

Якщо ви використали якісь хитрощі для поліпшення якості при використанні билинейного або бикубического фільтра (наприклад, зменшення зображення за кілька кроків або попереднє розмиття), тепер в них немає необхідності.

Antialias перейменовано в Ланцоша
Нова константа
Image.ЛАНЦОША
була додана взамін
Image.ANTIALIAS
.

Коли метод
ANTIALIAS
був вперше представлений, він був єдиним високоякісним методом, засновані на згортках. І його ім'я відображало цей факт. Тепер, коли всі методи засновані на згортках, вони всі стали «згладжують». А справжня назва фільтру, яке використовувалося раніше для цієї константи — фільтр Ланцоша.

Само собою, стара константа залишена для зворотної сумісності і є псевдонімом для нової. Гумор для лінгвістів: Antialias is alias now.

Якість фільтра Ланцоша при збільшенні
Як не дивно, з якістю звірок теж було не все гаразд. У попередніх версіях був баг, через який якість фільтра Ланцоша при збільшенні було практично таким же, як у фільтра
BILINEAR
. Цей баг був виправлений.

beforeafter
beforeafter

Зліва результат збільшення в 4,3 рази попередньої версії, праворуч — Pillow 2.7.0. Картинки ліворуч одночасно більш розмиті і пикселизированные.

Якість бикубического фільтра при збільшенні
Бікубічний фільтр, реалізований для афінних перетворень, давав різку, злегка пикселизованную картинку при збільшенні. Бікубічний фільтр, реалізований для згорток трохи м'якше.

beforeafter
beforeafter

Зліва результат збільшення в 4,3 рази попередньої версії, праворуч — Pillow 2.7.0. Картинки ліворуч більш пикселизированные (мають більш відчутні межі пікселів). У той же час, діагональні лінії на першому прикладі більш чіткі і менш схильні до ефекту драбинки. І те й інше — вплив параметра a бикубическом рівнянні. Уникнути обох ефектів можна тільки за допомогою більш якісного фільтра Ланцоша.

Продуктивність ресайза
У загальному випадку згортки — більш витратний алгоритм для зменшення, тому що на відміну від афінних перетворень, він враховує всі пікселі вихідного зображення. З-за цього чиста продуктивність билинейного і бикубического фільтрів може бути нижче, ніж раніше. З іншого боку, якщо ви до цього були задоволені билинейным і бикубическим фільтрами, які дають якість для зменшення, схоже з найближчим сусідом, можливо вам варто подумати над використанням
NEAREST
фільтра. Це суттєво збільшить продуктивність.

У той же час, одна з істотних поліпшень Pillow 2.7.0 в тому, що продуктивність згорток для зменшення була збільшена в середньому в 2 рази в порівнянні з попередньою версією і навіть порівняно з ImageMagick. Продуктивність при збільшенні реалізації на згортках для фільтра
BILINEAR
виявилася швидше в півтора рази, для
BICUBIC
в чотири, а для
ЛАНЦОША
залишилася на тому ж рівні.

Т. к. швидше за все ви не використовували в своєму додатку нічого, крім
ЛАНЦОША
(колишній
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 extended by box filtering від Mathematical Image Analysis Group. Так як реалізація
ImageFilter.UnsharpMask
базується на Гаусовому розмиття, все що описано в цій секції, також застосовується і до неї.

Радіус розмиття
У попередніх версіях Pillow була помилка, із-за якої радіус розмиття (стандартне відхилення Гауссианы) насправді ставив його діаметр. Тому, наприклад, щоб розмити зображення на радіус 5, потрібно було вказувати значення 10. Помилка була виправлена, і тепер значення радіуса інтерпретується так само, як у всьому іншому програмному забезпеченні.

Якщо до цього ви використовували гаусівських розподілу розмиття з певним радіусом, вам потрібно поділити його значення на два.

Продуктивність розмиття
Час обчислення бокс-фільтра постійно щодо його радіуса і залежить тільки від розмірів вхідного зображення. Т. к. нова реалізація Гауссового розмиття заснована на бокс-фільтрі, її обчислення також не залежить від радіуса розмиття.

Для радіуса в 1 піксель нова реалізація працює 5 разів швидше, для радіуса 10 — у 18 разів, для радіуса 50 вже в 85 разів. Ваш дизайнер, який малює інтерфейси в стилі iOS 8, повинен бути задоволений.

Якість розмиття
Теоретично при Гаусовому розмиття в обчисленні кожної точки кінцевого зображення повинні брати участь всі точки вихідного з певними коефіцієнтами. На практиці коефіцієнти точок далі 3×стандартне відхилення настільки малі, що враховувати їх немає сенсу.

Попередня реалізація враховувала тільки пікселі в радіусі 2×стандартне відхилення для кожного кінцевого пікселя. Це було недостатньо, тому якість було гірше в порівнянні з іншими реалізаціями Гаусового розмивання.

Незважаючи на те, що нова реалізація є лише математичної апроксимацією, вона не містить такого бага.

beforeafter
beforeafter

Зліва результат розмиття з радіусом 5 до попередньої версії (з урахуванням бага з подвоєнням радіуса), праворуч у нової. Зліва видно різкі межі об'єктів.



Всі ці зміни вже працюють на наших серверах. Завдяки їм ми підвищили якість і швидкість API для обробки зображень на льоту. Також ми реалізували операцію швидкого блюра. Але це ще не все. Ми готуємо наступний великий крок для Pillow, про який ми оголосимо трохи пізніше.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.