5.17. En katalog över standardkärnor¶
Klassisk bildbehandling har samlat på sig en ganska omfattande katalog av mönster för kärnvikter som dyker upp gång på gång – kantdetektorer, skärpare, reliefframställare, utjämnare, rörelseoskärpor – och var och en av dem körs genom morph(). Var och en är kort, var och en gör en enda sak, och de flesta är enkla att läsa när väl den grundläggande logiken bakom vikterna blivit begriplig.
Kärnorna nedan är alla 3-gånger-3 om inget annat anges, så de använder alla size=1 i anropet. Viktstrukturen för varje kärna beskrivs bredvid den, eftersom det är läsningen av vikterna som bygger intuitionen för varför en kärna skapar relief och en annan ökar skärpan.
5.17.1. Identitetskärnan¶
Den enklaste tänkbara kärnan är identiteten – en etta i mitten, nollor överallt annars:
identity = [0, 0, 0,
0, 1, 0,
0, 0, 0]
img.morph(1, identity)
Varje utdatapixel hämtar sitt värde från mitten av grannskapet, vilket är indatapixeln på samma position. Bilden passerar oförändrad igenom. Identiteten har ingen praktisk användning som filter, men den är den användbara utgångspunkten för att förstå alla andra kärnor: vilken kärna som helst som inte är identiteten är identiteten plus någon modifiering.
En kärna vars centrumvikt är stor med små negativa vikter runt omkring subtraherar omgivningen från mitten. En kärna med en centrumvikt på noll ignorerar själva pixeln och reagerar enbart på skillnader mellan dess grannar. Att läsa en kärna på detta sätt – vad centrumvikten gör med pixeln, vad de omgivande vikterna lägger till eller tar bort – är det snabbaste sättet att förutsäga dess effekt.
5.17.2. Kantdetektering¶
Kantdetekterande kärnor reagerar starkt på positioner där ljusstyrkan förändras snabbt i en viss riktning, och ger nästan noll i utdata där ljusstyrkan är jämn. De är den familj vars vikter summerar till noll: en jämn yta (varje pixel med samma värde) ger noll i utdata, eftersom varje positiv vikt exakt tas ut av en negativ vikt med lika stor magnitud.
Sobel-x är det kanoniska exemplet. Den detekterar vertikala kanter (ljusstyrkeövergångar vänster/höger):
sobel_x = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1]
img.morph(1, sobel_x, mul=0.25, add=128)
Den matchande Sobel-y är samma mönster roterat 90 grader; den detekterar horisontella kanter (ljusstyrkeövergångar upp/ned):
sobel_y = [-1, -2, -1,
0, 0, 0,
1, 2, 1]
Mittenraden i Sobel-x har vikterna -2 och 2 snarare än -1 och 1. Den extra vikten på centrumraden ger kärnan en liten inbyggd utjämning i riktningen längs kanten, vilket gör den mer robust mot brus än den enklare Prewitt-operatorn som utelämnar dessa extra magnituder:
prewitt_x = [-1, 0, 1,
-1, 0, 1,
-1, 0, 1]
prewitt_y = [-1, -1, -1,
0, 0, 0,
1, 1, 1]
Prewitt viktar varje rad lika, så dess respons är en aning skarpare än Sobels, till priset av att vara känsligare för enpixelsbrus (kostnaden för att köra kärnan är identisk – faltningen gör samma arbete oavsett vilka vikterna är). På en ren bild med starka kanter är den en fullt dugligt ersättning för Sobel.
Scharr går i motsatt riktning. Dess vikter är större och justerade för noggrann detektering av kantriktning vid finare vinklar:
scharr_x = [-3, 0, 3,
-10, 0, 10,
-3, 0, 3]
img.morph(1, scharr_x, mul=0.0625, add=128)
Divisorn mul=0.0625 (1/16) för utdatan tillbaka inom 0 – 255 efter den större summan av produkter. Scharr är det rätta svaret när tillämpningen behöver den mest geometriskt trogna gradientresponsen och är villig att betala något mer aritmetik för den.
5.17.3. Laplace-operatorn¶
En Laplace-kärna reagerar på kanter i alla riktningar samtidigt. Där Sobel-kärnorna var och en detekterar ljusstyrkeförändringar längs en axel, reagerar Laplace-operatorns symmetriska viktmönster på samma sätt oavsett vilken riktning kanten har:
laplacian_4 = [ 0, -1, 0,
-1, 4, -1,
0, -1, 0]
img.morph(1, laplacian_4, add=128)
Strukturen: centrumvikt 4, fyra horisontella/vertikala grannar viktade -1, de fyra diagonalerna viktade noll. Kärnan summerar till noll, så jämna ytor ger noll i utdata. Där ljusstyrkan förändras skiljer sig centrumvärdet från medelvärdet av dess fyra kardinalgrannar, och utdatan är storleken på den skillnaden.
Den 8-kopplade varianten inkluderar de diagonala grannarna:
laplacian_8 = [-1, -1, -1,
-1, 8, -1,
-1, -1, -1]
Varje kärna detekterar något olika saker. Den 4-kopplade versionen ger renare utdata på horisontella och vertikala kanter; den 8-kopplade är mer isotrop – den reagerar lika bra i varje riktning – men ger något brusigare utdata. Den 8-kopplade kärnan cirkulerar också under namnet outline, efter dess användning för att visualisera kanter.
5.17.5. Relief¶
En relief-kärna ger den sidobelysta effekt som finns i klassiska bildredigerare. Utdatan ser ut som om bilden extruderats till en relief och sedan belysts från ett hörn:
emboss = [-2, -1, 0,
-1, 1, 1,
0, 1, 2]
img.morph(1, emboss, add=128)
Knepet är asymmetrin tvärs över diagonalen. Det övre vänstra hörnet har den mest negativa vikten, det nedre högra har den mest positiva vikten, och diagonalen från hörn till hörn går från negativ via ett till positiv. Vid varje pixel beräknar kärnan i huvudsak ”ljusstyrkan nere till höger om mig minus ljusstyrkan uppe till vänster om mig”, vilket är positivt där bilden blir ljusare i den riktningen och negativt där den blir mörkare. Att addera 128 centrerar om den teckenförsedda utdatan till mellangrått så att effekten blir synlig.
Att rotera asymmetrin tvärs över den andra diagonalen skapar relief från motsatt riktning:
emboss_alt = [ 0, 1, 2,
-1, 1, 1,
-2, -1, 0]
img.morph(1, emboss_alt, add=128)
De två reliefriktningarna är användbara i kombination – att subtrahera den ena från den andra, eller köra var och en på samma bild och jämföra responserna – när en tillämpning behöver detektera orientering.
5.17.6. Utjämning¶
Utjämningskärnor är den familj vars vikter summerar till ett (och alla är icke-negativa). En jämn yta som passerar en sådan kärna ger samma jämna ljusstyrka, eftersom kärnan medelvärdesbildar pixelvärdena snarare än att förstärka deras skillnader.
Den enklaste är box-oskärpan, som är exakt vad mean() beräknar:
box_blur = [1, 1, 1,
1, 1, 1,
1, 1, 1]
img.morph(1, box_blur)
Kärnan summerar till 9, så den automatiska divisionen med kärnsumman förvandlar summan av produkter till ett sant medelvärde över de nio pixlarna i grannskapet. I praktiken är mean() det bättre sättet att köra denna kärna – den ger samma utdata snabbare, genom en väg optimerad för att beräkna medelvärdet och inget annat, medan morph kör det allmänna faltningsmaskineriet. Box-oskärpan finns med i katalogen eftersom den är den rätta utgångspunkten för att förstå alla andra utjämningskärnor.
En 3-gånger-3-approximation av Gauss-vikterna viktar mitten och kardinalgrannarna mer än hörnen:
gaussian = [1, 2, 1,
2, 4, 2,
1, 2, 1]
img.morph(1, gaussian)
Vikterna är raden 1, 2, 1 från Pascals triangel ytterprodukt med sig själv. Centrumvikten 4 är störst eftersom centrumpixeln bidrar mest till sin egen utdata; hörnen är 1 eftersom de ligger längst från mitten. Kärnan summerar till 16, och den automatiska divisionen med kärnsumman sköter normaliseringen – inget mul-argument behövs. 3-gånger-3-formen är en grov approximation av en sann Gauss-funktion och går inte att skilja från gaussian() vid size=1; morph-formen är mest användbar när en tillämpning vill kombinera utjämningen med en annan operation i samma pass.
5.17.7. Rörelseoskärpa¶
En rörelseoskärpe-kärna medelvärdesbildar pixlar längs en riktning och lämnar den vinkelräta riktningen oskärpefri. Det enklaste fallet är horisontellt:
motion_h = [0, 0, 0,
1, 1, 1,
0, 0, 0]
img.morph(1, motion_h)
Mittenraden medelvärdesbildar tre pixlar längs den horisontella axeln; den övre och nedre raden är noll. Kärnan summerar till 3, så den automatiska divisionen med kärnsumman ger ett sant trepixelsmedelvärde utan att något mul behövs. Utdatan är en horisontellt utsmetad kopia av indatan – den effekt en kamera fångar när motivet rör sig i sidled under exponeringen. Den vertikala rörelseoskärpan är samma mönster roterat:
motion_v = [0, 1, 0,
0, 1, 0,
0, 1, 0]
En diagonal rörelseoskärpa använder huvuddiagonalen:
motion_diag = [1, 0, 0,
0, 1, 0,
0, 0, 1]
img.morph(1, motion_diag)
Rörelseoskärpekärnor är användbara både som en effekt (att avsiktligt göra en bildruta oskarp i visuellt syfte) och som ett testmönster för algoritmer som behöver vara robusta mot rörelseartefakter (kör algoritmen på en rörelseoskarp indata och kontrollera att den fortfarande ger rätt svar).
5.17.8. Att läsa kärnor på ett ögonkast¶
Några tumregler gör nya kärnor lättare att läsa direkt:
Summa-till-ett med icke-negativa vikter ⇒ utjämning (bevarar genomsnittlig ljusstyrka).
Summa-till-noll med både positiva och negativa vikter ⇒ kantrespons (noll på jämna ytor).
Summa-till-ett med ett stort positivt centrum och små negativa omgivningar ⇒ skärpning (identitet plus kantrespons).
Asymmetrisk tvärs över en diagonal med summa till ett ⇒ relief (framhäver en sida av varje ljusstyrkeövergång).
Koncentrerad längs en axel med summa till ett ⇒ riktningsoskärpa.
Den första av dessa som kärnan matchar är vanligtvis den rätta gissningen om vad den gör. De flesta användbara kärnor går att känna igen enbart utifrån utseendet på deras viktmönster.
När ingen av standardkärnorna gör det tillämpningen önskar är nästa steg att handjustera en. Kombinationen av reglerna ovan och kontrollerna mul / add täcker nästan varje linjärt pass som en klassisk pipeline för maskinseende någonsin velat ha; därifrån handlar det om att prova vikter, titta på utdatan och iterera.