Programmering

Bildebehandling med Java 2D

Bildebehandling er kunsten og vitenskapen om å manipulere digitale bilder. Den står med en fot fast i matematikk og den andre i estetikk, og er en kritisk komponent i grafiske datasystemer. Hvis du noen gang har brydd deg med å lage dine egne bilder for websider, vil du uten tvil sette pris på viktigheten av Photoshops bildemanipuleringsfunksjoner for å rydde opp i skanninger og fjerne mindre enn optimale bilder.

Hvis du gjorde noe bildebehandlingsarbeid i JDK 1.0 eller 1.1, husker du sannsynligvis at det var litt stump. Den gamle modellen av bildedataprodusenter og forbrukere er uhåndterlig for bildebehandling. Før JDK 1.2 var bildebehandling involvert MemoryImageSources, PixelGrabbers, og andre slike arcana. Java 2D gir imidlertid en renere, enklere å bruke modell.

Denne måneden vil vi undersøke algoritmene bak flere viktige bildebehandlingsoperasjoner (ops) og viser deg hvordan de kan implementeres ved hjelp av Java 2D. Vi viser deg også hvordan disse opsene brukes til å påvirke utseendet på bildet.

Siden bildebehandling er en virkelig nyttig frittstående applikasjon av Java 2D, har vi bygget denne månedens eksempel, ImageDicer, for å være så gjenbrukbar som mulig for dine egne applikasjoner. Dette enkelt eksemplet viser alle bildebehandlingsteknikkene vi vil dekke i månedens kolonne.

Vær oppmerksom på at kort tid før denne artikkelen ble publisert, ga Sun ut Java 1.2 Beta 4 utviklingssett. Beta 4 ser ut til å gi bedre ytelse for våre eksempler på bildebehandlingsoperasjoner, men det legger også til noen nye feil som involverer grensekontroll av ConvolveOps. Disse problemene påvirker kantdeteksjon og slipeeksempler vi bruker i diskusjonen.

Vi synes disse eksemplene er verdifulle, så i stedet for å utelate dem helt kompromitterte vi: For å sikre at den kjører, gjenspeiler eksempelkoden Beta 4-endringene, men vi har beholdt tallene fra 1.2 Beta 3-utførelsen slik at du kan se operasjonene fungerer riktig.

Forhåpentligvis vil Sun adressere disse feilene før den endelige Java 1.2-utgivelsen.

Bildebehandling er ikke rakettvitenskap

Bildebehandling trenger ikke å være vanskelig. Faktisk er de grunnleggende begrepene ganske enkle. Et bilde er tross alt bare et rektangel med fargede piksler. Å behandle et bilde er bare å beregne en ny farge for hver piksel. Den nye fargen på hver piksel kan være basert på den eksisterende pikselfargen, fargen på omkringliggende piksler, andre parametere eller en kombinasjon av disse elementene.

2D API introduserer en enkel bildebehandlingsmodell for å hjelpe utviklere med å manipulere disse bildepikslene. Denne modellen er basert på java.awt.image.BufferedImage klasse- og bildebehandlingsoperasjoner som konvolusjon og terskling er representert av implementeringer av java.awt.image.BufferedImageOp grensesnitt.

Implementeringen av disse opsene er relativt grei. Anta for eksempel at du allerede har kildebildet som en BufferedImage kalt kilde. Å utføre operasjonen illustrert i figuren ovenfor vil ta bare noen få linjer med kode:

001 kort [] terskel = ny kort [256]; 002 for (int i = 0; i <256; i ++) 003 terskel [i] = (i <128)? (kort) 0: (kort) 255; 004 BufferedImageOp-terskelOp = 005 ny LookupOp (ny ShortLookupTable (0, terskel), null); 006 BufferedImage destination = thresholdOp.filter (kilde, null); 

Det er egentlig alt det er med det. La oss nå se nærmere på trinnene:

  1. Instantier bildedriften du ønsker (linjene 004 og 005). Her brukte vi en LookupOp, som er en av bildebehandlingene som inngår i Java 2D-implementeringen. Som enhver annen bildebehandling, implementerer den BufferedImageOp grensesnitt. Vi snakker mer om denne operasjonen senere.

  2. Ring operasjonen filter() metode med kildebildet (linje 006). Kilden behandles og destinasjonsbildet returneres.

Hvis du allerede har opprettet en BufferedImage som holder målbildet, kan du sende det som den andre parameteren til filter(). Hvis du passerer null, som vi gjorde i eksemplet ovenfor, en ny destinasjon BufferedImage er skapt.

2D API inkluderer en håndfull av disse innebygde bildedriftene. Vi diskuterer tre i denne kolonnen: konvolusjon,oppslagstabeller, og terskling. Se Java 2D-dokumentasjonen for informasjon om gjenværende operasjoner tilgjengelig i 2D API (Resources).

Konvolusjon

EN konvolusjon operasjonen lar deg kombinere fargene på en kilde-piksel og naboene for å bestemme fargen på en destinasjonspiksel. Denne kombinasjonen spesifiseres ved hjelp av a kjerne, en lineær operator som bestemmer andelen av hver kildepikselfarge som brukes til å beregne destinasjonspikselfargen.

Tenk på kjernen som en mal som er lagt på bildet for å utføre en konvolusjon på en piksel om gangen. Når hver piksel er kronglete, flyttes malen til neste piksel i kildebildet, og konvolusjonsprosessen gjentas. En kildekopi av bildet brukes til inndataverdier for konvolusjonen, og alle utgangsverdiene lagres i en destinasjonskopi av bildet. Når konvolusjonsoperasjonen er fullført, returneres destinasjonsbildet.

Senteret til kjernen kan tenkes å ligge over kildepikslen som er kronglete. For eksempel har en konvolusjonsoperasjon som bruker følgende kjerne ingen innvirkning på et bilde: hver destinasjonspiksel har samme farge som sin tilsvarende kildepiksel.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

Hovedregelen for å lage kjerner er at alle elementene skal legge opp til 1 hvis du vil bevare lysstyrken på bildet.

I 2D API representeres en konvolusjon av en java.awt.image.ConvolveOp. Du kan konstruere en ConvolveOp ved hjelp av en kjerne, som er representert av en forekomst av java.awt.image.Kernel. Følgende kode konstruerer a ConvolveOp ved hjelp av kjernen presentert ovenfor.

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 new ConvolveOp (new Kernel (3, 3, identityKernel)); 

Konvolusjonsoperasjonen er nyttig for å utføre flere vanlige operasjoner på bilder, som vi vil detaljere i et øyeblikk. Ulike kjerner gir radikalt forskjellige resultater.

Nå er vi klare til å illustrere noen bildebehandlingskerner og deres effekter. Vårt umodifiserte bilde er Lady Agnew of Lochnaw, malt av John Singer Sargent i 1892 og 1893.

Følgende kode oppretter en ConvolveOp som kombinerer like store mengder av hver kildepiksel og naboene. Denne teknikken resulterer i en uskarphet.

001 flyte niende = 1.0f / 9.0f; 002 float [] blurKernel = {003 niende, niende, niende, 004 niende, niende, niende, 005 niende, niende, niende 006}; 007 BufferedImageOp blur = new ConvolveOp (new Kernel (3, 3, blurKernel)); 

En annen vanlig konvolusjonskjerne understreker kantene i bildet. Denne operasjonen kalles ofte kantdeteksjon. I motsetning til de andre kjernene som presenteres her, legger ikke denne kjernens koeffisienter til 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 = new ConvolveOp (new Kernel (3, 3, edgeKernel)); 

Du kan se hva denne kjernen gjør ved å se på koeffisientene i kjernen (linje 002-004). Tenk et øyeblikk på hvordan kantdeteksjonskjernen brukes til å operere i et område som er helt en farge. Hver piksel havner uten farge (svart) fordi fargen på omkringliggende piksler avbryter kildepikselens farge. Lyse piksler omgitt av mørke piksler vil forbli lyse.

Legg merke til hvor mye mørkere det bearbeidede bildet er i forhold til originalen. Dette skjer fordi elementene i kantgjenkjenningskjernen ikke legger opp til 1.

En enkel variant på kantdeteksjon er sliping kjernen. I dette tilfellet blir kildebildet lagt til i en kjernekjernekjerne som følger:

 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 

Slipekjernen er faktisk bare en mulig kjerne som skjerper bilder.

Valget av en 3 x 3 kjerne er noe vilkårlig. Du kan definere kjerner av hvilken som helst størrelse, og antagelig trenger de ikke engang å være firkantede. I JDK 1.2 Beta 3 og 4 produserte imidlertid en ikke-kvadratisk kjerne en applikasjonskrasj, og en 5 x 5-kjerne tygget opp bildedataene på en mest spesiell måte. Med mindre du har en overbevisende grunn til å avvike fra 3 x 3 kjerner, anbefaler vi det ikke.

Du lurer kanskje også på hva som skjer i utkanten av bildet. Som du vet tar konvolusjonsoperasjonen en kilde-pikselens naboer i betraktning, men kildepiksler i kantene av bildet har ikke naboer på den ene siden. De ConvolveOp klasse inkluderer konstanter som spesifiserer hvordan oppførselen skal være i kantene. De EDGE_ZERO_FILL konstant angir at kantene på destinasjonsbildet er satt til 0. EDGE_NO_OP konstant spesifiserer at kildepiksler langs kanten av bildet kopieres til destinasjonen uten å bli endret. Hvis du ikke spesifiserer en kantadferd når du konstruerer en ConvolveOp, EDGE_ZERO_FILL benyttes.

Følgende eksempel viser hvordan du kan opprette en skarpeoperatør som bruker EDGE_NO_OP regel (NO_OP blir bestått som en ConvolveOp parameter i linje 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 sharpen = new ConvolveOp (007 new Kernel (3, 3, sharpKernel), 008 ConvolveOp.EDGE_NO_OP, null); 

Oppslagstabeller

En annen allsidig bildeoperasjon innebærer å bruke en oppslagstabell. For denne operasjonen blir kildepikselfarger oversatt til målpikslerfarger ved bruk av en tabell. Husk at en farge består av røde, grønne og blå komponenter. Hver komponent har en verdi fra 0 til 255. Tre tabeller med 256 oppføringer er tilstrekkelig til å oversette hvilken som helst kildefarge til en destinasjonsfarge.

De java.awt.image.LookupOp og java.awt.image.LookupTable klasser innkapsler denne operasjonen. Du kan definere separate tabeller for hver fargekomponent, eller bruke en tabell for alle tre. La oss se på et enkelt eksempel som inverterer fargene til hver komponent. Alt vi trenger å gjøre er å lage en matrise som representerer tabellen (linje 001-003). Så lager vi en Oppslagstabell fra matrisen og a LookupOp fra Oppslagstabell (linje 004-005).

001 kort [] invert = ny kort [256]; 002 for (int i = 0; i <256; i ++) 003 invertere [i] = (kort) (255 - i); 004 BufferedImageOp invertOp = new LookupOp (005 new ShortLookupTable (0, invert), null); 

Oppslagstabell har to underklasser, ByteLookupTable og ShortLookupTable, som innkapsler byte og kort arrays. Hvis du oppretter en Oppslagstabell som ikke har en oppføring for noen inngangsverdi, blir et unntak kastet.

Denne operasjonen skaper en effekt som ser ut som en fargenegativ i konvensjonell film. Vær også oppmerksom på at bruk av denne operasjonen to ganger vil gjenopprette originalbildet; du tar i utgangspunktet et negativt av det negative.

Hva om du bare ville påvirke en av fargekomponentene? Lett. Du konstruerer en Oppslagstabell med separate tabeller for hver av de røde, grønne og blå komponentene. Følgende eksempel viser hvordan du oppretter en LookupOp som bare inverterer den blå komponenten i fargen. Som med den forrige inversjonsoperatøren, gjenoppretter originalbildet to ganger ved å bruke denne operatøren.

001 kort [] invert = ny kort [256]; 002 kort [] rett = ny kort [256]; 003 for (int i = 0; i <256; i ++) {004 invertere [i] = (kort) (255 - i); 005 rett [i] = (kort) i; 006} 007 kort [] [] blueInvert = ny kort [] [] {rett, rett, invertert}; 008 BufferedImageOp blueInvertOp = 009 ny LookupOp (ny ShortLookupTable (0, blueInvert), null); 

Posterisering er en annen fin effekt du kan bruke ved hjelp av a LookupOp. Posterisering innebærer å redusere antall farger som brukes til å vise et bilde.

EN LookupOp kan oppnå denne effekten ved å bruke en tabell som tilordner inngangsverdier til et lite sett med utgangsverdier. Følgende eksempel viser hvordan inngangsverdier kan tilordnes til åtte spesifikke verdier.

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

Terskel

Den siste bildeoperasjonen vi undersøker er terskling. Terskel gjør fargeforandringer over en programmerer-bestemt "grense" eller terskel, mer åpenbar (ligner på hvordan konturlinjene på et kart gjør høydegrenser mer åpenbare). Denne teknikken bruker en spesifisert terskelverdi, minimumsverdi og maksimumsverdi for å kontrollere fargekomponentverdiene for hver piksel i et bilde. Fargeverdier under terskelen tildeles minimumsverdien. Verdier over terskelen tildeles maksimumsverdien.

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