Programiranje

Obdelava slik z Java 2D

Obdelava slik je umetnost in znanost manipulacije z digitalnimi slikami. Z eno nogo trdno stoji v matematiki, z drugo pa v estetiki in je ključna sestavina grafičnih računalniških sistemov. Če ste se kdaj ukvarjali z ustvarjanjem lastnih slik za spletne strani, boste nedvomno cenili pomen Photoshopovih zmožnosti manipulacije s slikami za čiščenje optičnih bralnikov in čiščenje manj kot optimalnih slik.

Če ste v JDK 1.0 ali 1.1 izvajali kakršno koli obdelavo slik, se verjetno spomnite, da je bilo malo okorno. Stari model proizvajalcev in potrošnikov slikovnih podatkov je močan za obdelavo slik. Pred JDK 1.2 je bila vključena obdelava slik MemoryImageSources, PixelGrabbers in druge take arkane. Java 2D pa zagotavlja čistejši in enostavnejši model.

Ta mesec bomo preučili algoritme za več pomembnih operacij obdelave slik (ops) in vam pokažejo, kako jih je mogoče uporabiti z uporabo Java 2D. Pokazali vam bomo tudi, kako se te operacije uporabljajo za vpliv na videz slike.

Ker je obdelava slik resnično koristna samostojna aplikacija Java 2D, smo za ta mesec izdelali primer ImageDicer, ki ga je mogoče čim večkrat uporabiti za vaše aplikacije. Ta posamezen primer prikazuje vse tehnike obdelave slik, ki jih bomo zajeli v tem mesečnem stolpcu.

Upoštevajte, da je tik pred objavo tega članka Sun izdal razvojni komplet Java 1.2 Beta 4. Zdi se, da Beta 4 daje boljše rezultate za naše primere postopkov obdelave slik, dodaja pa tudi nekaj novih napak, ki vključujejo preverjanje omejitev ConvolveOps. Te težave vplivajo na primere zaznavanja in ostrenja robov, ki jih uporabljamo v razpravi.

Menimo, da so ti primeri dragoceni, zato smo, namesto da bi jih popolnoma izpustili, naredili kompromis: za zagotovitev, da se koda primera odraža v spremembah Beta 4, vendar smo ohranili podatke iz izvedbe 1.2 Beta 3, tako da lahko vidite operacije deluje pravilno.

Upamo, da bo Sun te napake odpravil pred zadnjo izdajo Java 1.2.

Obdelava slik ni raketna znanost

Obdelava slik ne sme biti težavna. Pravzaprav so temeljni koncepti v resnici dokaj preprosti. Slika je navsezadnje le pravokotnik barvnih slikovnih pik. Obdelava slike je preprosto vprašanje izračuna nove barve za vsak piksel. Nova barva vsake slikovne pike lahko temelji na obstoječi barvi slikovnih pik, barvi okoliških slikovnih pik, drugih parametrih ali kombinaciji teh elementov.

2D API uvaja preprost model obdelave slik, ki razvijalcem pomaga pri manipulaciji s temi slikovnimi pikami. Ta model temelji na java.awt.image.BufferedImage razred in obdelava slik, kot so konvolucija in prag so predstavljene z izvedbami java.awt.image.BufferedImageOp vmesnik.

Izvajanje teh operacij je razmeroma enostavno. Recimo, na primer, da že imate izvorno sliko kot BufferedImage poklical vir. Izvedba postopka, prikazanega na zgornji sliki, bi trajala le nekaj vrstic kode:

001 kratek [] prag = nov kratek [256]; 002 za (int i = 0; i <256; i ++) 003 prag [i] = (i <128)? (kratko) 0: (kratko) 255; 004 BufferedImageOp pragOp = 005 nov LookupOp (nova ShortLookupTable (0, prag), null); 006 Namen BufferedImage = pragOp.filter (vir, nič); 

To je res vse. Zdaj pa si oglejmo podrobneje korake:

  1. Ponastavite izbrano operacijo slike (vrstici 004 in 005). Tu smo uporabili Iskanje Op, ki je ena od slikovnih operacij, vključenih v implementacijo Java 2D. Kot katera koli druga slikovna operacija izvaja tudi BufferedImageOp vmesnik. O tej operaciji bomo govorili kasneje.

  2. Pokliči operacijo filter () z izvorno sliko (vrstica 006). Vir se obdela in ciljna slika se vrne.

Če ste že ustvarili BufferedImage , ki bo vsebovala ciljno sliko, jo lahko prenesete kot drugi parameter filter (). Če greš mimo nič, kot smo storili v zgornjem primeru, nova destinacija BufferedImage je ustvarjen.

2D API vključuje peščico teh vgrajenih operacij s sliko. V tem stolpcu bomo obravnavali tri: konvolucija,iskalne tabele, in prag. Za informacije o preostalih operacijah, ki so na voljo v 2D API (viri), glejte dokumentacijo Java 2D.

Konvolucija

A konvolucija operacija vam omogoča, da kombinirate barve izvorne slikovne pike in njenih sosedov, da določite barvo ciljne slikovne pike. Ta kombinacija je določena z uporabo jedro, linearni operator, ki določa delež vsake barve izvornih slikovnih pik, ki se uporablja za izračun ciljne barve slikovnih pik.

Jedro si predstavljajte kot predlogo, ki je prekrita s sliko za izvedbo zvijanja na posameznih slikovnih pikah. Ko je vsaka slikovna pika zvita, se predloga premakne na naslednjo slikovno piko v izvorni sliki in postopek zvijanja se ponovi. Izvorna kopija slike se uporablja za vhodne vrednosti zavoja, vse izhodne vrednosti pa se shranijo v ciljno kopijo slike. Ko je konvolucija končana, se vrne ciljna slika.

Središče jedra lahko razumemo kot prekrivanje izvornega piksla, ki je zvit. Na primer operacija zvijanja, ki uporablja naslednje jedro, ne vpliva na sliko: vsaka ciljna slikovna pika ima enako barvo kot ustrezna izvorna slikovna pika.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

Glavno pravilo za ustvarjanje jeder je, da se vsi elementi seštejejo do 1, če želite ohraniti svetlost slike.

V 2D API je zvitek predstavljen z java.awt.image.ConvolveOp. Lahko sestavite a ConvolveOp z uporabo jedra, ki ga predstavlja primerek java.awt.image.Kernel. Naslednja koda gradi a ConvolveOp z zgoraj predstavljenim jedrom.

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 identiteta = 007 novo ConvolveOp (novo jedro (3, 3, identityKernel)); 

Operacija zvijanja je uporabna pri izvajanju več pogostih operacij na slikah, ki jih bomo podrobneje opisali v trenutku. Različna jedra dajejo radikalno različne rezultate.

Zdaj smo pripravljeni ponazoriti nekaj jeder za obdelavo slik in njihove učinke. Naša nespremenjena podoba je Lady Agnew iz Lochnawa, naslikal John Singer Sargent v letih 1892 in 1893.

Naslednja koda ustvari a ConvolveOp ki združuje enake količine vsakega izvornega piksela in njegovih sosedov. Ta tehnika povzroči učinek zameglitve.

001 float deveta = 1,0f / 9,0f; 002 float [] blurKernel = {003 deveta, deveta, deveta, 004 deveta, deveta, deveta, 005 deveta, deveta, deveta 006}; 007 zamegljeno BufferedImageOp = novo ConvolveOp (novo jedro (3, 3, blurKernel)); 

Še eno pogosto zavojno jedro poudarja robove na sliki. Ta operacija se običajno imenuje zaznavanje robov. Za razliko od ostalih tukaj predstavljenih jeder koeficienti tega jedra ne seštevajo do 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 rob = nov ConvolveOp (novo jedro (3, 3, edgeKernel)); 

Kaj počne to jedro, si lahko ogledate tako, da pogledate koeficiente v jedru (vrstice 002-004). Za trenutek pomislite, kako jedro za zaznavanje robov deluje na enobarvnem območju. Na koncu bo vsaka slikovna pika brez barve (črna), ker barva okoliških slikovnih pik izniči barvo izvorne slikovne pike. Svetle slikovne pike, obkrožene s temnimi slikovnimi pikami, bodo ostale svetle.

Upoštevajte, kako temnejša je obdelana slika v primerjavi z izvirnikom. To se zgodi, ker se elementi jedra za zaznavanje robov ne seštejejo v 1.

Preprosta sprememba zaznavanja robov je ostrenje jedro. V tem primeru se izvorna slika doda v jedro za zaznavanje robov, kot sledi:

 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 

Jedro za ostrenje je pravzaprav le eno možno jedro, ki izostri slike.

Izbira jedra 3 x 3 je nekoliko poljubna. Določite lahko jedra katere koli velikosti in verjetno niti ni treba, da so kvadratna. V JDK 1.2 Beta 3 in 4 pa je jedro, ki ni kvadratno, povzročilo zrušitev aplikacije in 5 x 5 jedro je na najbolj nenavaden način prežvečilo slikovne podatke. Ne priporočamo, razen če imate prepričljiv razlog, da se oddaljite od 3 x 3 jeder.

Morda se tudi sprašujete, kaj se zgodi na robu slike. Kot veste, postopek zvijanja upošteva sosede izvorne slikovne pike, vendar izvorne slikovne pike na robovih slike nimajo sosedov na eni strani. The ConvolveOp razred vključuje konstante, ki določajo, kakšno vedenje naj bo na robovih. The EDGE_ZERO_FILL konstanta določa, da so robovi ciljne slike nastavljeni na 0. The EDGE_NO_OP konstanta določa, da se izvorne slikovne pike vzdolž roba slike kopirajo na cilj, ne da bi jih spremenili. Če pri gradnji a ne določite vedenja robov ConvolveOp, EDGE_ZERO_FILL se uporablja.

Naslednji primer prikazuje, kako lahko ustvarite operater ostrenja, ki uporablja EDGE_NO_OP pravilo (NO_OP se prenese kot a ConvolveOp parameter v vrstici 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 = novo ConvolveOp (007 novo jedro (3, 3, sharpKernel), 008 ConvolveOp.EDGE_NO_OP, null); 

Iskalne tabele

Druga vsestranska operacija slike vključuje uporabo a iskalna tabela. Pri tej operaciji se izvorne barve slikovnih pik s pomočjo tabele prevedejo v ciljne barve slikovnih pik. Ne pozabite, da je barva sestavljena iz rdečih, zelenih in modrih komponent. Vsaka komponenta ima vrednost od 0 do 255. Tri tabele z 256 vnosi zadostujejo za prevajanje katere koli izvorne barve v ciljno barvo.

The java.awt.image.LookupOp in java.awt.image.LookupTable razredi zaokrožijo to operacijo. Za vsako barvno komponento lahko določite ločene tabele ali pa za vse tri uporabite eno tabelo. Oglejmo si preprost primer, ki obrne barve vsake komponente. Vse, kar moramo storiti, je ustvariti matriko, ki predstavlja tabelo (vrstice 001-003). Nato ustvarimo a LookupTable iz polja in a Iskanje Op Iz LookupTable (vrstice 004-005).

001 kratek [] invert = nov kratek [256]; 002 za (int i = 0; i <256; i ++) 003 invert [i] = (kratko) (255 - i); 004 BufferedImageOp invertOp = new LookupOp (005 new ShortLookupTable (0, invert), null); 

LookupTable ima dva podrazreda, ByteLookupTable in ShortLookupTable, ki enkapsulira bajt in kratek nizi. Če ustvarite datoteko LookupTable ki nima vnosa za nobeno vhodno vrednost, bo vržena izjema.

Ta postopek ustvari učinek, ki je v običajnem filmu videti kot barvni negativ. Upoštevajte tudi, da bo uporaba te operacije dvakrat obnovila prvotno sliko; v bistvu jemljete negativ negativnega.

Kaj če bi želeli vplivati ​​samo na eno od barvnih komponent? Enostavno Sestaviš a LookupTable z ločenimi tabelami za vsako rdečo, zeleno in modro komponento. Naslednji primer prikazuje, kako ustvariti datoteko LookupOp ki samo obrne modro komponento barve. Tako kot pri prejšnjem operatorju inverzije tudi dvakrat uporaba tega operaterja obnovi prvotno sliko.

001 kratek [] invert = nov kratek [256]; 002 kratek [] naravnost = nov kratek [256]; 003 za (int i = 0; i <256; i ++) {004 invert [i] = (kratko) (255 - i); 005 naravnost [i] = (kratko) i; 006} 007 kratek [] [] blueInvert = nov kratek [] [] {naravnost, naravnost, obrnjen}; 008 BufferedImageOp blueInvertOp = 009 new LookupOp (nova ShortLookupTable (0, blueInvert), null); 

Posteriziranje je še en lep učinek, ki ga lahko uporabite s pomočjo LookupOp. Posterizacija vključuje zmanjšanje števila barv, ki se uporabljajo za prikaz slike.

A LookupOp lahko ta učinek doseže z uporabo tabele, ki preslika vhodne vrednosti na majhen nabor izhodnih vrednosti. Naslednji primer prikazuje, kako je mogoče vhodne vrednosti preslikati na osem določenih vrednosti.

001 kratka [] posterize = nova kratka [256]; 002 za (int i = 0; i <256; i ++) 003 posterize [i] = (kratko) (i - (i% 32)); 004 BufferedImageOp posterizeOp = 005 new LookupOp (nova ShortLookupTable (0, posterize), null); 

Prag

Zadnja operacija slike, ki jo bomo pregledali, je prag. Zaradi mejnih vrednosti so spremembe barv čez "mejo" ali prag, ki jo določi programer, bolj očitne (podobno kot konturne črte na zemljevidu naredijo višinske meje bolj očitne). Ta tehnika uporablja določeno vrednost praga, najmanjšo vrednost in največjo vrednost za nadzor vrednosti barvnih komponent za vsak piksel slike. Barvnim vrednostim pod pragom je dodeljena najmanjša vrednost. Vrednostim nad pragom se dodeli največja vrednost.

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