Programovanie

Spracovanie obrazu pomocou Java 2D

Spracovanie obrazu je umenie a veda manipulácie s digitálnymi obrázkami. Jednou nohou pevne stojí v matematike a druhou v estetike a je kritickou súčasťou grafických počítačových systémov. Ak ste sa niekedy obťažovali s vytváraním vlastných obrázkov pre webové stránky, nepochybne oceníte dôležitosť možností manipulácie s obrázkami vo Photoshope pre čistenie skenov a čistenie nie optimálnych obrázkov.

Ak ste robili prácu na spracovaní obrazu v JDK 1.0 alebo 1.1, asi si pamätáte, že to bolo trochu tupé. Starý model výrobcov a spotrebiteľov obrazových údajov je na spracovanie obrazu nepraktický. Pred JDK 1.2 sa týkalo spracovania obrazu MemoryImageSources, PixelGrabbers a ďalšie podobné arkány. Java 2D však poskytuje čistejší a ľahšie použiteľný model.

Tento mesiac preskúmame algoritmy niekoľkých dôležitých operácií spracovania obrazu (ops) a ukážeme vám, ako je možné ich implementovať pomocou Java 2D. Ukážeme vám tiež, ako sa tieto operácie používajú na ovplyvnenie vzhľadu obrázka.

Pretože spracovanie obrazu je skutočne užitočná samostatná aplikácia Java 2D, vytvorili sme tento mesiac príklad, ImageDicer, aby bol pre vaše vlastné aplikácie čo najviac opakovane použiteľný. Tento jediný príklad demonštruje všetky techniky spracovania obrázkov, ktorým sa budeme venovať v stĺpci tohto mesiaca.

Upozorňujeme, že krátko pred vydaním tohto článku spoločnosť Sun vydala vývojovú súpravu Java 1.2 Beta 4. Zdá sa, že verzia Beta 4 poskytuje lepší výkon pre naše príkladné operácie spracovania obrazu, ale pridáva aj niekoľko nových chýb týkajúcich sa kontroly hraníc ConvolveOps. Tieto problémy ovplyvňujú príklady detekcie a ostrenia hrán, ktoré používame v našej diskusii.

Myslíme si, že tieto príklady sú cenné, a preto sme ich radšej nevynechali, ale kompromitovali sme: aby sme zaistili jeho spustenie, ukážkový kód odráža zmeny verzie Beta 4, ale ponechali sme si údaje z vykonania verzie 1.2 Beta 3, aby ste videli operácie pracuje správne.

Dúfajme, že Sun bude tieto chyby riešiť pred finálnym vydaním Java 1.2.

Spracovanie obrazu nie je žiadna raketová veda

Spracovanie obrazu nemusí byť zložité. V skutočnosti sú základné pojmy skutočne jednoduché. Obrázok je koniec koncov iba obdĺžnik farebných pixelov. Spracovanie obrázka je jednoducho otázka výpočtu novej farby pre každý pixel. Nová farba každého pixelu môže byť založená na existujúcej farbe pixelov, farbe okolitých pixelov, iných parametroch alebo kombinácii týchto prvkov.

2D API predstavuje jednoduchý model spracovania obrazu, ktorý vývojárom pomáha manipulovať s týmito obrazovými pixelmi. Tento model je založený na java.awt.image.BufferedImage triedy a operácie spracovania obrazu ako konvolúcia a prahovanie sú predstavované implementáciami java.awt.image.BufferedImageOp rozhranie.

Implementácia týchto operácií je pomerne jednoduchá. Predpokladajme napríklad, že už máte zdrojový obrázok ako a BufferedImage zavolal zdroj. Vykonanie operácie znázornenej na obrázku vyššie by trvalo iba pár riadkov kódu:

Prahová hodnota 001 short [] = nová krátka hodnota [256]; 002 pre (int i = 0; i <256; i ++) prahová hodnota 003 [i] = (i <128)? (krátke) 0: (krátke) 255; 004 BufferedImageOp thresholdOp = 005 nový LookupOp (nový ShortLookupTable (0, threshold), null); 006 Cieľ BufferedImage = thresholdOp.filter (zdroj, null); 

To je naozaj všetko. Teraz sa pozrime na kroky podrobnejšie:

  1. Vytvorte okamžitú obrazovú operáciu podľa vášho výberu (riadky 004 a 005). Tu sme použili a LookupOp, čo je jedna z obrazových operácií zahrnutých do implementácie Java 2D. Ako každá iná obrazová operácia, aj táto implementuje BufferedImageOp rozhranie. O tejto operácii si ešte povieme neskôr.

  2. Zavolajte na operáciu filter () metóda so zdrojovým obrázkom (riadok 006). Zdroj sa spracuje a vráti sa cieľový obrázok.

Ak ste si už vytvorili BufferedImage ktorý bude obsahovať cieľový obrázok, môžete ho odovzdať ako druhý parameter filter (). Ak prejdete nulový, ako sme to urobili v príklade vyššie, nový cieľ BufferedImage je vytvorený.

2D API obsahuje niekoľko týchto zabudovaných obrazových operácií. V tomto stĺpci si povieme tri: konvolúcia,vyhľadávacie tabuľky, a prahovanie. V dokumentácii Java 2D nájdete ďalšie informácie o zostávajúcich operáciách dostupných v rozhraní 2D API (Zdroje).

Konvolúcia

A konvolúcia Táto operácia umožňuje kombinovať farby zdrojového pixelu a jeho susedov s cieľom určiť farbu cieľového pixela. Táto kombinácia je špecifikovaná pomocou a jadro, lineárny operátor, ktorý určuje podiel každej farby zdrojového pixela použitej na výpočet farby cieľového pixelu.

Predstavte si jadro ako šablónu, ktorá je prekrytá obrázkom, aby sa mohla uskutočniť konverzia na jednom pixeli súčasne. Keď je každý pixel zmotaný, šablóna sa presunie na ďalší pixel v zdrojovom obrázku a proces konvolúcie sa opakuje. Pre vstupné hodnoty pre konvolúciu sa použije zdrojová kópia obrázka a všetky výstupné hodnoty sa uložia do cieľovej kópie obrázka. Po dokončení operácie konvolúcie sa vráti cieľový obrázok.

Stred jadra sa dá považovať za prekrývajúci spletitý zdrojový pixel. Napríklad operácia konvolucie, ktorá používa nasledujúce jadro, nemá na obrázok žiadny vplyv: každý cieľový pixel má rovnakú farbu ako jeho zodpovedajúci zdrojový pixel.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

Zásadným pravidlom pre vytváranie jadier je, že ak chcete zachovať jasnosť obrázka, všetky prvky by mali obsahovať maximálne 1.

V 2D API predstavuje konvolúciu a java.awt.image.ConvolveOp. Môžete zostrojiť a ConvolveOp pomocou jadra, ktoré predstavuje inštancia java.awt.image.Kernel. Nasledujúci kód vytvára a ConvolveOp pomocou vyššie uvedeného jadra.

001 float [] identityKernel = {002 0,0f, 0,0f, 0,0f, 003 0,0f, 1,0f, 0,0f, 004 0,0f, 0,0f, 0,0f 005}; 006 BufferedImageOp identity = 007 nový ConvolveOp (nové jadro (3, 3, identityKernel)); 

Konvolučná operácia je užitočná pri vykonávaní niekoľkých bežných operácií s obrázkami, ktoré podrobne rozpíšeme o chvíľu. Rôzne jadrá poskytujú radikálne odlišné výsledky.

Teraz sme pripravení ilustrovať niektoré jadrá na spracovanie obrázkov a ich účinky. Náš nemodifikovaný obraz je Lady Agnew z Lochnaw, namaľoval John Singer Sargent v rokoch 1892 a 1893.

Nasledujúci kód vytvára a ConvolveOp ktorá kombinuje rovnaké množstvá každého zdrojového pixelu a jeho susedov. Výsledkom tejto techniky je efekt rozmazania.

001 float deviaty = 1,0f / 9,0f; 002 float [] blurKernel = {003 deviaty, deviaty, deviaty, 004 deviaty, deviaty, deviaty, 005 deviaty, deviaty, deviaty 006}; 007 BufferedImageOp blur = nový ConvolveOp (nové jadro (3, 3, blurKernel)); 

Ďalšie bežné konvolučné jadro zdôrazňuje okraje na obrázku. Táto operácia sa bežne nazýva detekcia hrany. Na rozdiel od iných tu prezentovaných jadier, koeficienty tohto jadra nesčítajú až 1.

001 float [] edgeKernel = {002 0,0f, -1,0f, 0,0f, 003 -1,0f, 4,0f, -1,0f, 004 0,0f, -1,0f, 0,0f 005}; 006 BufferedImageOp edge = nový ConvolveOp (nové jadro (3, 3, edgeKernel)); 

To, čo toto jadro robí, môžete zistiť pri pohľade na koeficienty v jadre (riadky 002-004). Zamyslite sa na chvíľu nad tým, ako sa jadro detekcie okrajov používa na prácu v oblasti, ktorá má úplne jednu farbu. Každý pixel nebude mať žiadnu farbu (čiernu), pretože farba okolitých pixelov ruší farbu zdrojového pixela. Svetlé pixely obklopené tmavými pixelmi zostanú svetlé.

Všimnite si, o koľko tmavší je spracovaný obrázok v porovnaní s originálom. Stáva sa to preto, že prvky jadra na detekciu hrán nepridávajú až 1.

Jednoduchou variáciou na detekciu hrany je ostrenie jadro. V takom prípade sa zdrojový obrázok pridá do jadra detekcie okrajov nasledovne:

 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 -1.0 0.0 -1.0 4.0 -1.0 + 0.0 1.0 0.0 = -1.0 5.0 -1.0 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 -1.0 0.0 

Ostriace jadro je v skutočnosti iba jedno možné jadro, ktoré ostrí obrázky.

Výber jadra 3 x 3 je trochu svojvoľný. Môžete definovať jadrá akejkoľvek veľkosti a pravdepodobne nemusia byť ani štvorcové. V JDK 1.2 Beta 3 a 4 však štvorcové jadro spôsobilo zlyhanie aplikácie a jadro 5 x 5 najzvláštnejším spôsobom rozhryzlo obrazové dáta. Pokiaľ nemáte pádny dôvod zablúdiť z jadier 3 x 3, neodporúčame to.

Možno by vás tiež zaujímalo, čo sa deje na okraji obrázka. Ako viete, operácia konvolucie berie do úvahy susedov zdrojových pixelov, ale zdrojové pixely na okrajoch obrázka nemajú susedov na jednej strane. The ConvolveOp trieda obsahuje konštanty, ktoré určujú, aké správanie by malo byť na okrajoch. The EDGE_ZERO_FILL konštanta určuje, že okraje cieľového obrázka sú nastavené na 0. The EDGE_NO_OP konštanta určuje, že zdrojové pixely pozdĺž okraja obrázka sa skopírujú do cieľa bez úpravy. Ak neurčíte správanie hrany pri konštrukcii a ConvolveOp, EDGE_ZERO_FILL sa používa.

Nasledujúci príklad ukazuje, ako môžete vytvoriť operátor ostrenia, ktorý používa EDGE_NO_OP pravidlo (NO_OP sa odovzdáva ako a ConvolveOp parameter v riadku 008):

001 float [] sharpKernel = {002 0,0f, -1,0f, 0,0f, 003 -1,0f, 5,0f, -1,0f, 004 0,0f, -1,0f, 0,0f 005}; 006 BufferedImageOp zostrenie = nový ConvolveOp (007 nové jadro (3, 3, sharpKernel), 008 ConvolveOp.EDGE_NO_OP, null); 

Vyhľadávacie tabuľky

Ďalšia všestranná operácia obrazu zahŕňa použitie a Vyhľadávacia tabuľka. Pri tejto operácii sa farby zdrojových pixelov prevedú do farieb cieľových pixelov pomocou tabuľky. Pamätajte, že farba sa skladá z červenej, zelenej a modrej zložky. Každý komponent má hodnotu od 0 do 255. Tri tabuľky s 256 položkami sú dostatočné na preloženie akejkoľvek zdrojovej farby na cieľovú farbu.

The java.awt.image.LookupOp a java.awt.image.LookupTable triedy túto operáciu zapuzdrujú. Môžete definovať samostatné tabuľky pre každú farebnú zložku alebo použiť jednu tabuľku pre všetky tri. Pozrime sa na jednoduchý príklad, ktorý invertuje farby všetkých komponentov. Všetko, čo musíme urobiť, je vytvoriť pole, ktoré predstavuje tabuľku (riadky 001-003). Potom vytvoríme a Vyhľadávacia tabuľka z poľa a a LookupOp z Vyhľadávacia tabuľka (riadky 004-005).

001 short [] invert = nový short [256]; 002 pre (int i = 0; i <256; i ++) 003 invert [i] = (krátke) (255 - i); 004 BufferedImageOp invertOp = nový LookupOp (005 nový ShortLookupTable (0, invert), null); 

Vyhľadávacia tabuľka má dve podtriedy, ByteLookupTable a ShortLookupTable, ktoré zapuzdrujú bajt a krátky polia. Ak vytvoríte a Vyhľadávacia tabuľka ktorý nemá záznam pre žiadnu vstupnú hodnotu, bude vyvolaná výnimka.

Táto operácia vytvorí efekt, ktorý vyzerá ako farebný negatív v konvenčnom filme. Nezabudnite, že dvojnásobné použitie tejto operácie obnoví pôvodný obrázok; v podstate berieš negatívum negatívov.

Čo keby ste chceli ovplyvniť iba jednu z farebných zložiek? Ľahké. Konštruujete a Vyhľadávacia tabuľka so samostatnými tabuľkami pre každú z červených, zelených a modrých zložiek. Nasledujúci príklad ukazuje, ako vytvoriť a LookupOp že iba invertuje modrú zložku farby. Rovnako ako v prípade predchádzajúceho operátora inverzie, aj pri použití tohto operátora sa dvakrát obnoví pôvodný obrázok.

001 short [] invert = nový short [256]; 002 short [] straight = new short [256]; 003 pre (int i = 0; i <256; i ++) {004 invert [i] = (krátke) (255 - i); 005 priamy [i] = (krátky) i; 006} 007 short [] [] blueInvert = new short [] [] {straight, straight, invert}; 008 BufferedImageOp blueInvertOp = 009 nový LookupOp (nový ShortLookupTable (0, blueInvert), null); 

Posterizácia je ďalší pekný efekt, ktorý môžete použiť pomocou a LookupOp. Posterizácia zahŕňa zníženie počtu farieb použitých na zobrazenie obrázka.

A LookupOp tento efekt je možné dosiahnuť použitím tabuľky, ktorá mapuje vstupné hodnoty na malú množinu výstupných hodnôt. Nasledujúci príklad ukazuje, ako je možné mapovať vstupné hodnoty na osem konkrétnych hodnôt.

001 short [] posterize = nový short [256]; 002 pre (int i = 0; i <256; i ++) 003 posterize [i] = (krátke) (i - (i% 32)); 004 BufferedImageOp posterizeOp = 005 nový LookupOp (nový ShortLookupTable (0, posterize), null); 

Prahové hodnoty

Posledná obrazová operácia, ktorú preskúmame, je prahovanie. Prahové hodnoty umožňujú zreteľnejšie zmeny farieb na „hranici“ alebo prahu určenej programátorom (podobne ako obrysové čiary na mape zvýrazňujú hranice nadmorskej výšky). Táto technika používa určenú prahovú hodnotu, minimálnu hodnotu a maximálnu hodnotu na riadenie hodnôt farebných zložiek pre každý pixel obrázka. Farebným hodnotám pod prahovou hodnotou je priradená minimálna hodnota. Hodnotám nad prahovou hodnotou je priradená maximálna hodnota.

$config[zx-auto] not found$config[zx-overlay] not found