Programování

Zpracování obrazu pomocí Java 2D

Zpracování obrazu je umění a věda manipulace s digitálními obrázky. Stojí jednou nohou pevně v matematice a druhou v estetice a je kritickou součástí grafických počítačových systémů. Pokud jste se někdy obtěžovali s vytvářením vlastních obrázků pro webové stránky, nepochybně oceníte důležitost možností manipulace s obrázky aplikace Photoshop pro čištění skenů a čištění méně než optimálních obrázků.

Pokud jste v JDK 1.0 nebo 1.1 prováděli jakékoli zpracování obrazu, pravděpodobně si pamatujete, že to bylo trochu tupé. Starý model výrobců a spotřebitelů obrazových dat je pro zpracování obrazu nepraktický. Před JDK 1.2 zahrnovalo zpracování obrazu MemoryImageSources, PixelGrabbers a další podobné arkány. Java 2D však poskytuje čistší a snadněji použitelný model.

Tento měsíc prozkoumáme algoritmy několika důležitých operací zpracování obrazu (ops) a ukážeme vám, jak je lze implementovat pomocí Java 2D. Ukážeme vám také, jak se tyto operace používají k ovlivnění vzhledu obrázku.

Protože zpracování obrazu je skutečně užitečná samostatná aplikace Java 2D, vytvořili jsme tento měsíc příklad, ImageDicer, aby byl pro vaše vlastní aplikace co nejvíce opakovaně použitelný. Tento jediný příklad ukazuje všechny techniky zpracování obrazu, které pokryjeme ve sloupci pro tento měsíc.

Krátce před vydáním tohoto článku společnost Sun vydala vývojovou sadu Java 1.2 Beta 4. Zdá se, že Beta 4 poskytuje lepší výkon pro naše příkladné operace zpracování obrazu, ale také přidává některé nové chyby zahrnující kontrolu hranic ConvolveOps. Tyto problémy ovlivňují příklady detekce a ostření hran, které používáme v naší diskusi.

Myslíme si, že tyto příklady jsou cenné, takže místo toho, abychom je úplně vynechali, jsme udělali kompromis: abychom zajistili jeho běh, ukázkový kód odráží změny Beta 4, ale zachovali jsme si čísla z provedení 1.2 Beta 3, abyste viděli operace funguje správně.

Doufejme, že Sun tyto chyby vyřeší před finálním vydáním Java 1.2.

Zpracování obrazu není raketová věda

Zpracování obrazu nemusí být obtížné. Ve skutečnosti jsou základní pojmy opravdu jednoduché. Koneckonců, obraz je jen obdélník barevných pixelů. Zpracování obrázku je prostě otázkou výpočtu nové barvy pro každý pixel. Nová barva každého pixelu může být založena na existující barvě pixelu, barvě okolních pixelů, dalších parametrech nebo kombinaci těchto prvků.

2D API zavádí přímý model zpracování obrazu, který vývojářům pomáhá manipulovat s těmito pixely obrazu. Tento model je založen na java.awt.image.BufferedImage třída a operace zpracování obrazu jako konvoluce a prahování jsou představovány implementacemi java.awt.image.BufferedImageOp rozhraní.

Implementace těchto operací je poměrně přímočará. Předpokládejme například, že již máte zdrojový obrázek jako a BufferedImage volala zdroj. Provedení operace znázorněné na obrázku výše by trvalo jen několik řádků kódu:

Práh 001 short [] = nový short [256]; 002 pro (int i = 0; i <256; i ++) 003 prahová hodnota [i] = (i <128)? (short) 0: (short) 255; 004 BufferedImageOp thresholdOp = 005 nový LookupOp (nový ShortLookupTable (0, práh), null); 006 BufferedImage cíl = thresholdOp.filter (zdroj, null); 

To je opravdu vše. Nyní se podívejme na kroky podrobněji:

  1. Vytvořte instanci obrazové operace podle vašeho výběru (řádky 004 a 005). Tady jsme použili a LookupOp, což je jedna z obrazových operací obsažených v implementaci Java 2D. Jako každá jiná operace s obrázkem implementuje BufferedImageOp rozhraní. O této operaci si povíme později.

  2. Zavolejte operaci filtr() metoda se zdrojovým obrazem (řádek 006). Zdroj je zpracován a cílový obraz je vrácen.

Pokud jste již vytvořili BufferedImage který bude obsahovat cílový obrázek, můžete jej předat jako druhý parametr filtr(). Pokud projdete nula, jak jsme udělali ve výše uvedeném příkladu, nový cíl BufferedImage je vytvořen.

2D API obsahuje několik těchto integrovaných obrazových operací. V tomto sloupci probereme tři: konvoluce,vyhledávací tabulky, a prahování. Další informace o zbývajících operacích dostupných v 2D API (Zdroje) najdete v dokumentaci Java 2D.

Konvoluce

A konvoluce operace umožňuje kombinovat barvy zdrojového pixelu a jeho sousedů a určit barvu cílového pixelu. Tato kombinace se zadává pomocí a jádro, lineární operátor, který určuje podíl každé barvy zdrojového pixelu použité k výpočtu barvy cílového pixelu.

Představte si jádro jako šablonu, která je překryta obrazem a provede konvoluci na jednom pixelu najednou. Jak je každý pixel svinut, šablona se přesune na další pixel ve zdrojovém obrazu a proces konvoluce se opakuje. Zdrojová kopie obrazu se používá pro vstupní hodnoty pro konvoluci a všechny výstupní hodnoty se ukládají do cílové kopie obrazu. Jakmile je operace konvoluce dokončena, vrátí se cílový obrázek.

Střed jádra lze považovat za překrytí spletitého zdrojového pixelu. Například operace konvoluce, která používá následující jádro, nemá žádný vliv na obrázek: každý cílový pixel má stejnou barvu jako jeho odpovídající zdrojový pixel.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

Zásadním pravidlem pro vytváření jader je, že všechny prvky by měly přidat až 1, pokud chcete zachovat jas obrazu.

Ve 2D API je konvoluce představována a java.awt.image.ConvolveOp. Můžete postavit a ConvolveOp pomocí jádra, které je reprezentováno instancí java.awt.image.Kernel. Následující kód konstruuje a ConvolveOp pomocí výše uvedeného jádra.

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é jádro (3, 3, identityKernel)); 

Operace konvoluce je užitečná při provádění několika běžných operací s obrázky, které za okamžik podrobně rozvedeme. Různá jádra produkují radikálně odlišné výsledky.

Nyní jsme připraveni ilustrovat některá jádra pro zpracování obrazu a jejich efekty. Náš nemodifikovaný obraz je Lady Agnew z Lochnaw, namaloval John Singer Sargent v letech 1892 a 1893.

Následující kód vytvoří a ConvolveOp který kombinuje stejné množství každého zdrojového pixelu a jeho sousedů. Výsledkem této techniky je efekt rozmazání.

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

Další běžné konvoluční jádro zdůrazňuje okraje v obrazu. Tato operace se běžně nazývá Detekce hrany. Na rozdíl od ostatních zde prezentovaných jader nepřesahují koeficienty tohoto jádra 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é jádro (3, 3, edgeKernel)); 

Můžete vidět, co toto jádro dělá, když se podíváte na koeficienty v jádře (řádky 002-004). Zamyslete se na chvíli nad tím, jak se jádro detekce hran používá k provozu v oblasti, která má úplně jednu barvu. Každý pixel skončí bez barvy (černý), protože barva okolních pixelů ruší barvu zdrojového pixelu. Světlé pixely obklopené tmavými pixely zůstanou jasné.

Všimněte si, o kolik tmavší je zpracovaný obraz ve srovnání s originálem. Stává se to proto, že prvky jádra detekce hran nepřidávají až 1.

Jednoduchou variantou detekce hran je zostření jádro. V tomto případě je zdrojový obrázek přidán do jádra detekce hran následujícím způsobem:

 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 

Ostřící jádro je ve skutečnosti pouze jedno možné jádro, které ostří obrázky.

Volba jádra 3 x 3 je poněkud libovolná. Můžete definovat jádra jakékoli velikosti a pravděpodobně ani nemusí být čtvercová. V JDK 1.2 Beta 3 a 4 však jiné než čtvercové jádro způsobilo selhání aplikace a jádro 5 x 5 nejzvláštnějším způsobem rozžvýkala obrazová data. Pokud nemáte pádný důvod odchýlit se od jader 3 x 3, nedoporučujeme to.

Možná vás také zajímá, co se stane na okraji obrazu. Jak víte, operace konvoluce zohledňuje sousedy zdrojových pixelů, ale zdrojové pixely na okrajích obrazu nemají sousedy na jedné straně. The ConvolveOp třída zahrnuje konstanty, které určují, jaké chování by mělo být na okrajích. The EDGE_ZERO_FILL konstanta určuje, že okraje cílového obrázku jsou nastaveny na 0. The EDGE_NO_OP konstanta určuje, že zdrojové pixely podél okraje obrazu jsou zkopírovány do cíle bez úpravy. Pokud nezadáte chování hrany při konstrukci a ConvolveOp, EDGE_ZERO_FILL se používá.

Následující příklad ukazuje, jak můžete vytvořit operátor zostření, který používá EDGE_NO_OP pravidlo (NO_OP je předán jako a ConvolveOp parametr v řádku 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 zostřit = nový ConvolveOp (007 nové jádro (3, 3, sharpKernel), 008 ConvolveOp.EDGE_NO_OP, null); 

Vyhledávací tabulky

Další univerzální operace s obrazem zahrnuje použití a vyhledávací tabulka. Pro tuto operaci jsou barvy zdrojových pixelů převedeny do barev cílových pixelů pomocí tabulky. Pamatujte, že barva se skládá z červené, zelené a modré složky. Každá komponenta má hodnotu od 0 do 255. Tři tabulky s 256 položkami jsou dostatečné k překladu jakékoli zdrojové barvy na cílovou barvu.

The java.awt.image.LookupOp a java.awt.image.LookupTable třídy zapouzdřují tuto operaci. Můžete definovat samostatné tabulky pro každou barevnou složku nebo použít jednu tabulku pro všechny tři. Podívejme se na jednoduchý příklad, který převádí barvy každé komponenty. Jediné, co musíme udělat, je vytvořit pole, které představuje tabulku (řádky 001-003). Pak vytvoříme a Vyhledávací tabulka z pole a LookupOp z Vyhledávací tabulka (řádky 004-005).

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

Vyhledávací tabulka má dvě podtřídy, ByteLookupTable a ShortLookupTable, které zapouzdřují byte a krátký pole. Pokud vytvoříte a Vyhledávací tabulka který nemá záznam pro žádnou vstupní hodnotu, bude vyvolána výjimka.

Tato operace vytvoří efekt, který vypadá jako barevný negativ v konvenčním filmu. Pamatujte také, že použití této operace dvakrát obnoví původní obrázek; v zásadě berete negativ negativu.

Co kdybyste chtěli ovlivnit pouze jednu z barevných složek? Snadný. Postavíte a Vyhledávací tabulka se samostatnými tabulkami pro každou z červených, zelených a modrých komponent. Následující příklad ukazuje, jak vytvořit a LookupOp který převrátí pouze modrou složku barvy. Stejně jako u předchozího operátoru inverze, použití tohoto operátoru dvakrát obnoví původní obrázek.

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

Plakátování je další pěkný efekt, který můžete použít pomocí a LookupOp. Posterizace zahrnuje snížení počtu barev použitých k zobrazení obrázku.

A LookupOp může dosáhnout tohoto efektu pomocí tabulky, která mapuje vstupní hodnoty na malou sadu výstupních hodnot. Následující příklad ukazuje, jak lze vstupní hodnoty mapovat na osm konkrétních hodnot.

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

Prahové hodnoty

Poslední obrazová operace, kterou prozkoumáme, je prahování. Prahová hodnota zvyšuje viditelnost změn barev napříč „hranicí“ nebo prahovou hodnotou určenou programátorem (podobně jako u vrstevnic na mapě jsou hranice výšek jasnější). Tato technika používá zadanou prahovou hodnotu, minimální hodnotu a maximální hodnotu k řízení hodnot barevné složky pro každý pixel obrázku. Barevným hodnotám pod prahovou hodnotou je přiřazena minimální hodnota. Hodnotám nad prahovou hodnotou je přiřazena maximální hodnota.

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