5.13. Linjära filter och grannskapsfilter

Pixelmatematikoperationerna tidigare i kapitlet kombinerade två bilder punkt för punkt. Filter utför besläktat arbete på ett annat sätt: de beräknar värdet för varje utdatapixel utifrån ett litet grannskap av indatapixlar som omger motsvarande position. Utdata vid (x, y) är någon statistik – medelvärdet, medianen, det vanligaste värdet – av indatapixlarna i en liten ruta centrerad kring (x, y).

Den lilla förändringen i synsätt – att gå från en pixel i taget till ett fönster av pixlar i taget – är det som får en hel familj av användbara operationer att fungera. Ett enkelt medelvärde över ett litet fönster jämnar ut sensorbrus. Medianen över samma fönster tar bort enstaka pixelstörningar utan att mjuka upp kanter lika mycket. Ett bilateralt medelvärde vägrar att jämna ut över starka kontrastgränser, och bevarar därmed objektens kanter samtidigt som texturerna inuti dem städas upp. Grannskapet är arbetsenheten; valet av statistik avgör vad filtret gör.

5.13.1. Kärnstorleken

Varje grannskapsfilter tar en size-parameter som anger fönstrets radie i pixlar. Själva fönstret är kvadratiskt och täcker (2 * size + 1) pixlar på varje sida – så size=1 betyder ett 3-gånger-3-grannskap, size=2 betyder 5-gånger-5, size=3 betyder 7-gånger-7, och så vidare.

Ett litet bildrutnät med ett markerat 3-gånger-3-delrutnät som representerar filtrets grannskap. En pil visar grannskapet glida en pixel åt höger. En andra pil visar det glida ned till nästa rad i slutet av raden. Utdatapixeln för varje position ritas under grannskapet, med en liten notering om att utdata är någon statistik av indatagrannskapet.

Grannskapet glider över bilden en pixel i taget, från övre vänstra till nedre högra hörnet. Varje utdatapixel är resultatet av att filtrets statistik tillämpas på indatagrannskapet som är centrerat kring den.

Större storlekar innebär större grannskap, vilket innebär jämnare (eller mer aggressiv) filtrering. Kostnaden växer med fönstrets area, så ett size=3-filter utför ungefär nio gånger så mycket arbete per pixel som ett size=1-filter. Det praktiska standardvalet för det mesta uppstädningsarbetet är size=1 eller size=2; använd större storlekar endast när små grannskap inte räcker för att undertrycka det särdrag som tillämpningen försöker undertrycka.

5.13.2. Medelvärdesfiltret

mean() ersätter varje pixel med det aritmetiska medelvärdet av dess grannskap. Resultatet jämnar ut variationer från pixel till pixel över fönstrets storlek, vilket gör det till det billigaste sättet att undertrycka brusstörningar från sensorn: högfrekventa variationer jämnas ut, lågfrekvent innehåll överlever.

Avvägningen är att även kanter och andra skarpa särdrag jämnas ut. En ljus kant som var en pixel bred före filtret är två eller tre pixlar bred efter ett size=1-medelvärdesfilter, med ljusstyrkan nedtonad vid flankerna. För ren brusreducering på en texturfattig bild (en ren vägg, insidan av en färgad markör) är avvägningen bra. För en livlig scen där kanter spelar roll passar ofta något av följande filter bättre.

img.mean(1)        # 3x3 box average -- fast, gentle smoothing
img.mean(2)        # 5x5 box average -- stronger, slower

5.13.3. Median, typvärde, mittpunkt

De tre andra statistiska grannskapsfiltren byter det enkla aritmetiska medelvärdet mot något som är mer robust mot avvikande värden.

median() returnerar medianen av grannskapet – värdet som hamnar i mitten av den sorterade listan av fönsterpixlar. En enda mycket ljus eller mycket mörk pixel i fönstret drar inte medianen; den blir bara ett av de förkastade extremvärdena. Den praktiska effekten är att medianfiltrering tar bort enstaka pixelstörningar och salt-och-peppar-brus utan att mjuka upp kanter på det sätt som mean gör. Kostnaden är mer beräkning per pixel – att sortera ett fönster är långsammare än att beräkna dess medelvärde – och resultatet är inte strikt ett medelvärde, vilket ibland spelar roll för efterföljande matematik.

En percentile-parameter (standard 0.5) flyttar det valda värdet bort från den strikta medianen. percentile=0.0 returnerar grannskapets minimum, percentile=1.0 dess maximum; mellanliggande värden väljer proportionellt mellan dem i det sorterade fönstret. Det ger median förmågan att framhäva mörka eller ljusa delar av grannskapet utan att förlora ordningsstatistikens robusthet mot avvikande värden.

mode() returnerar det vanligaste värdet i grannskapet. Användbart när brusmodellen är ”de flesta pixlar är korrekta, ett fåtal har förvanskats i varierande grad”, där det rätta svaret är vilket värde som än förekommer oftast – vilket medianen kan missa när de förvanskade värdena hopar sig på ena sidan av det sorterade fönstret.

midpoint() returnerar en viktad kombination av grannskapets minimum och maximumbias=0.5 ger mittpunkten mellan dem, bias=0.0 ger minimum, bias=1.0 ger maximum. Mindre vanligt använt än de andra men värt att känna till när målet specifikt är att extrahera mörka eller ljusa särdrag.

5.13.4. Bilateral, den kantbevarande varianten

bilateral() är det grannskapsfilter som är mest värt att förstå väl. Det ger samma utjämnande effekt som mean(), men med en extra begränsning: ju mer en grannskapspixel skiljer sig från mittpixeln, desto mindre räknas den in i medelvärdet. Resultatet jämnar ut insidan av varje enhetligt område utan att blöda över de kanter som skiljer dem åt, vilket är precis vad de flesta tillämpningar faktiskt vill ha.

Två parametrar styr hur aggressivt filtret nedvärderar pixlar:

  • color_sigma avgör hur färgskillnad påverkar viktningen. Mindre värden innebär att filtret är striktare med att nedvärdera pixlar som skiljer sig från mitten.

  • space_sigma avgör hur rumsligt avstånd påverkar viktningen. Mindre värden ger större vikt åt pixlar nära mitten.

Standardvärdena (color_sigma=0.1, space_sigma=1.0) är rimliga utgångspunkter; att finjustera dem är vanligtvis en fråga om att köra filtret på en exempelbildruta och justera tills kanterna är skarpa och insidorna är rena.

Bilateral är dyrare än median() och betydligt dyrare än mean(), så det är värt att ta till endast när det kantbevarande beteendet är det tillämpningen behöver.

5.13.5. Adaptiv tröskling

Filtren mean, median, mode och midpoint bär alla samma par av nyckelordsargument som förvandlar deras utdata till ett binärt tröskelvärde:

  • threshold=True växlar filtret till trösklingsläge.

  • offset=N förskjuter den lokala brytpunkten med N enheter före jämförelsen.

Mekaniken bygger direkt på filtrets vanliga beteende. Utan threshold=True beräknar filtret sin statistik över grannskapet och skriver den statistiken till utdatapixeln. Med threshold=True beräknar filtret samma statistik, jämför sedan källpixeln på samma position mot statistiken plus förskjutningen, och skriver formatets maxvärde om källan är större, annars noll.

Resultatet är en binär bild vars brytpunkt flyttar sig med den lokala ljusstyrkan över bildrutan. Ljusa områden får en hög brytpunkt, mörka områden får en låg brytpunkt, och en förgrundspixel som är lokalt ljusare än sina grannar matchar oavsett om den ligger i ett ljust eller ett mörkt område – vilket är precis det beteende som ett enda globalt tröskelvärde inte kunde åstadkomma på en ojämnt belyst bild.

img.mean(3, threshold=True, offset=5)

offset-parametern är där tillämpningen styr hur strikt testet är. En liten positiv förskjutning kräver att källpixeln är märkbart ljusare än sina grannar för att räknas som en matchning, vilket undertrycker falska positiva från sensorbrus på bekostnad av att svag förgrund tappas bort. En liten negativ förskjutning fångar svag förgrund på bekostnad av att en del brus släpps igenom. Valet beror på vad resten av pipelinen ska göra med den binära utdatan.

Tre bildpaneler i rad. Den första är en gråskalebildruta i indata med en ljusstyrkegradient och förgrundsmarkeringar utspridda med jämn mörkhet. Den andra panelen visar ett globalt tröskelvärde tillämpat på den: förgrunden klassificeras korrekt på den ljusa sidan, men hela den mörka sidan läses som förgrund eftersom sida och förgrund båda hamnar under brytpunkten. Den tredje panelen visar ett adaptivt tröskelvärde tillämpat på samma indata: förgrunden klassificeras korrekt över hela bildrutan.

Under ojämn belysning kan ett enda globalt tröskelvärde inte beskriva förgrunden vid varje position. Ett grannskapsfilter som körs med threshold=True ger en brytpunkt som flyttar sig med den lokala ljusstyrkan och klassificerar förgrunden korrekt över hela bildrutan.

Filterfamiljen kör den adaptiva trösklingen, så att välja rätt filter är viktigt: mean() för det billigaste adaptiva tröskelvärdet, median() när indatan har salt-och-peppar-brus som filtret bör avvisa innan den lokala brytpunkten beräknas.