5.21. Skalowanie, odbicie i przycinanie

Wszystkie poprzednie podrozdziały operowały na pikselach w tych samych pozycjach, w których się zaczynały. Rodzina transformacji to zmienia. Skalowanie przenosi każdy piksel wejściowy do innej pozycji wyjściowej, możliwe że do kilku pozycji wyjściowych naraz (przy powiększaniu) albo do pozycji współdzielonej z kilkoma innymi pikselami wejściowymi (przy pomniejszaniu). Odbicie i obrót robią to samo poprzez inne odwzorowanie. Przycinanie zachowuje prostokątny podzbiór pikseli wejściowych, a resztę odrzuca.

Moduł image udostępnia tę rodzinę poprzez trzy metody, które dzielą większość swoich argumentów i większość zachowania:

  • copy() – tworzy kopię obrazu, ewentualnie przeskalowaną, przyciętą lub przeorientowaną.

  • crop() – ta sama operacja co copy, ale z założeniem, że aplikacja zamierza wybrać podprostokąt ze źródła.

  • scale() – znów to samo, z założeniem, że aplikacja zamierza zmienić rozmiar wyniku.

Te trzy dzielą te same argumenty i tę samą maszynerię transformacji; różnica polega na tym, gdzie domyślnie ląduje wynik. copy() tworzy nowy obraz, podczas gdy crop() i scale() modyfikują źródło w miejscu.

5.21.1. Wspólne argumenty

Pojedyncze wywołanie łączy dowolną kombinację skalowania, przycinania, orientacji i ekstrakcji kanału, o którą prosi aplikacja:

x_scale i y_scale skalują wejście wzdłuż osi poziomej i pionowej niezależnie. Oba domyślnie wynoszą 1.0 (brak skalowania). Różne wartości dla każdej z nich dają skalowanie niejednorodne – na przykład ramkę rozciągniętą dwa razy szerszą niż wyższą.

roi ogranicza wejście do prostokąta obrazu źródłowego, przepuszczając przez resztę transformacji tylko te piksele. To jest część operacji odpowiadająca „przycinaniu”: przekaż roi, aby wyodrębnić podobszar.

hint to pole bitowe flag, które wybiera metodę interpolacji oraz ewentualne odbicia orientacji. Wiele flag łączy się poprzez bitowe OR (hint=image.BILINEAR | image.HMIRROR). Flagi dzielą się na dwie grupy – rodzinę interpolacji i rodzinę orientacji – które nie mają ze sobą nic wspólnego, ale dzielą to samo pole bitowe.

rgb_channel wybiera pojedynczy kanał źródła RGB565. 0 oznacza czerwony, 1 oznacza zielony, 2 oznacza niebieski; wynik powstaje jako obraz w skali szarości zawierający tylko ten kanał. Przydatne na przykład do progowania wyłącznie na kanale czerwonym.

color_palette i alpha_palette przemapowują wartości pikseli przez tablicę wyszukiwania na wyjściu, w taki sam sposób, jak robią to metody konwersji to_rainbow() i to_ironbow().

copy=True i copy_to_fb=True stosują tę samą konwencję, której używa każda inna metoda tworząca wynik – domyślnie w miejscu, copy=True alokuje osobny wynik, a copy_to_fb=True umieszcza wynik w buforze ramki na potrzeby podglądu w IDE.

5.21.2. Interpolacja: AREA, BILINEAR, BICUBIC

Gdy skalowanie przenosi każdy piksel wyjściowy do pozycji, która nie pokrywa się z żadnym pojedynczym pikselem wejściowym, metoda musi wybrać, jaką wartość zapisać. Sterują tym trzy flagi:

image.BILINEAR interpoluje między czterema najbliższymi pikselami wejściowymi, ważonymi ich odległością od pozycji wyjściowej. Wynik jest gładszy niż przy metodzie najbliższego sąsiada, bez widocznych schodków na liniach ukośnych, ale dodatkowe obliczenia kosztują około czterokrotnie więcej niż przebieg metodą najbliższego sąsiada. Właściwy wybór do większości prac z powiększaniem oraz dla dowolnego niecałkowitego współczynnika skali.

image.BICUBIC interpoluje między szesnastoma najbliższymi pikselami wejściowymi przy użyciu krzywej sześciennej, co daje jeszcze gładsze wyniki kosztem znów większej liczby obliczeń. Najlepsza jakość dla aplikacji wrażliwych na koszt, które tego potrzebują; rzadko wart dodatkowych obliczeń przy ramkach na żywo, które IDE i tak tylko wyświetli.

image.AREA uśrednia każdy piksel wejściowy, który mieści się w obrysie piksela wyjściowego – właściwy algorytm do pomniejszania. Interpolacja dwuliniowa i dwusześcienna to interpolatory: szacują wartość pomiędzy pikselami źródłowymi, czego potrzebuje powiększanie, ale przy pomniejszaniu każdy piksel wyjściowy pokrywa wiele pikseli źródłowych, a interpolator czyta tylko kilka najbliższych – detal, który pomija, powraca jako aliasing. image.AREA zamiast tego włącza każdy pokryty piksel do średniej.

Domyślnym algorytmem skalowania bez żadnej podpowiedzi jest metoda najbliższego sąsiada, która jest najtańsza i właściwa, gdy źródło ma już rozdzielczość pikselową odpowiadającą docelowej.

5.21.3. Orientacja: odbicia i obroty

Flagi orientacji to niewielki zestaw transformacji logicznych, które swobodnie składają się ze sobą oraz z flagami interpolacji:

  • image.VFLIP odbija obraz w pionie (góra staje się dołem).

  • image.HMIRROR odbija go w poziomie (lewa staje się prawą).

  • image.TRANSPOSE zamienia osie x i y (wiersze stają się kolumnami).

Większość obrotów powstaje ze składania tych trzech. Moduł udostępnia również nazwane skróty:

  • image.ROTATE_90 (= VFLIP | TRANSPOSE)

  • image.ROTATE_180 (= HMIRROR | VFLIP)

  • image.ROTATE_270 (= HMIRROR | TRANSPOSE)

W kodzie:

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

5.21.4. Obsługa proporcji

Gdy proporcje źródła nie pasują do prostokąta, w który jest rysowane, trzy flagi decydują, co zrobić z tą rozbieżnością:

image.SCALE_ASPECT_KEEP zachowuje proporcje źródła i dodaje pasy do wyniku (letterbox) – źródło jest skalowane, aż zmieści się wewnątrz celu, a pozostałą część celu wypełniają puste (zerowe) piksele. Właściwy wybór, gdy zachowanie niezniekształconego źródła ma większe znaczenie niż wypełnienie całego wyjścia.

image.SCALE_ASPECT_EXPAND zachowuje proporcje źródła i przycina je – źródło jest skalowane, aż wypełni cel, a części wykraczające poza cel zostają odcięte. Właściwy wybór, gdy wypełnienie całego wyjścia ma większe znaczenie niż zobaczenie każdej części źródła.

image.SCALE_ASPECT_IGNORE ignoruje proporcje i rozciąga źródło tak, aby wypełnić cel, akceptując wszelkie wprowadzane przez to zniekształcenia. Właściwy wybór, gdy aplikacja już uwzględniła zniekształcenie – na przykład gdy wymiary celu nie są w rzeczywistości prostokątem tej samej sceny.

Domyślnie (gdy nie ustawiono żadnej flagi proporcji) działa tak samo jak SCALE_ASPECT_IGNORE: rozciągnij, aby wypełnić. Aplikacje, którym zależy na proporcjach, jawnie określają jedną z tych trzech opcji.

5.21.5. Kiedy sięgnąć po którą

Większość zmian rozmiaru używa scale() z parą x_scale / y_scale oraz podpowiedzią interpolacji:

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

Większość obrotów używa tego samego wywołania z hint=image.ROTATE_90 lub podobnym.

Przycinanie używa crop() z niedomyślnym roi:

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

Gdy źródło musi przetrwać operację – przy przechwytywaniu ramki odniesienia, przy robieniu miniatury ramki, która zaraz zostanie przetworzona destrukcyjnie – copy() tworzy wynik jako nowy obraz i pozostawia źródło nienaruszone:

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

Ta domyślność to prawdziwa różnica kryjąca się za tymi trzema nazwami: scale i crop transformują w miejscu, copy alokuje. Słowa kluczowe rozmieszczenia wyniku wypełniają tę lukę: copy=True przy scale lub crop alokuje wynik jako osobny bufor sterty zamiast nadpisywać źródło, a copy_to_fb=True przy dowolnej z tych trzech umieszcza go w buforze ramki na potrzeby podglądu w IDE.