Obdelava slik1
Poudarjanje robov (angl. Edge enhancement) je slikovni filter, ki poveča kontrast robov. Rob je definiran kot znatna lokalna sprememba v intenziteti slike. Pri idealnem robu (angl. step) se sprememba intenzitete zgodi v koraku ene slikovne točke. Idealni robovi so v slikah zelo redki, še posebej, če slike prej gladimo, da iz njih odstranimo šum. Spremembe intenzitete se zato zgodijo v nekaj zaporednih slikovnih točkah, takemu robu pravimo nagib (angl. ramp). Filter za poudarjanje robov deluje tako, da na sliki prepozna ostre robove (na primer rob med motivom in ozadjem) in poveča kontrast slike na območju neposredno okoli roba. Zaradi tega je rob videti bolj definiran. Spodnja slika prikazuje primer poudarjanja robov:
Vhodna slika | Izhodna slika |
---|---|
Za postopek poudarjanja robov uporabimo Laplacovo jedro velikosti 3x3. Vsako slikovno točko in njeno okolico v izvorni sliki pomnožimo z Laplacovim jedrom, produkte seštejemo in vsoto uporabimo za vrednost slikovne točke v novi sliki s poudarjenimi robovi. Temu procesu pravimo konvolucija. Spodnja slika prikazuje en korak konvolucije z Laplacovim jedrom.
Na sliki je prikazano Lapacovo jedro, ki ga bomo uporabili za poudarjanje robov na sliki. Vidimo, kako poudarimo vrednost slikovne točke z vrednostjo 2, ki tvori rob s slikovno točko na svoji desni (vrednost 0). Novo vrednost, 6, dobimo tako, da jedro položimo na izvorno sliko, zmnožimo istoležne elemente ter seštejemo delne produkte.
Delo s slikami
Za branje slik z diska v pomnilnik ter zapisovanje nazaj na disk bomo uporabljali javno dostopno knjižnico STB. Knjižnica je dokaj obsežna, vendar bomo uporabili le funkciji stbi_load
in stbi_write
, ki sta definirani v zaglavjih stb_image.h
in stb_image_write.h
. Navodila za uporabo funkcij najdemo tukaj.
Predstavitev slik v pomnilniku
Sliko bomo najprej iz datotek na disku prebrali v pomnilnik. Slike so sestavljene iz množice slikovnih točk. Pri sivinskih slikah je običajno ena slikovna točka predstavljena z osmimi biti, ki določajo sivinski nivo (od črne do bele). Slikovna ravnina dimenzije Width x Height
pa je v pomnilniku predstavljena kot vektor slikovnih točk, pri čemer do posamezne slikovne točke dostopamo na naslednji način:
image[Y x Width + X]
. Dostop do posamezne slikovne točke prikazuje spodnja slika.
Količine X
(stolpec), Y
(vrstica), Width
(širina) in Height
(višina) so izražene v slikovnih točkah.
Pri barvnih slikah je tipično vsaka slikovna točka predstavljena s 4 x 8 biti ali štirimi bajti. Prvi trije bajti določajo tri komponente barve (angl. RGB, Red, Green in Blue), medtem ko pri določenih zapisih (na primer png) zadnji bajt določa prosojnost. Pravimo tudi, da je slika sestavljena iz štirih kanalov. Vsaka slikovna točka je torej v pomnilniku predstavljena s štirimi vrednostmi, pri čemer je vsaka vrednost zapisana z enim bajtom. Štirikanalno slika dimenzij Width x Height
v pomnilniku prikazuje spodnja slika.
Do posamezne slikovne točke sedaj dostopamo na naslednji način: image[(Y x Width + X) x cpp + channel]
, pri čemer je CPP
število kanalov na slikovno točko (ang. channels per pixel) in je lahko med 1 in 4, channel
pa je indeks kanala z vrednostjo od 0 do 3.
V nadaljevanju se bomo omejili na štirikanalne slike (četudi so sivniske) in jih bomo s funkcijo stbi_load
zapisali v pomnilnik. V primeru sivinskih slik, bodo zadnji trije bajti slikovne točke neuporabljeni. Na prvi pogled je rešitev potratna, saj sivinske slike v pomnilniku zasedajo štirikrat več prostora, ampak bomo zaradi tega imeli isto kodo za obdelavo vseh vrst slik.
Ščepec za poudarjanje robov
Vsaka nit bo izračunala novo vrednost ene slikovne točke v sliki. V ta namen bo morala pri filtrih velikosti 3x3 dostopati še do osmih slikovnih točk v neposredni okolici slikovne točke, za katero računa novo vrednost. Pri izbranem Laplacovem jedru je dovolj, da dostopa samo do štirih sosednjih slikovnih točk, pri katerih ima jedro neničelne vrednosti. Ščepec za poudarjanje robov je prikazan spodaj.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
V zanki for
se sprehodimo čez vse kanale in za vsak kanal posebej nit prebere vrednosti petih slikovnih točk iz slike: slikovne točke, za katero računa novo vrednost, ter njenih štirih sosedov (levo, desno, zgoraj in spodaj), kot to zahteva Laplacovo jedro. Za dostop do vrednosti posameznih slikovnih točk pripravimo funkcijo getIntensity
:
1 2 3 4 5 6 7 8 9 |
|
Organizacijo niti nastavimo na naslednji način:
1 2 |
|
Niti se bodo izvajale v delovnih skupinah velikosti BLOCK_SIZE x BLOCK_SIZE
. Celotno število niti je odvisno od velikosti vhodne slike in mora biti v obeh dimenzijah deljivo z velikostjo delovne skupine, v našem primeru 16. Ker dimenzije vhodnih slik niso nujno deljive s 16, pri določanju števila niti zaokrožimo velikost slike navzgor tako, da bo deljiva s 16.
Ščepec sharpen
se na Nvidia Tesla V100 pri obdelavi slike velike 2580x1319 slikovnih točk izvede v 0,845 milisekunde:
$ srun --partition=gpu --gpus=1 prog helmet_in.png helmet_out.png
Loaded image helmet_in.png of size 2580x1319.
Kernel Execution time is: 0.845 miliseconds
Zgornja koda se nahaja v mapi repozitorija delavnice skupaj z navodili za prevajanje in zagon 07-image-filter
.
-
© Patricio Bulić, Davor Sluga, Univerza v Ljubljani, Fakulteta za računalništvo in informatiko. Gradivo je objavljeno pod licenco Creative Commons Priznanje avtorstva-Nekomercialno-Deljenje pod enakimi pogoji 4.0 Mednarodna. ↩