5.21. Масштабування, відображення та обрізання

Попередні підрозділи працювали з пікселями на тих самих позиціях, де вони знаходилися спочатку. Сімейство перетворень змінює це. Масштабування надсилає кожен вхідний піксель на іншу вихідну позицію – можливо, на кілька вихідних позицій одночасно (при збільшенні) або на позицію, спільну для кількох вхідних пікселів (при зменшенні). Відображення і обертання роблять те саме через інше відображення. Обрізання зберігає прямокутну підмножину вхідних пікселів і відкидає решту.

Модуль image надає цю функціональність через три методи, які поділяють більшість аргументів і більшість поведінки:

  • copy() – створює копію зображення, можливо масштабовану, обрізану або переорієнтовану.

  • crop() – та сама операція, що й copy, але з очікуванням, що застосунок вибере підпрямокутник із джерела.

  • scale() – те саме, з очікуванням, що застосунок змінить розмір результату.

Усі три мають однакові аргументи і однакову машинерію перетворень; різниця полягає в тому, куди за замовчуванням потрапляє результат. copy() створює нове зображення, тоді як crop() та scale() змінюють джерело на місці.

5.21.1. Спільні аргументи

Один виклик поєднує будь-яку комбінацію масштабування, обрізання, орієнтації та виокремлення каналів, яку запитує застосунок:

x_scale та y_scale масштабують вхідні дані вздовж горизонтальної та вертикальної осей незалежно. Обидва за замовчуванням мають значення 1.0 (без масштабування). Різні значення для кожного дають нерівномірне масштабування – наприклад, кадр, розтягнутий удвічі ширше своєї висоти.

roi обмежує вхідні дані прямокутником вихідного зображення, пропускаючи через решту перетворення лише ці пікселі. Це частина операції «обрізання»: передайте roi, щоб виокремити підобласть.

hint – це бітове поле прапорів, яке вибирає метод інтерполяції та будь-які орієнтаційні відображення. Кілька прапорів поєднуються через побітове АБО (hint=image.BILINEAR | image.HMIRROR). Прапори розподіляються на дві групи – сімейство інтерполяції і сімейство орієнтації – які не пов’язані між собою, але використовують те саме бітове поле.

rgb_channel вибирає один канал джерела RGB565. 0 означає червоний, 1 – зелений, 2 – синій; результат виходить як зображення у відтінках сірого, що містить лише цей канал. Корисно, наприклад, для порогової обробки лише червоного каналу.

color_palette та alpha_palette переспрямовують значення пікселів через таблицю пошуку на виході, так само, як методи конвертування to_rainbow() та to_ironbow().

copy=True та copy_to_fb=True дотримуються тієї самої угоди, яку використовує кожен інший метод, що виробляє результат – за замовчуванням на місці, copy=True виділяє окремий результат, copy_to_fb=True розміщує результат у кадровому буфері для попереднього перегляду в IDE.

5.21.2. Інтерполяція: AREA, BILINEAR, BICUBIC

Коли масштабування надсилає кожен вихідний піксель на позицію, яка не збігається з жодним окремим вхідним пікселем, метод має вирішити, яке значення записати. Три прапори контролюють цей процес:

image.BILINEAR інтерполює між чотирма найближчими вхідними пікселями, зважуючи за відстанню від вихідної позиції. Результат плавніший, ніж при найближчому сусідові, без видимих зубців на діагональних лініях, але додаткові обчислення коштують приблизно вчетверо більше, ніж прохід найближчого сусіда. Правильний вибір для більшості робіт із збільшенням і для будь-якого нецілочисельного масштабного коефіцієнта.

image.BICUBIC інтерполює між шістнадцятьма найближчими вхідними пікселями за допомогою кубічної кривої, що дає ще плавніші результати за рахунок більших обчислень. Найкраща якість для чутливих до ресурсів застосунків, яким вона потрібна; рідко варте додаткових обчислень для живих кадрів, які IDE лише відображатиме.

image.AREA усереднює кожен вхідний піксель, що потрапляє у відбиток вихідного пікселя – правильний алгоритм для зменшення масштабу. Білінійна і бікубічна інтерполяції оцінюють значення між пікселями джерела, що саме й потрібно при збільшенні; але при зменшенні кожен вихідний піксель охоплює багато пікселів джерела, а інтерполятор зчитує лише кілька найближчих – пропущені деталі повертаються як артефакти. image.AREA включає всі охоплені пікселі в середнє значення.

Алгоритм масштабування за замовчуванням без підказок – найближчий сусід, який є найдешевшим і правильним вибором, коли джерело вже має роздільну здатність призначення.

5.21.3. Орієнтація: відображення та обертання

Прапори орієнтації – це невеликий набір булевих перетворень, які вільно поєднуються між собою та з прапорами інтерполяції:

  • image.VFLIP відображає зображення вертикально (верх стає низом).

  • image.HMIRROR відображає його горизонтально (ліво стає правом).

  • image.TRANSPOSE міняє місцями осі x і y (рядки стають стовпцями).

Більшість обертань отримують поєднанням цих трьох. Модуль також надає іменовані скорочення:

  • image.ROTATE_90 (= VFLIP | TRANSPOSE)

  • image.ROTATE_180 (= HMIRROR | VFLIP)

  • image.ROTATE_270 (= HMIRROR | TRANSPOSE)

У коді:

img.copy(hint=image.ROTATE_90, copy_to_fb=True)

5.21.4. Обробка пропорцій

Коли пропорції джерела не збігаються з прямокутником, у який воно відмальовується, три прапори вирішують, що робити з цією розбіжністю:

image.SCALE_ASPECT_KEEP зберігає пропорції джерела і додає рамки до результату – джерело масштабується до розміру всередині призначення, а порожні (нульові) пікселі заповнюють решту призначення. Правильний вибір, коли збереження джерела без спотворень важливіше за заповнення всього виходу.

image.SCALE_ASPECT_EXPAND зберігає пропорції джерела і обрізає його – джерело масштабується до заповнення призначення, а частини, що виходять за межі призначення, відрізаються. Правильний вибір, коли заповнення всього виходу важливіше за відображення кожної частини джерела.

image.SCALE_ASPECT_IGNORE ігнорує пропорції і розтягує джерело до заповнення призначення, погоджуючись на будь-яке спотворення. Правильний вибір, коли застосунок уже врахував спотворення – наприклад, коли розміри призначення насправді не є прямокутником тієї самої сцени.

За замовчуванням (прапор пропорцій не встановлено) – те саме, що й SCALE_ASPECT_IGNORE: розтягнути до заповнення. Застосунки, що дбають про пропорції, явно вказують один із трьох прапорів.

5.21.5. Коли яким методом користуватися

Більшість змін розміру використовують scale() з парою x_scale / y_scale та підказкою інтерполяції:

img.scale(x_scale=0.5, y_scale=0.5, hint=image.AREA)

Більшість обертань використовують той самий виклик з hint=image.ROTATE_90 або подібним.

Для обрізання використовують crop() з нестандартним roi:

img.crop(roi=(40, 30, 200, 150))

Коли джерело має вижити після операції – захоплення опорного кадру, створення мініатюри кадру, який ось-ось буде деструктивно оброблений – copy() повертає результат як нове зображення і залишає джерело незайманим:

thumbnail = img.copy(x_scale=0.25, y_scale=0.25, hint=image.AREA)

Цей стандартний підхід і є реальною різницею між трьома іменами: scale і crop перетворюють на місці, copy виділяє пам’ять. Ключові слова розміщення результату усувають цю різницю: copy=True у scale або crop виділяє результат як окремий буфер у купі замість перезапису джерела, а copy_to_fb=True у будь-якому з трьох розміщує його у кадровому буфері для попереднього перегляду в IDE.