5.17. A standard kernelek katalógusa¶
A klasszikus képfeldolgozás az évek során egy jókora katalógust gyűjtött össze a kernel-súlymintázatokból, amelyek újra meg újra előbukkannak – éldetektorok, élesítők, dombornyomások, simítók, mozgási elmosások –, és mindegyik a morph() metóduson keresztül fut. Mindegyik rövid, mindegyik egyetlen dolgot csinál, és a legtöbbjük könnyen olvasható, amint a súlyok alapvető logikája világossá válik.
Az alábbi kernelek mind 3-szor-3-asak, hacsak nincs másként jelölve, így mindegyik size=1 értéket használ a hívásban. Minden kernel súlyszerkezetét mellette ismertetjük, mert a súlyok olvasása alakítja ki azt az intuíciót, amely megmagyarázza, miért domborít az egyik kernel és miért élesít a másik.
5.17.1. Az identitás-kernel¶
A lehető legegyszerűbb kernel az identitás – a középen egy, mindenhol máshol nulla:
identity = [0, 0, 0,
0, 1, 0,
0, 0, 0]
img.morph(1, identity)
Minden kimeneti képpont az értékét a környezet közepéből veszi, ami az ugyanazon a pozíción lévő bemeneti képpont. A kép változatlanul halad át. Az identitásnak szűrőként nincs gyakorlati haszna, de hasznos kiindulási alap minden más kernel megértéséhez: bármely nem-identitás kernel az identitás plusz valamilyen módosítás.
Az a kernel, amelynek a középső súlya nagy, körülötte pedig kis negatív súlyok vannak, kivonja a környezetet a középpontból. Az a kernel, amelynek a középső súlya nulla, figyelmen kívül hagyja magát a képpontot, és csak a szomszédai közti különbségekre reagál. Ha egy kernelt így olvasunk – mit tesz a középső súly a képponttal, mit ad hozzá vagy von el a környező súlyok –, az a leggyorsabb módja annak, hogy megjósoljuk a hatását.
5.17.2. Éldetektálás¶
Az éldetektáló kernelek erősen reagálnak azokon a pozíciókon, ahol a fényerő egy adott irányban gyorsan változik, és közel nulla kimenetet adnak ott, ahol a fényerő egyenletes. Ők az a család, amelynek súlyai nullára összegződnek: egy egyenletes folt (ahol minden képpont azonos értékű) nulla kimenetet ad, mert minden pozitív súlyt pontosan kiolt egy azonos nagyságú negatív súly.
A Sobel-x a kanonikus példa. Függőleges éleket detektál (bal/jobb fényerőátmeneteket):
sobel_x = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1]
img.morph(1, sobel_x, mul=0.25, add=128)
A párját jelentő Sobel-y ugyanaz a mintázat 90 fokkal elforgatva; vízszintes éleket detektál (fel/le fényerőátmeneteket):
sobel_y = [-1, -2, -1,
0, 0, 0,
1, 2, 1]
A Sobel-x középső sorában -2 és 2 súlyok vannak, nem pedig -1 és 1. A középső soron lévő extra súly egy kis beépített simítást ad a kernelnek az él mentén haladó irányban, ami zajjal szemben robusztusabbá teszi, mint az egyszerűbb Prewitt operátort, amely elhagyja ezeket az extra nagyságokat:
prewitt_x = [-1, 0, 1,
-1, 0, 1,
-1, 0, 1]
prewitt_y = [-1, -1, -1,
0, 0, 0,
1, 1, 1]
A Prewitt minden sort egyformán súlyoz, így a válasza egy árnyalattal élesebb a Sobelénél, azon az áron, hogy érzékenyebb az egyetlen képpontos zajra (a kernel futtatásának költsége azonos – a konvolúció ugyanazt a munkát végzi, bármilyenek is a súlyok). Tiszta, erős élekkel rendelkező képen tökéletesen használható helyettesítője a Sobelnek.
A Scharr a másik irányba megy. Súlyai nagyobbak, és arra vannak hangolva, hogy finomabb szögeknél is pontosan detektálják az él irányát:
scharr_x = [-3, 0, 3,
-10, 0, 10,
-3, 0, 3]
img.morph(1, scharr_x, mul=0.0625, add=128)
A mul=0.0625 osztó (1/16) a kimenetet a nagyobb szorzatösszeg után visszahozza a 0 – 255 tartományba. A Scharr a helyes válasz, ha az alkalmazásnak a leginkább geometriailag hű gradiensválaszra van szüksége, és hajlandó ezért egy kicsivel több aritmetikát fizetni.
5.17.3. A Laplace-operátor¶
A Laplace-kernel egyszerre reagál a bármely irányú élekre. Míg a Sobelek mindegyike egyetlen tengely menti fényerőváltozásokat detektál, a Laplace szimmetrikus súlymintázata ugyanúgy reagál, függetlenül attól, hogy az él milyen irányba halad:
laplacian_4 = [ 0, -1, 0,
-1, 4, -1,
0, -1, 0]
img.morph(1, laplacian_4, add=128)
A szerkezet: középső súly 4, négy vízszintes/függőleges szomszéd -1 súllyal, a négy átló nulla súllyal. A kernel nullára összegződik, így az egyenletes foltok nulla kimenetet adnak. Ahol a fényerő változik, ott a középső érték eltér négy fő szomszédjának átlagától, és a kimenet ennek a különbségnek a nagysága.
A 8-szomszédos változat magában foglalja az átlós szomszédokat is:
laplacian_8 = [-1, -1, -1,
-1, 8, -1,
-1, -1, -1]
Mindegyik kernel kissé más dolgokat detektál. A 4-szomszédos változat tisztább kimenetet ad a vízszintes és függőleges éleken; a 8-szomszédos izotrópabb – minden irányban egyformán jól reagál –, de kissé zajosabb kimenetet ad. A 8-szomszédos kernel outline (körvonal) néven is terjed, az élek vizualizálására való felhasználása nyomán.
5.17.5. Dombornyomás (Emboss)¶
Az emboss (dombornyomás) kernel a klasszikus képszerkesztőkben megtalálható oldalról megvilágított hatást hozza létre. A kimenet úgy néz ki, mintha a képet domborművé alakították, majd egy sarokból megvilágították volna:
emboss = [-2, -1, 0,
-1, 1, 1,
0, 1, 2]
img.morph(1, emboss, add=128)
A trükk az átló menti aszimmetria. A bal felső saroknak van a legnegatívabb súlya, a jobb alsónak a legpozitívabb súlya, és a saroktól sarokig tartó átló a negatívtól az egyen át a pozitívig fut. Minden képpontnál a kernel lényegében azt számolja ki, hogy „a jobb alsó oldalamon lévő fényerő mínusz a bal felső oldalamon lévő fényerő”, ami pozitív ott, ahol a kép abba az irányba világosabb lesz, és negatív ott, ahol sötétebb. A 128 hozzáadása az előjeles kimenetet közepes szürkére centrálja vissza, hogy a hatás látható legyen.
Az aszimmetria másik átló menti elforgatása az ellenkező irányból domborít:
emboss_alt = [ 0, 1, 2,
-1, 1, 1,
-2, -1, 0]
img.morph(1, emboss_alt, add=128)
A két emboss-irány kombinációban hasznos – az egyiket a másikból kivonva, vagy mindkettőt ugyanazon a képen futtatva és a válaszokat összehasonlítva –, amikor egy alkalmazásnak az orientációt kell detektálnia.
5.17.6. Simítás¶
A simító kernelek az a család, amelynek súlyai egyre összegződnek (és mind nemnegatívak). Egy egyenletes folt egy ilyen kernelen keresztül ugyanazt az egyenletes fényerőt adja, mert a kernel a képpontértékeket átlagolja, ahelyett, hogy felerősítené a különbségeiket.
A legegyszerűbb a box blur (dobozos elmosás), ami pontosan az, amit a mean() számol ki:
box_blur = [1, 1, 1,
1, 1, 1,
1, 1, 1]
img.morph(1, box_blur)
A kernel 9-re összegződik, így a kernelösszeggel való automatikus osztás a szorzatösszeget valódi átlaggá alakítja a kilenc szomszédos képpont felett. A gyakorlatban a mean() a jobb módja ennek a kernelnek a futtatására – ugyanazt a kimenetet gyorsabban állítja elő, egy kizárólag az átlag kiszámítására optimalizált útvonalon keresztül, míg a morph az általános konvolúciós gépezetet futtatja. A box blur azért van a katalógusban, mert ez a helyes kiindulási alap minden más simító kernel megértéséhez.
A Gaussian 3-szor-3-as közelítése a középpontot és a fő szomszédokat jobban súlyozza, mint a sarkokat:
gaussian = [1, 2, 1,
2, 4, 2,
1, 2, 1]
img.morph(1, gaussian)
A súlyok a Pascal-háromszög 1, 2, 1 sora önmagával képzett külső szorzata. A 4 középső súly a legnagyobb, mert a középső képpont járul hozzá leginkább a saját kimenetéhez; a sarkok 1 értékűek, mert a legtávolabb vannak a középponttól. A kernel 16-ra összegződik, és a kernelösszeggel való automatikus osztás elvégzi a normalizálást – nincs szükség mul argumentumra. A 3-szor-3-as forma egy valódi Gaussian durva közelítése, és size=1 esetén megkülönböztethetetlen a gaussian() metódustól; a morph-forma többnyire akkor hasznos, ha egy alkalmazás a simítást egy másik művelettel akarja összevonni ugyanabban a menetben.
5.17.7. Mozgási elmosás¶
A motion-blur (mozgási elmosás) kernel a képpontokat egy irány mentén átlagolja, a merőleges irányt elmosatlanul hagyva. A legegyszerűbb eset a vízszintes:
motion_h = [0, 0, 0,
1, 1, 1,
0, 0, 0]
img.morph(1, motion_h)
A középső sor három képpontot átlagol a vízszintes tengely mentén; a felső és alsó sor nulla. A kernel 3-ra összegződik, így a kernelösszeggel való automatikus osztás valódi háromképpontos átlagot ad anélkül, hogy bármilyen mul szükséges lenne. A kimenet a bemenet vízszintesen elkent másolata – az a hatás, amelyet egy kamera rögzít, amikor a téma oldalirányban mozog az expozíció során. A függőleges mozgási elmosás ugyanaz a mintázat elforgatva:
motion_v = [0, 1, 0,
0, 1, 0,
0, 1, 0]
Az átlós mozgási elmosás a fő átlót használja:
motion_diag = [1, 0, 0,
0, 1, 0,
0, 0, 1]
img.morph(1, motion_diag)
A mozgási elmosás kernelek hasznosak mind effektusként (egy képkocka szándékos elmosása vizuális célokból), mind tesztmintázatként olyan algoritmusokhoz, amelyeknek robusztusnak kell lenniük a mozgási műtermékekkel szemben (futtasd az algoritmust egy mozgásilag elmosott bemeneten, és ellenőrizd, hogy még mindig a helyes választ adja-e).
5.17.8. Kernelek ránézésre olvasása¶
Néhány ökölszabály megkönnyíti az új kernelek ránézésre olvasását:
Egyre összegződő nemnegatív súlyokkal ⇒ simítás (megőrzi az átlagos fényerőt).
Nullára összegződő pozitív és negatív súlyokkal egyaránt ⇒ élválasz (egyenletes foltokon nulla).
Egyre összegződő nagy pozitív középponttal és kis negatív környezettel ⇒ élesítés (identitás plusz élválasz).
Átló mentén aszimmetrikus egyre összegződéssel ⇒ dombornyomás (minden fényerőátmenet egyik oldalát kiemeli).
Egy tengely mentén koncentrált egyre összegződéssel ⇒ irányított elmosás.
E szabályok közül az első, amelyikre a kernel illeszkedik, általában a helyes tipp arra nézve, hogy mit csinál. A legtöbb hasznos kernel pusztán a súlymintázat elrendezéséből felismerhető.
Amikor a standard kernelek egyike sem azt teszi, amit az alkalmazás szeretne, a következő lépés egy kézi hangolása. A fenti szabályok és a mul / add vezérlők kombinációja szinte minden lineáris menetet lefed, amelyet egy klasszikus gépi látás csővezeték valaha is akart; onnantól már csak a súlyok kipróbálásáról, a kimenet megfigyeléséről és az iterálásról van szó.