Ohjelmointi

Kuvankäsittely Java 2D: llä

Kuvankäsittely on digitaalisten kuvien manipuloinnin taidetta ja tiedettä. Se seisoo yhdellä jalalla tukevasti matematiikassa ja toisella estetiikassa, ja se on kriittinen osa graafisia tietojärjestelmiä. Jos olet koskaan vaivautunut luomaan omia kuvia verkkosivuille, tulet epäilemättä ymmärtämään Photoshopin kuvankäsittelyominaisuuksien merkitystä skannausten puhdistamisessa ja optimaalista vähemmän optimaalisten kuvien puhdistamisessa.

Jos teit jotain kuvankäsittelytyötä JDK 1.0: ssa tai 1.1: ssä, muistat todennäköisesti, että se oli hieman tylsä. Vanha kuvadatan tuottajien ja kuluttajien malli on hankala kuvankäsittelylle. Ennen JDK 1.2: ta mukana kuvankäsittely MemoryImageSources, PixelGrabbers, ja muut sellaiset arcana. Java 2D tarjoaa kuitenkin puhtaamman ja helppokäyttöisemmän mallin.

Tässä kuussa tarkastelemme useiden tärkeiden kuvankäsittelytoimintojen taustalla olevia algoritmeja (ops) ja näyttää kuinka ne voidaan toteuttaa Java 2D: n avulla. Näytämme myös, miten näitä toimintoja käytetään kuvan ulkonäköön.

Koska kuvankäsittely on aidosti hyödyllinen erillinen Java 2D -sovellus, olemme rakentaneet tämän kuukauden esimerkin ImageDicerin mahdollisimman uudelleenkäytettäväksi omille sovelluksillesi. Tämä yksittäinen esimerkki osoittaa kaikki kuvankäsittelytekniikat, jotka käsittelemme tämän kuukauden sarakkeessa.

Huomaa, että vähän ennen tämän artikkelin julkaisua Sun julkaisi Java 1.2 Beta 4 -kehityspaketin. Beeta 4 näyttää antavan paremman suorituskyvyn esimerkkikuvankäsittelytoiminnoillemme, mutta se lisää myös joitain uusia virheitä, joihin liittyy rajojen tarkistus ConvolveOps. Nämä ongelmat vaikuttavat reunojen havaitsemiseen ja teroittamiseen, joita käytämme keskustelussamme.

Mielestämme nämä esimerkit ovat arvokkaita, joten sen sijaan, että jätettäisimme ne kokonaan pois, kompromissimme: sen varmistamiseksi, että esimerkkikoodi heijastaa Beta 4 -muutoksia, mutta olemme säilyttäneet luvut 1.2 Beta 3 -suorituskyvystä, jotta voit nähdä toiminnot toimii oikein.

Toivottavasti Sun korjaa nämä virheet ennen viimeistä Java 1.2 -julkaisua.

Kuvankäsittely ei ole raketitiede

Kuvankäsittelyn ei tarvitse olla vaikeaa. Itse asiassa peruskäsitteet ovat todella yksinkertaisia. Kuva on loppujen lopuksi vain suorakulmio värillisiä pikseleitä. Kuvan käsittely on yksinkertaisesti uuden värin laskeminen jokaiselle pikselille. Jokaisen pikselin uusi väri voi perustua olemassa olevaan pikseliväriin, ympäröivien pikselien väriin, muihin parametreihin tai näiden elementtien yhdistelmään.

2D-sovellusliittymä esittelee yksinkertaisen kuvankäsittelymallin, jonka avulla kehittäjät voivat käsitellä näitä kuvapikseleitä. Tämä malli perustuu java.awt.image.BufferedImage luokka ja kuvankäsittelytoiminnot, kuten kääntyminen ja kynnys ovat edustettuina java.awt.image.BufferedImageOp käyttöliittymä.

Näiden toimintojen toteuttaminen on suhteellisen yksinkertaista. Oletetaan esimerkiksi, että sinulla on jo lähdekuva a Puskuroitu kuva olla nimeltään lähde. Yllä olevassa kuvassa esitetyn toiminnon suorittaminen vie vain muutaman koodirivin:

001 lyhyt [] kynnys = uusi lyhyt [256]; 002 (int i = 0; i <256; i ++) 003 -kynnykselle [i] = (i <128)? (lyhyt) 0: (lyhyt) 255; 004 BufferedImageOp raja-arvo = 005 uusi LookupOp (uusi ShortLookupTable (0, kynnys), nolla); 006 BufferedImage määränpää = raja-arvo.suodatin (lähde, nolla); 

Siinä on oikeastaan ​​kaikki. Katsotaanpa nyt vaiheet tarkemmin:

  1. Välitön valitsemasi kuvan käyttö (rivit 004 ja 005). Tässä käytimme a LookupOp, joka on yksi Java 2D -toteutukseen sisältyvistä kuvatoiminnoista. Kuten mikä tahansa muu kuvatoiminto, se toteuttaa Puskuroitu kuva käyttöliittymä. Puhumme lisää tästä toiminnasta myöhemmin.

  2. Soita operaatioon suodattaa() menetelmä lähdekuvan kanssa (rivi 006). Lähde käsitellään ja kohdekuva palautetaan.

Jos olet jo luonut a Puskuroitu kuva johon tulee kohdekuva, voit välittää sen toiseksi parametriksi suodattaa(). Jos ohitat tyhjä, kuten teimme yllä olevassa esimerkissä, uusi määränpää Puskuroitu kuva on luotu.

2D-sovellusliittymä sisältää muutaman näistä sisäänrakennetuista kuvatoiminnoista. Keskustelemme kolmesta tässä sarakkeessa: kääntyminen,hakutaulukot, ja kynnysarvo. Lisätietoja Java 2D -sovellusliittymässä (Resurssit) käytettävissä olevista toiminnoista on Java 2D -dokumentaatiossa.

Konvoluutio

A kääntyminen toiminnon avulla voit yhdistää lähdepikselin ja sen naapureiden värit kohdepikselin värin määrittämiseksi. Tämä yhdistelmä määritetään käyttämällä a ydin, lineaarinen operaattori, joka määrittää kunkin lähdepikselivärin osuuden, jota käytetään kohdepikselivärin laskemiseen.

Ajattele ydintä mallina, joka on kuvan päällä, jotta konvoluutio voidaan suorittaa yhdellä pikselillä kerrallaan. Kun jokainen pikseli on sekoitettu, malli siirretään lähdekuvan seuraavaan pikseliin ja konvoluutioprosessi toistetaan. Kuvan lähdekopiota käytetään kierron syöttöarvoihin, ja kaikki lähtöarvot tallennetaan kuvan kohdekopioon. Kun konvoluutio on valmis, kohdekuva palautetaan.

Ytimen keskuksen voidaan ajatella olevan sekoitettavan lähdepikselin päällekkäinen. Esimerkiksi seuraavaa ydintä käyttävällä konvoluutiooperaatiolla ei ole vaikutusta kuvaan: jokaisella kohdepikselillä on sama väri kuin vastaavalla lähdepikselillä.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

Ytimen luomisen perussääntö on, että kaikkien elementtien tulee olla yhteensä 1, jos haluat säilyttää kuvan kirkkauden.

2D-sovellusliittymässä konvoluutiota edustaa a java.awt.image.ConvolveOp. Voit rakentaa a ConvolveOp käyttämällä ydintä, jota edustaa java.awt.image.ydin. Seuraava koodi muodostaa a ConvolveOp käyttämällä yllä esitettyä ydintä.

001 float [] identiteettiKernel = {002 0,0f, 0,0f, 0,0f, 003 0,0f, 1,0f, 0,0f, 004 0,0f, 0,0f, 0,0f 005}; 006 BufferedImageOp-identiteetti = 007 uusi ConvolveOp (uusi ydin (3, 3, identiteetin ydin)); 

Konvoluutiooperaatio on hyödyllinen useiden yleisten operaatioiden suorittamisessa kuville, jotka tarkennamme hetkessä. Eri ytimet tuottavat radikaalisti erilaisia ​​tuloksia.

Nyt olemme valmiita havainnollistamaan joitain kuvankäsittelyydinjä ja niiden vaikutuksia. Muokkaamaton kuvamme on Lady Agnew Lochnawista, maalannut John Singer Sargent vuosina 1892 ja 1893.

Seuraava koodi luo a ConvolveOp joka yhdistää saman määrän kutakin lähdepikseliä ja sen naapureita. Tämä tekniikka johtaa hämärtyvään vaikutukseen.

001 kelluva yhdeksäs = 1,0f / 9,0f; 002 float [] blurKernel = {003 yhdeksäs, yhdeksäs, yhdeksäs, 004 yhdeksäs, yhdeksäs, yhdeksäs, 005 yhdeksäs, yhdeksäs, yhdeksäs 006}; 007 BufferedImageOp-sumennus = uusi ConvolveOp (uusi ydin (3, 3, blurKernel)); 

Toinen yleinen konvoluution ydin korostaa kuvan reunoja. Tätä toimintoa kutsutaan yleisesti reunan tunnistus. Toisin kuin muut tässä esitetyt ytimet, tämän ytimen kertoimet eivät ole yhtä suuria.

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 = uusi ConvolveOp (uusi ydin (3, 3, edgeKernel)); 

Voit nähdä, mitä tämä ydin tekee katsomalla ytimen kertoimia (rivit 002-004). Ajattele hetkeksi, kuinka reunanilmaisimen ydintä käytetään toimimaan kokonaan värillisellä alueella. Jokaisella pikselillä ei ole väriä (mustaa), koska ympäröivien pikselien väri kumoaa lähdepikselin värin. Kirkkaat pikselit, joita ympäröivät tummat pikselit, pysyvät kirkkaina.

Huomaa, kuinka paljon tummempi käsitelty kuva on alkuperäiseen verrattuna. Tämä tapahtuu, koska reunanilmaisun ytimen elementit eivät lisää yhteen.

Yksinkertainen muunnelma reunan havaitsemisessa on teroitus ydin. Tällöin lähdekuva lisätään reunan havaitsemisytimeen seuraavasti:

 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 

Teroitusydin on itse asiassa vain yksi mahdollinen ydin, joka terävöittää kuvia.

3 x 3 ytimen valinta on jonkin verran mielivaltaista. Voit määrittää minkä tahansa kokoiset ytimet, ja oletettavasti niiden ei tarvitse edes olla neliömetriä. JDK 1.2 Beta 3: ssa ja 4: ssä ei-neliöinen ydin aiheutti kuitenkin sovelluksen kaatumisen, ja 5 x 5 -ydin pureskeli kuvadataa erikoisimmalla tavalla. Ellei sinulla ole pakottavaa syytä poiketa 3 x 3 ytimestä, emme suosittele sitä.

Saatat myös miettiä, mitä tapahtuu kuvan reunalla. Kuten tiedätte, konvoluutiooperaatiossa otetaan huomioon lähdepikselin naapurit, mutta kuvan reunojen lähdepikseleissä ei ole naapureita toisella puolella. ConvolveOp luokka sisältää vakioita, jotka määrittelevät käyttäytymisen reunoilla. EDGE_ZERO_FILL vakio määrittää, että kohdekuvan reunat ovat 0 EDGE_NO_OP vakio määrittää, että lähteen pikselit kuvan reunalla kopioidaan kohteeseen ilman, että niitä muokataan. Jos et määritä reunakäyttäytymistä a: ta rakennettaessa ConvolveOp, EDGE_ZERO_FILL käytetään.

Seuraava esimerkki osoittaa, kuinka voit luoda teroitusoperaattorin, joka käyttää EDGE_NO_OP sääntö (EI_OP hyväksytään a ConvolveOp parametri rivillä 008):

001 float [] sharpKernel = {002 0,0f, -1,0f, 0,0f, 003 -1,0f, 5,0f, -1,0f, 004 0,0f, -1,0f, 0,0f005}; 006 BufferedImageOp sharpen = uusi ConvolveOp (007 uusi ydin (3, 3, sharpKernel), 008 ConvolveOp.EDGE_NO_OP, nolla); 

Hakutaulukot

Toinen monipuolinen kuvatoiminto sisältää a hakutaulukkoon. Tätä toimintoa varten lähdepikselivärit muunnetaan kohdepikseliväreiksi taulukon avulla. Muista, että väri koostuu punaisista, vihreistä ja sinisistä komponenteista. Jokaisella komponentilla on arvo välillä 0-255. Kolme taulukkoa, joissa on 256 merkintää, riittävät kääntämään minkä tahansa lähdevärin kohdeväriksi.

java.awt.image.LookupOp ja java.awt.image.LookupTable luokat kapseloivat tämän operaation. Voit määrittää erilliset taulukot kullekin värikomponentille tai käyttää yhtä taulukkoa kaikille kolmelle. Katsotaanpa yksinkertaista esimerkkiä, joka kääntää jokaisen komponentin värit. Meidän on vain luotava taulukkoa edustava taulukko (rivit 001-003). Sitten luomme Hakutaulukkoon taulukosta ja a LookupOp alkaen Hakutaulukkoon (rivit 004-005).

001 lyhyt [] invertti = uusi lyhyt [256]; 002 (int i = 0; i <256; i ++) 003: lle invertti [i] = (lyhyt) (255 - i); 004 BufferedImageOp invertOp = new LookupOp (005 uusi ShortLookupTable (0, invert), null); 

Hakutaulukkoon on kaksi alaluokkaa, TavuHakutaulukko ja ShortLookupTable, joka kapseloi tavu ja lyhyt taulukot. Jos luot Hakutaulukkoon jolla ei ole merkintää mille tahansa syötetylle arvolle, heitetään poikkeus.

Tämä toimenpide luo vaikutuksen, joka näyttää värigegatiiviselta tavallisessa elokuvassa. Huomaa myös, että tämän toiminnon soveltaminen kahdesti palauttaa alkuperäisen kuvan; otat periaatteessa negatiivisen negatiivisen.

Entä jos haluaisit vaikuttaa vain yhteen värikomponenteista? Helppo. Rakennat a Hakutaulukkoon erilliset taulukot kullekin punaiselle, vihreälle ja siniselle komponentille. Seuraava esimerkki näyttää, miten a LookupOp joka kääntää vain värin sinisen osan. Kuten edellisellä inversiooperaattorilla, tämän operaattorin käyttäminen palauttaa alkuperäisen kuvan kahdesti.

001 lyhyt [] invertti = uusi lyhyt [256]; 002 lyhyt [] suora = uusi lyhyt [256]; 003 for (int i = 0; i <256; i ++) {004 invertti [i] = (lyhyt) (255 - i); 005 suora [i] = (lyhyt) i; 006} 007 lyhyt [] [] blueInvert = uusi lyhyt [] [] {suora, suora, käänteinen}; 008 BufferedImageOp blueInvertOp = 009 uusi LookupOp (uusi ShortLookupTable (0, blueInvert), null); 

Julistaminen on toinen mukava vaikutus, jota voit käyttää käyttämällä a LookupOp. Julistaminen tarkoittaa kuvan näyttämiseen käytettävien värien määrän vähentämistä.

A LookupOp voi saavuttaa tämän vaikutuksen käyttämällä taulukkoa, joka kuvaa tuloarvot pieneksi joukoksi lähtöarvoja. Seuraava esimerkki osoittaa, kuinka syöttöarvot voidaan yhdistää kahdeksaan tiettyyn arvoon.

001 lyhyt [] posterize = uusi lyhyt [256]; 002 (int i = 0; i <256; i ++) 003 posterisoida [i] = (lyhyt) (i - (i% 32)); 004 BufferedImageOp posterizeOp = 005 uusi LookupOp (uusi ShortLookupTable (0, posterize), null); 

Kynnysarvo

Viimeinen tarkasteltava kuvatoiminto on kynnys. Kynnysarvot tekevät värien muutoksista ohjelmoijan määrittelemän "rajan" tai kynnyksen yli selvemmät (samanlainen kuin kartan ääriviivat tekevät korkeusrajoista selvemmät). Tämä tekniikka käyttää määritettyä kynnysarvoa, vähimmäisarvoa ja maksimiarvoa värikomponenttien arvojen hallintaan kuvan jokaiselle pikselille. Kynnyksen alapuolella oleville väriarvoille määritetään vähimmäisarvo. Kynnyksen yläpuolella oleville arvoille annetaan suurin arvo.