5.16. Mukautetut konvoluutioytimet¶
Tähän mennessä käsitellyillä naapurustosuodattimilla oli kullakin sisäänrakennettu tunnusluku, jonka suodatin sovelsi ikkunaan jokaisessa kohdassa – keskiarvo, Gaussin painotettu keskiarvo, mediaani. morph() on se ainoa suodatin, joka antaa sovelluksen toimittaa tunnusluvun itse, ytimen muodossa: pienenä painokertoimien matriisina, joka kuvaa, miten suodattimen tulisi yhdistää naapuruston pikselit yhdeksi tulosarvoksi.
Mekanismi on klassinen konvoluutio-operaatio. Jokaisessa tuloskohdassa jokainen naapuruston pikseli kerrotaan ytimen vastaavalla painokertoimella, tulot lasketaan yhteen, tulos skaalataan ja siirretään valinnaisesti, ja arvo kirjoitetaan tulospikseliin. Eri ytimet tuottavat samasta syötteestä eri tuloksia. Ydin, jonka painokertoimet ovat kaikki yhtä suuria ja positiivisia, toistaa mean()-suodattimen; kellomainen toistaa gaussian()-suodattimen. Näitä monimutkaisemmat kuviot tuottavat reunavasteita, kohokuviointeja, gradientteja, terävöintiä, liike-epäterävyyttä ja pitkän luettelon muita tehosteita – kaiken, mitä klassinen kuvankäsittely on koskaan halunnut tehdä yhdellä lineaarisella läpikäynnillä.
5.16.1. morph-metodi¶
Signatuuri näyttää samalta kuin muilla naapurustosuodattimilla, mutta siinä on yksi lisäargumentti:
img.morph(size, kernel, mul=1.0, add=0.0)
size on säde samalla tavalla kuin kaikkialla muuallakin, joten ytimen on oltava täsmälleen (2 * size + 1) riviä kertaa (2 * size + 1) saraketta. Ydin itse on litteä Python-lista, jossa on tämä määrä lukuja, rivijärjestyksessä – ensimmäiset (2 * size + 1) alkiota muodostavat ylimmän rivin, seuraavat (2 * size + 1) toisen rivin ja niin edelleen aina alimpaan riviin asti. mul skaalaa tulojen summan ennen kuin se kirjoitetaan tulospikseliin, ja add lisää vakion. Oletusarvot mul=1.0 ja add=0.0 jättävät konvoluution tuloksen ennalleen.
Yksi yksityiskohta, joka kannattaa tuoda esiin selvästi: metodi jakaa tulojen summan automaattisesti ytimen alkioiden summalla ennen tuloksen kirjoittamista. Tämä automaattinen jako tarkoittaa, että keskiarvoistava ydin, jonka alkioiden summa on yhdeksän – esimerkiksi 3 kertaa 3 -kokoinen laatikkosumennus – tulee ulos yhdesosanvälisesti skaalattuna ilman ylimääräistä vaivaa, ja Gaussin approksimaatioydin, jonka summa on kuusitoista, tulee ulos kuudestoistaosaskaalattuna, molemmat ilman että sovelluksen täytyy laskea jakoa itse. Sovellus asettaa mul-arvon vain silloin, kun se haluaa lisäskaalauksen automaattisen normalisoinnin päälle – tai, yleisemmin, kun ytimen summa on nolla (reunavasteydin) ja automaattinen jako olisi jako olemattomalla. Framework käsittelee summan tällöin ykkösenä, ja mul jää ainoaksi säätimeksi, jolla skaalaamattomien tulojen summa pysyy alueellaan.
Adaptiivisen kynnystyksen osiosta tuttu pari threshold=True / offset=N toimii myös morph()-metodin kanssa, joten samalla mukautettujen ytimien frameworkilla voidaan tuottaa binäärinen kynnystys, jonka katkaisukohta lasketaan mukautetulla tunnusluvulla.
5.16.2. Ytimen asettelu¶
3 kertaa 3 -kokoinen ydin (size=1) on litteä lista yhdeksästä luvusta, jotka on aseteltu vasemmalta oikealle ja ylhäältä alas. Käytäntö hahmottuu luontevasti, jos lista jaetaan kolmelle Python-riville:
sobel_x = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1]
Tämä on Sobel-x -gradienttioperaattori – ensimmäinen vakioydin, jonka mikä tahansa sovellus haluaa, ja hyödyllinen käydä läpi alusta loppuun. Kuvio on suoraviivainen: negatiiviset painokertoimet vasemmassa sarakkeessa, positiiviset oikeassa sarakkeessa, keskisarakkeen ollessa nolla. Rivin painokertoimet -1, -2, -1 (tai 1, 2, 1 oikealla) ovat suurempia keskellä kuin kulmissa, mikä antaa keskiriville enemmän vaikutusta tulokseen kuin kulmariveille.
Kun ydin pyyhkäisee pystysuoran reunan yli – pikselisarakkeen, joka muuttuu vasemmalla tummasta oikealla kirkkaaksi – negatiiviset painokertoimet poimivat tumman puolen ja positiiviset painokertoimet kirkkaan puolen. Tulojen summa on suuri positiivinen luku, jonka suodatin kirjoittaa kirkkaaksi tulospikseliksi. Vaakasuora tasaisen kirkkauden alue tuottaa nollan, koska jokaista positiivista painokerrointa vastaa yhtä suuri negatiivinen painokerroin pikselillä, jonka arvo on sama.
Ytimen ajaminen:
img.morph(1, sobel_x, mul=0.25)
Sobel-ytimen summa on nolla – jokaista vasemman puolen negatiivista painokerrointa vastaa yhtä suuri positiivinen painokerroin oikealla – joten automaattinen jako ei jaa millään, ja mul on ainoa tulojen summan skaalaus. mul=0.25 pitää vasteen alueellaan: suurin itseisarvoltaan oleva summa, jonka Sobel-x voi tuottaa 3 kertaa 3 -alueesta, on noin 4 * 255 = 1020 (kahdeksan kirkasta pikseliä painotettuna jopa arvoon 2), ja sen jakaminen neljällä saa ääritapaukset arvoon 255, jossa formaatti leikkaa ne siististi.
Vastaava Sobel-y -ydin tunnistaa vaakasuorat reunat kiertämällä samaa painokerroinkuviota 90 astetta:
sobel_y = [-1, -2, -1,
0, 0, 0,
1, 2, 1]
Sovellukset, jotka haluavat tunnistaa minkä tahansa reunan suunnasta riippumatta, ajavat tyypillisesti molemmat Sobelit ja yhdistävät vasteet.
5.16.3. Tuloksen siirtäminen¶
add on skaalaustarinan toinen puolisko. Nollasummaisen ytimen vaste on etumerkillinen – positiivinen reunan toisella puolella, negatiivinen toisella – ja negatiivinen puolisko leikkaantuu nollaan, kun se kirjoitetaan etumerkittömään pikseliin. add=128 siirtää vasteen keskitetyksi keskiharmaaseen, joten negatiiviset vasteet säilyvät arvoina, jotka ovat alle 128, ja positiiviset asettuvat sen yläpuolelle: reunavaste tai kohokuviointi tulee näkyväksi molempiin suuntiin, kunkin alueen puolikkaan kustannuksella.
Mitä mul- ja add-arvojen yhdistelmää ydin odottaa, on osa ytimen suunnittelua; vakioytimien luettelo listaa oikeat asetukset kullekin yleiselle ytimelle.
5.16.4. Suuremmat ytimet¶
Kaikki tällä sivulla on kuvattu 3 kertaa 3 -kokoisilla ytimillä (size=1), koska vakioluettelo käyttää tätä kokoa ja koska rivijärjestyksessä oleva asettelu on tällä koolla helppo kirjoittaa käsin. Mikään mekanismissa ei kuitenkaan rajoita ydintä 3 kertaa 3 -kokoon. size=2 ajaa 5 kertaa 5 -kokoisen ytimen, jossa on kaksikymmentäviisi alkiota litteässä listassa; size=3 ajaa 7 kertaa 7 -kokoisen, jossa on neljäkymmentäyhdeksän; ja niin edelleen, aina siihen säteeseen asti, jonka sovellus on valmis maksamaan. Framework käsittelee sekä litteän listan että sisäkkäisten rivien asettelun millä tahansa parittomalla koolla.
Syy tarttua suurempaan ytimeen on sama kuin syy tarttua suurempaan naapurustoon millä tahansa sisäänrakennetulla suodattimella: enemmän keskiarvoistusta, laajempi piirteiden tunnistus, vähemmän herkkyyttä yksittäisen pikselin kohinalle. Kustannus kasvaa säteen neliönä – 5 kertaa 5 tekee noin 2,8-kertaisen pikselikohtaisen työn 3 kertaa 3 -kokoiseen verrattuna, 7 kertaa 7 noin 5,4-kertaisen – ja tämä kerroin tulee suoraan kehystaajuudesta.
Käytännöllinen toimintamalli on pysyä koossa size=1 vakioluettelon osalta ja tarttua suurempiin kokoihin vain silloin, kun algoritmi tarvitsee suuremman naapuruston. Reunantunnistimet hyötyvät harvoin 3 kertaa 3 -kokoa suuremmasta; tasoitussuodattimet joskus hyötyvät; oikea koko riippuu niiden piirteiden mittakaavasta, joita sovellus yrittää korostaa tai vaimentaa.
5.16.5. Milloin morphiin kannattaa tarttua¶
Jokapäiväiseen tasoitukseen mean(), gaussian() ja bilateral() ovat nopeampia ja siistimpiä. Reunantunnistukseen laplacian() ja find_edges() on rakennettu juuri sitä varten. Peruste tarttua suoraan morph()-metodiin on silloin, kun sovellus tarvitsee tietyn konvoluution, jota sisäänrakennetut suodattimet eivät tarjoa – suuntaavan Sobelin, mukautetun reunamallin, tiettyyn tekstuuriin viritetyn ytimen, jota muu putki tulee etsimään, tai minkä tahansa vakioluettelon hyödyllisistä ytimistä, joita klassinen kuvankäsittely on rakentanut vuosikymmenten saatossa. Mielivaltaisten ytimien täysi joustavuus on käytettävissä; hintana on, että sovellus vastaa sellaisten ytimen arvojen valinnasta, jotka tuottavat halutun tuloksen.