5.19. Korekcje tonalne

Korekcje tonalne zmieniają sposób rozłożenia jasności i koloru w przechwyconym obrazie – są to poprawki, które aplikacja stosuje, gdy ramka jest zbyt ciemna, zbyt jasna, zbyt płaska lub przekrzywiona w stronę niewłaściwego koloru.

Korekcje należą do dwóch rodzin: regulacji jasności i kontrastu, które redystrybuują jasność, oraz regulacji koloru, które zmieniają, jaki kolor odczytuje każdy piksel. Obie mają swoje odpowiedniki w ISP sensora, który koryguje każdą ramkę w trakcie jej wczytywania; metody opisane tutaj stosuje się do już przechwyconego Image, po fakcie, w przypadkach, gdy ramka wymaga większej korekcji, niż zapewnił ISP.

5.19.1. Wyrównanie histogramu

Najprostszą operacją rozciągania kontrastu jest wyrównanie histogramu. Idea polega na przemapowaniu wartości pikseli tak, aby histogram wyniku był możliwie jak najbardziej płaski – każda wartość pojawia się mniej więcej równie często. Efektem wizualnym jest to, że obraz o niskim kontraście (taki, którego histogram jest skupiony w wąskim paśmie) staje się obrazem o wysokim kontraście, którego piksele pokrywają cały zakres 0255.

histeq() wykonuje wyrównanie:

img.histeq()

Mechanizm jest bezpośredni. Obliczana jest dystrybuanta (CDF) histogramu źródła; każda wartość wejściowego piksela jest mapowana na swoją pozycję w CDF, przeskalowaną do zakresu wyjściowego. Tam, gdzie piksele były już równomiernie rozłożone, mapowanie jest bliskie tożsamości; tam, gdzie piksele były spiętrzone przy jednej jasności, mapowanie rozkłada je, rozciągając tę jasność na szerszy zakres wartości wyjściowych.

Wynik jest spektakularny w scenach o niskim kontraście – różnica między ciemnym zdjęciem wykonanym w pomieszczeniu a tym samym zdjęciem po histeq jest często różnicą między „nieczytelnym” a „doskonale czytelnym”. Kompromisem jest to, że operacja wzmacnia wszystko, w tym szum sensora. W scenie z prawdziwymi, niskokontrastowymi detalami do odzyskania histeq jest właściwą odpowiedzią; w czystej, dobrze naświetlonej scenie, która po prostu tego nie potrzebuje, histeq wprowadza widoczny szum.

5.19.2. CLAHE: wyrównanie adaptacyjne

Wyrównanie histogramu jest globalne: używa jednej CDF obliczonej z całego obrazu i stosuje ją wszędzie. Działa to na obrazach, których zakres jasności jest mniej więcej jednolity, ale zawodzi w scenach z lokalnymi ciemnymi i jasnymi obszarami – CDF zostaje przeciągnięta w stronę tej strony, która ma więcej pikseli, a przeciwna strona zostaje nadmiernie skorygowana.

Wariantem adaptacyjnym jest Contrast Limited Adaptive Histogram Equalisation, powszechnie nazywany CLAHE. Zamiast jednej globalnej CDF, CLAHE oblicza osobną CDF dla każdego małego kafelka obrazu, wyrównuje każdy kafelek względem jego własnej CDF i zlewa granice kafelków ze sobą. W rezultacie regulacje jasności następują lokalnie – zacieniony narożnik otrzymuje własne wyrównanie, bez przeciągania go w niewłaściwą stronę przez jasny narożnik.

Flaga adaptive=True przełącza histeq() w tryb CLAHE:

img.histeq(adaptive=True, clip_limit=10)

Parametr clip_limit jest tą częścią CLAHE, do której odnosi się „contrast limited” w nazwie. Lokalne wyrównanie może nadmiernie wzmacniać szum w płaskich obszarach, gdzie CDF ma bardzo niewiele odrębnych wartości; limit przycinania ogranicza, jak agresywnie pojedynczy bin może być redystrybuowany, co zapobiega wzmacnianiu szumu, pozwalając jednocześnie na rozciąganie kontrastu tam, gdzie ma to znaczenie. Wartość około 10 jest rozsądnym punktem wyjścia; większe wartości pozwalają CLAHE pracować intensywniej kosztem bardziej widocznego szumu, mniejsze sprawiają, że działa łagodniej.

CLAHE jest bardziej kosztowny niż globalny histeq, ale daje czystsze wyniki w scenach, gdzie jasność jest nierównomiernie rozłożona – czyli w większości scen ze świata rzeczywistego.

5.19.3. Gamma, kontrast i jasność

Wyrównanie histogramu to sterowany danymi sposób przemapowania jasności. Sposób niezależny od danych polega na zastosowaniu wybranej krzywej, sparametryzowanej kilkoma łatwymi do dostrojenia pokrętłami. gamma() udostępnia trzy:

img.gamma(gamma=1.0, contrast=1.0, brightness=0.0)

Każdy parametr stosuje jedno konkretne przekształcenie do każdego piksela:

gamma przepuszcza wartość każdego piksela przez funkcję potęgową output = input ** (1 / gamma). Standardowe znaczenie: wartości większe niż 1.0 rozjaśniają obraz i podnoszą tony średnie (klasyczna korekcja „gammy monitora”); wartości mniejsze niż 1.0 przyciemniają go. Parametr jest nieliniowy – zachowuje punkty czerni i bieli, a jedynie zmienia kształt rozkładu pomiędzy nimi, co jest właściwym zachowaniem, gdy celem jest odzyskanie detali w cieniach lub obszarach jasnych bez zmiażdżenia istniejących skrajności.

contrast przepuszcza każdy piksel przez proste mnożenie wokół punktu średniej szarości. Wartości większe niż 1.0 zwiększają kontrast (ciemne staje się ciemniejsze, jasne staje się jaśniejsze, średnia szarość pozostaje bez zmian); wartości mniejsze niż 1.0 zmniejszają kontrast.

brightness dodaje stałą do wartości każdego piksela. Wartości dodatnie rozjaśniają, ujemne przyciemniają. Przesunięcie jest jednolite – nic nie zostaje zachowane – co rzadko jest tym, czego aplikacja chce samodzielnie, ale dobrze łączy się z przejściem kontrastowym w celu ponownego wyśrodkowania wyniku.

Te trzy parametry składają się ze sobą: pojedyncze wywołanie gamma() może zastosować krzywą gamma, następnie mnożenie kontrastu, a potem przesunięcie jasności, wszystko w jednym przebiegu. Kolejność to najpierw gamma, potem kontrast, potem jasność, co odpowiada kolejności dającej najbardziej intuicyjne wyniki, gdy wszystkie trzy mają wartości inne niż domyślne.

5.19.4. Automatyczny balans bieli

Rodzina korekcji tonalnych dotyczących koloru zaczyna się od automatycznego balansu bieli. Ten sam mechanizm, który ISP sensora uruchamia jako część potoku obrazowania – regulując względne wzmocnienia kanałów czerwonego, zielonego i niebieskiego tak, aby przeciętny szary fragment odczytywany był jako rzeczywista szarość – jest również dostępny jako operacja po przechwyceniu na gotowym Image:

img.awb()

Domyślnie używany jest algorytm gray-world: zakłada się, że średni kolor całego obrazu jest neutralną szarością, a wzmocnienia poszczególnych kanałów są regulowane, aby tak było. Alternatywna forma max=True używa algorytmu white-patch: zakłada się, że najjaśniejsze piksele są neutralną bielą, a wzmocnienia są regulowane, aby tak było. Oba działają na RGB565 i na surowych danych Bayera; żaden nie działa na skali szarości (gdzie nie ma koloru do zbalansowania) ani na YUV (gdzie reprezentacja koloru nie jest tym, na czym te algorytmy operują).

Kiedy sięgać po formę po przechwyceniu zamiast po automatyczny balans bieli ISP: gdy wybór ISP był słabo dopasowany do konkretnej sceny, gdy aplikacja wczytuje z dysku ramki referencyjne przechwycone w innych warunkach, lub gdy ocena koloru jest na tyle istotna, że aplikacja chce ją uruchomić ponownie z własnym wyborem algorytmu.

5.19.5. Macierz korekcji koloru

Gdy korekcja koloru, której obraz potrzebuje, nie jest skalowaniem poszczególnych kanałów, jakie daje balans bieli, lecz bardziej ogólnym mieszaniem kanałów, operacją, po którą należy sięgnąć, jest macierz korekcji koloru. Metoda ccm() stosuje macierz 3 na 3 (lub 3 na 4 z przesunięciem), która mnoży wektor (r, g, b) każdego piksela, aby uzyskać nowy wektor (r, g, b):

img.ccm([[1.1, -0.05, -0.05],
        [-0.05, 1.1, -0.05],
        [-0.05, -0.05, 1.1]])

Macierz pozwala aplikacji korygować przesłuch między kanałami koloru – na przykład tam, gdzie odpowiedź czerwonego sensora obejmuje część zielonego światła, macierz może odjąć ułamek kanału zielonego od wyjścia czerwonego, aby to skompensować. W połączeniu z przesunięciem poszczególnych kanałów, forma 3 na 4 pozwala aplikacji również ponownie wyzerować każdy kanał.

Materiał o potoku ISP omawia dlaczego macierzy korekcji koloru. Forma po przechwyceniu na Image to po prostu ta sama operacja, zastosowana po fakcie.