5.13. Filtry liniowe i sąsiedztwa

Operacje matematyczne na pikselach z wcześniejszej części rozdziału łączyły dwa obrazy punkt po punkcie. Filtry wykonują pokrewną pracę w inny sposób: obliczają wartość każdego piksela wyjściowego na podstawie małego sąsiedztwa pikseli wejściowych otaczających odpowiadającą mu pozycję. Wynik w (x, y) to pewna wartość statystyczna – średnia, mediana, wartość najczęściej występująca – pikseli wejściowych w małym oknie wyśrodkowanym na (x, y).

Ta drobna zmiana ujęcia – przejście od jednego piksela naraz do okna pikseli naraz – jest tym, co sprawia, że działa cała rodzina przydatnych operacji. Prosta średnia po małym oknie wygładza szum sensora. Mediana po tym samym oknie usuwa pojedyncze błędne piksele bez tak silnego rozmywania krawędzi. Średnia dwustronna nie wygładza w poprzek silnych granic kontrastu, zachowując krawędzie obiektów przy jednoczesnym oczyszczeniu tekstur wewnątrz nich. Sąsiedztwo jest jednostką pracy; wybór statystyki decyduje o tym, co filtr robi.

5.13.1. Rozmiar jądra

Każdy filtr sąsiedztwa przyjmuje parametr size, który ustawia promień okna w pikselach. Samo okno jest kwadratowe i obejmuje (2 * size + 1) pikseli na każdym boku – zatem size=1 oznacza sąsiedztwo 3 na 3, size=2 oznacza 5 na 5, size=3 oznacza 7 na 7 i tak dalej.

Mała siatka obrazu z wyróżnioną podsiatką 3 na 3 reprezentującą sąsiedztwo filtra. Strzałka pokazuje przesuwanie się sąsiedztwa o jeden piksel w prawo. Druga strzałka pokazuje jego przesunięcie w dół do następnego wiersza na końcu bieżącego wiersza. Piksel wyjściowy dla każdej pozycji jest narysowany pod sąsiedztwem, z krótką notką mówiącą, że wynik jest pewną statystyką sąsiedztwa wejściowego.

Sąsiedztwo przesuwa się po obrazie o jeden piksel naraz, od lewego górnego do prawego dolnego rogu. Każdy piksel wyjściowy jest wynikiem zastosowania statystyki filtra do wyśrodkowanego na nim sąsiedztwa wejściowego.

Większe rozmiary oznaczają większe sąsiedztwa, co oznacza gładsze (lub bardziej agresywne) filtrowanie. Koszt rośnie wraz z powierzchnią okna, więc filtr size=3 wykonuje około dziewięciokrotnie więcej pracy na piksel niż filtr size=1. Praktyczną wartością domyślną dla większości prac porządkowych jest size=1 lub size=2; po większe rozmiary sięgaj tylko wtedy, gdy małe sąsiedztwa nie wystarczają do stłumienia cechy, którą aplikacja stara się stłumić.

5.13.2. Filtr średniej

mean() zastępuje każdy piksel średnią arytmetyczną jego sąsiedztwa. Wynik wygładza zmienność piksel-do-piksela w obrębie rozmiaru okna, co czyni go najtańszym sposobem tłumienia szumu sensora: zmienność wysokoczęstotliwościowa uśrednia się, a treść niskoczęstotliwościowa przetrwa.

Kompromisem jest to, że krawędzie i inne ostre cechy również zostają uśrednione. Jasna krawędź, która przed filtrem miała szerokość jednego piksela, po filtrze średniej size=1 ma szerokość dwóch lub trzech pikseli, z jasnością opadającą na zboczach. Dla czystej redukcji szumu na obrazie ubogim w tekstury (czysta ściana, wnętrze kolorowego markera) ten kompromis jest w porządku. Dla zatłoczonej sceny, gdzie krawędzie mają znaczenie, jeden z poniższych filtrów zwykle pasuje lepiej.

img.mean(1)        # 3x3 box average -- fast, gentle smoothing
img.mean(2)        # 5x5 box average -- stronger, slower

5.13.3. Mediana, moda, punkt środkowy

Pozostałe trzy statystyczne filtry sąsiedztwa wymieniają prostą średnią arytmetyczną na coś bardziej odpornego na wartości odstające.

median() zwraca medianę sąsiedztwa – wartość, która ląduje na środku posortowanej listy pikseli okna. Pojedynczy bardzo jasny lub bardzo ciemny piksel w oknie nie wpływa na medianę; staje się po prostu jedną z odrzuconych wartości skrajnych. Praktyczny efekt jest taki, że filtrowanie medianą usuwa pojedyncze błędne piksele oraz szum typu sól i pieprz bez rozmywania krawędzi tak, jak robi to mean. Kosztem jest większa liczba obliczeń na piksel – sortowanie okna jest wolniejsze niż jego uśrednianie – a wynik nie jest ściśle średnią, co czasem ma znaczenie dla dalszych obliczeń.

Parametr percentile (domyślnie 0.5) przesuwa wybraną wartość poza ścisłą medianę. percentile=0.0 zwraca minimum sąsiedztwa, percentile=1.0 maksimum; wartości pośrednie wybierają proporcjonalnie pomiędzy nimi w posortowanym oknie. Daje to median zdolność do uwypuklenia ciemnych lub jasnych części sąsiedztwa bez utraty odporności statystyki pozycyjnej na wartości odstające.

mode() zwraca wartość najczęściej występującą w sąsiedztwie. Przydatne, gdy model szumu brzmi: „większość pikseli jest poprawna, kilka zostało uszkodzonych w różnym stopniu”, gdzie poprawną odpowiedzią jest ta wartość, która pojawia się najczęściej – czego mediana może nie wychwycić, gdy uszkodzone wartości spiętrzają się po jednej stronie posortowanego okna.

midpoint() zwraca ważoną kombinację minimum i maksimum sąsiedztwa – bias=0.5 daje punkt środkowy między nimi, bias=0.0 daje minimum, bias=1.0 daje maksimum. Rzadziej używany niż pozostałe, ale warto o nim wiedzieć, gdy celem jest konkretnie wyodrębnienie ciemnych lub jasnych cech.

5.13.4. Filtr dwustronny – wersja zachowująca krawędzie

bilateral() to filtr sąsiedztwa, który najbardziej warto dobrze zrozumieć. Daje on efekt wygładzania jak mean(), ale z dodatkowym ograniczeniem: im bardziej piksel sąsiedztwa różni się od piksela centralnego, tym mniej liczy się w średniej. Wynik wygładza wnętrze każdego jednolitego obszaru bez przenikania przez krawędzie, które je rozdzielają, co jest dokładnie tym, czego większość aplikacji w rzeczywistości potrzebuje.

Dwa parametry sterują tym, jak agresywnie filtr deprecjonuje piksele:

  • color_sigma decyduje o tym, jak różnica kolorów wpływa na wagę. Mniejsze wartości oznaczają, że filtr jest bardziej rygorystyczny w deprecjonowaniu pikseli różniących się od piksela centralnego.

  • space_sigma decyduje o tym, jak odległość przestrzenna wpływa na wagę. Mniejsze wartości nadają większą wagę pikselom bliskim centrum.

Wartości domyślne (color_sigma=0.1, space_sigma=1.0) są rozsądnymi punktami wyjścia; ich strojenie zwykle sprowadza się do uruchomienia filtra na przykładowej ramce i regulowania, aż krawędzie będą ostre, a wnętrza czyste.

Filtr dwustronny jest droższy niż median() i znacznie droższy niż mean(), więc warto po niego sięgać tylko wtedy, gdy zachowujące krawędzie zachowanie jest tym, czego aplikacja potrzebuje.

5.13.5. Progowanie adaptacyjne

Filtry średniej, mediany, mody i punktu środkowego mają wspólną parę argumentów kluczowych, które zamieniają ich wynik w binarny próg:

  • threshold=True przełącza filtr w tryb progowania.

  • offset=N przesuwa lokalny próg odcięcia o N jednostek przed porównaniem.

Mechanizm bazuje bezpośrednio na zwykłym zachowaniu filtra. Bez threshold=True filtr oblicza swoją statystykę po sąsiedztwie i zapisuje tę statystykę do piksela wyjściowego. Z threshold=True filtr oblicza tę samą statystykę, a następnie porównuje piksel źródłowy w tej samej pozycji ze statystyką powiększoną o offset i zapisuje maksymalną wartość formatu, jeśli źródło jest większe, w przeciwnym razie zero.

Wynikiem jest obraz binarny, którego próg odcięcia przesuwa się wraz z lokalną jasnością w obrębie ramki. Jasne obszary otrzymują wysoki próg odcięcia, ciemne obszary niski próg odcięcia, a piksel pierwszego planu, który jest lokalnie jaśniejszy od swoich sąsiadów, pasuje niezależnie od tego, czy znajduje się w jasnym czy ciemnym obszarze – co jest dokładnie tym zachowaniem, którego pojedynczy globalny próg nie mógłby uzyskać na nierównomiernie oświetlonym obrazie.

img.mean(3, threshold=True, offset=5)

Parametr offset jest miejscem, w którym aplikacja kontroluje, jak rygorystyczny jest test. Mały dodatni offset wymaga, by piksel źródłowy był wyraźnie jaśniejszy od swoich sąsiadów, zanim zostanie uznany za dopasowanie, co tłumi fałszywe trafienia wynikające z szumu sensora kosztem pominięcia słabego pierwszego planu. Mały ujemny offset wychwytuje słaby pierwszy plan kosztem przepuszczenia części szumu. Wybór zależy od tego, co reszta potoku zamierza zrobić z wyjściem binarnym.

Trzy panele obrazu w rzędzie. Pierwszy to wejściowa ramka w skali szarości z gradientem jasności i znakami pierwszego planu rozrzuconymi w jednolitej ciemności. Drugi panel pokazuje zastosowany do niej próg globalny: pierwszy plan jest poprawnie sklasyfikowany po stronie jasnej, ale cała ciemna strona odczytywana jest jako pierwszy plan, ponieważ strona i pierwszy plan oba spadają poniżej progu odcięcia. Trzeci panel pokazuje próg adaptacyjny zastosowany do tego samego wejścia: pierwszy plan jest poprawnie sklasyfikowany w obrębie całej ramki.

Przy nierównomiernym oświetleniu pojedynczy globalny próg nie potrafi opisać pierwszego planu w każdej pozycji. Filtr sąsiedztwa uruchomiony z threshold=True daje próg odcięcia, który przesuwa się wraz z lokalną jasnością i poprawnie klasyfikuje pierwszy plan w obrębie całej ramki.

Rodzina filtrów obsługuje próg adaptacyjny, więc wybór właściwego filtra ma znaczenie: mean() dla najtańszego progu adaptacyjnego, median(), gdy wejście zawiera szum typu sól i pieprz, który filtr powinien odrzucić przed obliczeniem lokalnego progu odcięcia.