Programavimas

Vaizdų apdorojimas naudojant „Java 2D“

Vaizdų apdorojimas yra menas ir mokslas, kaip manipuliuoti skaitmeniniais vaizdais. Viena koja tvirtai stovi matematikoje, kita - estetikoje ir yra kritinė grafinių kompiuterinių sistemų sudedamoji dalis. Jei kada nors vargote kurdami savo atvaizdus tinklalapiams, neabejotinai įvertinsite „Photoshop“ vaizdo manipuliavimo galimybių svarbą valant nuskaitymus ir išvalant mažiau nei optimalius vaizdus.

Jei atlikote kokį nors vaizdų apdorojimo darbą naudodami JDK 1.0 arba 1.1, tikriausiai atsimenate, kad tai buvo šiek tiek tylus. Senas vaizdo duomenų gamintojų ir vartotojų modelis yra nepatogus vaizdo apdorojimui. Prieš JDK 1.2 dalyvavo vaizdo apdorojimas „MemoryImageSource“s, „PixelGrabber“s ir kitos tokios arkanos. Tačiau „Java 2D“ suteikia švaresnį, lengviau naudojamą modelį.

Šį mėnesį mes išnagrinėsime kelių svarbių vaizdo apdorojimo operacijų algoritmus (ops) ir parodykite, kaip juos galima įgyvendinti naudojant „Java 2D“. Mes taip pat parodysime, kaip šios operacijos naudojamos paveikti vaizdo išvaizdą.

Kadangi vaizdo apdorojimas yra tikrai naudinga atskira „Java 2D“ programa, mes sukūrėme šio mėnesio pavyzdį „ImageDicer“, kad jis būtų kuo daugiau pakartotinai naudojamas jūsų pačių programoms. Šis vienintelis pavyzdys parodo visas vaizdo apdorojimo technikas, kurias apžvelgsime šio mėnesio stulpelyje.

Atkreipkite dėmesį, kad prieš pat šio straipsnio paskelbimą „Sun“ išleido „Java 1.2 Beta 4“ kūrimo rinkinį. Atrodo, kad „Beta 4“ geriau veikia mūsų vaizdo apdorojimo operacijas, tačiau ji taip pat prideda keletą naujų klaidų, susijusių su ribų tikrinimu „ConvolveOp“s. Šios problemos turi įtakos kraštų aptikimo ir galandimo pavyzdžiams, kuriuos naudojame diskusijoje.

Mes manome, kad šie pavyzdžiai yra vertingi, todėl užuot juos visiškai praleidę, mes sukompromitavome: norėdami užtikrinti, kad jis veikia, kodo pavyzdys atspindi „Beta 4“ pakeitimus, tačiau mes išsaugojome duomenis iš 1.2 „Beta 3“ vykdymo, kad galėtumėte matyti operacijas veikia teisingai.

Tikimės, kad „Sun“ ištaisys šias klaidas prieš paskutinį „Java 1.2“ leidimą.

Vaizdų apdorojimas nėra raketų mokslas

Vaizdų apdorojimas neturi būti sunkus. Tiesą sakant, pagrindinės sąvokos iš tikrųjų yra gana paprastos. Vaizdas juk yra tik spalvotų taškų stačiakampis. Apdorojant vaizdą paprasčiausiai reikia apskaičiuoti naują kiekvieno taško spalvą. Nauja kiekvieno pikselio spalva gali būti pagrįsta esama pikselių spalva, aplinkinių pikselių spalva, kitais parametrais arba šių elementų deriniu.

„2D API“ pristato paprastą vaizdo apdorojimo modelį, kuris padės kūrėjams manipuliuoti šiais vaizdo taškais. Šis modelis pagrįstas java.awt.image.BufferedImage klasės ir vaizdo apdorojimo operacijos, pvz konvoliucija ir riba yra atstovaujama įgyvendinant java.awt.image.BufferedImageOp sąsaja.

Šių operacijų įgyvendinimas yra gana paprastas. Tarkime, pavyzdžiui, kad jūs jau turite šaltinio vaizdą kaip BuferinisVaizdas paskambino šaltinis. Atliekant operaciją, pavaizduotą aukščiau esančiame paveikslėlyje, reikės tik kelių kodo eilučių:

001 trumpas [] slenkstis = naujas trumpas [256]; 002 (int i = 0; i <256; i ++) 003 slenkstis [i] = (i <128)? (trumpas) 0: (trumpas) 255; 004 „BufferedImageOp thresholdOp“ = 005 naujas „LookupOp“ (naujas „ShortLookupTable“ (0, slenkstis), nulis); 006 „BufferedImage“ paskirtis = thresholdOp.filter (šaltinis, nulis); 

Tai iš tikrųjų viskas. Dabar pažvelkime į veiksmus išsamiau:

  1. Momentinė jūsų pasirinkto vaizdo operacija (004 ir 005 eilutės). Čia mes panaudojome a „LookupOp“, kuri yra viena iš vaizdo operacijų, įtrauktų į „Java 2D“ diegimą. Kaip ir bet kuri kita vaizdo operacija, ji įgyvendina „BufferedImageOp“ sąsaja. Apie šią operaciją pakalbėsime vėliau.

  2. Skambinkite operacijai filtras() metodas su šaltinio atvaizdu (006 eilutė). Šaltinis apdorojamas ir paskirties vaizdas grąžinamas.

Jei jau sukūrėte a BuferinisVaizdas kuriame bus paskirties vaizdas, galite jį perduoti kaip antrąjį parametrą filtras(). Jei praeisi niekinis, kaip tai darėme aukščiau pateiktame pavyzdyje, nauja paskirties vieta BuferinisVaizdas yra sukurtas.

2D API apima keletą šių integruotų vaizdų operacijų. Šiame stulpelyje aptarsime tris: konvoliucija,paieškos lentelės, ir riba. Informacijos apie likusias 2D API (šaltinius) pasiekiamas operacijas ieškokite „Java 2D“ dokumentuose.

Konvoliucija

A konvoliucija operacija leidžia derinti šaltinio ir jo kaimynų spalvas, kad nustatytumėte paskirties taško spalvą. Šis derinys nurodomas naudojant a branduolys, linijinis operatorius, kuris nustato kiekvieno šaltinio taško spalvos, naudojamos paskirties taško spalvai apskaičiuoti, dalį.

Pagalvokite apie branduolį kaip apie šabloną, kuris yra uždengtas paveikslėlyje, kad vienu metu įvyktų vieno pikselio konvoliucija. Kiekvienam pikseliui susisukus, šablonas perkeliamas į kitą pikselį šaltinio vaizde ir konvekcijos procesas kartojamas. Vaizdo šaltinio kopija naudojama įvesties reikšmėms konvoliucijai, o visos išvesties vertės įrašomos į paskirties vaizdo kopiją. Baigus konvoliucijos operaciją, tikslo vaizdas grąžinamas.

Galima manyti, kad branduolio centras uždengia sukausto šaltinio pikselį. Pvz., Konvoliucijos operacija, kurioje naudojamas šis branduolys, paveikslui neturi jokios įtakos: kiekvieno paskirties taško spalva yra tokia pati kaip ir atitinkamo šaltinio taško.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

Pagrindinė branduolių kūrimo taisyklė yra ta, kad visi elementai turėtų sudaryti iki 1, jei norite išsaugoti vaizdo ryškumą.

2D API konvoliuciją vaizduoja a java.awt.image.ConvolveOp. Galite sukonstruoti a „ConvolveOp“ naudojant branduolį, kurį vaizduoja java.awt.image.Kernel. Šis kodas sukonstruoja a „ConvolveOp“ naudojant pirmiau pateiktą branduolį.

001 plūduriuojantis [] identiteto branduolys = {002 0,0f, 0,0f, 0,0f, 003 0,0f, 1,0f, 0,0f, 004 0,0f, 0,0f, 0,0f 005}; 006 „BufferedImageOp“ tapatybė = 007 nauja „ConvolveOp“ (naujas branduolys (3, 3, identiteto branduolys)); 

Konvoliucijos operacija yra naudinga atliekant keletą įprastų vaizdų operacijų, kurias mes išsamiai aprašysime akimirksniu. Skirtingi branduoliai duoda radikaliai skirtingus rezultatus.

Dabar mes pasirengę iliustruoti kai kuriuos vaizdo apdorojimo branduolius ir jų efektus. Mūsų nemodifikuotas vaizdas yra Ledi Agnew iš Lochnaw, tapė Johnas Singeris Sargentas 1892 ir 1893 m.

Šis kodas sukuria „ConvolveOp“ kuris sujungia vienodą kiekvieno šaltinio pikselio ir jo kaimynų kiekį. Ši technika lemia neryškų efektą.

001 plūdinė devinta = 1,0f / 9,0f; 002 float [] blurKernel = {003 devinta, devinta, devinta, 004 devinta, devinta, devinta, 005 devinta, devinta, devinta 006}; 007 „BufferedImageOp blur“ = naujas „ConvolveOp“ (naujas branduolys (3, 3, „blurKernel“)); 

Kitas įprastas konvoliucijos branduolys pabrėžia vaizdo kraštus. Ši operacija paprastai vadinama krašto aptikimas. Skirtingai nuo kitų čia pateiktų branduolių, šio branduolio koeficientai nesudaro 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 = naujas ConvolveOp (naujas branduolys (3, 3, edgeKernel)); 

Galite pamatyti, ką daro šis branduolys, žiūrėdami į branduolio koeficientus (eilutės 002-004). Akimirką pagalvokite, kaip krašto aptikimo branduolys naudojamas visiškai vienos spalvos srityje. Kiekvienas pikselis neturės spalvos (juoda), nes aplinkinių pikselių spalva panaikina šaltinio pikselio spalvą. Šviesūs pikseliai, apsupti tamsių pikselių, išliks ryškūs.

Atkreipkite dėmesį, kiek tamsesnis apdorotas vaizdas, palyginti su originalu. Taip atsitinka todėl, kad krašto aptikimo branduolio elementai nesudaro 1.

Paprastas krašto aptikimo variantas yra galandimas branduolys. Tokiu atveju šaltinio vaizdas į krašto aptikimo branduolį pridedamas taip:

 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 

Galandimo branduolys iš tikrųjų yra tik vienas galimas branduolys, galandantis vaizdus.

3 x 3 branduolio pasirinkimas yra šiek tiek savavališkas. Galite apibrėžti bet kokio dydžio branduolius, kurie, tikėtina, net neturi būti kvadratiniai. Tačiau „JDK 1.2 Beta 3“ ir „4“ versijose ne kvadratinis branduolys sukėlė programos gedimą, o 5 x 5 branduolys savotiškai sukramtė vaizdo duomenis. Mes to nerekomenduojame, nebent turite įtikinamų priežasčių nuklysti nuo 3 x 3 branduolių.

Jums taip pat gali būti įdomu, kas vyksta vaizdo krašte. Kaip žinote, atliekant konvoliuciją atsižvelgiama į šaltinio pikselio kaimynus, tačiau vaizdo kraštuose esančiuose šaltinio pikseliuose nėra kaimynų vienoje pusėje. „ConvolveOp“ klasėje yra konstantos, nurodančios, koks elgesys turėtų būti kraštuose. EDGE_ZERO_FILL konstanta nurodo, kad paskirties vaizdo kraštai nustatomi į 0. The EDGE_NO_OP konstanta nurodo, kad šaltinio taškai išilgai vaizdo krašto yra nukopijuojami į paskirties vietą nekeičiant. Jei kurdami nenurodote krašto elgesio „ConvolveOp“, EDGE_ZERO_FILL yra naudojamas.

Šis pavyzdys rodo, kaip galėtumėte sukurti galandimo operatorių, kuris naudoja EDGE_NO_OP taisyklė (NE_OP yra perduodamas kaip a „ConvolveOp“ 008 eilutės parametras):

001 plūdė [] 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 naujas branduolys (3, 3, sharpKernel), 008 ConvolveOp.EDGE_NO_OP, null); 

Paieškos lentelės

Kita universali vaizdo operacija apima a paieškos lentelė. Šiai operacijai naudojant lentelę šaltinio pikselių spalvos paverčiamos paskirties taškų spalvomis. Spalvą sudaro raudonos, žalios ir mėlynos spalvos komponentai. Kiekvieno komponento vertė yra nuo 0 iki 255. Pakanka trijų lentelių su 256 įrašais, kad bet kokia šaltinio spalva būtų paversta paskirties spalva.

java.awt.image.LookupOp ir java.awt.image.LookupTable klasės apima šią operaciją. Kiekvienam spalvų komponentui galite nustatyti atskiras lenteles arba naudoti vieną lentelę visiems trims. Pažvelkime į paprastą pavyzdį, apverčiantį kiekvieno komponento spalvas. Viskas, ką mums reikia padaryti, yra sukurti lentelę vaizduojančią masyvą (eilutės 001-003). Tada mes sukuriame a „LookupTable“ iš masyvo ir a „LookupOp“ nuo „LookupTable“ (004-005 eilutės).

001 trumpas [] invertas = naujas trumpas [256]; 002 (int i = 0; i <256; i ++) 003 invertuoti [i] = (trumpas) (255 - i); 004 BufferedImageOp invertOp = new LookupOp (005 new ShortLookupTable (0, invert), null); 

„LookupTable“ turi du poklasius, „ByteLookupTable“ ir „ShortLookupTable“, kuris apima baitas ir trumpas masyvai. Jei sukursite a „LookupTable“ kad nėra įvesties jokiai įvesties vertei, bus išimtis.

Ši operacija sukuria efektą, kuris atrodo kaip spalvinis neigiamas įprastu filmu. Taip pat atkreipkite dėmesį, kad du kartus pritaikius šią operaciją bus atkurtas originalus vaizdas; jūs iš esmės imate neigiamą neigiamą.

Ką daryti, jei norėtumėte paveikti tik vieną iš spalvų komponentų? Lengva. Jūs sukonstruojate a „LookupTable“ su atskiromis lentelėmis kiekvienam raudonam, žaliam ir mėlynam komponentui. Šis pavyzdys rodo, kaip sukurti „LookupOp“ kad apverčia tik mėlyną spalvos komponentą. Kaip ir ankstesnio inversijos operatoriaus atveju, taikant šį operatorių du kartus atkuriamas pradinis vaizdas.

001 trumpas [] invertas = naujas trumpas [256]; 002 trumpas [] tiesus = naujas trumpas [256]; 003 (int i = 0; i <256; i ++) {004 invertuoti [i] = (trumpas) (255 - i); 005 tiesus [i] = (trumpas) i; 006} 007 trumpas [] [] blueInvert = naujas trumpas [] [] {tiesus, tiesus, apverstas}; 008 BufferedImageOp blueInvertOp = 009 new LookupOp (nauja ShortLookupTable (0, blueInvert), null); 

Plakatas yra dar vienas gražus efektas, kurį galite pritaikyti naudodami a „LookupOp“. Plakatas reiškia spalvų, naudojamų vaizdui rodyti, skaičiaus mažinimą.

A „LookupOp“ gali pasiekti šį efektą naudodamas lentelę, kuri įvesties reikšmes susieja su nedideliu išvesties verčių rinkiniu. Šis pavyzdys parodo, kaip įvesties reikšmes galima susieti su aštuoniomis konkrečiomis reikšmėmis.

001 trumpas [] posterize = naujas trumpas [256]; 002, skirtas (int i = 0; i <256; i ++) 003, skelbia [i] = (trumpas) (i - (i% 32)); 004 BufferedImageOp posterizeOp = 005 new LookupOp (nauja ShortLookupTable (0, posterize), null); 

Ribų nustatymas

Paskutinė vaizdo operacija, kurią išnagrinėsime, yra riba. Dėl slenksčio spalvos pasikeičia per programuotojo nustatytą „ribą“ arba slenkstį - akivaizdžiau (panašiai kaip žemėlapio kontūro linijos daro akivaizdžiausias aukščio ribas). Ši technika naudoja nurodytą ribinę vertę, mažiausią ir didžiausią vertę, kad valdytų kiekvieno vaizdo taško spalvų komponentų vertes. Spalvų reikšmėms žemiau slenksčio priskiriama mažiausia vertė. Virš slenksčio esančioms vertėms priskiriama didžiausia vertė.