5.13. Lineární a sousedské filtry

Operace s matematikou pixelů z dřívější části kapitoly kombinovaly dva obrazy bod po bodu. Filtry dělají podobnou práci jiným způsobem: hodnotu každého výstupního pixelu počítají z malého okolí vstupních pixelů obklopujících odpovídající pozici. Výstup na pozici (x, y) je nějaká statistika – průměr, medián, nejčastější hodnota – vstupních pixelů v malém čtverci se středem v (x, y).

Tato drobná změna pohledu – přechod od jednoho pixelu po druhém k oknu pixelů najednou – je to, co umožňuje fungovat celé rodině užitečných operací. Jednoduché zprůměrování přes malé okno vyhladí šum senzoru. Medián přes stejné okno odstraní jednopixelové tečky, aniž by změkčoval hrany do té míry. Bilaterální zprůměrování odmítá vyhlazovat přes hranice se silným kontrastem, takže zachovává hrany objektů, zatímco čistí textury uvnitř nich. Okolí je jednotkou práce; volba statistiky rozhoduje o tom, co filtr dělá.

5.13.1. Velikost jádra

Každý sousedský filtr přijímá parametr size, který nastavuje poloměr okna v pixelech. Samotné okno je čtvercové a pokrývá (2 * size + 1) pixelů na každé straně – takže size=1 znamená okolí 3 na 3, size=2 znamená 5 na 5, size=3 znamená 7 na 7 a tak dále.

Malá mřížka obrazu se zvýrazněnou podmřížkou 3 na 3 představující okolí filtru. Šipka ukazuje, jak se okolí posouvá o jeden pixel doprava. Druhá šipka ukazuje, jak se na konci řádku posouvá dolů na další řádek. Výstupní pixel pro každou pozici je nakreslen pod okolím, s malou poznámkou, která říká, že výstup je nějaká statistika vstupního okolí.

Okolí se posouvá přes obraz po jednom pixelu, zleva nahoře doprava dolů. Každý výstupní pixel je výsledkem aplikace statistiky filtru na vstupní okolí se středem v něm.

Větší velikosti znamenají větší okolí, což znamená hladší (nebo agresivnější) filtrování. Náklady rostou s plochou okna, takže filtr size=3 vykoná na pixel asi devětkrát více práce než filtr size=1. Praktickou výchozí volbou pro většinu čisticí práce je size=1 nebo size=2; po větších velikostech sahejte jen tehdy, když malá okolí nestačí k potlačení příznaku, který se aplikace snaží potlačit.

5.13.2. Průměrový filtr

mean() nahrazuje každý pixel aritmetickým průměrem jeho okolí. Výsledek vyhlazuje variaci mezi pixely přes velikost okna, což z něj činí nejlevnější způsob, jak potlačit tečky šumu senzoru: vysokofrekvenční variace se zprůměruje, nízkofrekvenční obsah přežije.

Kompromisem je, že se zprůměrují i hrany a další ostré příznaky. Jasná hrana, která byla před filtrem široká jeden pixel, je po průměrovém filtru size=1 široká dva nebo tři pixely, s jasem zvolna klesajícím na okrajích. Pro čisté potlačení šumu na obraze chudém na textury (čistá stěna, vnitřek barevné značky) je tento kompromis v pořádku. Pro rušnou scénu, kde na hranách záleží, je obvykle vhodnější některý z následujících filtrů.

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

5.13.3. Medián, modus, střední bod

Zbylé tři statistické sousedské filtry vyměňují jednoduchý aritmetický průměr za něco odolnějšího vůči odlehlým hodnotám.

median() vrací medián okolí – hodnotu, která skončí uprostřed seřazeného seznamu pixelů okna. Jediný velmi jasný nebo velmi tmavý pixel v okně medián netáhne; stane se prostě jedním z vyřazených extrémů. Praktickým efektem je, že mediánové filtrování odstraňuje jednopixelové tečky a šum typu sůl a pepř, aniž by změkčovalo hrany tak jako mean. Cenou je více výpočtů na pixel – řazení okna je pomalejší než jeho průměrování – a výsledek není striktně průměr, na čemž někdy v navazující matematice záleží.

Parametr percentile (výchozí 0.5) posouvá zvolenou hodnotu mimo striktní medián. percentile=0.0 vrací minimum okolí, percentile=1.0 maximum; mezilehlé hodnoty volí poměrně mezi nimi v seřazeném okně. To dává median schopnost zdůraznit tmavé nebo jasné části okolí bez ztráty odolnosti vůči odlehlým hodnotám, kterou pořádková statistika přináší.

mode() vrací nejčastější hodnotu v okolí. Užitečné, když je model šumu „většina pixelů je správná, několik bylo různou měrou poškozeno“, kde správnou odpovědí je ta hodnota, která se objevuje nejčastěji – což medián může minout, když se poškozené hodnoty nahromadí na jedné straně seřazeného okna.

midpoint() vrací váženou kombinaci minima a maxima okolí – bias=0.5 dává střední bod mezi nimi, bias=0.0 dává minimum, bias=1.0 dává maximum. Používá se méně často než ostatní, ale je dobré o něm vědět, když je cílem konkrétně extrakce tmavých nebo jasných příznaků.

5.13.4. Bilaterální filtr, varianta zachovávající hrany

bilateral() je sousedský filtr, který se nejvíce vyplatí dobře pochopit. Vytváří vyhlazovací efekt jako mean(), ale s jedním omezením navíc: čím více se pixel v okolí liší od středového pixelu, tím méně se počítá do průměru. Výsledek vyhladí vnitřek každé jednolité oblasti, aniž by prosakoval přes hrany, které je oddělují, což je přesně to, co většina aplikací ve skutečnosti chce.

Dva parametry řídí, jak agresivně filtr potlačuje pixely:

  • color_sigma rozhoduje, jak barevný rozdíl ovlivňuje váhování. Menší hodnoty znamenají, že filtr je přísnější při potlačování pixelů, které se liší od středu.

  • space_sigma rozhoduje, jak prostorová vzdálenost ovlivňuje váhování. Menší hodnoty dávají větší váhu pixelům blízko středu.

Výchozí hodnoty (color_sigma=0.1, space_sigma=1.0) jsou rozumnými výchozími body; jejich ladění je obvykle otázkou spuštění filtru na vzorovém snímku a úprav, dokud nejsou hrany ostré a vnitřky čisté.

Bilaterální filtr je nákladnější než median() a podstatně nákladnější než mean(), takže se po něm vyplatí sáhnout jen tehdy, když aplikace potřebuje právě chování zachovávající hrany.

5.13.5. Adaptivní prahování

Filtry mean, median, mode a midpoint všechny nesou stejnou dvojici klíčových argumentů, které jejich výstup mění na binární práh:

  • threshold=True přepíná filtr do režimu prahování.

  • offset=N posouvá lokální mez o N jednotek před porovnáním.

Mechanika přímo navazuje na běžné chování filtru. Bez threshold=True filtr počítá svou statistiku přes okolí a tuto statistiku zapisuje do výstupního pixelu. S threshold=True filtr počítá stejnou statistiku, poté porovná zdrojový pixel na stejné pozici se statistikou plus offsetem a zapíše maximální hodnotu formátu, je-li zdroj větší, jinak nulu.

Výsledkem je binární obraz, jehož mez se pohybuje s lokálním jasem napříč snímkem. Jasné oblasti dostanou vysokou mez, tmavé oblasti nízkou mez a pixel popředí, který je lokálně jasnější než jeho sousedé, odpovídá bez ohledu na to, zda leží v jasné nebo tmavé oblasti – což je přesně chování, jaké by jediný globální práh na nerovnoměrně osvětleném obraze nedokázal vytvořit.

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

Parametr offset je místo, kde aplikace řídí, jak přísný test je. Malý kladný offset vyžaduje, aby byl zdrojový pixel měřitelně jasnější než jeho sousedé, než se započítá jako shoda, což potlačuje falešně pozitivní výsledky šumu senzoru za cenu vynechání slabého popředí. Malý záporný offset zachytí slabé popředí za cenu propuštění části šumu. Volba závisí na tom, co se zbytek řetězce s binárním výstupem chystá udělat.

Tři obrazové panely v řadě. První je vstupní snímek ve stupních šedi s gradientem jasu a značkami popředí rozesetými napříč při jednotné tmavosti. Druhý panel ukazuje globální práh na něj aplikovaný: popředí je správně klasifikováno na jasné straně, ale celá tmavá strana se čte jako popředí, protože pozadí i popředí spadají pod mez. Třetí panel ukazuje adaptivní práh aplikovaný na stejný vstup: popředí je správně klasifikováno napříč celým snímkem.

Při nerovnoměrném osvětlení nedokáže jediný globální práh popsat popředí na každé pozici. Sousedský filtr spuštěný s threshold=True vytváří mez, která se pohybuje s lokálním jasem a klasifikuje popředí správně napříč celým snímkem.

Adaptivní práh provádí celá rodina filtrů, takže výběr správného filtru je důležitý: mean() pro nejlevnější adaptivní práh, median(), když má vstup šum typu sůl a pepř, který by měl filtr odmítnout dříve, než spočítá lokální mez.