5.17. Katalog standardnih jezgri

Klasična obrada slike skupila je poprilično velik katalog uzoraka težina jezgri koji se neprestano pojavljuju – detektori rubova, izoštravači, reljefi (emboss), zaglađivači, zamuća kretanja (motion blur) – i svaki od njih prolazi kroz morph(). Svaki je kratak, svaki radi jednu stvar, a većina je jednostavna za čitanje čim se shvati osnovna logika težina.

Jezgre u nastavku sve su veličine 3 puta 3 osim ako je drugačije naznačeno, pa sve koriste size=1 u pozivu. Struktura težina svake jezgre opisana je uz nju, jer čitanje težina je ono što gradi intuiciju o tome zašto jedna jezgra stvara reljef, a druga izoštrava.

5.17.1. Identitetska jezgra

Najjednostavnija moguća jezgra jest identitet – jedinica u sredini, nule posvuda drugdje:

identity = [0, 0, 0,
            0, 1, 0,
            0, 0, 0]

img.morph(1, identity)

Svaki izlazni piksel preuzima svoju vrijednost iz središta susjedstva, što je ulazni piksel na istom položaju. Slika prolazi nepromijenjena. Identitet nema praktičnu primjenu kao filtar, ali je korisna polazišna točka za razumijevanje svake druge jezgre: svaka jezgra koja nije identitet jest identitet plus neka izmjena.

Jezgra čija je središnja težina velika s malim negativnim težinama oko nje oduzima okolinu od središta. Jezgra s nultom središnjom težinom zanemaruje sam piksel i reagira samo na razlike među svojim susjedima. Čitanje jezgre na ovaj način – što središnja težina čini pikselu, što okolne težine dodaju ili oduzimaju – najbrži je način predviđanja njezina učinka.

5.17.2. Detekcija rubova

Jezgre za detekciju rubova snažno reagiraju na položaje gdje se svjetlina brzo mijenja u određenom smjeru, a daju izlaz blizu nule tamo gdje je svjetlina ujednačena. To je obitelj čije se težine zbrajaju u nulu: ravna ploha (svaki piksel iste vrijednosti) daje nulti izlaz, jer je svaka pozitivna težina točno poništena negativnom težinom jednake veličine.

Sobel-x je kanonski primjer. On detektira okomite rubove (prijelaze svjetline lijevo/desno):

sobel_x = [-1,  0,  1,
           -2,  0,  2,
           -1,  0,  1]

img.morph(1, sobel_x, mul=0.25, add=128)

Odgovarajući Sobel-y isti je uzorak zakrenut za 90 stupnjeva; on detektira vodoravne rubove (prijelaze svjetline gore/dolje):

sobel_y = [-1, -2, -1,
            0,  0,  0,
            1,  2,  1]

Srednji redak Sobel-x ima težine -2 i 2 umjesto -1 i 1. Dodatna težina na središnjem retku daje jezgri malo ugrađeno zaglađivanje u smjeru uzduž ruba, što je čini otpornijom na šum od jednostavnijeg Prewitt operatora koji izostavlja te dodatne veličine:

prewitt_x = [-1, 0, 1,
             -1, 0, 1,
             -1, 0, 1]

prewitt_y = [-1, -1, -1,
              0,  0,  0,
              1,  1,  1]

Prewitt jednako vrednuje svaki redak, pa je njegov odziv malo oštriji od Sobelovog, po cijenu veće osjetljivosti na šum pojedinačnog piksela (trošak izvođenja jezgre je identičan – konvolucija obavlja isti posao bez obzira na težine). Na čistoj slici sa snažnim rubovima, on je posve upotrebljiva zamjena za Sobel.

Scharr ide u drugom smjeru. Njegove su težine veće i ugođene za precizno detektiranje smjera ruba pod finijim kutovima:

scharr_x = [-3,   0,  3,
            -10,  0, 10,
            -3,   0,  3]

img.morph(1, scharr_x, mul=0.0625, add=128)

Djelitelj mul=0.0625 (1/16) vraća izlaz natrag unutar raspona 0255 nakon većeg zbroja umnožaka. Scharr je pravi odgovor kada aplikacija treba geometrijski najvjerniji gradijentni odziv i spremna je za to platiti malo više aritmetike.

5.17.3. Laplacian

Laplacian jezgra reagira na rubove u bilo kojem smjeru istodobno. Tamo gdje Sobeli svaki detektiraju promjene svjetline duž jedne osi, simetrični uzorak težina Laplaciana reagira jednako bez obzira na to u kojem smjeru ide rub:

laplacian_4 = [ 0, -1,  0,
               -1,  4, -1,
                0, -1,  0]

img.morph(1, laplacian_4, add=128)

Struktura: središnja težina 4, četiri vodoravna/okomita susjeda s težinom -1, četiri dijagonalna s težinom nula. Jezgra se zbraja u nulu, pa ravne plohe daju nulti izlaz. Tamo gdje se svjetlina mijenja, središnja se vrijednost razlikuje od prosjeka svojih četiriju glavnih susjeda, a izlaz je veličina te razlike.

Inačica s 8 povezanih uključuje dijagonalne susjede:

laplacian_8 = [-1, -1, -1,
               -1,  8, -1,
               -1, -1, -1]

Svaka jezgra detektira malo drugačije stvari. Inačica s 4 povezana daje čistiji izlaz na vodoravnim i okomitim rubovima; ona s 8 povezanih je izotropnija – reagira jednako dobro u svakom smjeru – ali daje nešto šumovitiji izlaz. Jezgra s 8 povezanih kruži i pod nazivom outline, prema njezinoj uporabi za vizualizaciju rubova.

5.17.4. Izoštravanje

Jezgra za izoštravanje jest identitet plus jezgra odziva na rubove. Izlaz je izvorna slika plus kopija rubova, pa se visokofrekventne značajke pojačavaju u odnosu na glatke unutrašnjosti.

Standardna jezgra za izoštravanje s 4 povezana dodaje Laplacian s 4 povezana na identitet:

sharpen = [ 0, -1,  0,
           -1,  5, -1,
            0, -1,  0]

img.morph(1, sharpen)

Čitanje jezgre: središnja težina je identity (1) + Laplacian centre (4) = 5, a okolina odgovara Laplacianovoj. Ravne plohe daju 5 * 1 - 4 * 1 = 1 puta središnju vrijednost – identitet. Rubovi daju izvornu vrijednost plus Laplacianov odziv. Zbroj težina je 1, pa mul i add ostaju na svojim zadanim vrijednostima.

Za jače izoštravanje, inačica s 8 povezanih ide dalje:

sharpen_strong = [-1, -1, -1,
                  -1,  9, -1,
                  -1, -1, -1]

img.morph(1, sharpen_strong)

Središnja težina 9 jest identity (1) + Laplacian-8 centre (8). Ista logika, više pojačanja, veći rizik od pojačavanja i šuma senzora.

Jezgre za snažno izoštravanje u biti su gaussian() s unsharp=True, samo izravno izražene kao jezgra umjesto kroz zastavicu unsharp-maske. Ponašanje na razini piksela je isto; izbor je između praktičnosti imenovane metode i fine kontrole ručno ugođene jezgre.

5.17.5. Emboss (reljef)

Emboss jezgra stvara učinak osvjetljenja sa strane kakav nalazimo u klasičnim uređivačima slika. Izlaz izgleda kao da je slika istisnuta u reljef i potom osvijetljena iz jednog kuta:

emboss = [-2, -1,  0,
          -1,  1,  1,
           0,  1,  2]

img.morph(1, emboss, add=128)

Trik je u asimetriji preko dijagonale. Gornji lijevi kut ima najnegativniju težinu, donji desni ima najpozitivniju težinu, a dijagonala od kuta do kuta ide od negativnog kroz jedinicu do pozitivnog. Na svakom pikselu jezgra u biti računa „svjetlina dolje desno od mene minus svjetlina gore lijevo od mene”, što je pozitivno tamo gdje slika postaje svjetlija u tom smjeru, a negativno tamo gdje postaje tamnija. Dodavanje 128 ponovno centrira predznačeni izlaz na srednje sivo kako bi učinak bio vidljiv.

Zakretanje asimetrije preko druge dijagonale stvara reljef iz suprotnog smjera:

emboss_alt = [ 0,  1,  2,
              -1,  1,  1,
              -2, -1,  0]

img.morph(1, emboss_alt, add=128)

Dva smjera reljefa korisna su u kombinaciji – oduzimanjem jednog od drugog, ili izvođenjem svakog na istoj slici i usporedbom odziva – kada aplikacija treba detektirati orijentaciju.

5.17.6. Zaglađivanje

Jezgre za zaglađivanje obitelj su čije se težine zbrajaju u jedinicu (i sve su nenegativne). Ravna ploha kroz takvu jezgru daje istu ravnu svjetlinu, jer jezgra usrednjava vrijednosti piksela zajedno umjesto da pojačava njihove razlike.

Najjednostavnija je box blur (kutijasto zamuće), što je upravo ono što računa mean():

box_blur = [1, 1, 1,
            1, 1, 1,
            1, 1, 1]

img.morph(1, box_blur)

Jezgra se zbraja u 9, pa automatsko dijeljenje sa zbrojem jezgre pretvara zbroj umnožaka u pravi prosjek preko devet piksela susjedstva. U praksi je mean() bolji način izvođenja ove jezgre – daje isti izlaz brže, kroz putanju optimiziranu za računanje srednje vrijednosti i ničeg drugog, dok morph pokreće opću konvolucijsku mašineriju. Box blur je u katalogu jer je prava polazišna točka za razumijevanje svake druge jezgre za zaglađivanje.

Aproksimacija veličine 3 puta 3 Gaussove jezgre vrednuje središte i glavne susjede više od kutova:

gaussian = [1, 2, 1,
            2, 4, 2,
            1, 2, 1]

img.morph(1, gaussian)

Težine su redak Pascalovog trokuta 1, 2, 1 vanjski umnožen sam sa sobom. Središnja težina 4 najveća je jer središnji piksel najviše doprinosi vlastitom izlazu; kutovi su 1 jer su najudaljeniji od središta. Jezgra se zbraja u 16, a automatsko dijeljenje sa zbrojem jezgre obavlja normalizaciju – nije potreban argument mul. Oblik 3 puta 3 gruba je aproksimacija prave Gaussove funkcije i nerazlučiv je od gaussian() pri size=1; morph oblik uglavnom je koristan kada aplikacija želi sastaviti zaglađivanje s drugom operacijom u istom prolazu.

5.17.7. Zamuće kretanja (motion blur)

Jezgra za zamuće kretanja usrednjava piksele duž jednog smjera, ostavljajući okomiti smjer nezamućenim. Najjednostavniji slučaj je vodoravni:

motion_h = [0, 0, 0,
            1, 1, 1,
            0, 0, 0]

img.morph(1, motion_h)

Srednji redak usrednjava tri piksela duž vodoravne osi; gornji i donji redak su nula. Jezgra se zbraja u 3, pa automatsko dijeljenje sa zbrojem jezgre daje pravi prosjek triju piksela bez potrebe za bilo kakvim mul. Izlaz je vodoravno razmazana kopija ulaza – učinak koji kamera bilježi kada se subjekt kreće u stranu tijekom ekspozicije. Okomito zamuće kretanja isti je uzorak zakrenut:

motion_v = [0, 1, 0,
            0, 1, 0,
            0, 1, 0]

Dijagonalno zamuće kretanja koristi glavnu dijagonalu:

motion_diag = [1, 0, 0,
               0, 1, 0,
               0, 0, 1]

img.morph(1, motion_diag)

Jezgre za zamuće kretanja korisne su i kao efekt (namjerno zamućivanje sličice u vizualne svrhe) i kao testni uzorak za algoritme koji trebaju biti otporni na artefakte kretanja (pokrenite algoritam na ulazu zamućenom kretanjem i provjerite da li još uvijek daje točan odgovor).

5.17.8. Čitanje jezgri na prvi pogled

Nekoliko praktičnih pravila čini nove jezgre lakšima za prepoznavanje na prvi pogled:

  • Zbroj u jedinicu s nenegativnim težinama ⇒ zaglađivanje (čuva prosječnu svjetlinu).

  • Zbroj u nulu s pozitivnim i negativnim težinama ⇒ odziv na rubove (nula na ravnim plohama).

  • Zbroj u jedinicu s velikim pozitivnim središtem i malim negativnim okolinama ⇒ izoštravanje (identitet plus odziv na rubove).

  • Asimetrija preko dijagonale sa zbrojem u jedinicu ⇒ stvaranje reljefa (ističe jednu stranu svakog prijelaza svjetline).

  • Koncentracija duž jedne osi sa zbrojem u jedinicu ⇒ usmjereno zamuće.

Prvo od ovih kojem jezgra odgovara obično je dobra pretpostavka o tome što radi. Većina korisnih jezgri prepoznatljiva je već iz rasporeda svog uzorka težina.

Kada nijedna od standardnih jezgri ne radi ono što aplikacija želi, sljedeći je korak ručno ugoditi jednu. Kombinacija gornjih pravila i kontrola mul / add pokriva gotovo svaki linearni prolaz koji je klasični cjevovod strojnog vida ikada poželio; odatle je samo stvar isprobavanja težina, gledanja izlaza i ponavljanja.