5.5. Obszary i maski¶
Każda operacja w module image domyślnie dotyka każdego piksela obrazu źródłowego. Jest to najprostsze zachowanie do opisania i właściwe wtedy, gdy zadanie algorytmu rzeczywiście obejmuje całą ramkę – jednolita korekcja koloru, globalny histogram, przebieg kodowania na potrzeby transmisji. W praktyce jednak większość algorytmów chce patrzeć na mniej niż to. Śledzący plamy (blob) tracker obserwujący kolorowy znacznik interesuje się tą częścią sceny, w której znacznik może się pojawić, a nie ścianą za nim. Przebieg czyszczenia morfologicznego jest bezpieczny tylko nad tymi pikselami, które wcześniejszy etap oznaczył jako kandydatów. Detektor twarzy może działać wyłącznie wewnątrz ramki ograniczającej, którą zgrubniejszy detektor już zawęził. Moduł image wspiera taką pracę za pomocą dwóch mechanizmów ograniczających zakres operacji do podzbioru pikseli: prostokątnych obszarów zainteresowania oraz binarnych masek. Składają się one swobodnie, a niemal każda metoda dotykająca pikseli przyjmuje jeden lub drugi – albo oba – jako argument nazwany.
5.5.1. Obszary zainteresowania¶
Obszar zainteresowania to prostokąt pikseli określany przez czteroelementową krotkę (x, y, w, h) wprowadzoną na stronie poświęconej współrzędnym. Około trzydziestu metod w interfejsie przyjmuje argument nazwany roi; gdy jest on obecny, operacja działa wyłącznie na pikselach wewnątrz tego prostokąta i pozostawia resztę obrazu nietkniętą. Gdy roi ma wartość None lub zostanie pominięte, operacja działa na całym obrazie – tak samo, jakby przekazano roi=(0, 0, width, height).
W kodzie słowo kluczowe znajduje się obok pozostałych argumentów, które przyjmuje operacja:
# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))
Pierwszą korzyścią z ROI jest kontrola nad fałszywymi trafieniami. Tracker koloru, który patrzy tylko na stół, nigdy nie zareaguje na przechodzącą obok koszulę; detektor krawędzi, który działa wyłącznie wewnątrz zdefiniowanego obszaru roboczego, nigdy nie zgłosi krawędzi samego mocowania kamery. Zawężenie obszaru przeszukiwania do tej części sceny, która rzeczywiście interesuje algorytm, jest najtańszym usprawnieniem, jakie potok może wprowadzić do własnej niezawodności.
Drugą korzyścią jest potok od zgrubnego do dokładnego. Obiekty wyników wykrywania – blob, rect, apriltag i tak dalej – udostępniają swoje ramki ograniczające jako tę samą czteroelementową krotkę (x, y, w, h), którą przyjmuje roi. Dzięki temu zgrubny pierwszy etap może zwrócić ramkę ograniczającą, ramka trafia bezpośrednio do roi kolejnego etapu, a drugi etap działa na węższym obszarze. Każde kolejne zawężenie zarówno przyspiesza następny etap, jak i czyni jego wyniki bardziej niezawodnymi, ponieważ przestrzeń przeszukiwania została już przefiltrowana.
5.5.2. Maski binarne¶
Prostokąt jest właściwą formą, gdy obszar zainteresowania jest wyrównany do osi. Gdy tak nie jest – obszar zakrzywiony, niewypukły, piksele, które jakiś wcześniejszy etap sklasyfikował jako „dopasowania” – operacji trzeba zamiast tego nakazać ograniczenie do dowolnego wzoru pikseli. Mechanizmem do tego jest maska binarna: oddzielny Image o tych samych wymiarach co źródło, używany jako przełącznik włącz/wyłącz dla każdego piksela. Niezerowy piksel w masce mówi „uwzględnij odpowiadający piksel źródłowy”; piksel zerowy mówi „pozostaw piksel źródłowy w spokoju”.
Maska to zwykle obraz BINARY – format jednego bitu na piksel, który istnieje dokładnie w tym celu – ale zadziała każdy obraz jednokanałowy, ponieważ konsument traktuje każdą wartość niezerową jako włączoną.
Metody filtrowania, progowania i arytmetyczne przyjmują argument nazwany mask. Forma jest na każdej z nich taka sama: oddzielnie zaalokowany obraz binarny o tych samych wymiarach co źródło, przekazany dalej.
ROI i maski składają się. Przekaż oba, a operacja zadziała tylko na pikselach, które są wewnątrz ROI i włączone w masce. Te dwa mechanizmy dają kodowi aplikacji niezależne dźwignie – jedną dla prostokątnego obszaru zainteresowania, drugą dla dowolnego wzoru w jego obrębie – bez wymuszania, by którakolwiek z form dziedziczyła ograniczenia od drugiej.
ROI ogranicza operację do prostokąta wyrównanego do osi. Maska zawęża ją dalej do dowolnego wzoru pikseli. Te dwa mechanizmy się składają: modyfikowane są tylko piksele wewnątrz ROI i włączone w masce.¶
5.5.3. Budowanie masek¶
Trzy metody Image budują w miejscu typowe geometrie masek, zerując piksele poza wybranym obszarem:
mask_rectangle()zachowuje prostokąt.mask_circle()zachowuje okrąg.mask_ellipse()zachowuje elipsę.
Każda przyjmuje (x, y, w, h) (dla prostokąta i elipsy) lub (x, y, radius) (dla okręgu). Wywołanie którejkolwiek z nich bez argumentów wyśrodkowuje geometrię i dobiera jej rozmiar tak, aby wypełniła obraz, co jest formą, po którą aplikacja sięga, gdy celem jest prosty owal lub okrąg na cały obraz, który nie ukrywa niczego poza narożnikami.
mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear() # start from all zeros
mask.mask_ellipse() # centred, full-size oval
Interesujące maski rzadko pochodzą wyłącznie z metod mask_*. Pochodzą one z wcześniejszych etapów potoku: przebieg progowania tworzy obraz binarny, którego niezerowe piksele oznaczają dopasowania, dokładnie we właściwej formie do podania do argumentu mask= kolejnego etapu. Przebieg czyszczenia morfologicznego udoskonala tę maskę bez zmiany jej formy. Cokolwiek, co kończy jako obraz jednokanałowy, jest samo w sobie prawidłową maską.
5.5.4. Jak operacje modyfikują obraz¶
Wzorzec widoczny w każdym fragmencie kodu na ostatnich kilku stronach – operacja zwracająca ten sam img na potrzeby łańcuchowania – warto wydobyć jawnie, aby nie trzeba było go powtarzać za każdym razem, gdy wprowadzana jest nowa metoda. W interfejsie Image występują trzy rodziny metod, z których każda traktuje obraz źródłowy inaczej:
Metody operujące modyfikują piksele źródła w miejscu i zwracają ten sam obraz na potrzeby łańcuchowania. Rodziny metod rysowania, arytmetycznych, progowania i filtrów zachowują się w ten sposób.
img.gaussian(1)rozmywaimgi zwraca ten samimg; ponowne przypisanie –img = img.gaussian(1)– jest nieszkodliwe, ale niepotrzebne.Metody konwersji domyślnie działają w miejscu, tak samo jak metody operujące, ale przyjmują
copy=Trueicopy_to_fb=True, aby zaalokować oddzielny obraz wynikowy, gdy źródło musi zostać zachowane. Konwersje formatów oraz kopie geometryczne to główni członkowie tej rodziny.Metody inspekcji odczytują piksele i zwracają obiekt wyniku – listę wykrytych cech, histogram, zestaw statystyk – w ogóle nie modyfikując obrazu źródłowego.
Ten podział na trzy rodzaje jest spójny w całym interfejsie. Wiedza o tym, do której rodziny należy dana metoda, mówi aplikacji, czego oczekiwać po wywołaniu: czy piksele źródła pozostaną nienaruszone, czy zostanie zaalokowany oddzielny obraz wynikowy oraz czy wartością zwracaną jest samo źródło, czy coś innego.