ROBOTSKI VID Priroˇ cnik za laboratorijske vaje Žiga Špiclin _________________________________________________ Kataložni zapis o publikaciji (CIP) pripravili v Narodni in univerzitetni knjižnici v Ljubljani COBISS.SI-ID 232295427 ISBN 978-961-243-479-3 (PDF) __________________________________________________ URL: https://lit.fe.uni-lj.si/gradivo/RobotskiVid-PrirocnikZaVaje.pdf Copyright © 2025 Založba FE. All rights reserved. Razmnoževanje (tudi fotokopiranje) dela v celoti ali po delih brez predhodnega dovoljenja Založbe FE prepovedano. Naslov: Robotski vid; Priročnik za laboratorijske vaje Recenzenta: prof. dr. Miran Bürmen, prof. dr. Tomaž Vrtovec Založnik: Založba FE, Ljubljana Izdajatelj: Fakuleta za elektrotehniko, Ljubljana Urednik: prof. dr. Sašo Tomažič Kraj in leto izida: Ljubljana, 2025 1. elektronska izdaja Kazalo 1 Uvod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 1.1 Robotski vid 15 1.1.1 Kratka zgodovina . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 1.1.2 Aplikacije . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 1.2 Uporaba priroˇ cnika 19 1.2.1 Minimalne strojne zahteve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 I Obdelava digitalnih slik 2 Upravljanje in prikazovanje slik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.1 Python knjižnica numpy 25 2.2 Python knjižnica pillow 26 2.3 Python knjižnica matplotlib 26 2.4 Vaje z rešitvami 26 2.5 Naloge in vprašanja 32 3 Parametri kakovosti slik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.1 Referenˇ cni objekti in parametri kakovosti 36 3.2 Histogram slike 36 3.3 Vaje z rešitvami 37 3.4 Naloge in vprašanja 47 4 Preslikave sivin in barv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 4.1 Sivinske preslikave 49 4.2 Preslikave med barvnimi prostori 49 4.3 Vaje z rešitvami 50 4.4 Naloge in vprašanja 59 5 Osnovna obdelava slik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 5.1 Filtriranje slik z diskretno konvolucijo 61 5.2 Interpolacija slik 61 5.3 Decimacija slik 62 5.4 Vaje z rešitvami 62 5.5 Naloge in vprašanja 69 6 Robustno iskanje objektov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 6.1 Gradient sivinske slike 73 6.2 Harrisov detektor oglišˇ c 74 6.3 Cannyev detektor robov 75 6.4 Houghov algoritem 76 6.5 Konvolucijske nevronske mreže 77 6.6 Vaje z rešitvami 78 6.6.1 Gradient sivinske slike . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 6.6.2 Harrisov detektor oglišˇ c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 6.6.3 Cannyev detektor robov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 6.6.4 Zaznavanje premic s Houghovo preslikavo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 6.6.5 Konvolucijske nevronske mreže . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 6.7 Naloge in vprašanja 92 6.7.1 Gradient sivinske slike . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 6.7.2 Harrisov detektor oglišˇ c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 6.7.3 Cannyev detektor robov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 6.7.4 Zaznavanje krogov s Houghovo preslikavo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 6.7.5 Konvolucijske nevronske mreže . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 7 Prostorske preslikave in poravnave . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 7.1 Geometrijske preslikave 97 7.2 Poravnava pripadajoˇ cih parov toˇ ck 98 7.3 Vaje z rešitvami 99 7.4 Naloge in vprašanja 106 II Aplikacije robotskega vida 8 Geometrijska kalibracija kamere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 8.1 Geometrijski kalibri 113 8.2 Model preslikave prostor-kamera 113 8.3 Prileganje modela s slikami kalibra 114 8.4 Vaje z rešitvami 114 8.5 Naloge in vprašanja 124 9 Doloˇ canje poze objektov v prostoru . . . . . . . . . . . . . . . . . . . . . . . . . . 127 9.1 Projekcija iz 3D v 2D 127 9.2 Mera podobnosti 127 9.3 Optimizacijski postopek 129 9.3.1 Primer: optimizacija nelinearne funkcije . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 9.3.2 Optimizacija mere podobnosti za 3D togo preslikavo . . . . . . . . . . . . . . . . . . 129 9.4 Vaje z rešitvami 130 9.5 Naloge in vprašanja 145 10 Rekonstrukcija 3D oblik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 10.1 Fotometriˇ cni stereo in Lambertov model 149 10.2 Izraˇ cun albeda in normal na površino objekta 150 10.3 Rekonstrukcija 3D oblike 150 10.4 Vaje z rešitvami 151 10.5 Naloge in vprašanja 158 11 Sledenje in analiza gibanja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 11.1 Parametrizacija preslikave 161 11.2 Linearizacija nelinearne kriterijske funkcije 161 11.3 Zaprto-zanˇ cna posodobitev preslikave 162 11.4 Piramidna implementacija 162 11.5 Vaje z rešitvami 163 11.6 Naloge in vprašanja 172 12 Lokalizacija in mapiranje okolja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 12.1 Algoritem stereo vida 176 12.1.1 Ocenjevanje esencialne matrike E . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 12.1.2 Doloˇ canje poze (R, T ) iz esencialne matrike E . . . . . . . . . . . . . . . . . . . . . . . . 178 12.2 Lokalizacija in mapiranje okolja 178 12.2.1 Posplošitev na n-pogledov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 12.3 Vaje z rešitvami 179 12.4 Naloge in vprašanja 194 13 Vizualna kontrola kakovosti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 13.1 Sistem za razpoznavanje vzorcev 195 13.1.1 Razgradnja objektov zanimanja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 13.1.2 Analiza objektov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 13.1.3 Razvršˇ canje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 13.2 Vrednotenje sistemov odloˇ canja 199 13.2.1 Obˇ cutljivost in specifiˇ cnost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 13.2.2 ROC krivulja in AUC metrika . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 13.3 Vaje z rešitvami 200 13.4 Naloge in vprašanja 210 III Dodatek A Singularni razcep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 A.1 Matematiˇ cna formulacija 215 A.2 Povezava z lastnimi vrednostmi in lastnimi vektorji 216 A.3 Lastnosti singularnega razcepa 216 A.4 Geometrijska interpretacija 217 A.5 Reševanje predoloˇ cenih sistemov enaˇ cb 217 B Aproksimacijska 3D toga preslikava . . . . . . . . . . . . . . . . . . . . . . . . . . 219 B.1 Toga preslikava v 3D 219 B.1.1 Homogena 3D toga preslikava . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220 B.2 Izpeljava aproksimacijske preslikave 220 B.3 Aproksimacijska preslikava v raˇ cunskih korakih 222 C Razvojna okolja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 C.1 Osnovna namestitev in konfiguracija Pythona 226 C.1.1 Namestitev Pythona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 C.1.2 Preverjanje namestitve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 C.2 Upravljanje Python razvojnih okolij s pip 227 C.2.1 Namen in uporaba requirements.txt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228 C.2.2 Izbor verzij knjižnic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228 C.3 Upravljanje Python razvojnih okolij s conda 229 C.4 Uporaba Docker virtualnega okolja 230 C.4.1 Razumevanje osnovnih Docker konceptov . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 C.4.2 Ustvarjanje virtualnega okolja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 C.5 Uporaba Docker mape 235 C.5.1 Nastavitev Docker kontejnerja za daljinski dostop . . . . . . . . . . . . . . . . . . . . . 236 C.6 Uporaba spletnih razvojnih okolij: Google Colab 237 C.6.1 Navodila za ustvarjanje in konfiguracijo projekta . . . . . . . . . . . . . . . . . . . . . . 238 C.6.2 Primer uporabe Google Colab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 C.6.3 Namigi za uporabo GPU/TPU: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 C.7 Lokalno razvojno okolje: Visual Studio Code 240 C.7.1 Namestitev in osnovna konfiguracija Visual Studio Code . . . . . . . . . . . . . . . . 240 C.7.2 Uporaba lokalne Python distribucije . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 C.7.3 Uporaba lokalnega Docker kontejnerja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 D Vodenje razliˇ cic kode z Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 D.1 Kaj je Git in zakaj ga uporabljamo? 245 D.2 Namestitev Gita 245 D.2.1 Namestitev na Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 D.2.2 Namestitev na macOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 D.2.3 Namestitev na Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 D.2.4 Preverjanje namestitve . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 D.3 Osnovne nastavitve 246 D.3.1 Nastavitev uporabniškega imena in e-pošte . . . . . . . . . . . . . . . . . . . . . . . . . 246 D.3.2 Privzeto nastavljanje urejevalnika . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246 D.3.3 Prikaz konfiguracije . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 D.4 Ustvarjanje in kloniranje repozitorija 247 D.4.1 Inicializacija novega repozitorija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 D.4.2 Kloniranje obstojeˇ cega repozitorija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 D.5 Osnovni Git ukazi 248 D.5.1 Preverjanje statusa datotek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 D.5.2 Dodajanje sprememb v pripravo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 D.5.3 Potrjevanje sprememb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 D.5.4 Ogled zgodovine sprememb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 D.6 Uporaba vejitev 249 D.6.1 Ustvarjanje in preklapljanje vej . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 D.6.2 Združevanje vej . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249 D.6.3 Reševanje konfliktov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 D.7 Povezava z oddaljenim repozitorijem 250 D.7.1 Dodajanje oddaljenega repozitorija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 D.7.2 Pošiljanje sprememb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 D.7.3 Pridobivanje sprememb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 D.8 Pogoste napake in koristni namigi 251 D.8.1 Razveljavljanje sprememb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 D.8.2 Pridobivanje izgubljenih potrjenih sprememb . . . . . . . . . . . . . . . . . . . . . . . . . 252 D.8.3 Ignoriranje datotek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 D.9 Zakljuˇ cek 253 D.10 Dobra praksa priprave Git repozitorija 253 D.11 Zakljuˇ cek 253 D.11.1 Zakaj je Git kljuˇ cen za razvijalce? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 D.11.2 Nadaljnji viri za uˇ cenje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 Bibliografija . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 Knjige 255 Clanki ˇ 256 Konferenˇ cni ˇ clanki 256 Razno 256 O avtorju . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 Samotu, Svitu in Tanji ■ Predgovor Priˇcujoˇci priroˇcnik predstavlja študijsko gradivo, navodila za vaje in domaˇce naloge pri laborator-ijskih vajah predmeta Robotski vid na univerzitetnem magistrskem študiju elektrotehnike druge stopnje na študijski smeri Robotika. Predmet Robotski vid med študenti velja za zahteven predmet. Dostopnost kakovostne študijske literature in gradiv lahko olajša obvladovanje snovi ter tako omili zahtevnost, kar želim doseˇci s pripravo priˇcujoˇcega prostodostopnega priroˇcnika. Nastal je iz posamiˇcnih navodil za izvedbo laboratorijskih vaj pri tem predmetu v študijskih letih od 2012/2013 do 2024/2025. Predhodnik tega priroˇcnika so zbrana gradiva za laboratorijske vaje naslovljena Robotski vid: laboratorijske vaje v programskem jeziku Matlab [9], na osnovi katerih so, kot pove naslov, izvajanja vaj temeljila na uporabi razvojnega okolja Matlab. Leta 2017 sem vaje prilagodil za uporabo programskega jezika Python, saj ponuja odprtokodno rešitev brez licenˇcnih omejitev, široko podporo skupnosti, bogate knjižnice za raˇcunalniški vid in strojno uˇcenje (npr. OpenCV, TensorFlow) ter boljšo prilagodljivost za sodobne aplikacije. Namen priroˇcnika je seznaniti študente s teorijo algoritmov obdelave in analize digitalnih slik, in podati jasna navodila in smernice za izvedbo laboratorijskih vaj ter dodatnih in/ali domaˇcih nalog. Priroˇcnik obsega 12 laboratorijskih vaj, ki študente seznanijo z uporabo programskega jezika Python in pripadajoˇcih programskih knjižnic za specifiˇcne naloge in cilje. Laboratorijske vaje so organizirane v dva sklopa, in sicer na (i) vaje s podroˇcja obdelave in analize digitalnih slik in (ii) vaje s podroˇcja robotskega vida. Vaje s podroˇcja obdelave in analize digitalnih slik vkljuˇcujejo upravljanje in prikazovanje slik, parametri kakovosti slik, preslikave sivin in barv, osnovna obdelava slik, robustno iskanje objektov in prostorske preslikave in poravnave slik. Na osnovi teh vaj študenti pridobijo temeljna znanja za kasnejše zahtevnejše laboratorijske vaje, kjer morajo ta znanja uporabiti in nadgraditi v aplikacije robotskega vida. Vaje s podroˇcja robotskega vida vkljuˇcujejo pogoste, a raznolike aplikacije robotskega vida kot so geometrijska kalibracija kamere, doloˇcanje poze objektov v prostoru, rekonstrukcija 3D oblik, sledenje in analiza gibanja, lokalizacija in mapiranje okolja in vizualna kontrola kakovosti. Zasnova laboratorijskih vaj temelji na principu od spodaj navzgor tako, da študentje tekom izvajanja ustvarjajo lastno programsko knjižnico za vse obravnavane postopke in algoritme. V kasnejših vajah posamezne ˇcasovno kritiˇcne algoritme ali dele algoritmov nadomestijo s klici 14 implementacij v uveljavljenih Python knjižnicah. Na ta naˇcin študenti podrobno spoznajo zasnovo in delovanje posameznih algoritmov ter njihovo programsko implementacijo, vpliv implementacije na pravilnost in ˇcasovno zahtevnost izvajanja in osvojijo razhrošˇcevanje kode. Pomemben vidik uporabe principa od spodaj navzgor je izgradnja samozavesti za samostojno implementacijo zahtevnih algoritmov ter kritiˇcnega mišljenja pri vrednotenju delovanja. Kljub temu, da bodo študenti pri morebitnem kasnejšem profesionalnem delu na tem podroˇcju praviloma uporabljali implementacije v uveljavljenih programskih knjižnicah kot je OpenCV, pa bodo gotovo usposobljeni za implementacijo morebitnih manjkajoˇcih funkcij za povezovanje obstojeˇcih implementacij v delujoˇco rešitev. Poleg teorije in algoritmov obdelave in analize digitalnih slik ter aplikacij robotskega vida študenti tekom izvajanja laboratorijskih vaj osvojijo matematiˇcna in mnoga programska orodja. Za ˇcim lažje osvajanje dela je v dodatku tega priroˇcnika dodan sklop poglavij III, v katerih je podana razlaga singularnega razcep in njegova uporaba, toga 3D aproksimacijska poravnava, kratek pregled dela z orodji za upravljanje namešˇcanja Python knjižnic Conda in Docker in pregled razvojnih okolij kot so Visual Studio Code in Google Colab, ter kratek pregled dela z orodjem Git za arhiviranje in vodenje razliˇcic kode. Obvladovanje omenjenih orodij je nedvomno splošno uporabno pri reševanju inženirskih problemov. Nastanek tega priroˇcnika ne bi bil mogoˇc brez izjemnih sodelavcev, tako bivših kot sedanjih, iz Laboratorija za slikovne tehnologije na Fakulteti za elektrotehniko, Univeze v Ljubljani, ki so vsak po svoje prispevali k zasnovi in vsebini laboratorijskih vaj pri predmetu Robotski vid. Posebej se zahvaljujem recenzentoma prof. dr. Miranu Bürmenu in prof. dr. Tomažu Vrtovcu za natanˇcen pregled osnutka tega priroˇcnika in koristne komentarje in popravke. Iskreno zahvalo posveˇcam asistentom Jaki Katrašniku, Timu Jermanu, Žigi Bizjaku, Lari Dular in Domnu Preložniku, ki so sodelovali oz. sodelujejo pri praktiˇcni izvedbi laboratorijskih vaj in so dodobra preverili pedagoško smiselnost podane teorije, navodil in smernic za izvedbo vaj kot dodatnih nalog in vprašanj. Ljubljana, April 2025 Žiga Špiclin 1. Uvod Ljudje z neverjetno lahkoto zaznavamo tridimenzionalno (3D) strukturo sveta okoli nas. Na primer, iz slike 1.1a bi vsak ˇclovek zlahka sklepal, da "bližnji moški, ki ga vidimo v hrbet, hodi proti levi strani ulice, široka tlakovana ulica rahlo zavije v desno približno 100 metrov od toˇcke, kjer je bila slika posneta, gosta pozidava nakazuje, da je bila slika posneta v mestu, in na podlagi oblaˇcil (vsi moški nosijo klobuk in temno obleko) se zdi, da izvira izpred veˇc kot 100 let." Tovrstna opažanja je matematiˇcno nemogoˇce opisati. A kako pridemo do njih? Temeljijo na predpostavkah, predhodnem znanju in vsakodnevnih izkušnjah iz realnega sveta. Kljub navidezni lahkotnosti, s katero zaznavamo svet okoli sebe, ostaja definicija ˇcloveškega vida še vedno izmuzljiva – celo za najveˇcje mislece v zgodovini. Razmislimo o naslednjih poskusih, da bi ga opredelili: Citat 1.1 — ˇ Cloveški vid. “Vision is the act of knowing what is where by looking.” — Aristotle (384 – 322 pr. n. št.) “Vision is the act of unconscious inference.” — Hermann von Helmholtz (1821 – 1894) Kar se sprva zdi preprost proces – odpreti oˇci in videti – se hitro zaplete v prepleteno mrežo nevroznanosti, fizike, psihologije, filozofije, umetne inteligence in kognitivne znanosti. Verjamete, da v sliki 1.1b ni niti pikice rdeˇce barve? Paradoksalno je, da bolj ko razkrivamo mehanizme svetlobe, nevronov in zaznavanja, bolj zabrisana postaja definicija ˇcloveškega vida. ˇ Ce je bila sprva pomembno vprašanje “ali veste, kaj je kje?”, se je nadalje razvilo v razpravo o tem, “kaj sploh pomeni vedeti?” . Lahko morda bolj toˇcno odgovorimo na vprašanje “kaj je robotski vid?” 1.1 Robotski vid Robotski vid je podroˇcje, ki omogoˇca robotom zaznavanje, razumevanje in interpretacijo vizualnih informacij iz okolja, da se lahko ustrezno odzovejo. Robotski vid ˇcrpa znanja iz številnih sorodnih podroˇcij, kot je prikazano na sliki 1.2. Povezan je z razliˇcnimi znanstvenimi in inženirskimi disciplinami, saj združuje metode za zaznavo, interpretacijo in odzivanje na vizualne informacije. Tesno je povezan z raˇcunalniškim vidom (ang. computer vision), ki se ukvarja z obdelavo in 16 Poglavje 1. Uvod Slika 1.1: (a) Slika prizora iz preteklosti – kaj vidite na tej sliki? (b) Slika verjetno najbolj poznane ploˇcevinke na svetu – na tej sliki ni rdeˇce barve, a zakaj potem vidimo rdeˇco ploˇcevinko? analizo slik ter videoposnetkov, vendar robotski vid vkljuˇcuje tudi dejanje – torej uporabo zaznanih podatkov za fiziˇcno interakcijo robota z okoljem. Po drugi strani pa strojni vid (ang. machine vision ) pogosto oznaˇcuje industrijsko uporabo vizualnih sistemov, kjer so kamere in algoritmi namenjeni specifiˇcnim nalogam, kot je kontrola kakovosti ali avtonomna proizvodnja. Tako lahko sklenemo, da je robotski vid posebna veja raˇcunalniškega vida, ki presega zgolj razumevanje slike in vkljuˇcuje sposobnost odloˇcanja ter delovanja v fiziˇcnem svetu. Z robotskim, raˇcunalniškim in strojnim vidom povezano temeljno teoretiˇcno podroˇcje je obdelava signalov, pri ˇcemer obravnavamo slike in videje kot veˇcdimenzionalne signale. Obdelava signalov se v splošnem osredotoˇca na preoblikovanje surovih podatkov v uporabne informacije. Fizikalni temelji robotskega vida izhajajo iz fizike, optike in slikovnih tehnologij, saj razumevanje svetlobe, leˇc, senzorjev in kamer vpliva na zajem in obdelavo slik. Ker je ˇcloveški vid navdih za robotski vid, igra kljuˇcno vlogo tudi nevrobiologija in biologija vida, ki pomagata pri razumevanju, kako naravni sistemi zaznavajo in interpretirajo vizualne informacije. Matematiˇcne discipline, kot so geometrija, statistika in optimizacija, so osnova za algoritme, ki omogoˇcajo modeliranje prostora, analizo oblik in izboljšanje raˇcunalniških metod za vid. Robotski vid je moˇcno povezan s strojnim uˇcenjem, kognitivnim vidom, umetno inteligenco in raˇcunalniško inteligenco, ki omogoˇcajo samodejno uˇcenje vizualnih vzorcev in izboljšanje razumevanja slik. Nenazadnje sta robotsko upravljanje in teorija vodenja kljuˇcna elementa, ki omogoˇcata, da sistem vida ni le pasiven opazovalec, ampak aktivni del avtonomnega robota, ki se na podlagi vizualnih informacij odloˇca in deluje v resniˇcnem svetu. 1.1 Robotski vid 17 Obdelava nelinearnih in vodenja multivariatnih signalov Računalniška T eorija inteligenca inteligenca Umetna signalov Obdelava Kognitivni vid Str Računalniški Strojni vid učenje ojno Optika vid Robotski vid Fizika Obdelava slik Matematika Slikovne Statistika tehnologije Nevro- Geometrija biologija Optimizacija Biološki vid Slika 1.2: Robotski vid in sorodna podroˇcja. 1.1.1 Kratka zgodovina Zaˇcetek raˇcunalniške interpretacije slik vida sega v leto 1966, ko je profesor Marvin Minsky (MIT; 1927–2016) uvedel raˇcunalniški vid kot poletni dodiplomski projekt, pri ˇcemer je svojemu študentu naroˇcil: “”tekom poletja poveži kamero z raˇcunalnikom in pripravi raˇcunalnik tako, da opiše, kar vidi kamera.” Še danes bi bil to zelo zahteven (dodiplomski) projekt. Od tistega leta dalje se je razvoj raˇcunalniškega vida postopoma premikal od interpretacije sintetiˇcnih prizorov do prepoznavanja kompleksnih vizualnih podatkov iz resniˇcnega sveta. V grobem lahko za vsako desetletje identificiramo kljuˇcne dosežke in mejnike. V šestdesetih je bil poudarek na interpretaciji sintetiˇcnih scen, saj je bil svet vektorjev in enostavnih geometrijskih oblik edini, ki ga je lahko raˇcunalnik razumel. Sedemdeseta so prinesla prvo interpretacijo izbranih resniˇcnih slik, ˇceprav je bil postopek še vedno daleˇc od avtomatizacije. V osemdesetih je sledil vzpon umetne inteligence (AI; ang. Artificial Intelligence), vendar se je podroˇcje zaradi nezadostnih raˇcunskih zmogljivosti tedanjih raˇcunalnikov zaˇcelo nagibati k geometriji in matematiˇcnemu modeliranju, ki sta omogoˇcila robustne metode analize slik umetno ustvarjenih objektov, ki imajo dobro definirano obliko in druge lastnosti. V devetdesetih je prišlo do preboja v razpoznavanju obrazov, pri ˇcemer je statistiˇcna analiza postala prevladujoˇca metodologija. V dvatisoˇcih je raˇcunalniški vid dobil nov zagon z velikimi oznaˇcenimi bazami slik (kot so ImageNet [17]) in prvimi uspešnimi metodami obdelave videa. Dvatisoˇcdeseta so bila obdobje strojnega uˇcenja (ML; ang. Machine Learning) in globokih nevronskih mrež, ki so s svojimi zmogljivostmi zaˇcela presegati klasiˇcne algoritme. V dvatisoˇcdvajsetih smo priˇca vzponu temeljnih globokih modelov (ang. foundational models) in velikih jezikovnih modelov (ang. large language models), ki ne prepoznavajo le slik, ampak jih tudi razumejo, opisujejo in celo ustvarjajo. Ironiˇcno je, da se danes zdi Minskyjev študentski projekt skoraj otroˇcje preprost—dokler ne vprašamo sodobnega raˇcunalnika, naj “opredeli, kar vidi”, in dobimo odgovor, ki je ali neverjetno natanˇcen ali pa popolnoma absurden. “Resniˇcno izkušnjo sveta” s svojo bogato kompleksnostjo težko matematiˇcno 18 Poglavje 1. Uvod opredelimo, definiramo, modeliramo in na koncu eksplicitno zakodiramo v rešitve na podroˇcju vida. Zato se moramo pogosto zanašati na fizikalne in verjetnostne modele, da razjasnimo med možnimi rešitvami danega problema. 1.1.2 Aplikacije Tipiˇcne naloge robotskega vida pokrivajo širok spekter aplikacij, kjer mora robot vizualno zaznavati, analizirati in se odzvati na okolje. Nekatere kljuˇcne naloge, ki jih obravnavamo v tem priroˇcniku vkljuˇcujejo: • Geometrijska merjenja: Z doloˇcitvijo notranjih (gorišˇcna razdalja, popaˇcenje leˇc) in zunanjih (položaj, orientacija) paramet- rov kamere zagotavlja toˇcne meritve in ro- bustnost v razliˇcnih pogojih. To omogoˇca geometrijska kalibracija kamere, s katerim natanˇcno modeliramo preslikavo med 2D slikami in 3D svetom. Glej poglavje 8. Slika 1.3: Geometrijske meritve ujemanja zob- nikov. • Zaznavanje in pobiranje predmetov (ang. pick and place ): Roboti v proizvodnji ali skladišˇcih uporabljajo vid za natanˇcno za- znavanje poze predmetov, kar omogoˇca njihovo pobiranje z robotskimi prijemali. Zanimiva sorodna aplikacija je doloˇcanje poze anatomskih struktur v medicinskih slikah, za namen slikovno podprtih pose- gov ali za vodenje kirurškega robota. Glej Slika 1.4: Zaznavanje in pobiranje kosov na poglavje 9. tekoˇcem traku [30]. • Rekonstrukcija 3D oblike: Omogoˇca pri- dobivanje 3D modelov objektov in okolja na podlagi 2D slik ali senzorskih podatkov. Primeri uporabe so (i) merjenje dimenzij izdelkov in preverjanje napak na proiz- vodni liniji, (ii) izboljšano prepoznavanje objektov, ocena razdalj in naˇcrtovanje gib- anja v prostoru, (iii) digitalizacija zgodov- inskih artefaktov in arhitekturnih struktur omogoˇca ohranjanje kulturne dedišˇcine ter virtualne rekonstrukcije izgubljenih ob- jektov. Glej poglavje 10. Slika 1.5: Strukturirana svetloba za 3D rekon- strukcijo kipa [27]. 1.2 Uporaba priroˇ cnika 19 • Sledenje premikajoˇcim objektom: Sis- tem robotskega vida lahko spremlja gibanje doloˇcenih objektov v realnem ˇcasu, kar je uporabno pri robotih, ki lovijo premika- joˇce se tarˇce ali spremljajo delavce v sode- lovalnih robotskih sistemih. Glej poglavje 11. Slika 1.6: Sledenje gibanja oseb na ulici [26]. • Lokalizacija in navigacija: Robot s po- moˇcjo kamer in senzorjev zaznava svojo okolico, gradi mapo okolja in prepoznava ovire ter doloˇca svojo pot v prostoru. To je kljuˇcnega pomena za avtonomne mobilne robote, droni in samovozeˇca vozila. Glej poglavje 12. Slika 1.7: Mapa okolja in trajektorija kamere [23]. • Preverjanje kakovosti objektov (ang. vi- sual inspection ): Robotski vid omogoˇca prepoznavo razliˇcnih predmetov na pod- lagi barve, oblike ali teksture, kar se upor- ablja v industriji za sortiranje izdelkov in avtomatizirano proizvodnjo, za odkrivanje napak v izdelkih, preverjanje skladnosti dimenzij ali odkrivanje nepravilnosti na površinah. Glej poglavje 13. Slika 1.8: Vizualno preverjanje kakovosti svedra [29]. Uveljavljajo se tudi številne druge aplikacije robotskega vida. Velja, da z lahko malce domišljije in spretnosti v industrijskem ali pa domaˇcem okolju katerokoli kamero razvijemo v inteligentni senzor, ki je prav tako aplikacija robotskega vida. 1.2 Uporaba priroˇ cnika Namen tega priroˇcnika je z vajami okrepiti spretnosti in vešˇcine tako matematike, fizike, programir-anja in uporabe programskih orodij in knjižnic tako, da bo uporabnik priroˇcnika zmožen samostojne obravnave in reševanja problemov na podroˇcju robotskega vida. Vsako poglavje poda teoretiˇcno podlago, razgradi problem na posamezne podprobleme in nato skozi praktiˇcne primere blokov programske kode Python vodi uporabnika do rešitve posameznega podproblema, katerih rešitve nato sestavi v konˇcno rešitev. Vsa koda iz vaje in pripadajoˇce rešitve ter primeri so kot sestavni del tega priroˇcnika objavljeni na javnem Git repozitoriju: • https://gitlab.lst.fe.uni-lj.si/vaje/robotski-vid.git Priroˇcnik se deli na tri dele. Prva dva dela podajata navodila za vaje z rešitvami in dodatne naloge 20 Poglavje 1. Uvod ter vprašanja. V delu I so vaje iz obdelave digitalnih slik in v delu II vaje iz specifiˇcnih aplikacij robotskega vida. Vaje v priroˇcniku so strukturirane po uˇcnem naˇcrtu, ki ga prikazuje slika 1.3. Vsaki vaji je pridruženo predavanje, ki zagotavlja širšo teoretiˇcno podlago posameznega podroˇcja vaje. Drsnice predavanj so prosto dostopne na spletu v .pdf obliki: • [10] Žiga Špiclin. Robotski vid: predavanja. Ljubljana: Univerza v Ljubljani, Fakulteta za elektrotehniko, 2019. URL: https://plus.cobiss.net/cobiss/si/sl/bib/13747203 (pridobljeno 25. 3. 2025) Omenjena predavanja in vaje v tem priroˇcniku se izvajajo pri obveznem strokovnem predmetu Robotski vid, na drugostopenjskem študiju robotike na Fakulteti za elektrotehniko Univerze v Ljubljani [25]. Za nadaljnje branje so pri vsaki temi v drsnicah predavanj navedeni sklici na ustrezno poglavje v eni izmed navedenih knjig: • [11] Richard Szelizki. Computer Vision: Algorithms and Applications, 2nd ed. Predogled knjige za osebno rabo je dostopen tu: https://szeliski.org/Book/. Springer, 2022. URL: https://link.springer.com/book/10.1007/978-3-030-34372-9 • [6] Boštjan Likar. Biomedicinska slikovna informatika in diagnostika. Založba FE in FRI, 2008 • [5] Reinhard Klette. Concise Computer Vision. Springer, 2014 • [1] Wilhelm Burger in Mark J. Burge. Principles of Digital Image Processing: Fundamendal/ Core Techniques. Springer, 2009 Zadnji del III je Dodatek, ki podaja osnovna matematiˇcna orodja kot so singularni razcep, toga aproksimacijska poravnava v 3D, ter uvod v uporabo programskih orodij kot so razvojna okolja Google Colab, Visual Studio Code, Conda in Docker ter Git za vodenje in sledenje razvoju programske kode. Za uvod v programski jezik Python in pregled osnovnih knjižnic priporoˇcamo uˇcbenik: • [2] Miran Bürmen. Uvod v programski jezik Python. 1. izd. Ljubljana: Založba FE, 2016. ISBN: 978-961-243-318-5. URL: https : / / plus . cobiss . net / cobiss / si / sl / bib / 287674112 (pridobljeno 7. 4. 2025) 1.2.1 Minimalne strojne zahteve Vaje so pripravljene tako, da se lahko izvajajo na prostodostopni oblaˇcni raˇcunalniški platformi Google Colab (https://colab.google), zato bo za izvedbo vaj bo primeren katerikoli prenosnik z dostopom do spleta. Za izvajanje kode na lastnem raˇcunalniku, ki vkljuˇcuje v programskem jeziku Python napisane skripte z uporabo knjižnic OpenCV, TensorFlow in PyTorch za osnovne algoritme raˇcunal-niškega vida ter uˇcenje in testiranje globokih nevronskih mrež na barvnih 2D slikah (npr. Im- ageNet [17]), je priporoˇcljiva naslednja minimalna strojna oprema: • Procesor (CPU): Intel Core i5 (10. generacija ali novejši) ali AMD Ryzen 5 (3000 serija ali novejši); vsaj 4 jedra / 8 niti (veˇc je bolje za predprocesiranje podatkov). • Grafiˇcna kartica (GPU) [opcijsko]: NVIDIA RTX 3060 (6GB VRAM) ali ekvivalentna kartica, alternativno AMD GPU s podporo ROCm (ni popolnoma združljivo s TensorFlow/Py-Torch). Za poganjanje prednauˇcenih globokih modelov GPU ni nujno potreben, a bo v takem primeru izvajanje na CPU poˇcasnejše • Pomnilnik (RAM): 16GB DDR4, za veˇcje nabor podatkov 32GB DDR4/DDR5. • Shranjevanje podatkov (SSD/HDD): 512GB NVMe SSD, za nabor podatkov, kot je Im- ageNet veˇcja kapaciteta, naprimer 1TB NVMe SSD. • Operacijski sistem (OS): Linux Ubuntu 24.04+ (za najboljšo združljivost z orodji za globoko uˇcenje), Windows 10/11 (z uporabo WSL2 za boljšo združljivost s Python knjižnicami). • Dodatne zahteve: Podpora za NVidia CUDA in cuDNN programski knjižnici (za GPU pospeševanje TensorFlow/PyTorch), namešˇcen Python 3.8+, dober hladilni sistem saj uˇcenje 1.2 Uporaba priroˇ cnika 21 Predavanja Laboratorijske vaje ² ² Uvod v vizualno zaznavanje Upravljanje in prikazovanje slik ² Splošna znanja ² Zajem slik in slikovni formati Parametri kakovosti slik ² ² Kakovost slik in sivinske preslikave Preslikave sivin in barv ² ² Osnovna obdelava digitalnih slik Osnovna obdelava slik ² ² Zaznavanje objekov v slikah Robustno zaznavanje objekov ² ² Poravnava slik in oblik Prostorske preslikave in poravnave Cilj je pridobiti splošno uporabna znanja o zajemu in obdelavi digitalnih slik. « « Kalibracija optičnih sistemov Geometrijska kalibracija kamere « « Določanje poze objektov v prostoru Specifična znanja Določanje poze objektov v prostoru « « Rekonstrukcija 3D oblik Rekonstrukcija 3D oblik « « Vizualna navigacija Sledenje in analiza gibanja « « Lokalizacija in mapiranje okolja Lokalizacija in mapiranje okolja « « Odločanje na osnovi slik Vizualna kontrola kakovosti Cilj je pridobiti specifična znanja za uporabo v aplikacijah robotskega vida. Slika 1.3: Primer uˇcnega naˇcrta z uporabo priroˇcnika. globokih modelov lahko pregrevanje. Zakaj sta študij in delo na podroˇcju robotskega vida zanimiva? Podroˇcje robotskega vida je eno najhitreje rastoˇcih tehnoloških podroˇcij, saj združuje raˇcunal- niški vid, umetno inteligenco, obdelavo signalov, robotiko in optimizacijo. Študij in delo na tem podroˇcju ponujata številne zanimive izzive, hkrati pa omogoˇcata široke zaposlitvene možnosti in pomemben osebnostni razvoj. Študentje, ki se ukvarjajo s tem podroˇcjem, se sreˇcujejo z vrhunskimi tehnologijami, kot so globoke nevronske mreže, 3D rekonstrukcija, avtonomna navigacija in inteligentni vizualni sistemi. Študij robotskega vida ne razvija le tehniˇcnih vešˇcin (programiranje, obdelava podatkov, strojno uˇcenje), temveˇc tudi analitiˇcno razmišljanje, sposobnost reševanja problemov in interdis-ciplinarno povezovanje znanja. Prav tako krepi ustvarjalnost, saj zahteva iskanje novih pristopov k zaznavanju in interpretaciji vizualnih informacij. Vsak nov napredek vodi v inovacije, ki imajo realne aplikacije v industriji in vsakdanjem življenju. Povpraševanje po strokovnjakih za robotski vid in umetno inteligenco strmo narašˇca. Podjetja v avtomobilski industriji, robotiki, medicinski diagnostiki, avtomatizaciji proizvodnje, varnostnih sistemih in pametnih napravah išˇcejo kadre, ki obvladajo analizo slik in video signalov. Poleg klasiˇcnih inženirskih vlog se odpirajo tudi priložnosti v raziskavah, razvoju in podjetništvu, v visokotehnoloških podjetjih doma in po vsem svetu, zato je robotski vid odliˇcna izbira za karierno pot. I Obdelava digitalnih slik 2 Upravljanje in prikazovanje slik . . . . . . . 25 2.1 Python knjižnica numpy 2.2 Python knjižnica pillow 2.3 Python knjižnica matplotlib 2.4 Vaje z rešitvami 2.5 Naloge in vprašanja 3 Parametri kakovosti slik . . . . . . . . . . . . . . 35 3.1 Referenˇ cni objekti in parametri kakovosti 3.2 Histogram slike 3.3 Vaje z rešitvami 3.4 Naloge in vprašanja 4 Preslikave sivin in barv . . . . . . . . . . . . . . . 49 4.1 Sivinske preslikave 4.2 Preslikave med barvnimi prostori 4.3 Vaje z rešitvami 4.4 Naloge in vprašanja 5 Osnovna obdelava slik . . . . . . . . . . . . . . 61 5.1 Filtriranje slik z diskretno konvolucijo 5.2 Interpolacija slik 5.3 Decimacija slik 5.4 Vaje z rešitvami 5.5 Naloge in vprašanja 6 Robustno iskanje objektov . . . . . . . . . . . 73 6.1 Gradient sivinske slike 6.2 Harrisov detektor oglišˇ c 6.3 Cannyev detektor robov 6.4 Houghov algoritem 6.5 Konvolucijske nevronske mreže 6.6 Vaje z rešitvami 6.7 Naloge in vprašanja 7 Prostorske preslikave in poravnave . . . 97 7.1 Geometrijske preslikave 7.2 Poravnava pripadajoˇ cih parov toˇ ck 7.3 Vaje z rešitvami 7.4 Naloge in vprašanja 2. Upravljanje in prikazovanje slik Vaja služi spoznavanju osnovnih ukazov za nalaganje, prikazovanje, shranjevanje slik in upravljanje z digitalnimi slikami v programskem jeziku Python. Spoznali boste nizkonivojske knjižnice za branje in pisanje poljubnih podatkovnih datotek, kot tudi knjižnice za branje in pisanje standardnih slikovnih formatov kot so .jpg, .png, .tif, itd. 2.1 Python knjižnica numpy V jeziku Python lahko sivinske slike predstavimo z dvorazsežnimi polji ndarray v knjižnici numpy, v katerih so slikovni elementi obiˇcajno shranjeni kot nepreznaˇcena 8-, 16- ali 32-bitna cela števila ali v zapisu s plavajoˇco vejico (Tabela 2.1). Tabela 2.1: Zapis sivinskih vrednosti s podatkovnimi tipi knjižnice numpy. Zapis sivin Podatkovni tip (dtype) Zaloga vrednosti binarna slika ’bool’ { False , True } 8-bitni nepredznaˇceni ’uint8’ [0, 255] 16-bitni nepredznaˇceni ’uint16’ [0, 65535] 32-bitni predznaˇceni 31 31 ’int32’ [-2 , 2-1] 32-bitni s plavajoˇco vejico ’float32’ ali ’single’ [0.0, 1.0] 64-bitni s plavajoˇco vejico ’float64’ ali ’double’ [0.0, 1.0] Knjižnico numpy naložimo z ukazom import numpy as np, do funkcij in spremenljivk v knjižn- ici pa dostopamo z np.___. Nekatere uporabne funkcije za inicializacijo polja ndarray so zeros(), ones(), zeros_like(), ones_like(), asarray(), za pretvorbo tipa podatka astype() ali array( mArray, dtype= ... ), za branje števila dimenzij ndim() in velikosti shape() polja in za preoblikovanje polja reshape() in transpose(). Dvodimenzionalno polje mArray naslavl-jamo z npr. mArray[0,3] (element v prvi vrstici, ˇcetrtem stolpcu), mArray[:,1] (drugi stolpec), mArray[-1,:] (zadnja vrstica), itd. Indekse v sliki za logiˇcne izraze nad elementi slike lahko išˇcemo z ukazom where(). Koordinatni sistem slike in primeri naslavljanja elementov v sliki so 26 Poglavje 2. Upravljanje in prikazovanje slik prikazani na sliki 2.1. Za branje in pisanje slik v surovem (nezgošˇcenem) zapisu sta v knjižnici numpy uporabni funkciji fromfile() in tofile(). Slika 2.1: Koordinatni sistem digitalne slike in naslavljanje elementov slike. 2.2 Python knjižnica pillow Za branje in pisanje slik v standardnih formatih (bmp, png, gif, eps, jpeg, itd.) pa lahko uporabimo knjižnico PIL in modul PIL.Image (ˇce pri uvažanju knjižnice Python javi napako, potem jo najprej naložite v ukaznem oknu z ukazom pip install pillow). Knjižnica pillow (uvozi se kot modul PIL) je ena najbolj priljubljenih knjižnic za obdelavo slik v Pythonu in omogoˇca številne operacije, kot so nalaganje in shranjevanje slik, spreminjanje velikosti, obrezovanje, uporaba filtrov in druge transformacije slik. S PIL modulom sliko naložimo s funkcijo open(), ki ustvari spremenljivko tipa Image. Zapis slikovnih elementov v sliki preverimo z ukazom getbands(). S funkcijo numpy.array to spremenljivko pretvorimo v numpy podatkovno polje. Sliko v obliki numpy polja pretvorimo nazaj v tip Image s funkcijo Image.fromarray(), pretvorimo v poljuben format s funkcijo Image.convert() in shranimo z ukazom save(). 2.3 Python knjižnica matplotlib Matplotlib je vodilna Python knjižnica za vizualizacijo podatkov, ki omogoˇca profesionalno prikazovanje grafov in slik. Osnovni moduli knjižnice so: • pyplot- visokonivojski vmesnik za hitro ustvarjanje vizualizacij • Figure- osnovni objekt, ki predstavlja celotno sliko • Axes- posamezni grafi znotraj figure • Axis- številˇcne osi grafa Funkcije za prikazovanje slik so na voljo v modulu matplotlib.pyplot. Za izris slike je uporabna funkcija imshow(), za novo prikazno okno figure(), za urejanje osi pa suptitle(), xlabel() in ylabel(), axes(). Prikaz osvežite s funkcijo show(). Matplotlib ponuja številne dodatne možnosti, vkljuˇcno s 3D grafiki, animacijami in interakt- ivnimi prikazi. Za napredno uporabo je priporoˇcljivo razumeti objektno-orientiran pristop prek Figure in Axes razredov. 2.4 Vaje z rešitvami Koda vaje je dostopna na Git repozitoriju. 2.4 Vaje z rešitvami 27 Vaja 2.1 Z uporabo knjižnice PIL naložite sliko v datoteki slika.jpg, jo pretvorite v sivinsko sliko in shranite v formatu png. ■ Zaˇcnimo z uvozom potrebnih knjižnic kot je knjižnica PIL.Image (iz paketa pillow) omogoˇca delo s slikami. Prvi korak je nalaganje slike z ukazom im.open(’slika.jpg’), ki prebere slikovno datoteko iz trenutnega imenika. Metoda getbands() nato vrne seznam barvnih kanalov, kar nam omogoˇca vpogled v strukturo slike (npr. RGB kanali za barvno sliko). Ta informacija je koristna za razumevanje, kako je slika sestavljena. Glavna operacija je pretvorba barvne slike v sivinsko z metodo convert(’L’). Ta pretvorba uporablja standardno formulo za izraˇcun svetlosti (ang. Luminance), kjer vsak barvni kanal prispeva s specifiˇcno utežjo, na primer: rdeˇci kanal (R) prispeva 29.9%, zeleni (G) 58.7% in modri (B) 11.4%. Navedeni deleži oz. razmerje med njimi ustreza ˇcloveški percepciji svetlosti, saj je naše oko najbolj obˇcutljivo na zeleno svetlobo. Konˇcno, sivinska slika se shrani v novo datoteko slika.png z metodo save(). Format datoteke se samodejno doloˇci glede na podano konˇcnico. V našem primeru uporabimo format PNG (ang. Portable Network Graphics) s konˇcnico .png. Rešitev lahko napišemo: 1 import PIL.Image as im 2 import numpy as np 3 4 pilImage = im.open( ’slika.jpg’ ) # naloži sliko 5 print(pilImage.getbands()) # izpiši kanale naložene slike 6 7 # izvedi pretvorbo iz barvne v sivinsko sliko 8 # L = R * 299/1000 + G * 587/1000 + B * 114/1000 9 pilImageGray = pilImage.convert(’L’) 10 11 pilImageGray.save( ’slika.png’ ) # shrani sliko Koda izpiše rezultat v ukazni vrstici: ('R', 'G', 'B') Alternativno lahko svetlost preraˇcunamo tudi sami, po poljubni formuli. Uporabili bomo formulo za pretvorbo L(u, v) = 0.299 ·R(u,v) + 0.587 · G(u,v) + 0.114 ·B(u,v), ∀(u,v), pri ˇcemer uporabimo knjižnico numpy, ki ponuja podporo za numeriˇcne operacije. Rešitev lahko napišemo: 1 import PIL.Image as im 2 import numpy as np 3 4 pilImage = im.open( ’slika.jpg’ ) 5 # pretvorba v numpy polje z decimalno vejico, ker bomo 6 # množili vrednosti s poljubnimi skalarji 7 arrImage = np.array(pilImage, dtype=’float’) 8 9 # funkcija za pretvorbo iz RGB v svetlost 10 def rgb2gray(rgb): 11 # varianta 1: razˇ clenjeno množenje z utežmi po RGB kanalih 12 # return 0.299 * rgb[...,0] + 0.587 * rgb[...,1] + 0.114 * rgb[...,2] 13 # varianta 2: razˇ clenjeno množenje z utežmi po RGB kanalih 14 return np.dot(rgb, [0.299, 0.587, 0.114]) # zapis s skalarnim produktom 15 16 # klic funkcije za dejansko izvedbo pretvorbe 28 Poglavje 2. Upravljanje in prikazovanje slik 17 arrImage = rgb2gray(arrImage) 18 # pretvorba v uint8 zapis pred shranjevanjem 19 pilImageGray = im.fromarray(arrImage.astype(’uint8’)) 20 21 pilImageGray.save( ’slika2.png’ ) # shrani sliko Vaja 2.2 Datoteka slika-8bit.raw vsebuje dvodimenzionalno (2D) sliko v obliki surovih oz. nezgošˇcenih podatkov (raw). Širina in višina slike sta X ×Y = 975 × 650 slikovnih elementov, vsak slikovni element pa je zapisan kot nepredznaˇceno 8-bitno celo število. Napišite funkcijo za nalaganje poljubnih nezgošˇcenih slik: 1 def loadImageRaw( iPath, iSize, iFormat ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer je iPath pot do slike (mapa in ime datoteke), iSize vektor velikosti slike v slikovnih elementih, iFormat pa oblika zapisa oz. tip podatka. Funkcija naj vrne naloženo sliko oImage v obliki numpy polja. Naložite knjižnico numpy in uporabite funkcijo fromfile(). ■ Najprej uporabimo funkcijo np.fromfile za branje binarne datoteke v enodimenzionalno numpy polje, nato pa z np.reshape podatke reorganiziramo v dvodimenzionalno matriko (sliko) želene velikosti. Funkcija vrne sliko kot numpy polje, kar omogoˇca nadaljnjo obdelavo s knjižnicami za analizo slik. Rešitev lahko napišemo: 1 def loadImageRaw(iPath, iSize, iFormat): 2 ’’’ 3 Naloži sliko iz raw datoteke 4 5 Parameters 6 ---------- 7 iPath : str 8 Pot do datoteke 9 iSize : tuple 10 Velikost slike 11 iFormat : str 12 Tip vhodnih podatkov 13 14 Returns 15 --------- 16 oImage : numpy array 17 Izhodna slika 18 19 20 ’’’ 21 22 oImage = np.fromfile(iPath, dtype=iFormat) # nalozi raw datoteko 23 oImage = np.reshape(oImage, iSize) # uredi v matriko 24 25 return oImage 26 27 # klic funkcije in izpis dimenzij numpy polja 28 arrImage = loadImageRaw(’slika-8bit.raw’, (650, 975), ’uint8’) 2.4 Vaje z rešitvami 29 29 print(arrImage.shape) Koda izpiše rezultat v ukazni vrstici: (650, 975) V gornjem primeru smo dodali še dokumentacijo funkcije, z opisom funkcionalnosti funkcije, opisom pomena in tipa vhodnih ter izhodnih spremenljivk. Tipiˇcna uporaba te funkcije vkljuˇcuje nalaganje surovih slikovnih podatkov iz znanstvenih instrumentov ali medicinskih slikovnih sis-temov, kjer se slike shranjujejo v neobdelani binarni obliki. Vaja 2.3 Prikažite sliko na zaslon z uporabo funkcije imshow(). Prikaz sivin prilagodite zapisu s pomoˇcjo parametra cmap (uporabite cm.Greys_r v knjižnici matplotlib.cm). Razmerje osi prikaza prilagodite razmerju osi slike z ukazom matplotlib.axes().set_aspect( ’equal’ , ’datalim’ ). Napišite funkcijo za prikazovanje poljubnih slik v obliki numpy polja: 1 def showImage( iImage, iTitle ): 2 # Tu napišite python kodo funkcije kjer je iImage slika, ki jo želite prikazati, iTitle pa naslov prikaznega okna. ■ Knjižnico matplotlib za vizualizacij najprej uvozimo, in sicer podmodula pyplot in cm. Rešitev lahko napišemo: 1 import matplotlib.pyplot as plt 2 import matplotlib.cm as cm # uvozi barvne lestvice 3 4 def showImage(iImage, iTitle=’’): 5 ’’’ 6 Prikaže sliko iImage in jo naslovi z iTitle 7 8 Parameters 9 ---------- 10 iImage : numpy.ndarray 11 Vhodna slika 12 iTitle : str 13 Naslov za sliko 14 15 Returns 16 --------- 17 Nothing 18 19 ’’’ 20 21 plt.figure() # odpri novo prikazno okno 22 plt.imshow(iImage, cmap = cm.Greys_r) # prikazi sliko v novem oknu 23 plt.suptitle(iTitle) # oznaˇ ci naslov slike 24 plt.xlabel(’x’) # oznaˇ ci horizontalno os 25 plt.ylabel(’y’) # oznaˇ ci vertikalno os 26 plt.show() # prikaži sliko 27 28 # primer uporabe 29 showImage(arrImage, ’slika-8bit.raw’) 30 Poglavje 2. Upravljanje in prikazovanje slik Slika 2.2: Rezultat uporabe funkcije z ukazom showImage(arrImage, ’slika-8bit.raw’). Funkcija najprej ustvari novo prikazno okno, nato uporabi imshow za prikaz slike z ustreznimi nastavitvami osi in naslova. Kljuˇcna prednost je možnost hitrega pregledovanja vmesnih rezultatov pri obdelavi slik, kar je še posebej uporabno med razvojem algoritmov. Funkcija ne vraˇca nobene vrednosti, saj je njena naloga zgolj vizualizacija. Slika 2.2 prikazuje dobljeni rezultat. Vaja 2.4 Napišite funkcijo za shranjevanje slik v obliki numpy polja: 1 def saveImageRaw( iImage, iPath, iFormat ): 2 # Tu napišite python kodo funkcije kjer je iImage slika, ki jo želite shraniti, iPath pot do slike (mapa in ime datoteke), iFormat pa oblika zapisa. Naložite knjižnico numpy in uporabite funkcijo tofile(). ■ Funkcija naj najprej pretvori podatke v doloˇceni format z astype(), nato pa jih z tofile() zapiše v datoteko s funkcijo tofile(). Rešitev lahko napišemo: 1 def saveImageRaw(iImage, iPath, iFormat): 2 ’’’ 3 Shrani sliko na disk 4 5 Parameters 6 ---------- 7 iImage : numpy.ndarray 8 Vhodna slika za shranjevanje 9 iPath : str 10 Pot in ime datoteke, v katero želimo sliko shraniti 11 iFormat : str 12 Tip podatkov v matriki slike 13 14 Returns 15 --------- 16 Nothing 17 ’’’ 18 19 iImage = iImage.astype(iFormat) # spremeni tip zapisa 20 iImage.tofile(iPath) # zapisi v datoteko 21 22 # primer uporabe 2.4 Vaje z rešitvami 31 23 saveImageRaw(arrImage, ’slika-8bit-2.raw’, ’uint8’) Kljuˇcne znaˇcilnosti funkcije so: (1) ohranjanje originalnih podatkov brez stiskanja, (2) možnost shranjevanja poljubnih numeriˇcnih formatov in (3) enostavno branje s funkcijo np.fromfile. Funkcija ne vraˇca vrednosti, saj je njena naloga zgolj shranjevanje podatkov. Vaja 2.5 Datoteka slika-16bit.raw vsebuje 2D sliko v nezgošˇcenem zapisu, slika velikosti X ×Y = 975 ×650, vsak slikovni element pa je zapisan kot nepredznaˇceno 16-bitno celo število. Prilagodite in preizkusite delovanje zgoraj navedenih funkcij loadImageRaw(), showImage() in saveImageRaw(). ■ Za klic funkcij loadImageRaw() in saveImageRaw() doloˇcimo ustrezne argumente, kritiˇcen argument je iFormat, ki ga moramo nastaviti na ’uint16’. Funkcija showImage() je neodvisna od tipa zapisa, saj imshow() funkcija samodejno prilagodi prikaz. Rešitev lahko napišemo: 1 arrImage = loadImageRaw(’slika-16bit.raw’, (650, 975), ’uint16’) 2 showImage(arrImage, ’slika-16bit.raw’) 3 saveImageRaw(arrImage, ’slika-16bit-2.raw’, ’uint16’) Slika 2.3a prikazuje dobljeni rezultat. (a) (b) Slika 2.3: Rezultat uporabe funkcije z ukazom (a) showImage(arrImage, ’slika-16bit.raw’) v nalogi 2.5 in ukazom (b) showImage(arrImage, ’slika-24bit-rgb.raw’) v nalogi 2.6. Vaja 2.6 Datoteka slika-24bit-rgb.raw vsebuje barvno 2D sliko v nezgošˇcenem zapisu, slika ima velikost X ×Y = 975×650, posamezen slikovni element pa je predstavljen s 24 = 3× 8 biti. Barvna RGB slika je zapisana kot zaporedje treh nepreznaˇcenih 8 bitnih slik, kjer si sledijo rdeˇca (R), zelena (G) in modra (B) komponenta. Dopolnite funkcije loadImageRaw(), showImage() in saveImageRaw(), tako da bodo omogoˇcale nalaganje, prikaz in shranjevanje barvnih RGB slik. ■ Za klic funkcij loadImageRaw() in saveImageRaw() doloˇcimo ustrezne argumente, kritiˇcen argument je iSize, ki predstavlja dimenzije slike v prvih dveh vrednostih in število barvnih kanalov v tretji dimenziji (3 v primeru RGB barvne slike). Funkcija showImage() je neodvisna od tipa zapisa, saj imshow() funkcija samodejno prilagodi prikaz. Rešitev lahko napišemo: 1 arrImage = loadImageRaw(’slika-24bit-rgb.raw’, (650, 975, 3), ’uint8’) 2 showImage(arrImage, ’slika-24bit-rgb.raw’) 32 Poglavje 2. Upravljanje in prikazovanje slik 3 saveImageRaw(arrImage, ’slika-24bit-rgb-2.raw’, ’uint8’) Slika 2.3b prikazuje dobljeni rezultat. Opomba: Vse dosedaj napisane funkcije bomo shranili v datoteko rvlib.py za uporabo pri naslednjih vajah. 2.5 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.2: To je moj odgovor. . Naloga 2.1 Napišite funkcijo za nalaganje slik v standardnih formatih (bmp, jpg, png, tif, gif, idr.) s knjižnico PIL.Image: 1 def loadImage( iPath ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer je iPath pot do slike (mapa in ime datoteke). Funkcija naj vrne sliko v obliki numpy.ndarray polja v spremenljivki oImage. Naložite in prikažite sliko slika.jpg. Naloga 2.2 Napišite funkcijo za shranjevanje slik v standardnih formatih (bmp, jpg, png, tif, gif, idr.) s knjižnico PIL.Image: 1 def saveImage( iPath, iImage, iFormat ): 2 # Tu napišite python kodo funkcije kjer je iPath pot do slike (mapa in ime datoteke), spremenljivka iImage slika v obliki numpy.ndarray polja, iFormat pa beseda z željeno obliko zapisa slika (npr. ’bmp’, ’png’). Naloga 2.3 Iz barvne 2D slike slika.jpg izlušˇcite pravokotno barvno podsliko med oglišˇci (x 1, y1) = (260,70) in (x2,y2) = (390,180) in jo prikažite. Preverite pravilnost doloˇcanja podroˇcja s pomoˇcjo slike 2.4. Naloga 2.4 S pomoˇcjo indeksiranja prezrcalite sliko slika.jpg preko navpiˇcne osi in jo prikažite. Naloga 2.5 Uporabite funkcijo numpy.transpose za rotacijo slike za 90 stopinj, tako da transponir-ate x in y os slike slika.jpg. Naloga 2.6 1 1 Pretvorite barvno sliko C = [ R , G , B ] v sivinsko sliko S preko enaˇcbe S = R + G + 3 3 1 B in narišite projekciji maksimalne in povpreˇcne svetlosti v smeri x in y koordinat 2D slik. 3 Projekciji lahko ustvarite z uporabo for zanke ali funkcij numpy.max() in numpy.mean(), pri ˇcemer ustrezno nastavite parameter axis. Za izris projekcij uporabite funkcijo plot() v knjižnici matplotlib.pyplot. Naloga 2.7 Zamenjajte vrednosti slikovnih elementov rdeˇcega kanala 2D barvne slike, ki imajo vrednost med 160 in 200 z vrednostjo 255. Uporabite funkcijo where() v knjižnici numpy. Prikažite originalno in novo barvno sliko. 2.5 Naloge in vprašanja 33 Slika 2.4: Izrez barvne slike Slika 2.5: Izris daljic s sivinskimi na pravokotnem podroˇcju med vrednostmi 0 in krajišˇcema (x 1, y1) = oglišˇci (x1,y1) = (260,70) in (10,10) in (x2,y2) = (20, 80) ter (x1, y1) = (x2,y2) = (390, 180). (100,30) in (x2,y2) = (10, 100). Naloga 2.8 S pomoˇcjo Bresenhamovega algoritma 2.1 napišite funkcijo za risanje digitalnih daljic v sliko: 1 def drawLine( iImage, iValue, x1, y1, x2, y2 ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer je iImage slika, v katero želite risati daljico, iValue vrednost, ki jo pripišete slikovnemu elementu na daljici, (x1, y1) in (x2,y2) pa so koordinate zaˇcetne in konˇcne toˇcke daljice. Preverite delovanje funkcije, tako da ustvarite belo sivinsko sliko velikosti X ×Y = 100 × 100, nato vrišite dve ˇcrni daljici s krajišˇcema (x1,y1) = (10,10) in (x2,y2) = (20,80) ter (x1,y1) = (100,30) in (x 2, y2) = (10,100). Dobljeno sliko primerjajte s sliko 2.5. Podrobna razlaga delovanja algoritma je dana v [22]. Algoritem 2.1 — Bresenhamov algoritem. funkcija narišiDaljico(slika, vrednost, x1, y1, x2, y2) dx := abs(x2-x1) dy := abs(y2-y1) ˇ ce x1 < x2 potem sx := 1 sicer sx := -1 ˇ ce y1 < y2 potem sy := 1 sicer sy := -1 napaka := dx-dy zanka slika(x1,y1) = vrednost ˇ ce x1 = x2 in y1 = y2 potem izhod iz zanke e2 := 2*napaka ˇ ce e2 > -dy potem napaka := napaka - dy x1 := x1 + sx ˇ ce e2 < dx potem napaka := napaka + dx y1 := y1 + sy konec zanke Naloga 2.9 Spremenite funkcijo za risanje daljic drawLine() tako, da bo omogoˇcala risanje 34 Poglavje 2. Upravljanje in prikazovanje slik daljic v RGB barvno sliko z izbrano barvo. Ustvarite 2D barvno sliko velikosti X ×Y = 300 × 100 in vse slikovne elemente obarvajte belo. S pomoˇcjo posodobljene funkcije za risanje barvnih daljic v 2D sliko izpišite vaše ime, pri ˇcemer roˇcno doloˇcite ustrezna krajišˇca daljic. Vsaka ˇcrka vašega imena naj ima drugo barvo. 3. Parametri kakovosti slik Kakovost zajete slike je odvisna od številnih dejavnikov v procesu zajemanja slik, ki predstavlja preslikavo iz fiziˇcnega v slikovni prostor. Pomembni dejavniki, ki vplivajo na kakovost slik so osvetlitev, gostota vzorˇcenja, napake oz. aberacije optiˇcnega sistema, idr. Popaˇcenje slik kot posledico zajema v splošnem imenujemo šum; to je izraz za kakršnokoli nakljuˇcno spreminjanje sivinskih vrednosti in ima lahko pomemben vpliv na kakovost slike. Izvor, vrsta in velikost šuma so odvisni od fizikalnega procesa oziroma naˇcina zajemanja slik, najbolj znani in splošno uporabni model šuma pa je Gaussov šum. Gaussov šum je definiran s funkcijo √ − 1 2 2 gostote verjetnosti p ( z ) = ( 2 π σ ) exp − ( z − µ ) / 2 σ , kjer µ predstavlja srednjo vrednost, σ pa standardno deviacijo šuma. Šum v sliki ocenjujemo na podroˇcjih s predvidoma konstantno sivinsko vrednostjo. Popaˇcenje slik, ki povzroˇca sivinske nehomogenosti, pogosto izvira iz razliˇcnih dejavnikov, kot so neenakomerna osvetlitev, šum senzorjev v kameri, optiˇcne aberacije ali nepravilnosti v procesiranju signala. Na primer, pri slikanju z digitalnimi napravami lahko šum, kot je termiˇcni ali fotonski šum, povzroˇci nakljuˇcne spremembe v intenziteti sivin na posameznih pikslih, medtem ko neenakomerna osvetlitev ustvari svetlostne razlike v vidnem polju. Za odpravljanje tega popaˇcenja se uporabljajo metode, kot so korekcija osvetlitve z uporabo kalibracijskih slik (npr. "flat-field" korekcija, ki vkljuˇcuje slikanje homogene temne in/ali svetle podlage), filtriranje šuma z algoritmi, kot je lokalno povpreˇcenje ali Gaussov filter, ter naprednejše tehnike, kot je homomorfno filtriranje. V kompleksnejših primerih se lahko uporabi tudi strojno uˇcenje za prepoznavanje in odstranjevanje specifiˇcnih vzorcev popaˇcenj, kar zagotovi bolj homogeno in kakovostno sliko. Optiˇcne aberacije so napake v slikovnem sistemu, ki povzroˇcajo odstopanja od idealnega projiciranja svetlobe na slikovni senzor, kar poslediˇcno vpliva na ostrino, kontrast in popolnost zajete slike. Aberacije nastanejo zaradi neidealnosti leˇc ali zrcal, ki povzroˇcajo, da se svetlobni žarki iz enake toˇcke predmeta ne sreˇcajo povsem toˇcno na slikovni ravnini. Aberacije delimo na kromatske (nastanejo zaradi razliˇcnega loma svetlobe razliˇcnih valovnih dolžin) in monokromatske (pojavijo se tudi pri enobarvni svetlobi). Med monokromatske aberacije spadajo: • Sferiˇcna aberacija (neravnomerno lomljenje žarkov na robovih leˇce) 36 Poglavje 3. Parametri kakovosti slik • Koma (razmazovanje toˇckastih virov svetlobe v obliki repa) • Astigmatizem (razliˇcna gorišˇcna razdalja v navpiˇcni in vodoravni smeri) • Ukrivljenost polja (slika se fokusira na ukrivljeno površino namesto na ravnino) • Geometrijska distorzija (deformacija slike, bodisi blazinasta ali sodˇckasta) Najpogostejša je sferiˇcna aberacija, ki zmanjšuje kontrast in ostrino slike, še posebej na robovih polja. Prospektivno jo lahko zmanjšamo z uporabo asferiˇcnih leˇc, kombinacij veˇc leˇc ali diafragmiranjem (zožanje odprtine, kar izkljuˇci skrajne žarke). Retrospektivno pa jo lahko delno popravimo z algoritmi za obdelavo slik, kot je dekonvolucija z znano funkcijo razširitve toˇcke (PSF; ang. Point Spread Function) ali prilagajanjem kontrasta z digitalno obdelavo. Z modeliranjem prostorskih popaˇcitev lahko retrospektivno odpravimo geometrijsko distorzijo, ki jo bomo obravnavali v poglavju 8 o geometrijski kalibraciji kamere, ki je nujna za izvajanja toˇcnih geometrijskih merjenj objektov zanimanja na podlagi zajetih slik. Kljub ukrepom za zmanjšanje aberacij in drugih popaˇcenj je njihova popolna odprava teoretiˇcno nemogoˇca, zato je treba pri naˇcrtovanju optiˇcnih sistemov poiskati optimalno ravnovesje med kompleksnostjo, ceno in kakovostjo slike. 3.1 Referenˇ cni objekti in parametri kakovosti Kakovost slik lahko ovrednotimo z zajemom slik znanih kalibracijskih objektov (Slika 3.1), iz katerih izraˇcunamo parametre kakovosti slik kot so kontrast, prostorska in sivinska loˇcljivost ter šum oz. razmerje signal–šum (Slika 3.2). Ocenjevanje parametrov kakovosti slik vkljuˇcuje analizo veˇc kljuˇcnih lastnosti, ki vplivajo na zaznavanje in uporabnost slike: kontrast, prostorska loˇcljivost, sivinska loˇcljivost ter šum oziroma razmerje signal-šum (SNR). Kontrast meri razliko v svetlosti med najsvetlejšimi in najtemnejšimi deli slike; višji kontrast omogoˇca boljšo razloˇcnost podrobnosti. Ocenjuje se ga lahko z uporabo histogramov ali metrik, kot je Michelsonov kontrast. Prostorska loˇcljivost opisuje sposobnost slike, da prikaže fine podrobnosti, in je odvisna od števila pikslov na dolžinsko enoto (npr. dots per inch oz. DPI); meri se z vzorci, kot je USAF 1951 testna tarˇca, ki vsebuje ˇcrte razliˇcnih gostot. Sivinska loˇcljivost se nanaša na število razloˇcljivih sivinskih nivojev (npr. 8-bitna slika ima 256 nivojev) in vpliva na gladkost prehodov med odtenki; ocenjuje se z analizo bitne globine in vizualnimi testi. Šum oznaˇcuje neželene nakljuˇcne variacije v intenziteti pikslov, medtem ko razmerje signal-šum (SNR) primerja moˇc uporabnega signala z moˇcjo šuma, izraženo v decibelih (dB) – višji SNR pomeni boljšo kakovost. Za standardizirano ocenjevanje se uporabljajo kalibri, kot so že omenjena USAF 1951 tarˇca za prostorsko loˇcljivost, Siemensova zvezda za testiranje ostrine, ali ISO 12233 tarˇca, ki združuje meritve loˇcljivosti in kontrasta. Za šum in SNR se pogosto uporabljajo enobarvne testne slike ali specializirani vzorci, kot so tisti iz standarda ISO 15739. Ti kalibri omogoˇcajo objektivno primerjavo in optimizacijo slikovnih sistemov. 3.2 Histogram slike Histogram slike je grafiˇcno orodje za prikazovanje frekvenˇcnih porazdelitev sivinskih vrednosti slikovnih elementov na sliki (Slika 3.3a). Vrednosti k na abscisni osi histograma predstavljajo din-amiˇcno obmoˇcje sivinskih vrednosti slike. Za izbrano sivinsko vrednost na abscisni osi predstavlja vrednost na ordinatni osi hk število slikovnih elementov na sliki z izbrano sivinsko vrednostjo (Slika 3.3b). Normaliziran histogram predstavlja oceno relativne verjetnostne porazdelitve pr (z); v primeru karakterizacije šuma na podroˇcju slike s približno konstantno intenziteto lahko na podlagi p L r µ ( z ) doloˇcimo parametra in σ Gaussove (oz. normalne) porazdelitve šuma kot µ = ∑ z p ( ) z i=1 i r i in 2 L 2 − σ = ∑ ( z ) i i µ pr(zi). Parameter σ je v tem primeru sorazmeren magnitudi šuma in odraža = 1 širino Gaussove porazdelitve. 3.3 Vaje z rešitvami 37 (a) ColorChecker (b) ISO 12233 Slika 3.1: Sliki kalibracijskih objektov, ki ju uporabljamo pri vrednotenju (a) kakovosti oslikave sivin in barv, npr. z izraˇcunom sivinske loˇcljivosti in razmerja signal–šum, in (b) prostorske loˇcljivosti z modulacijsko prenosno funkcijo. Slika 3.2: Parametri kakovosti slik: (a) Michelsonov kontrast na periodiˇcnih vzorcih spremenljive frekvence, (b) prostorska loˇcljivost na podlagi poteka Michelsonovega kontrasta; (c) sivinska loˇcljivost, ki zavisi od dinamiˇcnega obmoˇcja (kvantizacije) in šuma; (d) razmerje signal-šum, ki se lahko uporabi za doloˇcanje razloˇcljivosti objektov zanimanja. Slika 3.3: (a) Barvna slika in histogram pripadajoˇce sivinske slike. (b) Histogram nepredznaˇcene 4-bitne sivinske slike z dinamiˇcnim obmoˇcjem 0–16, številom predalov 16 in vrednostmi hk . 3.3 Vaje z rešitvami Vaja je namenjena spoznavanju in razumevanju doloˇcanja parametrov kakovosti slik ter preizkušanju vpliva zunanjih dejavnikov na kakovost zajete slike. Koda vaje je dostopna na Git repozitoriju. 38 Poglavje 3. Parametri kakovosti slik Vaja 3.1 S pomoˇcjo telefona oz. fotoaparata zajemite dve sliki kalibra ColorChecker (Slika 3.1a), prvo ko so v predavalnici prižgane luˇci in drugo ko so luˇci ugasnjene. Bliskavica naj bo pri tem izklopljena. Ob prižganih luˇceh zajemite še sliko kalibra ISO 12233 (Slika 3.1b). Slike prenesite na raˇcunalnik in jih naložite Python s funkcijo loadImage() (Vaja 2). ■ Potrebne Python knjižnice in vse dosedaj napisane funkcije bomo najprej naložili iz naše knjižnice rvlib.py, nato zajeti sliki naložimo in prikažemo: 1 import matplotlib.pyplot as plt 2 import PIL.Image as im 3 import numpy as np 4 from rvlib import * 5 6 # naloži sliki 7 col_svetla = loadImage(’slika1_col_svetla.jpg’) 8 col_temna = loadImage(’slika2_col_temna.jpg’) 9 10 # prikaži sliki 11 showImage(col_svetla, ’Osvetljen kaliber’) 12 showImage(col_temna, ’Neosvetljen kaliber’) Primer zajetih slik prikazuje slika 3.4. (a) (b) Slika 3.4: Zajeti sliki ob (a) prižganih in (b) ugasnjenih luˇceh v predavalnici. Vaja 3.2 Zajete slike pretvorite v sivinske slike po enaˇcbi S = 0, 299R + 0,587G + 0,114B. Za shranjevanje in kasnejše nalaganje spremenljivk v obliki polja numpy.ndarray iz oziroma v datoteko uporabite funkciji saveImageRaw() in loadImageRaw() (Vaja 2). ■ 1 def colorToGray(iImage): 2 ’’’ 3 Pretvori barvno sliko v sivinsko. 4 5 Parameters 6 --------- 7 iImage : numpy.ndarray 8 Vhodna barvna slika 9 10 Returns 11 ------- 12 oImage : numpy.ndarray 13 Sivinska slika 3.3 Vaje z rešitvami 39 14 ’’’ 15 dtype = iImage.dtype 16 r = iImage[0,:,:].astype(’float’) 17 g = iImage[1,:,:].astype(’float’) 18 b = iImage[2,:,:].astype(’float’) 19 20 return (r*0.299 + g*0.587 + b*0.114).astype(dtype) 21 22 # pretvori obe sliki v sivinski 23 gray_svetla = colorToGray(col_svetla) 24 gray_temna = colorToGray(col_temna) 25 26 # shrani kot surove podatke v .raw datoteki 27 saveImageRaw(gray_svetla, ’slika1_gray_svetla.raw’, ’uint8’) 28 saveImageRaw(gray_temna, ’slika2_gray_temna.raw’, ’uint8’) 29 30 # naloži surove podatke iz .raw datotek 31 gray_svetla = loadImageRaw(’slika1_gray_svetla.raw’, (1836, 3264), ’uint8’) 32 gray_temna = loadImageRaw(’slika2_gray_temna.raw’, (1836, 3264), ’uint8’) Vaja 3.3 Napišite funkcijo za izraˇcun in prikaz histograma sivinske slike: 1 def computeHistogram( iImage, iNumBins, iRange=[], iDisplay=False, iTitle=’’ ): 2 # Tu napišite python kodo funkcije 3 return oHist, oEdges kjer je iImage slika, za katero se histogram raˇcuna, iNumBins število predalov as histograma, iRange pa spremenljivka tipa list, ki podaja obmoˇcje vrednosti histograma kot par minimalne in maksimalne vrednosti (npr. [0, 255] za nepredznaˇceno 8-bitno sliko). Histogram naj se prikaže le, kadar ima vhodna spremenljivka iDisplay vrednost True (privzeto False), iTitle pa naj bo naslov prikaznega okna, v katerem je histogram prikazan. Za prikazovanje pa uporabite funkcijo bar() v Python knjižnici matplotlib.pyplot. Funkcija vrne histogram slike oHist v obliki enodimenzionalnega polja tipa numpy.array, katerega dolžina je enaka številu predalov, kot jih podaja vhodna spremenljivka iNumBins. Izhodna spremenljivka oEdges naj vrne meje predalov histograma v obliki enodimenzionalnega polja tipa numpy.array, pri ˇcemer je dolžina za ena veˇcja od števila predalov. ■ Funkcijo computeHistogram() zastavimo tako, da izraˇcuna histogram slike iImage z razvršˇcan- jem vrednosti slikovnih pik v iNumBins enako širokih intervalov (binov) med najmanjšo (iMin) in najveˇcjo (iMax) vrednostjo, ki ju lahko podamo z iRange ali ju funkcija doloˇci sama. Nato prešteje, koliko pik spada v vsak interval, in vrne histogram (oHist) ter meje intervalov (oEdges). Ce je vhodni parameter ˇ iDisplay=True, prikaže histogram kot stolpiˇcni graf z naslovom iTitle. Funkcija uporablja NumPy za vektorske operacije in Matplotlib za vizualizacijo. Rešitev lahko napišemo: 1 def computeHistogram(iImage, iNumBins, iRange=[], iDisplay=False, iTitle=’’): 2 iImage = np.asarray(iImage) 3 iRange = np.asarray(iRange) 4 if iRange.size == 2: 5 iMin, iMax = iRange 6 else: 40 Poglavje 3. Parametri kakovosti slik Slika 3.5: Histogram slike 3.4a. 7 iMin, iMax = np.min(iImage), np.max(iImage) 8 oEdges = np.linspace(iMin, iMax+1, iNumBins+1) 9 oHist = np.zeros([iNumBins,]) 10 for i in range(iNumBins): 11 idx = np.where((iImage >= oEdges[i]) * (iImage < oEdges[i+1])) 12 if idx[0].size > 0: 13 oHist[i] = idx[0].size 14 if iDisplay: 15 plt.figure() 16 plt.bar(oEdges[:-1], oHist) 17 plt.suptitle(iTitle) 18 19 return oHist, oEdges 20 21 # primer izraˇ cuna in prikaza histograma svetle slike 22 oHist, oEdges = computeHistogram(gray_svetla[::4,::4], 255, [0, 255], \ 23 True, ’Histogram osvetljenega kalibra’) Slika 3.5 prikazuje histogram slike 3.4a. Vaja 3.4 Iz zajetih slik kalibra ColorChecker izlušˇcite pravokotne podslike tako, da bo vsaka podslika vsebovala le eno podroˇcje 1–24. Podslike tipa numpy.ndarray shranite v vrstiˇcni vektor tipa list, v vrstnem redu kot je oznaˇcen na sliki (1–24). Za hitro in enostavno doloˇcanje pravokotnih podroˇcij na sliki lahko oznaˇcite le položaje nasprotnih si oglišˇc pravokotnika, tako da uporabite funkcijo ginput() v knjižnici matplotlib.pyplot. ■ Naložimo sliko kalibra ColorChecker in roˇcno oznaˇcimo oglišˇca podpodroˇcij ter shranimo toˇcke: 1 plt.close(’all’) 2 showImage(gray_svetla, ’slika kalibra ColorChecker’) 3 plt.ioff() # izkljuˇ ci interaktivni naˇ cin 4 pts = plt.ginput(n=48, timeout=-1) 5 pts = np.array(pts, dtype=’float’) 6 saveImageRaw(pts, ’naloga4_pts.raw’, ’float’) Oznaˇcene toˇcke si lahko prikažemo na sivinski sliki kalibra in s histrogramom vizualiziramo porazdelitev intenzitet in tako kvalitativno ocenimo stopnjo šuma: 3.3 Vaje z rešitvami 41 (a) (b) Slika 3.6: Slika (a) prikazuje oznaˇcene toˇcke, slika (b) histogram gornjega levega podroˇcja z oznako 1. 1 pts = loadImageRaw(’data/naloga4_pts.raw’, (48, 2), ’float’) 2 plt.close(’all’) 3 4 plt.imshow(gray_svetla, cmap = cm.Greys_r) 5 plt.suptitle(’Slika kalibra’) 6 7 # nariši oznaˇ cene toˇ cke 8 pts = pts.reshape((24,2,2)) 9 sub_gray_svetla = [] 10 for i in range(np.size(pts,axis=0)): 11 plt.plot(pts[i,0,0],pts[i,0,1],’+r’) 12 plt.plot(pts[i,1,0],pts[i,1,1],’+b’) 13 xs = round(pts[i,0,0]) 14 ys = round(pts[i,0,1]) 15 xe = round(pts[i,1,0]) 16 ye = round(pts[i,1,1]) 17 sub_gray_svetla.append(gray_svetla[ys:ye,xs:xe]) 18 plt.show(block = True) 19 20 computeHistogram(sub_gray_svetla[0], 255, [0, 255], \ 21 True, ’Histogram podslike podroˇ cja 1’) Rezultat izvajanja gornjega bloka Python kode je prikazan v sliki 3.6. Vaja 3.5 Iz zajete slike kalibra ISO 12233 izlušˇcite pravokotni podsliki vodoravnih in navpiˇcnih linijskih parov tako, kot oznaˇcujeta rdeˇca pravokotnika na sliki 3.1b. Vsako od podslik tipa numpy.ndarray razrežite na 10 podslik vzdolž daljše osi in jih shranite v vektor tipa list, v vrstnem redu od podslik z najredkejšimi do najgostejšimi linijami (1–10). ■ Zajeto sliko kalibra ISO 12233 prikažemo in roˇcno oznaˇcimo oglišˇci pravokotnega podpodroˇcja, kjer se nahajajo bodisi horizontalni bodisi vertikalni vzorci ˇcrno-belih linij. Vzorci linij naj bodo nujno znotraj pravokotnega okvirja, brez belega ozadja, za pravilen izraˇcun Michelsonovega kontrasta. V primeru, da je slika zajeta postrani ponovite zajem tako, da bodo vzorci v smeri osi slike. Uporabimo lahko naslednjo kodo: 1 gray_space = colorToGray(loadImage(’slika3_space_res.jpg’)) 2 plt.close(’all’) 42 Poglavje 3. Parametri kakovosti slik Slika 3.7: Slika prikazuje izlušˇcena podpodroˇcja vertikalnih ˇcrno-belih prog kalibra ISO12233. 3 showImage( gray_space, ’Slika kalibra ISO 12233’ ) 4 plt.ioff() # turn off interactive mode 5 pts = plt.ginput(n=2, timeout=-1) 6 pts = np.array(pts, dtype=’float’) 7 saveImageRaw(pts, ’naloga5_pts.raw’, ’float’) Na osnovi oglišˇc v datoteki ’naloga5_pts.raw’ izlušˇcimo pravokotno sliko in jo razrežemo na 10 enakih delov. V spodnji kodi smo predvideli, da so bili oznaˇceni vertikalni vzorci, zato razrez naredimo po y osi. Rešitev lahko napišemo: 1 pts = loadImageRaw(’naloga5_pts.raw’, (2, 2), ’float’) 2 plt.ion() 3 4 xs = np.round(pts[0,0]) 5 xe = np.round(pts[1,0]) 6 yc = np.round(np.linspace(pts[0,1],pts[1,1],11)) 7 sub_gray_space = [] 8 for i in range(len(yc)-1): 9 sub_gray_space.append(gray_space[yc[i]:yc[i+1],xs:xe]) 10 11 # prikaz 12 fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(15, 6)) # matrika 2x5 sliˇ cic 13 fig.suptitle("Podpodroˇ cja v sliki", fontsize=16) 14 axes = axes.flatten() # indekse osi pretvorimo v vektor za lažji izris 15 16 # Plot data on each subplot 17 for i, ax in enumerate(axes): 18 ax.imshow(sub_gray_space[i], cmap = cm.Greys_r) # izris sliˇ cice 19 ax.set_title(f"{i+1}") # naslov sliˇ cice 20 ax.grid(True) # prikaz oslonilnih ˇ crt 21 plt.tight_layout() 22 plt.show(block=True) Slika 3.7 prikazuje izlušˇcena podpodroˇcja v sliki kalibra ISO12233. 3.3 Vaje z rešitvami 43 Vaja 3.6 Napišite funkcijo za izraˇcun mere kontrasta m na sivinskih podslikah kalibra ISO 12233 iImages: 1 def computeContrast( iImages ): 2 # Tu napišite python kodo funkcije 3 return oM ki vrne polje skalarnih vrednosti oM, ki ima število elementov enako številu vhodnih slik. Minimalno in maksimalno vrednost fmin in fmax doloˇcite kot 5- in 95-percentil vseh sivinskih vrednosti tako, da uporabite funkcijo percentile() v knjižnici numpy. Preverite, da je kontrast najvišji pri podslikah z najredkejšimi pari vodoravnih oz. navpiˇcnih linij. Prikažite modulacijsko prenosno funkcijo (MTF; ang. Modulation Transfer Function) in doloˇcite prostorsko loˇcljivost sistema pri poloviˇcni vrednosti kontrasta glede na najvišjo vrednost. ■ 1 def computeContrast(iImages): 2 ’’’ 3 Izracunaj kontrast slik 4 5 Parameters 6 --------- 7 iImages : list of numpy.ndarray 8 Vhodne slike, na katerih želimo izraˇ cunati kontrast 9 10 Returns : list 11 Seznam kontrastov za vsako vhodno sliko 12 ’’’ 13 oM = np.zeros((len(iImages),)) 14 for i in range(len(iImages)): 15 fmin = np.percentile(iImages[i].flatten(),5) 16 fmax = np.percentile(iImages[i].flatten(),95) 17 oM[i] = (fmax - fmin)/(fmax + fmin) 18 return oM 19 20 # prikaz modulacijske prenosne funkcije 21 mtf = computeContrast(sub_gray_space) 22 plt.figure() 23 plt.plot(mtf) 24 plt.suptitle(’Potek modulacijske prenosne funkcije (MTF) v izbrani osi’) 25 plt.xlabel(’Indeks podroˇ cja’) 26 plt.ylabel(’Michelsonov kontrast’) 27 plot.show(block=True) Izraˇcun števila parov ˇcrt na palec (LPPI) za ISO 12233 grafikon naredimo glede na velikost kalibra, ki smo ga slikali. Vrednosti od 1 do 10 na ISO 12233 grafikonu predstavljajo 100-kratno število parov ˇcrt na višino slike (LP/PH), kjer je višina slike v našem primeru 180 mm (7,09 palca). To pomeni, da vzorec z oznako n ustreza: LP/PH = n × 100 (3.1) 44 Poglavje 3. Parametri kakovosti slik Slika 3.8: Slika prikazuje modulacijsko prenosno funkcijo. Za izraˇcun dejanskega števila parov ˇcrt na palec (LPPI; ang. Line Pairs Per Inch; 1 palec ≈ 25,4 mm) uporabimo naslednje korake: • Doloˇcimo dejansko višino grafikona v palcih: H = 180 mm = 7, 09 palca (3.2) • Ker gre za vertikalno loˇcljivost (horizontalne ˇcrte) izraˇcunamo LPPI kot a: LP/PH n × 100 LPPI = = ≈ n × 14,11 (3.3) H 7,09 Tako dobimo naslednje vrednosti za posamezne vzorce: Oznaka (n) LP/PH LPPI 1 100 14,1 2 200 28,2 3 300 42,3 4 400 56,4 5 500 70,6 6 600 84,7 7 700 98,8 8 800 112,9 9 900 127,0 10 1000 141,1 aV primeru, da gre za horizontalno loˇcljivost (vertikalne ˇcrte), pretvorimo LP/PH v LP/PW (pari ˇcrt na širino slike) ob upoštevanju razmerja stranic. Standardni ISO grafikon ima razmerje stranic 4:3, zato je LP/PW 3 = LP/PH × = 4 n 3 × 100 × = n × 75. Kvocient LP/PW uporabimo namesto LP/PH v enaˇcbi (3.3), da dobimo pravilno pretvorbo za 4 vertikalno loˇcljivost. Slika 3.8 prikazuje potek modulacijske prenosne funkcije glede na izlušˇcena podpodroˇcja pri vaji 3.5. Iz slike ocenimo, da je poloviˇcna vrednost najvišjega kontrasta približno 0,9, kontrast pa 3.3 Vaje z rešitvami 45 pade polovico maksimalne vrednosti pri oznaki n=7, kar pomeni: Primer za n = 5: 7 × 100 LPPI7 = ≈ 98,8 LPPI (3.4) 7, 09 Za verifikacijo lahko neposredno izmerimo širino enega para ˇcrt (ˇcrna + bela ˇcrta) v milimetrih: 25,4 mm/palec LPPI = (3.5) širina para ˇcrt v mm Opomba: ˇ Ce je grafikon pri tiskanju spremenil razmerje stranic, je potrebno uporabiti dejanske dimenzije in prilagoditi izraˇcune. Pri merjenju loˇcljivosti optiˇcnih sistemov je treba upoštevati tudi Nyquistov limit in možnost pojava Moiré vzorcev (tj. preslikovanja višjih frekvenc v nižje, kar povzroˇca artefakte v slikah). Vaja 3.7 Napišite funkcijo za izraˇcun efektivnega dinamiˇcnega obmoˇcja b e f na sivinskih slikah kalibra ColorChecker iImages: 1 def computeEffDynRange( iImages ): 2 # Tu napišite python kodo funkcije 3 return oEDR ki vrne skalarno vrednost oEDR. Vrednosti Lmin in Lmax doloˇcite kot povpreˇcne vrednosti µ najtemnejše in najsvetlejše podslike, moˇc šuma σn pa kot povpreˇcje standardnih deviacij v vseh podslikah. Preverite, ali je oEDR veˇcje pri bolje osvetljenem kalibru. ■ Najprej za vsako podsliko izraˇcunamo standardni odklon (σn), nato doloˇci razliko med maksim- alno in minimalno povpreˇcno intenziteto podslik ter jo delimo s povpreˇcjem standardnih odklonov (σavg) vseh podslik. Konˇcni rezultat je logaritemska vrednost (osnova 2) razmerja teh dveh vrednosti, ki predstavlja število razloˇcljivih stopenj intenzitete, ki jih sistem lahko reproducira. Matematiˇcno je EDR izražen kot EDR = L max L − log min . Višja vrednost EDR oznaˇcuje boljšo sposobnost 2 σavg sistema za zajemanje širokega razpona svetlosti in razlikovanja med nivoji svetlosti. 1 def computeEffDynRange(iImages): 2 ’’’ 3 Izracunaj efektivno dinamicno obmocje 4 5 Parameters 6 ---------- 7 iImages : numpy.ndarray 8 Vhodne slike 9 10 Returns 11 -------- 12 oEDR : float 13 Vrednost efektivnega dinamicnega obmocja 14 ’’’ 15 L = np.zeros((len(iImages,))) 16 sig = np.zeros((len(iImages),)) 17 for i in range(len(iImages)): 18 L[i] = np.mean(iImages[i].flatten()) 19 sig[i] = np.std(iImages[i].flatten()) 46 Poglavje 3. Parametri kakovosti slik 20 oEDR = np.log2((L.max() - L.min())/sig.mean()) 21 return oEDR 22 23 # test funkcije 24 print(’Efektivno dinamiˇ cno obmoˇ cje v bitih: {:.2f}’.format( 25 computeEffDynRange(sub_gray_svetla))) Pri izvedbi gornje funkcije dobimo odgovor: Efektivno dinamiˇ cno obmoˇ cje v bitih: 6.61 kar pomeni, da pri trenutnih svetlobnih razmerah in nastavitvah kamere (zaslonka, ˇcas ekspozicije) ni izkorišˇceno celotno dinamiˇcno obmoˇcje svetlobnega tipala, ki je 8 bitov. Vaja 3.8 Napišite funkcijo za izraˇcun differencialnega razmerja signal–šum SNRD na dveh poljubnih sivinskih slikah iImage1 in iImage2: 1 def computeSNR( iImage1, iImage2 ): 2 # Tu napišite python kodo funkcije 3 return oSNR ki vrne skalarno vrednost oSNR. Preizkusite delovanje funkcije tako, da izraˇcunate SNRD med pari podslik kalibra Colorchecker z indeksoma 19 in 24 in med 23 in 24. Komentirajte dobljene vrednosti. ■ Odloˇcimo se za implementacijo diferencialnega razmerja signal-šum. Najprej izraˇcunamo povpreˇcni vrednosti (µ1, µ2) in standardna odklona (σ1, σ2) za obe vhodni (pod)sliki. Diferencialno razmerje signal/šum se nato doloˇci kot absolutna razlika povpreˇcij, deljena s kvadratnim korenom vsote kvadratov standardnih odklonov: SNR |µ1−µ2| D D 2 = √ . Višje vrednosti SNR 2 oznaˇcujejo boljšo σ +σ 12 loˇcljivost med signaloma v primerjavi s šumom oz. veˇcjo razliko med obravnavanima slikama glede na njihovo variabilnost. 1 def computeSNR(iImage1, iImage2): 2 ’’’ 3 Vrne diferencialno razmerje signal/sum 4 5 Parameters 6 --------- 7 iImage1, iImage2 : np.ndarray 8 Sliki podroˇ cij zanimanja, med katerima raˇ cunamo SNR 9 10 Returns 11 --------- 12 oSNR : float 13 Vrednost razmerja signal/sum 14 ’’’ 15 mu1 = np.mean(iImage1.flatten()) 16 mu2 = np.mean(iImage2.flatten()) 17 18 sig1 = np.std(iImage1.flatten()) 19 sig2 = np.std(iImage2.flatten()) 20 21 oSNR = np.abs(mu1 - mu2)/np.sqrt(sig1**2 + sig2**2) 22 3.4 Naloge in vprašanja 47 23 return oSNR 24 25 # test funkcije 26 print(’SNR med podroˇ cjema z indeksoma 19 in 24: {:.2f}’.format( 27 computeSNR(sub_gray_svetla[18], sub_gray_svetla[23]))) 28 print(’SNR med podroˇ cjem z indeksoma 23 in 24: {:.2f}’.format( 29 computeSNR(sub_gray_svetla[23], sub_gray_svetla[23]))) Dobimo naslednji rezultat: SNR med podroˇ cjema z indeksoma 19 in 24: 83.30 SNR med podroˇ cjem z indeksoma 23 in 24: 7.86 ki kaže da se najtemnejše podroˇcje pri indeksu 19 zelo dobro loˇcuje glede na najtemnejše podroˇcje (SNRD >> 1). Podroˇcji z indeksoma 23 in 24 se zaradi manjše razlike slabše, a še vedno oˇcino loˇcita glede na intenziteto in stopnjo šuma. 3.4 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.2: To je moj odgovor. . Naloga 3.1 Izraˇcunajte povpreˇcne sivinske vrednosti µi podroˇcij i = 19, . . . ,24 v dveh zajetih slikah ColorChecker kalibra in jih kot dve krivulji vrišite v skupen graf, ki prikazuje odvisnost µi od pripadajoˇcega indeksa podroˇcja i. Doloˇcite enaˇcbo, ki opisuje oblike teh dveh krivulj in obrazložite kako se med seboj razlikujeta pri bolj oz. manj osvetljenem kalibru? Naloga 3.2 Za izraˇcun histograma sivinske slike uporabite funkcijo histogram v Python knjižnici numpy . Pokažite, da funkcija vrne enak rezultat kot funkcija computeHistogram(), ki smo jo napisali na laboratorijskih vajah. Naloga 3.3 S pomoˇcjo podslik kalibra ISO 12233 izraˇcunajte modulacijski prenosni funkciji loˇceno za vodoravne in navpiˇcne linije in ju kot dve krivulji vrišite v skupen graf. Iz grafov pri vrednosti m50% doloˇcite prostorsko loˇcljivost vašega fotoaparata v vodoravni in navpiˇcni osi slike in jo izrazite v lp/mm. Upoštevajte, da ima kaliber višino H = 145 mm, pripadajoˇce oznake (1–10) pa oznaˇcujejo mnogokratnik 100× število linij na višino slike H, iz ˇcesar lahko izraˇcunate število parov linij na milimeter (lp/mm). Naloga 3.4 Doloˇcite maksimalno dosegljivo vrednost efektivnega dinamiˇcnega obmoˇcja be f vašega fotoaparata na zajetih slikah. Navedite naˇcine s katerimi bi lahko poveˇcali be f . Naloga 3.5 Poišˇcite tisti par sivinskih podslik kalibra ColorChecker, pri katerem je SNR D najveˇcji in navedite pripadajoˇce indekse podroˇcij (1–24). Ali bi lahko ti dve podroˇcji navedli brez izraˇcuna SNRD? Odgovor obrazložite. Naloga 3.6 Poišˇcite tiste pare sivinskih podslik kalibra ColorChecker, pri katerih je SNR D man-jši kot 1 in navedite pripadajoˇce indekse podroˇcij (1–24). Ali lahko na sivinski sliki kalibra ColorChecker med seboj razloˇcite pare podroˇcij, ki imajo SNRD < 1? 4. Preslikave sivin in barv 4.1 Sivinske preslikave Sivinske preslikave so poljubne preslikave T : R → R dinamiˇcnega obmoˇcja [0, Lr − 1] referenˇcne slike r(x,y) v dinamiˇcno obmoˇcje [0,Ls − 1] preslikane slike s(x,y). Preslikavo izvedemo na vseh slikovnih elementih referenˇcne slike kot s(x,y) = T (r(x,y)). Glavni namen sivinskih preslikav je poveˇcanje kontrasta struktur zanimanja, uporabljajo pa se tudi za prilagoditev dinamiˇcnega obmoˇcja referenˇcne slike za potrebe prikazovanja. Pri vaji boste spoznali linearno preslikavo, oknjenje, upragovljanje in nelinearno gama preslikavo (slika 4.1). Slika 4.1: Sivinske preslikave kot homogene toˇckovne operacije, pri ˇcemer je r(x, y) vhodna, s(x,y) pa izhodna slika. 4.2 Preslikave med barvnimi prostori Barvo slikovnega elementa obiˇcajno definiramo s tremi, vˇcasih pa celo z dvema komponentama oz. vrednostima. Zaradi naˇcina pretvorbe svetlobe v digitalni zapis se najpogosteje uporablja zapis barve s komponentami RGB, ki ustrezajo trem razliˇcnim detektorjem svetlobe in ki so selektivno obˇcutljivi pri valovnih dolžinah svetlobe okoli 700 nm (R), 550 nm (G) in 450 nm (B). Obstajajo tudi drugi barvni prostori, ki so bolj primerni za analizo digitalnih slik, naprimer ∗ ∗ ∗ HSV in L a b. 50 Poglavje 4. Preslikave sivin in barv Sliko v RGB barvnem prostoru lahko pretvorimo v drug barvni prostor z (ne)linearno preslikavo RGB komponent. Preslikava iz RGB v XY Z barvni prostor pri referenˇcni beli osvetlitvi D65 je definirana kot:       X 0 , 4125 0 , 3576 0 , 1804 R       Y = 0 , 2127 0 , 7151 0 , 0722 B , (4.1) Z 0, 0193 0, 1192 0,9505 G pri ˇcemer morajo vrednosti komponent R, G in B ležati na intervalu [0, 1]. Preslikavi iz barvnega prostora ∗ ∗ ∗ XY Z v barvni prostor L a b in obratno sta definirana kot: L∗ 1/3 ∗ 1/3 − = 116 Y 16 L = 116 Y − 16 a∗ 1/3 1/3 ∗ 1/3 1/3 − = 500 ( X Y ) in a = 500 ( X − Y) (4.2) b∗ 1/3 1/3 ∗ 1/3 1/3 − = 200 ( Y Z ) b = 200 ( Y − Z) Barvni prostor ∗ ∗ ∗ L a b je zaradi linearne metrike med razliˇcnimi barvnimi odtenki primeren za kvantitativno primerjavo in analizo barv. Spodaj je prikazana preslikava iz RGB v HSV barvni prostor ter pripadajoˇca inverzna preslikava. Pretvorba iz RGB v HSV Pretvorba iz HSV v RGB Predizraˇcun: Predizraˇcun: r = R/ , / 255 g = G / , = 255 b B255 C = V × S C ◦ = ) max ( r , g , b X = C × ( 1 − | H / 60mod 2 − 1|) max Cmin = min(r, g, b) m = V −C ∆ = Cmax −Cmin Komponente (r , g, b) doloˇcimo glede Barvni odtenek oz. ◦ ◦ ◦ ◦ ⊂ hue H ⊂ [ 0 , 360 ] : na vrednost H [ 0 , 360]: H   ◦ ◦ ◦ g − b ( C , X , 0 ) 0 × mod 6  ˇce 0 ≤ H < 60  ˇce ∆ ≡ 0 ∆      ◦ ◦   ( X , C , 0 )  ◦ g − b ˇce 60 ≤ H < 120 60 × mod 6 ˇce C ≡ r   ∆ max =  ◦◦  ( 0 , C , X ) ˇce 120 ≤ H < 180 ◦ b − r   60 × + 2 ˇce C ( r , g , b ) = max ≡ g  ∆ ◦ ◦  ( 0 , X , C ) ˇce 180 ≤ H < 240  ◦ r − g  60 × + 4 ˇce C  max ≡ b  ∆  ◦ ◦  ( X , 0 , C ) ˇce 240 ≤ H < 300   Barvno nasiˇcenje oz.  ◦  ◦ ( C , 0 , X ) ˇce 300 ≤ H < 360 saturation S ⊂ [ 0 , 1 ] : ( Komponente RGB nato dobimo z: 0 ˇce S = ∆ sicer ∆ = 0 C max (R, G, B) = (r + m, g + m, b + m) Svetlost oz. value V ⊂ [0, 1]: Izhodne RGB vrednosti so na intervalu [0, 1]. V = Cmax 4.3 Vaje z rešitvami Pri vaji boste napisali funkcije sivinskih preslikav in funkcije za pretvorbo RGB slik v HSV barvni prostor ter preizkusili teh funkcij delovanje na sivinski in barvni sliki. Koda vaje je dostopna na Git repozitoriju. 4.3 Vaje z rešitvami 51 Vaja 4.1 Naložite barvno RGB sliko slika.jpg v okolje Python z ukazom open() v knjižn-ici PIL.Image in jo pretvorite v 8-bitno sivinsko sliko kot S = 0, 299R + 0,587G + 0,114B. Poskrbite za ustrezno zaokroževanje in pretvorbo tipa sivinskih vrednosti slikovnih elementov. ■ Najprej uvozimo knjižnice, ki jih bomo potrebovali pri vaji: 1 import rvlib 2 import matplotlib.pyplot as plt 3 import numpy as np 4 import PIL.Image as im Opomba: uvozili smo knjižnico rvlib, v kateri so funkcije napisane na predhodnih vajah. Na primer, loadImage(), showImage() in saveImage(). Nalaganje in prikaz slik izvedemo s kodo: 1 def colorToGray(iImage): 2 dtype = iImage.dtype # zapomnimo si tip zapisa vhodne slike 3 rgb = np.array(iImage,dtype=’float’) # pretvorimo v zapis s plavajoˇ co vejico 4 return (rgb[:,:,0]*0.299 + rgb[:,:,1]*0.587 + rgb[:,:,2]*0.114).astype(dtype) # preslikavo in pretvorimo v originalni zapis 5 6 iImage = loadImage(’slika.jpg’) # nalaganja slike 7 iImageG = colorToGray( iImage ) # pretvorba slike 8 rvlib.showImage( iImage, ’rgb’ ) # prikaz originalne in 9 rvlib.showImage( iImageG, ’greyscale’ ) # preslikane slike Slika 4.2 prikazuje originalno RGB in pripadajoˇco sivinsko sliko. (a) (b) Slika 4.2: Slika (a) prikazuje dano RGB sliko slika.jpg, slika (b) pa pripadajoˇco sivinsko sliko. Vaja 4.2 Napišite funkcijo za poljubno linearno sivinsko preslikavo sivinske slike iImage: 1 def scaleImage( iImage, iSlopeA, iIntersectionB ): 2 # Tu napišite python kodo funkcije 52 Poglavje 4. Preslikave sivin in barv 3 return oImage kjer sta iSlopeA in iIntesectionB parametra a in b linearne preslikave. Funkcija vrne linearno preslikano sivinsko sliko oImage, ki naj bo enakega tipa kot vhodna slika. Minimalne in maksimalne vrednosti celoštevilskih podatkovnih tipov dobite z numpy.iinfo( dtype ).min in numpy.iinfo( dtype ).max. Preverite delovanje funkcije na sivinski sliki s poljubno linearno sivinsko preslikavo. ■ Funkcija scaleImage(iImage, iSlopeA, iIntersectionB) izvede linearno preslikavo sivinskih vrednosti slike po formuli s(x,y) = a · r(x, y) + b, kjer a predstavlja naklon (iSlopeA) in b preseˇcišˇce (iIntersectionB), pri ˇcemer sta a, b poljubni skalarni vrednosti: 1 def scaleImage( iImage, iSlopeA, iIntersectionB ): 2 """Linearna sivinska preslikava: a*I+b""" 3 iImageType = iImage.dtype 4 iImage = np.array( iImage, dtype=’float’ ) 5 oImage = iSlopeA * iImage + iIntersectionB # linearna preslikava 6 # zaokrozevanje vrednosti 7 if iImageType.kind in (’u’,’i’): 8 oImage[oImage 9 oImage[oImage>np.iinfo(iImageType).max] = np.iinfo(iImageType).max 10 return np.array( oImage, dtype=iImageType ) 11 12 # preizkus funkcije 13 iImageG = colorToGray( iImage ) 14 oImageG = scaleImage( iImageG, 2.0, 32 ) 15 rvlib.showImage( oImageG, ’preslikana slika’) Poleg preslikave poskrbimo za pravilno zaokroževanje preslikanih sivinskih vrednosti. Najprej pretvorimo vhodno sliko v tip float, nato uporabi linearno transformacijo in na koncu zagotovi pravilno zaokrožitev ter omejitev vrednosti znotraj obsega originalnega podatkovnega tipa. ˇ Ce je vhodna slika celoštevilska (uint8, funkcija skrbi, da so vse vrednosti v mejah [0,255]. Rezultat je normalizirana sivinska slika v originalnem formatu, primerna za nadaljnjo obdelavo ali prikaz. Slika 4.3 prikazuje preslikano sivinsko sliko 4.2b. Slika 4.3: Linearno preslikana sivinska slika 4.2b, kjer smo uporabili a = 2 (poveˇcanje kontrasta) in b = 32 (posvetlitev). 4.3 Vaje z rešitvami 53 Vaja 4.3 Napišite funkcijo za poljubno linearno sivinsko oknjenje sivinske slike iImage: 1 def windowImage( iImage, iCenter, iWidth ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer sta iCenter in iWidth pa parametra c in w linearnega oknjenja. Funkcija vrne lin-earno sivinsko oknjeno oImage, ki naj bo enakega tipa kot vhodna slika. Minimalne in mak-simalne vrednosti celoštevilskih podatkovnih tipov dobite z numpy.iinfo( dtype ).min in numpy.iinfo( dtype ).max. Preverite delovanje funkcije na sivinski sliki s poljubnim sivinskim oknjenjem. ■ Funkcija windowImage(iImage, iCenter, iWidth) izvaja linearno oknjenje slikovnih pod- atkov, kar je postopek, ki preslika obmoˇcje intenzitet v okolici središˇca iCenter s širino iWidth na celotno razpoložljivo obmoˇcje vrednosti. Algoritem najprej doloˇci dinamiˇcno obmoˇcje vhodne slike (razlikuje med celoštevilskimi in realnimi podatkovnimi tipi), nato izraˇcuna naklon iSlopeA in preseˇcišˇce (L s −1) w iInterceptB za linearno transformacijo po formuli s ( x, y) = · ( r ( x , y) − ( c − )), w 2 kjer Ls predstavlja obmoˇcje izhodnih vrednosti, w širino okna in c središˇce okna. Gre torej le za linearno preslikavo, ki ima drugaˇcen pomen parametrov. Rešitev lahko zapišemo: 1 def windowImage( iImage, iCenter, iWidth ): 2 """Linearno oknjenje: (Ls-1)/w*(I-(c-w/2)""" 3 iImageType = iImage.dtype 4 if iImageType.kind in (’u’,’i’): 5 iMaxValue = np.iinfo(iImageType).max 6 iMinValue = np.iinfo(iImageType).min 7 iRange = iMaxValue - iMinValue 8 else: 9 iMaxValue = np.max( iImage ) 10 iMinValue = np.min( iImage ) 11 iRange = iMaxValue - iMinValue 12 # preraˇ cunamo koeficiente linearne preslikave 13 iSlopeA = iRange / float(iWidth) 14 iInterceptB = - iSlopeA * ( float(iCenter) - iWidth / 2.0 ) 15 # izvedemo linearno preslikavo 16 return scaleImage( iImage, iSlopeA, iInterceptB ) 17 18 # preizkus funkcije 19 iImageG = colorToGray( iImage ) 20 oImageG = windowImage( iImageG, 64, 64 ) 21 rvlib.showImage( oImageG, ’preslikana slika’) Konˇcni rezultat je preslikana sivinska slika, kjer so vrednosti zunaj izbranega obmoˇcja oknjenja obrezane na minimalno ali maksimalno dovoljeno vrednost. Slika 4.4 prikazuje preslikano sivinsko sliko 4.2b. Vaja 4.4 Napišite funkcijo za poljubno sivinsko upragovljanje sivinske slike iImage: 1 def thresholdImage( iImage, iThreshold ): 2 # Tu napišite python kodo funkcije 3 return oImage 54 Poglavje 4. Preslikave sivin in barv Slika 4.4: Oknjena sivinska slika 4.2b, kjer smo uporabili c = 64 (center okna) in w = 64 (širina okna). kjer je iThreshold parameter upragovljanja oz. prag t. Funkcija vrne upragovljeno sliko oImage. Preverite delovanje funkcije na sivinski sliki s poljubnim pragom t. ■ Vhodno sliko pretvorimo v binarno sliko glede na izbrano pražno vrednost iThreshold oz. t, pri ˇcemer vrednosti slike r(x,y) > t postavimo na vrednost 255, ostale pa na vrednost 0. Rešitev lahko zapišemo: 1 def thresholdImage( iImage, iThreshold ): 2 """Upragovljanje: I>t""" 3 # iImage = np.asarray( iImage ) 4 oImage = 255 * np.array(iImage>iThreshold, dtype=’uint8’) 5 return oImage 6 7 # preizkus funkcije 8 iImageG = colorToGray( iImage ) 9 oImageG = thresholdImage( iImageG, 128 ) 10 rvlib.showImage( oImageG, ’preslikana slika’) Konˇcni rezultat je binarna slika kot prikazuje slika 4.5. Slika 4.5: Upragovljena sivinska slika 4.2b, kjer smo uporabili t = 128. 4.3 Vaje z rešitvami 55 Vaja 4.5 Napišite funkcijo za poljubno gama sivinsko preslikavo sivinske slike iImage: 1 def gammaImage( iImage, iGamma ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer je iGamma parameter γ. Funkcija vrne gama sivinsko preslikano sliko oImage, ki naj bo enakega tipa kot vhodna slika. Preverite delovanje funkcije na sivinski sliki z γ > 1 in γ < 1 in analizirajte vpliv na histogram slike. ■ Funkcija gammaImage(iImage, iGamma) izvaja nelinearno gama korekcijo slike po formuli s(x,y) = (Ls − γ r ( x, y) 1) , kjer r(x, y) predstavlja vhodne vrednosti piksla, s(x,y) izhodne vred-L r − 1 nosti, Ls obmoˇcje izhodnih vrednosti, Lr obmoˇcje vhodnih vrednosti in γ eksponent korekcije. Algoritem najprej normalizira vrednosti slike v obmoˇcje [0,1], nato uporabi potenˇcno preslikavo z eksponentom iGamma, nakar denormalizira rezultat nazaj v originalno obmoˇcje vrednosti. Za celoštevilske podatkovne tipe zagotovi pravilno zaokroževanje in omejitev vrednosti znotraj dovoljenega obmoˇcja. Rešitev lahko zapišemo: 1 def gammaImage( iImage, iGamma ): 2 """ Gama preslikava: (Ls-1)(I/(Lr-1))^gamma""" 3 iImageType = iImage.dtype 4 iImage = np.array( iImage, dtype=’float’ ) 5 # preberi mejne vrednosti in obmocje vrednosti 6 if iImageType.kind in (’u’,’i’): 7 iMaxValue = np.iinfo(iImageType).max 8 iMinValue = np.iinfo(iImageType).min 9 iRange = iMaxValue - iMinValue 10 else: 11 iMaxValue = np.max( iImage ) 12 iMinValue = np.min( iImage ) 13 iRange = iMaxValue - iMinValue 14 # izvedi gamma preslikavo 15 iImage = (iImage - iMinValue) / float(iRange) 16 oImage = iImage ** iGamma 17 oImage = float(iRange) * oImage + iMinValue 18 # zaokrozevanje vrednosti 19 if iImageType.kind in (’u’,’i’): 20 oImage[oImage 21 oImage[oImage>np.iinfo(iImageType).max] = np.iinfo(iImageType).max 22 # vrni sliko v originalnem formatu 23 return np.array( oImage, dtype=iImageType ) 24 25 # preizkus funkcije 26 iImageG = colorToGray( iImage ) 27 oImageG = gammaImage( iImageG, 1/2 ) 28 rvlib.showImage( oImageG, ’preslikana slika’) Gama korekcija omogoˇca prilagoditev kontrasta v razliˇcnih delih svetlosti, pri ˇcemer vrednosti γ < 1 poveˇcajo kontrast v temnih predelih, vrednosti γ > 1 pa v svetlih predelih slike. Za primer γ = 1 je konˇcni rezultat sivinska slika s poveˇcanim kontrastom na temnih delih originalne sivinske 2 slike (slika 4.5); poveˇcanje kontrasta je oˇcitno iz pripadajoˇcega histograma na sliki 4.7b, ki je v primerjavi s tistim na sliki 4.7a, razredˇcen na podroˇcju nizkih intenzitet – to pomeni, da poveˇca kontrast med nizkimi sivinskimi vrednostmi. 56 Poglavje 4. Preslikave sivin in barv Slika 4.6: Preslikana sivinska slika 4.2b z 1 γ =. 2 Za dani primer si oglejmo še histograma slik pred in po preslikavi, z uporabo kode: 1 plt.figure() 2 plt.hist(iImageG.flatten(),255, range=(0,255)) 3 plt.suptitle(’histogram vhodne sivinske slike’) 4 plt.figure() 5 plt.hist(oImageG.flatten(),255, range=(0,255)) 6 plt.suptitle(’histogram z gamma preslikane slike’) Histograma sta prikazana na sliki 4.7. (a) (b) Slika 4.7: Slika (a) prikazuje histogram vhodne sivinske slike slika.jpg na sliki 4.2b, slika (b) pa histogram z gammaImage() preslikano sivinsko sliko. Vaja 4.6 Napišite funkcijo za preslikavo barvne slike iImage med RGB in HSV barvnimi prostori: 1 def convertImageColorSpace( iImage, iConversionType ): 2 # Tu napišite python kodo funkcije 4.3 Vaje z rešitvami 57 3 return oImage kjer iConversionType doloˇca pretvorbo iz izvornega v ciljni barvni prostor in lahko zavzame vrednosti ’RGBtoHSV’ ali ’HSVtoRGB’. Preverite delovanje funkcije s pretvorbo barvne slike iz RGB v HSV prostor in nato nazaj iz HSV v RGB. Dobljena slika mora biti enaka vhodni sliki. ■ Funkcija convertImageColorSpace(iImage, iConversionType) izvaja dvosmerno pret- vorbo med barvnima prostoroma RGB in HSV. Pri pretvorbi iz RGB v HSV (’RGBtoHSV’) algor-item najprej normalizira vrednosti v obmoˇcje [0,1], nato izraˇcuna komponente HSV po standardnih formulah: barvni odtenek (H) se doloˇci glede na dominantno barvo, nasiˇcenost (S) kot razmerje med razliko najveˇcje in najmanjše komponente ter najveˇcjo komponento, svetlost (V) pa ustreza najveˇcji vrednosti med RGB komponentami. Obratna pretvorba (’HSVtoRGB’) uporablja segmentirano logiko za pretvorbo H v ustrezen sektor barvnega kolesa, nato pa izraˇcuna ustrezne RGB vrednosti z upoštevanjem nasiˇcenosti in svetlosti. Funkcija avtomatsko upravlja z mejnimi vrednostmi in zag-otavlja pravilno obmoˇcje vrednosti za vsak barvni prostor, pri ˇcemer ohranja natanˇcnost barvnega podajanja. Rešitev lahko zapišemo: 1 def convertImageColorSpace( iImage, iConversionType ): 2 """Pretvorba barvne slike med barvnima prostoroma RGB in HSV""" 3 iImage = np.array( iImage, dtype=’float’ ) 4 5 if iConversionType == ’RGBtoHSV’: 6 iImage = iImage / 255.0 7 r, g, b = iImage[:,:,0], iImage[:,:,1], iImage[:,:,2] 8 9 h = np.zeros_like( r ) 10 s = np.zeros_like( r ) 11 v = np.zeros_like( r ) 12 13 Cmax = np.maximum(r,np.maximum(g,b)) 14 Cmin = np.minimum(r,np.minimum(g,b)) 15 delta = Cmax - Cmin + 1e-7 16 17 h[Cmax == r] = 60.0 * ((g[Cmax == r] - b[Cmax == r])/delta[Cmax == r] % 6.0) 18 h[Cmax == g] = 60.0 * ((b[Cmax == g] - r[Cmax == g])/delta[Cmax == g] + 2.0) 19 h[Cmax == b] = 60.0 * ((r[Cmax == b] - g[Cmax == b])/delta[Cmax == b] + 4.0) 20 21 s[delta!=0.0] = delta[delta!=0.0] / (Cmax[delta!=0.0] + 1e-7) 22 23 v = Cmax 24 25 # ustvari izhodno sliko 26 oImage = np.zeros_like( iImage ) 27 oImage[:,:,0] = h; oImage[:,:,1] = s; oImage[:,:,2] = v; 28 29 return oImage 30 31 elif iConversionType == ’HSVtoRGB’: 32 h = iImage[:,:,0]; s = iImage[:,:,1]; v = iImage[:,:,2]; 33 34 C = v * s 58 Poglavje 4. Preslikave sivin in barv 35 X = C * (1.0 - np.abs( ( (h/60.0) % 2.0 ) - 1 ) ) 36 m = v - C 37 38 r = np.zeros_like( h ) 39 g = np.zeros_like( h ) 40 b = np.zeros_like( h ) 41 42 r[ (h>=0.0) * (h<60.0) ] = C[ (h>=0.0) * (h<60.0) ] 43 g[ (h>=0.0) * (h<60.0) ] = X[ (h>=0.0) * (h<60.0) ] 44 45 r[ (h>=60.0) * (h<120.0) ] = X[ (h>=60.0) * (h<120.0) ] 46 g[ (h>=60.0) * (h<120.0) ] = C[ (h>=60.0) * (h<120.0) ] 47 48 g[ (h>=120.0) * (h<180.0) ] = C[ (h>=120.0) * (h<180.0) ] 49 b[ (h>=120.0) * (h<180.0) ] = X[ (h>=120.0) * (h<180.0) ] 50 51 g[ (h>=180.0) * (h<240.0) ] = X[ (h>=180.0) * (h<240.0) ] 52 b[ (h>=180.0) * (h<240.0) ] = C[ (h>=180.0) * (h<240.0) ] 53 54 r[ (h>=240.0) * (h<300.0) ] = X[ (h>=240.0) * (h<300.0) ] 55 b[ (h>=240.0) * (h<300.0) ] = C[ (h>=240.0) * (h<300.0) ] 56 57 r[ (h>=300.0) * (h<360.0) ] = C[ (h>=300.0) * (h<360.0) ] 58 b[ (h>=300.0) * (h<360.0) ] = X[ (h>=300.0) * (h<360.0) ] 59 60 r = r + m 61 g = g + m 62 b = b + m 63 64 # ustvari izhodno sliko 65 oImage = np.zeros_like( iImage ) 66 oImage[:,:,0] = r; oImage[:,:,1] = g; oImage[:,:,2] = b; 67 68 # zaokrozevanje vrednosti 69 oImage = 255.0 * oImage 70 oImage[oImage>255.0] = 255.0 71 oImage[oImage<0.0] = 0.0 72 73 oImage = np.array( oImage, dtype=’uint8’ ) 74 75 return oImage 76 77 # preizkus funkcije 78 iImage = loadImage(’slika.jpg’) 79 oImageHSV = convertImageColorSpace( iImage, ’RGBtoHSV’ ) 80 oImageRGB = convertImageColorSpace( oImageHSV, ’HSVtoRGB’ ) 81 82 # prikaz HSV slike po komponentah 83 rvlib.showImage( oImageHSV[:,:,0]*255.0/360.0, ’H slika’) 84 rvlib.showImage( oImageHSV[:,:,1]*255.0, ’S slika’) 85 rvlib.showImage( oImageHSV[:,:,2]*255.0, ’V slika’) 86 87 # prikaz RBG slike po komponentah 88 rvlib.showImage( oImageRGB[:,:,0], ’R slika’) 89 rvlib.showImage( oImageRGB[:,:,1], ’G slika’) 4.4 Naloge in vprašanja 59 90 rvlib.showImage( oImageRGB[:,:,2], ’B slika’) Rezultat pretvorbe prikazuje slika 4.8, pri ˇcemer so posamezni barvni kanali prikazani kot sivinske slike z zalogo vrednosti [0, 255]. Slika 4.8: Prikaz barvne slike 4.2a po komponentah RGB in HSV. 4.4 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.2: To je moj odgovor. . Naloga 4.1 Doloˇcite vrednosti iSlope (a) ter iIntersection (b) linearne sivinske preslikave tako, da se bo najsvetlejša toˇcka sivinske slike preslikala v najtemnejšo toˇcko slike in nasprotno. Sliko prikažite. Naloga 4.2 Doloˇcite vrednosti iCenter (c) in iWidth (w) sivinskega oknjenja tako, da boste iz sivinske slike odstranili 10% najtemnejših in 10% najsvetlejših slikovnih elementov. Oknjeno sliko prikažite. Naloga 4.3 Doloˇcite vrednost iThreshold (t) funkciji upragovljanja tako, da bo natanko 50% najsvetlejših slikovnih elementov v vhodni sivinski sliki nad tem pragom. Upragovljeno sliko prikažite. Naloga 4.4 Obrazložite vpliv vrednosti iGamma (γ) v gama sivinski preslikavi na kontrast izhodne sivinske slike v primeru, ko je γ < 1 in v primeru, ko je γ > 1. Prikažite ustrezni sliki za oba primera. Naloga 4.5 Napišite funkcijo za gama sivinsko preslikavo RGB barvne slike tako, da sliko najprej pretvorite v HSV barvni prostor, nato izvedite gama preslikavo nad komponento V , in HSV sliko 60 Poglavje 4. Preslikave sivin in barv pretvorite nazaj v RGB barvni prostor. Prikažite primera gama preslikanih barvnih slik za γ < 1 in γ > 1. Ali ima tak naˇcin gama preslikave barvne slike enak vpliv na kontrast kot v primeru sivinske slike? Naloga 4.6 Napišite funkcijo za preslikavo barvne slike v sivinsko sliko tako, da v HSV barvnem prostoru komponento barvnega nasiˇcenja postavite na 0. Prikažite pridobljeno sivinsko sliko. Naloga 4.7 Potrdite, da v barvni sliki slika.jpg prevladuje modra barva tako, da izraˇcunate in izpišete razmerje med številom pikslov, ki predstavljajo modre odtenke, in številom vseh pikslov na sliki. Za modre odtenke vzemite piksle katerih vrednost H komponente je na obmoˇcju 240 ± 30 stopinj. 5. Osnovna obdelava slik Vaja je namenjena spoznavanju in razumevanju osnovnih postopkov za obdelavo slik kot so filtriranje, glajenje in ostrenje ter interpolacija in decimacija slik. 5.1 Filtriranje slik z diskretno konvolucijo Filtriranje slike lahko izvedemo s postopkom 2D diskretne konvolucije med podano sliko g(x,y) in konvolucijskim jedrom k(u,v) velikosti U ×V : U /2 V /2 f (x,y) = k(u,v) g(x − u, y − v) , (5.1) ∑ ∑ u=−U/2 v=−V /2 kjer je f (x, y) izhodna slika. Na podroˇcju, kjer slika g(x, y) ni definirana v skladu z definicijo konvolucije predpostavimo sivinsko vrednost 0. V Pythonu je 2D diskretno konvolucijo mogoˇce izraˇcunati s štirimi vgnezdenimi for zankami. Zunanji dve zanki uporabimo za naslavljanje slikovnih elementov slike g(x, y), notranji dve zanki pa za naslavljanje konvolucijskega jedra k(u, v). Konvolucijsko jedro k(u,v) je definirano kot 2D matrika, središˇce jedra k(0, 0) ↔ K pa v Pythonu ustreza indeksoma floor(K.shape/2), kar je potrebno upoštevati pri naslavljanju 2D matrike konvolucijskega jedra. 5.2 Interpolacija slik S postopkom interpolacije slik lahko priredimo sivinsko vrednost poljubni toˇcki v slikovni ravnini. Na ta naˇcin lahko poveˇcamo vzorˇcno frekvenco in s tem velikost slik ter tako navidezno zmanjšamo velikost slikovnih elementov. Glede na to, koliko sosednjih slikovnih elementov upoštevamo pri izraˇcunu sivinske vrednosti v podani toˇcki, delimo naˇcin interpolacije slik na: 1. niˇcti red ali interpolacija najbližjega soseda – upoštevamo le najbližji slikovni element, 2. prvi red ali (bi)linearna interpolacija – upoštevamo le štiri sosednje slikovne elemente, 3. višji red, npr. (bi)kubiˇcna interpolacija (drugi red), ki upošteva 16 sosednjih slikovnih elementov. 62 Poglavje 5. Osnovna obdelava slik Raˇcunska zahtevnost interpolacijskih postopkov 2D slik v grobem raste s kvadratom reda inter-polacije, kar pomeni, da je bikubiˇcna interpolacija (drugi red) približno štiri krat bolj zahtevna oziroma poˇcasnejša od bilinearne interpolacije (prvi red). Naštete interpolacijske postopke je mogoˇce posplošiti, tako da delujejo tudi za veˇcrazsežne slike. Bilinarna interpolacija 5.3 Decimacija slik S postopkom decimacije slik zmanjšamo vzorˇcno frekvenco ter s tem velikost slik. Skladno z Nyquistovim vzorˇcnim teoremom je pred postopkom decimacije potrebno sliko filtrirati z nizko prepustnim sitom in na ta naˇcin odstraniti visoko frekvenˇcno informacijo. Pri decimaciji se pogosto uporablja piramidna shema, kjer se vzorˇcna frekvenca izvirne slike zaporedoma zmanjšuje s celoštevilsko vrednostjo, obiˇcajno dva. 5.4 Vaje z rešitvami Pri vaji boste napisali funkcije za filtriranje slik z 2D konvolucijo in preizkusili delovanje z razliˇcnimi jedri za namen glajenja in ostrenja sivinskih in barvnih slik. Koda vaje je dostopna na Git repozitoriju. Funkcije za interpolacijo in decimacijo slik boste uporabili za poveˇcavo oziroma pomanjšavo sivinskih in barvnih slik. Naložite barvno RGB sliko slika.jpg in jo 5.4 Vaje z rešitvami 63 pretvorite v sivinsko sliko po enaˇcbi S = 0,299R + 0, 587G + 0, 114B. Sliko uporabite za testiranje pravilnosti delovanja funkcij. Vaja 5.1 Napišite funkcijo, ki izraˇcuna 2D diskretno konvolucijo med vhodno sivinsko sliko iImage in konvolucijskim jedrom iKernel: 1 def discreteConvolution2D( iImage, iKernel ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer je oImage izhodna sivinska slika. Preizkusite delovanje funkcije 2D diskretne konvolucije in preverite njen vpliv na vhodno sivinsko sliko, tako da uporabite konvolucijsko jedro iKernel = numpy.ones([k,k])/k**2 za razliˇcne vrednosti k = 2n + 1,n = 1,2, 3, . . .. ■ Naložimo knjižnice, nato sliko slika.jpg in jo pretvorimo v sivinsko: 1 import matplotlib.pyplot as plt 2 import numpy as np 3 import rvlib 4 5 iImage = rvlib.loadImage( ’slika.jpg’ ) 6 iImageG = rvlib.colorToGray( iImage ) 7 rvlib.showImage( iImageG, ’slika.jpg’ ) Rezultat je prikazan na sliki 5.1. Slika 5.1: Sivinska slika za analizo. Funkcija discreteConvolution2D(iImage, iKernel) izvaja diskretno 2D konvolucijo slike s poljubnim konvolucijskim jedrom z uporabo štirih gnezdnih zank. Najprej inicializira izhodno sliko enake velikosti kot vhodna, nato za vsak piksel (x,y) v izhodni sliki sešteje produkt vrednosti okoliških slikovnih toˇck (tx,ty) in ustreznih vrednosti jedra pri indeksih (u,v), pri ˇcemer upošteva robne pogoje s preverjanjem veljavnih indeksov. Funkcija uporablja uteženo povpreˇcje z zamikom jedra tako, da je center jedra nad pikslom za katerega raˇcunamo izhodno vrednost. Za celoštevilske podatkovne tipe funkcija rezultat omeji na dovoljeno obmoˇcje vrednosti z uporabo funkcije np.clip, kar prepreˇci prekoraˇcitev obmoˇcja zaloge vrednosti. Rešitev lahko zapišemo: 1 def discreteConvolution2D( iImage, iKernel ): 2 """Diskretna 2D konvolucija slike s poljubnim jedrom""" 3 # pretvori vhodne spremenljivke v np polje in 64 Poglavje 5. Osnovna obdelava slik 4 # inicializiraj izhodno np polje 5 iImage = np.asarray( iImage ) 6 iKernel = np.asarray( iKernel ) 7 oImage = np.zeros_like( iImage, dtype=’float’ ) 8 # preberi velikost slike in jedra 9 dy, dx = iImage.shape 10 dv, du = iKernel.shape 11 # izracunaj konvolucijo 12 for y in range( dy ): 13 for x in range( dx ): 14 for v in range( dv ): 15 for u in range( du ): 16 tx = int(x - u + np.floor(du/2)) 17 ty = int(y - v + np.floor(dv/2)) 18 if tx>=0 and tx and ty>=0 and ty 19 oImage[y, x] = oImage[y, x] + \ 20 float(iImage[ty, tx]) * float(iKernel[v, u]) 21 if iImage.dtype.kind in (’u’,’i’): 22 oImage = np.clip(oImage, np.iinfo(iImage.dtype).min,np.iinfo(iImage.dtype).max) 23 return np.array( oImage, dtype=iImage.dtype ) Konvolucija je kljuˇcna operacija v obdelavi slik, ki se uporablja za filtracijo, zamegljevanje, izostritev in zaznavanje robov. Prikazana implementacija v Python-u zaradi gnezdenja ni uˇcinkovita; naprednejše implementacije izkorišˇcajo tako strojno opremo kot nižjenivojski jezik C ali C++. V na-logi boste preverili uˇcinkovitejšo implementacijo convolve() v Python knjižnici scipy.ndimage. Preizkus funkcije discreteConvolution2D(iImage, iKernel) naredimo na naslednji naˇcin: 1 #box filter 2 k = 11 # 5, 3 3 iKernel = np.ones([k, k]) 4 iKernel = iKernel / np.sum(iKernel) 5 oImageG = discreteConvolution2D( iImageG, iKernel ) 6 rvlib.showImage( oImageG, f’k={k}’ ) Rezultatirajoˇci sliki za k = 5 in k = 11 sta prikazani na sliki 5.2. Za veˇcje jedro lahko opazimo temnenje roba slike, kar je posledica predpostavke, da so intenzitete izven slike privzeto vrednosti 0. To popaˇcenje lahko odpravimo z ustrezno razširitvijo slike (glej naloge). Vaja 5.2 Napišite funkcijo za interpolacijo niˇctega reda, ki vzorˇci vhodno 2D sivinsko sliko iImage: 1 def interpolate0Image2D( iImage, iCoorX, iCoorY ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer so iCoorX in iCoorY vzorˇcne koordinate v prostoru vhodne slike iImage, v katerih želimo izraˇcunati sivinsko vrednost. Izraˇcunane sivinske vrednosti predstavljajo sivinske vrednosti izhodne slike oImage. Interpolacija niˇctega reda priredi sivinsko vrednost v toˇcki (x,y) enako sivinski vrednosti najbližje toˇcke na diskretni vzorˇcni mreži. Pri iskanju najbližje toˇcke v diskretni mreži si pomagajte z Python funkcijo numpy.floor(), za definicijo vzorˇcnih toˇck pa 5.4 Vaje z rešitvami 65 (a) (b) Slika 5.2: Rezultat diskretne konvolucije z jedrom za lokalno aritmetiˇcno povpreˇcenje (ang. box filter) z dimenzijami (a) 5 × 5 (k = 5) in (b) 11 × 11 (k = 11). uporabite funkcijo numpy.meshgrid(..., indexing=’xy’). Preizkusite delovanje funkcije na podsliki sivinske slike med oglišˇcema (260, 210) in (360, 280) tako, da podsliko prevzorˇcite s trikrat bolj gosto vzorˇcno mrežo, kot je mreža originalne sivinske slike. ■ Funkcija interpolate0Image2D(iImage, iCoorX, iCoorY) izvaja interpolacijo niˇctega reda (najbližjega soseda) za 2D sliko tako, da najprej pretvori vhodne koordinate v celoštevilske indekse z zaokrožanjem navzdol (np.floor), nato za vsako koordinato (x,y) preveri, ali leži znotraj meja slike, in priredi vrednost intenzitet najbližjega piksla. Kljuˇcne znaˇcilnosti vkljuˇcujejo: avto-matsko ustvarjanje pravokotne mreže koordinat iz 1D razpona horizontalnih in vertikalnih indeksov z np.meshgrid, preverjanje veljavnosti indeksov za robustno obvladovanje robnih primerov, ter ohranjanje podatkovnega tipa originalne slike. Rešitev lahko zapišemo: 1 def interpolate0Image2D( iImage, iCoorX, iCoorY ): 2 """Funkcija za interpolacijo nictega reda""" 3 # pretvori vhodne spremenljivke v np polje 4 iImage = np.asarray( iImage ) 5 iCoorX = np.asarray( iCoorX, dtype=’float’) 6 iCoorY = np.asarray( iCoorY, dtype=’float’) 7 # preberi velikost slike in jedra 8 dy, dx = iImage.shape 9 # ustvari 2d polje koordinat iz 1d razpona vhodnih koordinat (!!!) 10 if np.size(iCoorX) != np.size(iCoorY): 11 print(’Stevilo X in Y koordinat je razliˇ cno!’) 12 iCoorX, iCoorY = np.meshgrid(iCoorX, iCoorY, indexing=’xy’) 13 # zaokrozi na najblizjo celostevilsko vrednost (predstavlja indeks!) 14 oShape = iCoorX.shape 15 iCoorX = np.floor(iCoorX.flatten()).astype(’int’) 16 iCoorY = np.floor(iCoorY.flatten()).astype(’int’) 17 # ustvari izhodno polje 18 oImage = np.zeros(iCoorX.shape, dtype=iImage.dtype ) 19 print(iCoorX.shape) 20 print(iCoorY.shape) 21 # priredi vrednosti 22 for idx in range(oImage.size): 23 tx = iCoorX[idx] 24 ty = iCoorY[idx] 66 Poglavje 5. Osnovna obdelava slik 25 if tx>=0 and tx and ty>=0 and ty 26 oImage[idx] = iImage[ty, tx] 27 # vrni izhodno sliko 28 return np.reshape( oImage, oShape ) Interpolacija niˇctega reda iz gladkih poševnih robov ustvari nazobˇcane robove. Po drugi strani je hitra, primerna za aplikacije, kjer je hitrost pomembnejša od gladkosti rezultata. Preizkus funkcije naredimo na naslednji naˇcin: 1 iImageGs = iImageG[210:280,260:360] 2 rvlib.showImage( iImageGs, ’Vhodna slika’ ) 3 # doloˇ ci diskretno mrežo za interpolacijo 4 dy, dx = iImageGs.shape 5 iCoorX, iCoorY = np.meshgrid(np.arange(0,dx,1/3.0), \ 6 np.arange(0,dy,1/3.0), indexing=’xy’) 7 # izvedi interpolacijo niˇ ctega reda 8 oImageG = interpolate0Image2D( iImageGs, iCoorX, iCoorY ) 9 rvlib.showImage( oImageG, ’Izhodna slika’ ) Rezultat prikazuje slika 5.3. (a) (b) Slika 5.3: Rezultat interpolacije niˇctega reda (a) originalne podslike za (b) s korakom 1/3 piksla. Vaja 5.3 Napišite funkcijo za interpolacijo prvega reda, ki vzorˇci vhodno 2D sivinsko sliko iImage: 1 def interpolate1Image2D( iImage, iCoorX, iCoorY ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer so iCoorX in iCoorY vzorˇcne koordinate v prostoru vhodne slike iImage, v katerih želimo izraˇcunati sivinsko vrednost. Izraˇcunane sivinske vrednosti predstavljajo sivinske vrednosti izhodne slike oImage. Interpolacija prvega reda priredi sivinsko vrednost v toˇcki (x, y) enako linearno uteženi vsoti sivinskih vrednosti sosednjih štirih toˇck na diskretni vzorˇcni mreži (glej opis bilinearne interpolacije). Pri iskanju izhodišˇcne leve gornje toˇcke v diskretni mreži si 5.4 Vaje z rešitvami 67 pomagajte s funkcijo numpy.floor(). Preizkusite delovanje funkcije na podsliki sivinske slike med oglišˇcema (260, 210) in (360,280) tako, da podsliko prevzorˇcite s trikrat bolj gosto vzorˇcno mrežo, kot je mreža originalne sivinske slike. ■ Funkcija interpolate1Image2D(iImage, iCoorX, iCoorY) izvaja bilinearno interpolacijo (prvega reda) 2D slike, kjer vrednost vsakega interpoliranega piksla izraˇcuna kot uteženo povpreˇcje štirih najbližjih sosedov v originalni sliki. Rešitev lahko zapišemo: 1 def interpolate1Image2D( iImage, iCoorX, iCoorY ): 2 """Funkcija za interpolacijo prvega reda""" 3 # pretvori vhodne spremenljivke v np polje 4 iImage = np.asarray( iImage ) 5 iCoorX = np.asarray( iCoorX, dtype=’float’) 6 iCoorY = np.asarray( iCoorY, dtype=’float’) 7 # preberi velikost slike in jedra 8 dy, dx = iImage.shape 9 # ustvari 2d polje koordinat iz 1d vhodnih koordinat (!!!) 10 if np.size(iCoorX) != np.size(iCoorY): 11 print(’Stevilo X in Y koordinat je razliˇ cno!’) 12 iCoorX, iCoorY = np.meshgrid(iCoorX, iCoorY, indexing=’xy’) 13 # pretvori v linearno polje 14 oShape = iCoorX.shape 15 iCoorX = iCoorX.flatten() 16 iCoorY = iCoorY.flatten() 17 # ustvari izhodno polje, pretvori v linearno polje 18 oImage = np.zeros( iCoorX.shape , dtype=’float’ ) 19 # priredi vrednosti 20 for idx in range(oImage.size): 21 lx = int(np.floor(iCoorX[idx])) 22 ly = int(np.floor(iCoorY[idx])) 23 sx = iCoorX[idx] - lx 24 sy = iCoorY[idx] - ly 25 if lx>=0 and lx<(dx-1) and ly>=0 and ly<(dy-1): 26 # izracunaj utezi 27 a = (1 - sx) * (1 - sy) 28 b = sx * (1 - sy) 29 c = (1 - sx) * sy 30 d = sx * sy 31 # izracunaj izhodno vrednost 32 oImage[idx] = a * iImage[ly, lx] + \ 33 b * iImage[ly, lx+1] + \ 34 c * iImage[ly+1, lx] + \ 35 d * iImage[ly+1, lx+1] 36 if iImage.dtype.kind in (’u’,’i’): 37 oImage[oImage 38 oImage[oImage>np.iinfo(iImage.dtype).max] = np.iinfo(iImage.dtype).max 39 return np.array( np.reshape( oImage, oShape ), dtype=iImage.dtype ) Algoritem najprej doloˇci osnovne koordinate (lx,ly) z zaokrožanjem navzdol in relativne pozicije (s x,sy) znotraj osnovne celice, nato izraˇcuna uteži (a,b,c,d ) za vsakega od štirih sosednjih pikslo v in izvede uteženo vsoto. Za celoštevilske podatkovne tipe funkcija zagotovi pravilno obmoˇcje vrednosti z uporabo np.clip. Bilinearna interpolacija omogoˇca bolj gladke rezultate kot metoda najbližjega soseda, vendar je raˇcunsko zahtevnejša. Preizkus funkcije naredimo na isti vhodni sliki kot pri vaji 5.3, le da zamenjamo funkciji za interpolacijo. Rezultat prikazuje slika 5.4. 68 Poglavje 5. Osnovna obdelava slik (a) (b) Slika 5.4: Rezultat interpolacije prvega reda (a) originalne podslike za (b) s korakom 1/3 piksla. Primerjava s sliko 5.3b (interpolacija 0. reda) kaže na veˇcjo gladkost robov. Vaja 5.4 Napišite funkcijo za piramidno decimacijo sivinskih slik: 1 def decimateImage2D( iImage, iLevel ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer je iImage vhodna sivinska slika, iLevel pa število zaporednih decimacij vhodne slike s faktorjem dva. Pred vsako decimacijo filtrirajte sliko z nizkoprepustnim sitom velikosti 3 × 3 (glej opis piramidne decimacije) s funkcijo discreteConvolution2D(). Funkcija vrne decimirano sivinsko sliko oImage. Preizkusite delovanje funkcije na sivinski sliki za vrednosti iLevel = 1, 2, 3. ■ Funkcija decimateImage2D(iImage, iLevel) izvaja piramidno decimacijo slike z uporabo rekurzivnega pristopa. Najprej uporabi Gaussovo glajenje s 3x3 jedrom za prepreˇcevanje pres-likovanja višjih frekvenc v nižje (ang. aliasing), nato sliko decimira z vzorˇcenjem vsakega drugega piksla v obeh oseh slike. Postopek se rekurzivno ponavlja za doloˇceno število nivojev (iLevel), kar ustvarja slikovno piramido z vedno manjšo loˇcljivostjo. Rešitev lahko zapišemo: 1 def decimateImage2D( iImage, iLevel ): 2 """Funkcija za piramidno decimacijo""" 3 print(’Decimacija pri iLevel = ’, iLevel) 4 # pretvori vhodne spremenljivke v np polje 5 iImage = np.asarray( iImage ) 6 iImageType = iImage.dtype 7 # gaussovo jedro za glajenje 8 iKernel = np.array( ((1/16,1/8,1/16),(1/8,1/4,1/8),(1/16,1/8,1/16)) ) 9 # glajenje slike pred decimacijo 10 # iImage = discreteConvolution2D( iImage, iKernel ) 11 # hitrejsa verzija glajenja 12 import scipy.ndimage as ni 13 iImage = ni.convolve( iImage, iKernel, mode=’nearest’ ) 14 # decimacija s faktorjem 2 15 iImage = iImage[::2,::2] 16 # vrni sliko oz. nadaljuj po piramidi 17 if iLevel <= 1: 5.5 Naloge in vprašanja 69 18 return np.array( iImage, dtype=iImageType ) 19 else: 20 return decimateImage2D( iImage, iLevel-1 ) Za hitrejše izvajanje uporablja optimizirano funkcijo za konvolucijo iz knjižnice SciPy. Vsak nivo zmanjša loˇcljivost slike za faktor 2, pri ˇcemer ohranja podatkovni tip originalne slike. Rekurzija se ustavi, ko doseže osnovni primer (iLevel <= 1). Funkcijo smo preverili z decimacije originalne slike 5.1 z faktor 8 × (iLevel = 3) na naslednji naˇcin: 1 # zaženi funkcijo 2 oImageG = decimateImage2D( iImageG, 3 ) 3 # nariši rezultat 4 rvlib.showImage( oImageG, ’decimacija za faktor 2^3’ ) Preverimo lahko tudi rezultat brez uporabe glajenje (zakomentiramo ustrezno vrstico funkcije). Rezultat prikazuje slika 5.5, kjer se v primer neuporabe filtriranja v izhodni sliki pojavijo novi robovi, kot posledica preslikovanja višjih frekvenc v nižje. (a) (b) Slika 5.5: Rezultat decimacije originalne slike 5.1 za faktor 8 3 × ( = 2) (a) z Gaussovim filtriranjem in (b) brez filtriranja. 5.5 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.2: To je moj odgovor. . Naloga 5.1 V funkciji diskretne 2D konvolucije smo privzeli, da so sivinske vrednosti toˇck zunaj slike enake 0. Pri uporabi funkcije z veˇcjimi konvolucijskimi jedri zaradi tega na izhodnih slikah dobimo popaˇcen rob. Eden izmed naˇcinov za odpravo teh popaˇcenj je ta, da vhodno sliko v vse smeri razširimo z robom, ki je enak polovici širine konvolucijskega jedra (U/2,V /2). Sivinske vrednosti v razširjenem delu slike nadomestimo s sivinskimi vrednostmi, ki so enake sivinskim vrednostim najbližjih slikovnih elementov v originalni vhodni sliki. 70 Poglavje 5. Osnovna obdelava slik Razširite funkcijo discreteConvolution2D() tako, da bo imela dodatni vhodni parameter iPadImage z vrednostjo ‘zeros’ ali ‘nearest’. Parameter iPadImage naj definira naˇcin razšir-itve slike pred izraˇcunom diskretne 2D konvolucije, in sicer za iPadImage = ‘zeros’ privzemite, da so sivinske vrednosti toˇck zunaj slike enake 0; za iPadImage = ‘nearest’ pa naj bodo enake sivinskim vrednostim najbližjih slikovnih elementov v originalni vhodni sliki. Prikažite delovanje razširjene funkcije na sivinski sliki z jedrom iKernel = numpy.ones([11,11])/121. Naloga 5.2 Uporabite funkcijo convolve() v Python knjižnici scipy.ndimage za izraˇcun 2D diskretne konvolucije na sivinski sliki z jedrom iKernel = numpy.ones([11,11])/121. Primer-jajte rezultate in ˇcas izraˇcuna (v milisekundah). ˇ Cas izraˇcunajte z uporabo funkcije clock() v knjižnjici time in ga nato ustrezno pretvorite. Naloga 5.3 V funkciji diskretne 2D konvolucije napisani na vajah smo privzeli, da so sivinske vrednosti toˇck zunaj slike enake 0. Pri uporabi funkcije z veˇcjimi konvolucijskimi jedri zaradi tega na izhodnih slikah dobimo popaˇcen rob. Obstajajo razliˇcni naˇcini za odpravo teh popaˇcenj. Opišite in analizirajte vse možne naˇcine, ki jih omogoˇca funkcija convolve() v Python knjižnici scipy.ndimage (parameter mode), ter prikažite rezultate. Vizualno ocenite kako se razlikujejo rezultati razliˇcnih naˇcinov pri manjših in veˇcjih velikostih jedra za povpreˇcenje (npr. 5 in 21). Naloga 5.4 Napišite funkcijo discreteConvolutionColorImage, ki bo omogoˇcala konvolucijo barvnih slik s poljubnim jedrom. Preizkusite delovanje na barvni sliki z jedrom iKernel = numpy.ones( [11,11])/121 in prikažite sliko. 1 def discreteConvolutionColorImage( iImage, iKernel ): 2 # Tu napišite python kodo funkcije 3 return oImage Naloga 5.5 Napišite funkcijo za izraˇcun konvolucijskega jedra v obliki 2D simetriˇcne Gaussove funkcije: 1 def discreteGaussian2D( iSigma ): 2 # Tu napišite python kodo funkcije 3 return oKernel kjer je iSigma standardna deviacija σ v 2D simetriˇcni Gaussovi funkciji, ki je definirana kot: k − 2 2 2 1 − 2 − ( u + v ) / 2 u , ) = ( 2 ) exp . (5.2) ( σ v π σ Velikost konvolucijskega jedra U ×V nastavite glede na vrednost iSigma kot U,V = 2 · ⌈3 ∗ σ⌉ + 1, kjer tako pridobljene vrednosti U in V zaokrožite s funkcijo numpy.ceil, vrednost k(0,0) pa naj bo v središˇcnem elementu izhodne 2D matrike oKernel. Zagotovite, da bo vsota vseh elementov oKernel enaka 1. Doloˇcite konvolucijski jedri za σ = 0.1 in 3. Z jedroma izvedite diskretno 2D konvolucijo na barvni sliki in ju prikažite. Obrazložite vpliv vrednosti σ . Naloga 5.6 Ostrenje slike je analogno prostorskemu odvajanju sivinskih vrednosti. Ostrenje slik z drugim odvodom izvedemo z uporabo Laplaceovega jedra tako, da vhodni sliki g(x,y) odštejemo uteženo sliko drugega odvoda slike: f (x,y) = g(x,y) − c ∆g(x,y) , 5.5 Naloge in vprašanja 71 kjer konstanta c doloˇca stopnjo ostrenja. Po odštevanju oz. prištevanju slike drugega odvoda ∆g(x, y) zagotovite, da bodo vrednosti 8-bitne sivinske slike na obmoˇcju od [0, 255], pri ˇcemer vrednosti manjše od 0 oz. vrednosti veˇcje od 255 ustrezno zaokrožite. Izostrite sivinsko sliko z vrednostmi c = 0,0.5,1, 2 in slike prikažite. Preizkusite razliˇcne vrednosti c in obrazložite njihov vpliv na rezultirajoˇco sivinsko sliko. Preizkusite ostrenje še na barvni sliki. Naloga 5.7 Uporabite funkcijo interpn() v Python knjižnici scipy.interpolate za izraˇcun 2D interpolacije niˇctega in prvega reda, podobno kot pri nalogah 2 in 3 iz vaj. Primerjajte rezultate in ˇcas izraˇcuna. Naloga 5.8 Napišite funkcijo interpolateColorImage, ki bo omogoˇcala uporabo interpolacije niˇctega (method=nearest) in prvega (method=linear) reda na barvnih slikah. Preizkusite de-lovanje funkcije tako, da z niˇctim in prvim redom prevzorˇcite celotno barvno sliko z dvakrat bolj gosto vzorˇcno mrežo, kot je mreža originalne barvne slike. 1 def interpolateColorImage( iImage, iCoorX, iCoorY, method ): 2 # Tu napišite python kodo funkcije 3 return oImage Naloga 5.9 Interpolacijo slike lahko uporabljamo tudi za poveˇcavo delov slike. Z interpolacijo prvega reda izvedite petkratno (5×) poveˇcavo pravokotnega podroˇcja barvne slike med oglišˇcema (100,50) in (250,200). Prikažite originalno in poveˇcano pravokotno podroˇcje barvne slike. Naloga 5.10 Prilagodite funkcijo za piramidno decimacijo tako, da bo omogoˇcala decimiranje barvnih slik in preizkusite delovanje na barvni sliki. Pomanšajte sliko za 2-krat in 4-krat. 72 Poglavje 5. Osnovna obdelava slik Rezultati nekaterih nalog: Gaussova funkcija pri razliˇcnih vrednostih parametra σ Ostrenje slike z uporabo Laplaceovega jedra Interpolacija in decimacija barvnih slik 6. Robustno iskanje objektov Pri vaji bomo najprej obravnavali nizkonivojske postopke za iskanje oz. detekcijo oglišˇc in robov v 2D slikah, ki temeljijo na analizi 1. odvoda oz. gradienta sivinske slike. Oglišˇca so izraziti strukturni elementi slike oz. objektov na sliki in jih lahko uporabimo v aplikacijah kot so sledenje objektov v zaporednih posnetkih, doloˇcanje preslikave med razliˇcnimi pogledi enakega objekta, kot refereˇcne toˇcke za geometrijske meritve, za kalibracijo optiˇcnih sistemov itd. Detektirane robove pa lahko nadgradimo za zaznavanje kompleksnejših objektov v 2D slikah. V nadaljevanju vaje bomo obravnavali Houghovo preslikavo, ki omogoˇca zaznavo parametriˇcnih oblik na osnovi zaznanih robov v sliki in s tem detekcijo predvsem enostavnih umetnih objektov (npr. pravokotne, okrogle, eliptiˇcne strukture). Za zaznavanje naravnih objektov, kjer so robovi lahko poljubnih obliki, pomembne pa so tudi druge znaˇcilnice, kot so barva, tekstura in kontekst, pa bomo uporabili konvolucijske nevronske mreže. Pri vaji boste sami implementirali funkcije za osnovne detektorje oglišˇc, robov in Houghovo preslikavo, kasneje pa preizkusili še obstojeˇce implementacije v knjižnici OpenCV in primerjali rezultate. OpenCV je knjižnica programskih funkcij, ki se izvajajo v realnem ˇcasu, namenjenim potrebam raˇcunalniškega vida. Knjižnica vsebuje osnovna orodja za obdelavo slik, torej tudi tista za robustno iskanje 2D objektov, s katerimi se bomo seznanili pri vaji. Za preizkus nevronskih mrež boste uporabili implementacije in prednauˇcene modele v tej knjižnici. OpenCV knjižnico naložite tako, da v ukazno vrstico terminala (ang. command prompt) vpišete: pip install opencv-python opencv-contrib-python V sledeˇcih podpoglavjih so podane izpeljave algoritmov, ki so temelj za kasnejše programske implementacije. 6.1 Gradient sivinske slike Oglišˇce je definirano kot preseˇcišˇce dveh robov, pri ˇcemer rob v sliki v splošnem lahko opišemo kot veliko lokalno spremembo sivinske vrednosti. Robove lahko detektiramo na podlagi odvodov 74 Poglavje 6. Robustno iskanje objektov oz. gradientov slike. Prvi odvod za poljubno 2D funkcijo f (x,y) zapišemo v obliki vektorja: g x ∂ ( x , y ) f ( x , y ) / ∂ x ∇ f (x, y) =⃗ g(x,y) = = . (6.1) gy(x,y) ∂ f (x, y)/∂ y Vektorska slika gradienta ⃗ g(x,y) v vsaki toˇcki (x,y) vsebuje vektor, ki s koordinatno osjo x oklepa kot α in ima dolžino G: α gy (x,y) q 2 2 ( x , y ) = arctan , G ( x , y ) = g y g x y x ( x , ) + y ( ,) . (6.2) g x ( x , y ) Gradient ⃗ g(x,y) kaže v smeri najveˇcje spremembe funkcije f (x,y), torej pravokotno na dani rob v sliki. Komponenti gradienta gx (x, y) in gy(x,y), ki predstavljata parcialna odvoda, na digitalni sliki izraˇcunamo z digitalnimi filtri, naprimer s Sobelovim jedrom. Vektorsko sliko ⃗ g(x,y) odvodov dobimo z dvema 2D diskretnima konvolucijama med vhodno sivinsko sliko in Sobelovima jedroma (slika 6.1 levo). Slika 6.1: Sobel jedri (levo) in odziv Harrisovega detektorja oglišˇc ter zaznana oglišˇca (desno). Zvezno obliko gradientnega operatorja, ki omogoˇca zasnovo poljubno velikega digitalnega filtra z nastavljivo stopnjo glajenja dobimo z odvajanjem Gaussove funkcije N (x,y| σ ): ∇ ( N 2 2 2 2 x + y − x 1 x + y x, y|σ ) = ∇ exp − = exp − . (6.3) 2 2 2 2 σ −y σ 2σ Stopnjo glajenja poljubno izberemo s parametrom standardne deviacije σ . 6.2 Harrisov detektor oglišˇ c Robovi v slikah imajo velik gradient v smeri pravokotno na rob, v oglišˇcih pa je gradient slike velik v veˇc smereh, zato veˇcina detektorjev oglišˇc temelji na primerjavi velikosti komponent gradienta v x in y smeri slike. V praksi se pogosto uporablja Harrisov detektor oglišˇc, pri katerem za vsako toˇcko (x,y) izraˇcunamo lokalno strukturno matriko M: 2 A ( x , y ) C ( x , y ) g ( x , y ) g ( x , y ) g ( x , y ) M x x y ( x , y ) = ∗ N ( x , y | σ ) = ∗ N(x,y | σ ) C 2 ( x , y ) B ( x , y ) g x y y x ( , y ) g ( x , y ) g(x,y) (6.4) Komponente lokalne strukturne matrike M, tj. slike A(x, y), B(x, y) in C(x,y) predstavljajo odvode slike, ki jih zgladimo z Gaussovim jedrom N(x,y | σ ) z uporabo diskretne 2D konvolucije (∗). 6.3 Cannyev detektor robov 75 Kriterijsko funkcijo za detekcijo oglišˇc v vsaki toˇcki slike (x, y) doloˇcimo z analizo lastnih vrednosti λ1 in λ2 matrike M: trace s 2 M ) trace ( M ) ( λ ± − 1 , 2 = det(M) , (6.5) 2 2 kjer sta trace(·) in det(·) funkciji za sled in determinanto matrike M. Na podroˇcjih slike s konstantno sivinsko vrednostjo bo M = 0 in λ1 = λ2 = 0, pri enakomerno narašˇcujoˇci sivinski vrednosti v eni smeri pa bo λ1 > 0 in λ2 = 0. Lastni vrednosti λ1 in λ2 torej kodirata izrazitost roba, pripadajoˇca lastna vektorja pa smer roba. V oglišˇcih je rob izrazit v smeri obeh lastnih vektorjev, lastni vrednosti sta veliki |λ 1,2| ≫ 0 in velja |λ1| ≈ |λ2|, rob pa je tem bolj izrazit, ˇcim manjša je razlika med lastnima vrednostima. Od tod sledi kriterijska funkcija za detekcijo oglišˇc: Q 2 2 H λ ( x , y ) = 1 λ − 2 κ ( λ1 + λ2 ) = det ( M ) − κ ( trace ( M )) . (6.6) Parameter 1 κ doloˇca obˇcutljivost detektorja in ga izberemo na intervalu [ 0 , ]. Toˇcke roba detek- 4 tiramo kot lokalne maksimume v Q H (x, y), nadalje pa detektirana oglišˇca preˇcistimo z upragov-ljanjem 4 6 Q ( x , y ) > T , kjer je T obiˇcajno na intervalu od 10 do 10. Primer polja Q(x,y) in min min zaznanih oglišˇc je prikazan na sliki 6.1 desno. 6.3 Cannyev detektor robov Zaznavanje robov v slikah je eden izmed pomembnejših postopkov na podroˇcju obdelave in analize slik, Cannyev detektor robov pa je eden izmed najpogosteje uporabljanih tovrstnih postopkov. Algoritem Cannyevega detektorja lahko povzamemo s štirimi osnovnimi koraki: 1. glajenje slike z Gaussovim filtrom, 2. izraˇcun slike velikosti in smeri gradienta, 3. odstranjevanje nemaksimalnih vrednosti, 4. dvojno upragovljanje in povezovanje robov. Slika 6.2: Odstranjevanje nemaksimalnih vrednosti (levo) in povezovanje robov (desno). Za glajenje in doloˇcanje slik gradienta lahko uporabimo poljubni digitalni filter. V sliki gradientov dobimo okoli robov širša podroˇcja ojaˇcitve, ki jih moramo zožiti oz. odstraniti, zato da ohranimo prave robove slike. Odstranjevanje nemaksimalnih vrednosti naredimo tako, da postavimo na niˇc vrednosti tistih slikovnih elementov, ki nimajo maksimalnih vrednosti v diskretni smeri gradienta (0 ◦ ◦ ◦ ◦ ,45 ,90 in 135) oz. katerih sosednji slikovni elementi imajo v smeri gradienta 76 Poglavje 6. Robustno iskanje objektov veˇcje vrednosti (slika 6.2 levo). V zadnjem koraku postopka izvedemo še upragovljanje slike robov in povezovanje robnih toˇck. Upragovljanje izvedemo z dvema pragoma. Z zgornjim pragom T H doloˇcimo izrazite robne toˇcke rH(x,y), ki jih bomo neposredno zadržali. To so tiste toˇcke, ki imajo magnitudo gradienta G(x,y) > TH. S spodnjim pragom TL pa doloˇcimo neizrazite robne toˇcke rL(x,y), ki jih bomo obdržali za postopek povezovanja robov. Povezovanje izrazitih robov rH(x,y) izvedemo tako, da vsako sosednjo toˇcko, ki pripada rL(x, y) (T L < G(x,y) ≤ TH) vkljuˇcimo v množico konˇcnih robnih toˇck (slika 6.2 desno). 6.4 Houghov algoritem Med postopke iskanje objektov na sliki spada tudi iskanje premic oz. ˇcrt. Uˇcinkovit postopek iskanja premic na sliki temelji na Houghovi transformaciji. Predpostavimo, da imamo binarno sliko robov r(x, y). Skozi poljubno izbrano robno toˇcko (xi,yi) lahko potegnemo poljubno število premic oblike: y i = axi + b. (6.7) Isto enaˇcbo lahko zapišemo v prostoru parametrov (a,b), kjer sta koordinati robne toˇcke xi in yi parametra premice: b = −x ia + yi. (6.8) Slika 6.3: Preslikava toˇcke roba iz prostora slike v parametriˇcni prostor za eksplicitno obliko zapisa premice (y = ax + b). Za neko drugo robno toˇcko (x j ,y j) dobimo v prostoru parametrov (a, b) še eno premico, ki se v neki toˇcki ′ ′ ′ ′ ( a , b ) seka s premico, ki pripada ( x , y ) . Parametra ( a , b) doloˇcata enaˇcbo premice v i i prostoru slike r(x, y), ki gre skozi toˇcki (xi,yi) in (x j, y j). ˇ Ce za vse robne toˇcke v sliki vnesemo pripadajoˇco premico v parametriˇcni prostor, potem lahko poišˇcemo premice oz. ˇcrte z detekcijo vrhov v parametriˇcnem prostoru. Situacijo prikazuje slika 6.3. Pri tovrstnemu zapisu enaˇcbe premice naletimo na problem pri navpiˇcnih premicah, pri katerih gre parameter a → inf, s tem pa tudi velikost parametriˇcnega prostora (a, b). Temu se lahko izognemu z zapisom premice v Hessejevi obliki oz. v polarnih koordinatah (r,ϕ): x cosϕ + y sin ϕ = r (6.9) Za navpiˇcno premico ( ◦ ◦ ϕ = 0 ) doloˇca r preseˇcišˇce z x osjo, za vodoravno premico ( ϕ = 90) pa doloˇca r preseˇcišˇce premice z y osjo. V polarnem prostoru prostoru (r,ϕ ) vsaka sinusna krivulja xi cosϕ + yi sin ϕ = r predstavlja množico premic, ki gredo v prostoru slike (x,y) skozi toˇcko (xi, yi). 6.5 Konvolucijske nevronske mreže 77 Slika 6.4: Preslikava toˇcke roba iz prostora slike v parametriˇcni prostor za Hessejevo obliko zapisa premice (x cos ϕ + y sin ϕ = r). Preseˇcišˇce dveh ali veˇcih sinusnih krivulj ′ ′ ( r , ϕ ) doloˇca parametra premice v prostoru (x,y), kot prikazuje slika 6.4 sredina. Prostor parametrov (r,ϕ) lahko enostavno diskretiziramo, in sicer 0 ≤ ϕ < π ter −rdiag ≤ r ≤ √ r 2 2 diag (slika 6.4 desno ). Pri tem je r diag = M + N/2 in M,N sta višina in širina slike r(x, y), referenˇcna toˇcka pa je center slike (M/2,N/2). Diskretiziran prostor imenujemo akumulator A(rn,ϕm), ki ga najprej postavimo na 0, nato pa za vsako robno toˇcko (xi, yi) in za vsako možno diskretno vrednost 0 ≤ ϕm < π izraˇcunamo ustrezno diskretno vrednost parametra rn, nato pa vrednost akumulatorja v celici (rn,ϕm) poveˇcamo za 1. Premice, ki predstavljajo robne toˇcke slike, doloˇcimo tako, da poišˇcemo tiste celice akumulatorja A(rn,ϕm), ki imajo dovolj velike in lokalno najveˇcje vrednosti. Primer rezultata je prikazan na sliki 6.5. Slika 6.5: Akumulator po Houghovi preslikavi robov s Hessejevo obliko premice (levo) in premice, ki pripadajo zaznanim maksimumom v akumulatorju (desno). 6.5 Konvolucijske nevronske mreže Konvolucijske nevronske mreže (CNN; ang. Convolutional Neural Networks) so specializirane arhitekture globokega uˇcenja za analizo strukturiranih podatkov kot so digitalne slike. Njihova osnovna zasnova temelji na: • Konvolucijskih slojih: avtomatiˇcno uˇcijo hierarhije prostorskih znaˇcilnic z lokalnimi filtri na osnovi diskretne konvolucije, 78 Poglavje 6. Robustno iskanje objektov • Pooling slojih: zmanjšujejo prostorsko loˇcljivost za poveˇcanje invariantnosti na transforma- cije, na primer z uporabo sloja združevanja (ang. pooling layer), • Povezovalnih slojih: klasiˇcne nevronske mreže, kot na primer veˇcplastni perceptron, ki kombinirajo visokonivojske znaˇcilnice za konˇcno razvršˇcanje. Najbolj uveljavljene arhitekture so Faster R-CNN [15], YOLO (ang. You Only Look Once) [20], SSD (ang. Single Shot MultiBox Detector) [19], RetinaNet [18] in EfficientDet [21]. Sodobne arhitekture vsebujejo tudi napredne arhitekture in mehanizme, kot na primer mehanizem pozornosti za dinamiˇcno uteževanje pomembnih prostorskih lokacij, veˇcnivojsko analizo znaˇcilnic in vizualne transformerje (ViT [12], DETR [16]) kot alternativne pristope. Knjižnica OpenCV vsebuje podporo za veˇc vnaprej nauˇcenih globokih modelov prek modula cv2.dnn . Modeli se delijo glede na namen uporabe, na primer za detekcijo objektov: • MobileNet-SSD: hitra detekcija objektov optimizirana za mobilne naprave (20 razredov), • YOLOv3/YOLOv4-tiny: pospešene razliˇcice YOLO za realnoˇcasovno detekcijo (80 razre- dov), • Faster R-CNN: natanˇcnejši model za detekcijo objektov (razliˇcni podatkovni nizi), • EfficientDet: uˇcinkovite arhitekture za veˇcnamensko zaznavanje. Ti modeli vrnejo oznako kategorije objekta (oz. razred objekta) in pripadajoˇco verjetnost. Za semantiˇcno segmentacijo, ki vsakemu pikslu v sliki pripiše pripadajoˇco oznako lahko uporabimo: • ENet: lahka arhitektura za semantiˇcno segmentacijo, • FCN: polno konvolucijske mreže za segmentacijo. Za razvršˇcanje slik glede na kategorije v bazi ImageNet [17] so na voljo: • AlexNet/GoogleNet/ResNet: klasiˇcne arhitekture za klasifikacijo, • SqueezeNet: kompaktni model z visoko natanˇcnostjo. Na voljo so tudi specifiˇcni modeli: • OpenPose: detekcija ˇcloveških poz, • Text Detection (EAST): detekcija besedila v naravnih scenah, • Style Transfer: umetniški prenos sloga med slikami. Modeli so na voljo v veˇc formatih: • TensorFlow (.pb konfiguracija in uteži), • Caffe (.prototxt in .caffemodel), • Darknet (.cfg in .weights za YOLO), • ONNX (.onnx format). V splošnem je za uporabo modelov potrebno izvesti naslednje korake: 1. prenesti konfiguracijsko datoteko in prednauˇcene uteži, 2. naložiti model z cv2.dnn.readNet(), 3. pripraviti vhodne podatke z cv2.dnn.blobFromImage(), 4. izvesti napoved prek net.forward(). Primer uporabe GoogLeNet mreže boste spoznali pri vaji. 6.6 Vaje z rešitvami Pri vaji boste sami implementirali funkcije za Harrisov detektor oglišˇc in Canneyenv detektor robov, medtem ko boste uporabili funkcije v knjižnici OpenCV za detekcijo parametriˇcnih objektov (premic, krogov) s Houghovim algoritmom. Vajo zaˇcnemo z izraˇcunom gradientnega polja sivinske slike in njegovo upodobitvijo, ker kasneje na osnovi analize polja gradientov išˇcemo tako oglišˇca kot robove. Koda vaje je dostopna na Git repozitoriju. 6.6.1 Gradient sivinske slike Najprej uvozimo Python knjižnice za vajo: 6.6 Vaje z rešitvami 79 1 import matplotlib.pyplot as plt 2 import scipy.ndimage as ni 3 import numpy as np 4 from rvlib import * 5 import cv2 Vaja 6.1 Napišite funkcijo za izraˇcun 1. odvoda vhodne sivinske slike iImage: 1 def imageGradient( iImage ): 2 # Tu napišite kodo funkcije 3 return oGx, oGy Funkcija vrne sliki parcialnih odvodov oGx in oGy, ki imata enako velikost kot vhodna slika iImage. Naložite barvno RGB sliko slika1.jpg v okolje Python in jo pretvorite v sivinsko sliko po enaˇcbi S = 0,299R + 0,587G + 0,114B. Izraˇcunajte prvi odvod sivinske slike in ga prikažite kot vektor v vsaki toˇcki slike tako, da uporabite funkcijo matplotlib.pyplot.quiver(). ■ Rešitev lahko zapišemo z uporabo Sobelovega jedra in diskretne konvolucije: 1 def imageGradient( iImage ): 2 """Gradient slike s Sobelovim operatorjem""" 3 iImage = iImage.astype(’float’) 4 iSobel = np.array( ((-1,0,1),(-2,0,2),(-1,0,1)) ) 5 oGx = ni.convolve( iImage, iSobel, mode=’nearest’ ) 6 oGy = ni.convolve( iImage, np.transpose( iSobel ), mode=’nearest’ ) 7 return oGx, oGy 8 9 # preizkus funkcije 10 iImage = loadImage( ’slika1.jpg’ ) 11 iImageG = colorToGray(iImage) 12 oGx, oGy = imageGradient(iImageG) 13 14 # prikaz slik 15 showImage(iImageG, ’Sivinska slika’) 16 showImage(oGx, ’Gx’) 17 showImage(oGy, ’Gy’) 18 19 # prikaz vektorskega polja (decimirano s faktorjem 2, za hitrejši prikaz) 20 dy, dx = iImageG.shape 21 oCx, oCy = np.meshgrid(range(0,dx,2), range(0,dy,2), indexing=’xy’) 22 plt.quiver(oCx, oCy, oGx[::2,::2], -oGy[::2,::2], color=’r’) Sivinsko razliˇcico vhodne barvne slike in rezultat izraˇcuna gradienta v horizontalni (oGx) in vertikalni (oGy) smeri ter upodobitev gradientnega polja z sliko vektorjev prikazuje slika 6.6. 6.6.2 Harrisov detektor oglišˇ c Vaja 6.2 Napišite funkcijo za izraˇcun odziva Harrisovega detektorja oglišˇc na vhodni sivinski sliki iImage: 1 def responseHarris( iImage, iKappa, iSigma ): 80 Poglavje 6. Robustno iskanje objektov (a) (b) (c) (ˇc) (d) Slika 6.6: Prikaz (a) sivinske slike in pripadajoˇce (b) slike horizontalnega gradienta gx (x, y) in (c) vertikalnega gradienta gy (x, y); obe komponenti gradienta sta (ˇc) upodobljeni kot vektorsko polje, za boljši vpogled je dana (d) poveˇcava spodnjega levega kota velikosti 100 × 100. 2 # Tu napišite kodo funkcije 3 return oQH kjer je iKappa obˇcutljivost detektorja oglišˇc (κ), iSigma pa standardna deviacija Gaussove funkcije za glajenje odziva. Funkcija vrne sliko oQH, ki predstavlja odziv detektorja oglišˇc. Preizkusite delovanje funkcije za razliˇcne vrednosti parametrov iKappa in iSigma. ■ Funkcijo responseHarris, ki izraˇcuna odziv Harrisovega detektorja oglišˇc lahko zapišemo kot: 1 def responseHarris( iImage, iKappa, iSigma ): 2 """Odziv Harrisovega detektorja oglišˇ c""" 3 iImage = iImage.astype(’float’) 4 # odvod slike 5 oGx, oGy = imageGradient( iImage ) 6 # komponente matrike M 7 A = oGx**2 6.6 Vaje z rešitvami 81 8 B = oGy**2 9 C = oGx * oGy 10 # Gaussovo jedro za iSigma 11 iGaussKernel = discreteGaussian2D( iSigma ) 12 # glajenje komponent matrike M 13 A = ni.convolve( A, iGaussKernel, mode=’nearest’ ) 14 B = ni.convolve( B, iGaussKernel, mode=’nearest’ ) 15 C = ni.convolve( C, iGaussKernel, mode=’nearest’ ) 16 # odziv Harrisovega detektorja oglišˇ c 17 trM = A + B 18 detM = A * B - C**2 19 oQH = detM - iKappa*(trM**2) 20 21 # PREKO IZRAˇ CUNA LASTNIH VREDNOSTI 22 # trM = A + B 23 # detM = A*B - C**2 24 ## # izraˇ cun lastnih vrednosti 25 # D = (trM/2.0)**2 - detM 26 # D[D<0] = 0; D = np.sqrt(D) 27 # l1 = trM/2.0 + D 28 # l2 = trM/2.0 - D 29 # oQH = l1*l2 - iKappa*(l1 + l2)**2 30 31 return oQH 32 33 # preizkus funkcije 34 showImage(iImageG) 35 oQH = responseHarris(iImageG, 1/32.0, 3.0) 36 showImage(oQH, ’Odziv Harrisovega detektorja oglišˇ c’) Izraˇcun temelji na analizi lastnih vrednosti strukturnih tenzorjev. Najprej izraˇcuna gradient slike v x- in y-smeri, nato pa iz gradientov oblikuje komponente strukturnih tenzorjev (A, B, C). Te komponente se zgladijo z Gaussovim filtrom, kar omogoˇca robustnejšo detekcijo oglišˇc pri razliˇcnih skalah. Kljuˇcni del algoritma je izraˇcun Harrisovega odziva QH = det(M) − 2 κ · tr ( M ), kjer je M strukturni tenzor, det(M) njegova determinanta, tr(M) sled tenzorja, κ pa empiriˇcni parameter, ki doloˇca obˇcutljivost detektorja. Alternativno (zakomentirano) je mogoˇce odziv izraˇcunati preko lastnih vrednosti strukturnih tenzorjev, kar daje enakovredne rezultate. Visoke vrednosti odziva oznaˇcujejo lokacije potencialnih oglišˇc v sliki. Rezultat prikazuje slika 6.7. Vaja 6.3 Napišite funkcijo za iskanje lokalnih maksimumov v poljubnem 2D polju iArray: 1 def findLocalMax( iArray ): 2 # Tu napišite kodo funkcije 3 return oLocalMax Funkcija vrne matriko oLocalMax dimenzij 2 × n, ki vsebuje (x, y) koordinate n lokalnih maksimumov. Lokalni maksimumi v 2D polju so tiste toˇcke, v katerih je vrednost veˇcja od vrednosti v sosednjih 8 elementih slike. Pri tem ne upoštevajte elementov slike, ki ležijo na robu ali izven slike. ■ Lokalni maksimumi v odzivu Harrisovega detektorja predstavljajo potencialna oglišˇca na vhodni sliki. Lokalni maksimumi v 2D polju so tiste toˇcke, v katerih je vrednost veˇcja od vrednosti v sosednjih 8 elementih slike. Rešitev lahko zapišemo: 82 Poglavje 6. Robustno iskanje objektov Slika 6.7: Prikaz odziva Harrisovega detektorja oglišˇc QH(x,y) za vrednost κ = 1/32 in Gaussovim glajenjem s σ = 3. 1 def findLocalMax( iArray, iThreshold=None ): 2 """Poišˇ ci lokalne optimume""" 3 iArray = np.asarray( iArray ) 4 dy, dx = iArray.shape 5 oLocalMax = [] # inicializiraj prazen seznam optimumov 6 for y in range(1,dy-1): 7 for x in range(1,dx-1): 8 cval = iArray[y,x] 9 # preskoˇ ci element, ˇ ce je cval manjše od iThreshold 10 if iThreshold is not None: 11 if cval < iThreshold: 12 continue 13 # preveri vse elemente v 3x3 sosešˇ cini 14 gx, gy = np.meshgrid((x-1,x,x+1), (y-1,y,y+1), sparse=False) 15 gx = gx.flatten() 16 gy = gy.flatten() 17 18 cvaldiff = iArray[gy, gx] - cval 19 cvaldiff[int(np.floor(len(gy)/2))] = -1 20 if cvaldiff.max() < 0: 21 oLocalMax.append( (x,y) ) 22 # vrni seznam maksimumov 23 return np.array( oLocalMax ) 24 25 # preizkus funkcije 26 oLocalMax = findLocalMax( oQH, 1e+6 ) 27 print(’Število lokalnih maksimumov: ’, str(oLocalMax.shape) ) 28 plt.imshow(iImageG, cmap = cm.Greys_r) # prikazi sliko v novem oknu 29 plt.suptitle(’Sivinska slika in oglišˇ ca’) # nastavi naslov slike 6.6 Vaje z rešitvami 83 30 plt.xlabel(’x’) 31 plt.ylabel(’y’) 32 plt.plot(oLocalMax[:,0],oLocalMax[:,1],’+m’,markersize=5.0) 33 plt.show(block=True) Algoritem preverja vsak element v polju (razen robnih) in ga primerja z njegovo 3 × 3 sosešˇcino. Ce je vrednost elementa veˇcja od vseh sosedov in preseže opcijski prag ( ˇiThreshold), se lokacija elementa doda v seznam lokalnih maksimumov. Kljuˇcna znaˇcilnost te implementacije je uporaba maskiranja centralnega elementa v sosešˇcini, kar omogoˇca pravilno detekcijo strogo lokalnih maksimumov. Funkcija je še posebej uporabna za detekcijo znaˇcilnih toˇck v odzivnih mapah detektorjev kot so Harrisov detektor ali LoG, kjer lokalni maksimumi predstavljajo potencialne lokacije interesnih toˇck. Preizkus funkcije za vhodno sivinsko sliko 6.6a vrne: Število lokalnih maksimumov: (433, 2) Prikaz lokacije lokalnih maksimumov je na sliki 6.8. Slika 6.8: Rezultat iskanja lokalnih optimumov v QH (x,y) Harrisovega detektorja oglišˇc za vrednost κ = 1/32 in Gaussovim glajenjem s σ = 3. Vaja 6.4 Napišite funkcijo za detekcijo oglišˇc na vhodni sivinski sliki iImage: 1 def cornerHarris( iImage, iKappa, iTmin ): 2 # Tu napišite kodo funkcije 3 return oCorners kjer je iKappa obˇcutljivost detektorja oglišˇc, iTmin pa prag za detekcijo oglišˇc. Funkcija vrne matriko oCorners dimenzij 2 × n, ki vsebuje (x,y) koordinate n detektiranih oglišˇc. Pri 84 Poglavje 6. Robustno iskanje objektov tem uporabite funkciji responseHarris() in findLocalMax(). Parameter iSigma v funkciji responseHarris() postavite na vrednost 3. ■ Funkcija integrira klica funkcij, ki smo jih napisali pri prejšnjih dveh vajah 6.2 in 6.3 in izpostavi vplivne parametre. Zapišemo jo kot: 1 def cornerHarris( iImage, iKappa, iTmin ): 2 """Izloˇ ci robne toˇ cke s Harrisovim detektorjem oglišˇ c""" 3 iSigma = 3.0 4 oQHt = responseHarris( iImage, iKappa, iSigma ) 5 oCorners = findLocalMax( oQHt, iTmin ) 6 return oCorners 7 8 # preizkus funkcije 9 oCorners = cornerHarris(iImageG, 1/32.0, 1e+6) 10 plt.imshow(iImageG, cmap = cm.Greys_r) # prikazi sliko v novem oknu 11 plt.suptitle(’Sivinska slika in oglišˇ ca’) # nastavi naslov slike 12 plt.xlabel(’x’) 13 plt.ylabel(’y’) 14 plt.plot(oCorners[:,0],oCorners[:,1],’+m’,markersize=5.0) Rezultat je prikazan na sliki 6.8. Dobljen seznam oglišˇc lahko še nadalje preˇcistimo, pri ˇcemer so možne strategij omejitev maksimalnega števila oglišˇc, minimalno evklidska razdalja med oglišˇci, ipd. 6.6.3 Cannyev detektor robov Cannyjev detektor boste uporabili za izloˇcanje robov iz slike, na podlagi robov pa boste kasneje s pomoˇcjo Houghovega algoritma poiskali bodisi premice, bodisi kroge, ali pa druge parametriˇcne oblike na sliki robov. Vaja 6.5 Napišite funkcijo za izraˇcun magnitude oGAbs in orientacije oGPhi 1. odvoda glajene sivinske slike iImage: 1 def smoothImageGradientAbs( iImage, iSigma ): 2 # Tu napišite kodo funkcije 3 return oGAbs, oGPhi kjer je iSigma standardna deviacija Gaussove funkcije za glajenje slike. Funkcijo preizkusite na sliki slika3.jpg, ki ste jo prej pretvorili v sivinsko sliko. Prikažite sliko magnitude 1. odvoda pri vrednosti iSigma je 1. ■ Rešitev lahko zapišemo: 1 def smoothImageGradientAbs( iImage, iSigma ): 2 iImage = iImage.astype(’float’) 3 # dušenje šuma z glajenjem 4 if iSigma > 0: 5 iGaussKernel = discreteGaussian2D( iSigma ) 6 oImage = ni.convolve( iImage, iGaussKernel, mode=’nearest’ ) 7 else: 8 oImage = iImage 9 # izraˇ cun gradienta slike (magnituda + orientacija) 10 oGx, oGy = imageGradient( oImage ) 11 oGAbs = np.sqrt(oGx**2.0 + oGy**2.0) 6.6 Vaje z rešitvami 85 12 oGPhi = np.arctan2(oGy, oGx) 13 14 return oGAbs, oGPhi 15 16 # preizkus funkcije 17 iImageG = colorToGray(loadImage(’slika3.jpg’)) 18 showImage(iImageG) 19 oGAbs, oGPhi = smoothImageGradientAbs(iImageG, 1.0) 20 showImage(oGAbs, ’Magnituda gradienta’) 21 showImage(oGPhi, ’Orientacija gradienta’) Funkcija smoothImageGradientAbs izraˇcuna glajen gradient slike z uporabo Gaussovega filtra in nato doloˇci magnitudo ter orientacijo gradienta. Najprej zgladi vhodno sliko z Gaussovim filtrom podane standardne deviacije (iSigma), ˇce je ta vrednost veˇcja od 0. Nato izraˇcuna gradient slike v x in y smeri s funkcijo imageGradient. Magnitudo gradienta (oGAbs) doloˇci kot evklidsko normo q parcialnih odvodov ( G 2 2 G x + y), orientacijo gradienta (oGPhi) pa kot arkus tangens razmerja parcialnih odvodov (arctan 2 (Gy ,Gx)). Rezultat prikazuje slika 6.9. (a) (b) (c) Slika 6.9: Prikaz (a) sivinske slike in pripadajoˇci sliki (b) magnitude in (c) orientacije gradienta. Vaja 6.6 Napišite funkcijo za odstranjevanje nemaksimalnih vrednosti s pomoˇcjo vhodnih slik oGAbs in oGPhi: 1 def nonMaximaSuppression( iGAbs, iGPhi ): 2 # Tu napišite kodo funkcije 3 return oEdge Funkcija naj vrne sliko oEdge, kjer naj elementi z vrednostjo 1 predstavljajo nepovezane robove v sliki. Funkcijo preizkusite na slikah 1. odvoda, ki ste jih predhodno izraˇcunali s funkcijo smoothImageGradientAbs. ■ Funkcija nonMaximaSuppression izvaja postopek odstranjevanja nemaksimalnih vrednosti, kljuˇcni korak v Cannyjevem detektorju robov. Najprej normalizira orientacije gradientov na interval [ ◦ ◦ ◦ 0 , π ] in jih kvantizira v štiri glavne kvadrante orientacije, ki so doloˇceni s smermi (0 , 45 , 90, 135◦). Nato za vsak piksel preveri, ali je njegova magnituda gradienta maksimalna v smeri gradienta v primerjavi z dvema sosednjima pikseloma v tej smeri. Le tisti pikseli, ki izpolnjujejo ta pogoj, so oznaˇceni kot potencialni robovi (vrednost 1), medtem ko vsi ostali pikseli dobijo vrednost 0. 86 Poglavje 6. Robustno iskanje objektov Rešitev lahko zapišemo: 1 def nonMaximaSuppression( iGAbs, iGPhi ): 2 # pretvori orientacijo gradienta na interval 0-2pi 3 iGPhi =iGPhi.flatten(); 4 iGPhi[iGPhi<0] = iGPhi[iGPhi<0] + np.pi; 5 6 # pretvori zvezni kot v indeks enega izmed štirih kvadrantov 7 distPi0 = np.minimum(iGPhi,np.abs(iGPhi-np.pi)); 8 distPi45 = np.abs(iGPhi-np.pi/4); 9 distPi90 = np.abs(iGPhi-np.pi/2); 10 distPi135 = np.abs(iGPhi-3*np.pi/4); 11 minIdx = np.argmin(np.vstack((distPi0, distPi45, distPi90, distPi135)), axis=0) 12 iGPhi = np.reshape(minIdx,iGAbs.shape) 13 14 # poišˇ ci maksimalne vrednosti gradienta 15 oEdge = np.zeros_like(iGAbs) 16 dy, dx = oEdge.shape 17 for y in range(1,dy-1): 18 for x in range(1,dx-1): 19 if iGPhi[y,x] == 0: 20 if iGAbs[y,x]>iGAbs[y,x-1] and iGAbs[y,x]>iGAbs[y,x+1]: 21 oEdge[y,x] = 1; 22 elif iGPhi[y,x] == 1: 23 if iGAbs[y,x]>iGAbs[y-1,x-1] and iGAbs[y,x]>iGAbs[y+1,x+1]: 24 oEdge[y,x] = 1; 25 elif iGPhi[y,x] == 2: 26 if iGAbs[y,x]>iGAbs[y-1,x] and iGAbs[y,x]>iGAbs[y+1,x]: 27 oEdge[y,x] = 1; 28 elif iGPhi[y,x] == 3: 29 if iGAbs[y,x]>iGAbs[y-1,x+1] and iGAbs[y,x]>iGAbs[y+1,x-1]: 30 oEdge[y,x] = 1; 31 32 return oEdge 33 34 # preizkus funkcije 35 oGAbs, oGPhi = smoothImageGradientAbs(iImageG, 1.0) 36 oEdge = nonMaximaSuppression(oGAbs, oGPhi) 37 showImage(oEdge, ’Slika robov po odstranjevanju nemaksimalnih vrednosti’) Ta postopek ohranja samo najmoˇcnejše robove v sliki, hkrati pa odstrani šibkejše odzive in šum, kar izboljša kvaliteto detekcije robov. Rezultat prikazuje slika 6.10. Vaja 6.7 Napišite funkcijo za povezovanje robov vhodni binarni sliki robov iEdge: 1 def connectEdge( iEdge, iGAbs, iThreshold): 2 # Tu napišite kodo funkcije 3 return oEdge kjer je iGAbs slika magnitude gradienta, iThreshold pa dvovrstiˇcni vektor z zgornjim in spodnjim pragom za detekcijo izrazitih in neizrazitih robov. Funkcija vrne binarno sliko preˇcišˇcenih in povezanih robov oEdge enakih dimenzij kot je vhodna binarna slika iEdge. ■ Funkcija connectEdge implementira naslednji kljuˇcni korak Cannyjevega detektorja robov -6.6 Vaje z rešitvami 87 Slika 6.10: Prikaz binarne slike robov po odstranitvi toˇck z nemaksimalno vrednostjo magnitude v smeri gradienta. povezovanje robov z uporabo histereze. Najprej normalizira magnitudo gradienta na interval [0, 1], saj je tako nastavitev spodnjega in gornjega praga standardizirana. Nato loˇci robove na izrazite (nad zgornjim pragom) in neizrazite (med spodnjim in zgornjim pragom). Iterativno povezuje neizrazite robove z izrazitimi, ˇce so v njihovi 3 × 3 sosešˇcini. Postopek se iterativno ponavlja, dokler se množici izrazitih in neizrazitih robov ne stabilizirati. Rešitev zapišemo: 1 def connectEdge( iEdge, iGAbs, iThreshold): 2 # upragovanje po magnitudi gradienta s histerezo oz. dvojnim pragom 3 iGAbs = iGAbs / np.max(iGAbs[iEdge>0]); # normaliziramo med 0 in 1 4 iGAbs[iGAbs>1] = 1; 5 # vrne seznam neizrazitih (weak) in izrazitih (strong) robov 6 edgeWeakIdx = np.nonzero(iEdge*(iGAbs>iThreshold[0])*(iGAbs<=iThreshold[1])) 7 edgeStrongIdx = np.nonzero(iEdge*(iGAbs>iThreshold[1])) 8 9 # povezovanje neizrazitih z izrazitimi robovi 10 oEdge = np.zeros_like(iEdge) 11 oEdge[edgeStrongIdx] = 1; 12 yxl = np.asarray(edgeWeakIdx) 13 oEdgeP = np.zeros_like(oEdge) 14 15 iterN = 0 16 while np.sum(np.abs(oEdge-oEdgeP)) > 0: 17 iterN = iterN + 1 18 print(iterN) 19 20 oEdgeP = np.copy(oEdge) 21 22 for i in range(yxl.shape[1]): 23 x = yxl[1,i] 24 y = yxl[0,i] 25 26 if oEdge[y,x] > 0: 27 continue 28 29 if x > 0 and y > 0 and y < oEdge.shape[0]-1 and x < oEdge.shape[1]-1: 30 31 gx, gy = np.meshgrid((x-1,x,x+1), (y-1,y,y+1), sparse=False) 32 gx = gx.flatten() 33 gy = gy.flatten() 34 88 Poglavje 6. Robustno iskanje objektov 35 if np.sum(oEdge[gy, gx] == 1) > 0: 36 oEdge[y,x] = 1; 37 38 return oEdge 39 40 # preizkus funkcije 41 oGAbs, oGPhi = smoothImageGradientAbs(iImageG, 1.0) 42 oEdge = nonMaximaSuppression(oGAbs, oGPhi) 43 oEdge = connectEdge( oEdge, oGAbs, [0.15, 0.3]) 44 showImage(oEdge, ’Slika robov po upragovanju s histerezo in povezovanju robov’) Ta pristop omogoˇca ohranjanje kontinuitete robov, hkrati pa zatira šum in šibke nepovezane odzive, kar vodi k robustni detekciji pomembnih robov v sliki. Rezultat prikazuje slika 6.11. Slika 6.11: Prikaz binarne slike robov 6.10 po upragovanju s histerezo in povezovanju robov. Vaja 6.8 . Napišite funkcijo za detekcijo robov v vhodni sivinski sliki iImage z uporabo Cannyjevega algoritma: 1 def edgeCanny( iImage, iSigma, iThreshold=0 ): 2 # Tu napišite kodo funkcije 3 return oEdge kjer je iThreshold prag, s katerim doloˇcimo prave robove slik. Funkcija naj uporabi funkciji smoothImageGradientAbs in nonMaximaSuppression, ki jima dodamo upragovljanje na naˇcin, da ohranimo samo tiste detektirane robove z odstranjenimi nemaksimalnimi vrednostmi in magnitudo 1. odvoda oGAbs veˇcjo od iThreshold. Sliko magnitude 1. odvoda predhodno normalizirajte na interval med 0 in 1 glede na minimalno in maksimalno vrednost. Primerjajte sliko robov za razliˇcne vrednosti iSigma in iThreshold. ■ Funkcija povezuje funkcije, ki smo jih napisali pri prejšnjih vajah 6.5, 6.6 in 6.7. Rešitev zapišemo: 1 def edgeCanny( iImage, iThreshold, iSigma ): 2 oGAbs, oGPhi = smoothImageGradientAbs(iImage,iSigma) 3 oEdge = nonMaximaSuppression(oGAbs, oGPhi) 4 oEdge = connectEdge( oEdge, oGAbs, iThreshold) 5 6 return oEdge 7 6.6 Vaje z rešitvami 89 8 # preizkus funkcije 9 iThreshold = [0.15, 0.3] 10 oEdge = edgeCanny(iImageG, iThreshold, 1) 11 showImage(oEdge, ’Slika robov po upragovljanju’) Rezultat prikazuje slika 6.11. 6.6.4 Zaznavanje premic s Houghovo preslikavo Vaja 6.9 S pomoˇcjo Houghove preslikave za parametriˇcni model premice, ki je že imple-mentirana v knjižnici OpenCV, poišˇcite vse premice, ki jih doloˇcajo robovi na sliki slika3.jpg (podobno kot slika 6.11). Implementacija Houghove preslikave sama izvede izloˇcanje robov iz dane slike, le ustrezne parametre je potrebno podati: 1 lines = HoughLines( iImage, rho, theta, threshold ) kjer polje lines vsebuje pare parametrov r in ϕ, ki doloˇcata položaj premice na sliki. Prikažite vse premice na sliki. ■ Rešitev zastavimo tako, da najprej naložimo vhodno sliko, zaznamo robove, nato pa z funkcijo cv.HoughLines() zaznamo premice, ki potekajo skozi ravne robove v sliki. Parametri v funkciji cv.HoughLines() ◦ doloˇcajo loˇcljivost akumulatorja v polarne koordinate (0.5 pikslov za ρ in 0 . 5 za θ) ter prag za število glasov (60; robovi vsaj dolžine 60 pikslov). Za vsako zaznano ˇcrto v polarne koordinate (ρ , θ) koda izraˇcuna dve toˇcki na ˇcrti v kartezijskem koordinatnem sistemu, ki sta oddaljeni 1000 pikslov od osnovne toˇcke (x0, y0). Koordinate teh toˇck pretvori v celoštevilske vrednosti in na sliko nariše rdeˇco ˇcrto debeline 1 piksel. Na koncu pretvori barvni prostor iz BGR v RGB za pravilen prikaz. 1 img = cv.imread(’slika3.jpg’) 2 edges = cv.Canny(imgG, 75, 151) # implementacija v OpenCV 3 lines = cv.HoughLines(edges, 0.5, 0.5*np.pi/180, 60) 4 for line in lines: 5 for rho, theta in line: 6 a = np.cos(theta) 7 b = np.sin(theta) 8 x0 = a*rho 9 y0 = b*rho 10 x1 = int(x0 + 1000*(-b)) 11 y1 = int(y0 + 1000*a) 12 x2 = int(x0 - 1000*(-b)) 13 y2 = int(y0 - 1000*a) 14 15 cv.line(img, (x1, y1), (x2, y2), (0, 0, 255), 1) 16 17 rgb = cv.cvtColor(iImage, cv.COLOR_BGR2RGB) 18 19 showImage(rgb) Rezultat prikazuje slika 6.12. 6.6.5 Konvolucijske nevronske mreže 90 Poglavje 6. Robustno iskanje objektov Slika 6.12: Prikaz premic, ki jih zaznamo s pomoˇcjo Houghove preslikave na podlagi binarne slike robov 6.10. Vaja 6.10 Konvolucijske nevronske mreže se uporabljajo tudi za razpoznavo objektov v sliki. Model GoogleNet je konvolucijska nevronska mreža (CNN), ki izvaja klasifikacijo slik na podlagi nabora podatkov ImageNet z 1000 razliˇcnimi razredi. Delovanje poteka v treh glavnih fazah: 1. Predobdelava slike: Vhodna slika se preoblikuje v blob format z dimenzijami 224x224 pikslov in normalizira z odštevanjem srednjih vrednosti (104, 117, 123) za vsak barvni kanal. Ta korak zagotavlja, da so vhodni podatki primerljivi s podatki, na katerih je bil model uˇcén. 2. Napovedovanje: Model vrne vektor verjetnosti (preds) dolžine 1000, kjer vsak element predstavlja verjetnost, da vhodna slika pripada doloˇcenemu razredu iz ImageNet nabora. 3. Interpretacija rezultatov: Izhodne vrednosti so: • preds[0]: Vektor verjetnosti za vse razrede (1000 vrednosti) • idxs: Indeksi najverjetnejših razredov, urejeni padajoˇce • classes[idx]: ˇ Cloveku berljiva oznaka razreda • preds[0][idx] * 100: Verjetnost v procentih Že nauˇceno nevronsko mrežo GoogLeNet v knjižnici OpenCV uporabite na danih oz. svojih slikah in preverite njeno natanˇcnost razpoznavanja objektov. ■ Rešitev lahko zapišemo: 1 # uvoz potrebnih knjižnic 2 import numpy as np 3 import time 4 import cv2 5 6 # nastavitve poti do vhodnih datotek 7 args = {"image": "slika6.png", 8 "prototxt": "bvlc_googlenet.prototxt", 9 "model": "bvlc_googlenet.caffemodel", 10 "labels": "synset_words.txt"} 11 12 # naloži vhodno sliko z diska 13 image = cv2.imread(args["image"]) 14 15 # naloži oznake razredov iz datoteke 16 rows = open(args["labels"]).read().strip().split("\n") 17 classes = [r[r.find(" ") + 1:].split(",")[0] for r in rows] 18 19 # nevronska mreža zahteva fiksne dimenzije vhodne slike (224x224 pikslov) 6.6 Vaje z rešitvami 91 20 # in normalizacijo z odštevanjem povpreˇ cnih vrednosti (104, 117, 123) 21 # po izvedbi tega ukaza ima "blob" obliko (1, 3, 224, 224) 22 blob = cv2.dnn.blobFromImage(image, 1, (224, 224), (104, 117, 123)) 23 24 # naloži prednauˇ cen model z diska 25 print("[INFO] nalagam model...") 26 net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"]) 27 28 # nastavi blob kot vhod mreže in izvedi klasifikacijo 29 net.setInput(blob) 30 start = time.time() 31 preds = net.forward() 32 end = time.time() 33 print("[INFO] klasifikacija je trajala {:.5} sekund".format(end - start)) 34 35 # uredi indekse verjetnosti v padajoˇ cem vrstnem redu in izberi top-5 napovedi 36 idxs = np.argsort(preds[0])[::-1][:5] 37 38 # prikaži top-5 napovedi 39 for (i, idx) in enumerate(idxs): 40 # nariši najboljšo napoved na sliko 41 if i == 0: 42 text = "Oznaka: {}, {:.2f}%".format(classes[idx], 43 preds[0][idx] * 100) 44 cv2.putText(image, text, (5, 25), cv2.FONT_HERSHEY_SIMPLEX, 45 0.7, (0, 0, 255), 2) 46 47 # izpiši napovedano oznako in verjetnost 48 print("[INFO] {}. oznaka: {}, verjetnost: {:.5}".format(i + 1, 49 classes[idx], preds[0][idx])) 50 51 # prikaži rezultate na sliki 52 cv2.imshow("Slika", image) 53 cv2.waitKey(0) Model vrne naslednje izhodne vrednosti: • Verjetnost (preds[0][idx]): kaže stopnjo zaupanja modela, da slika pripada doloˇcenemu razredu. Vrednosti so med 0 (niˇc verjetno) in 1 (popolnoma verjetno). Obiˇcajno upoštevamo samo vrednosti nad 0.5 (50%). • Oznaka razreda (classes[idx]): besedilni opis razreda (npr. "orel", "avtomobil"). Te oznake se naložijo iz datoteke synset_words.txt in so vnaprej doloˇcene z nauˇcenim modelom. • ˇ Cas klasifikacije: ˇcas v sekundah, ki ga model potrebuje za obdelavo slike, je kljuˇcen indikator zmogljivosti za realnoˇcasovne aplikacije. Model za dano vhodno sliko na izhodu razvrsti oznake glede na padajoˇco verjetnost in izpiše pet najbolj verjetnih oznak ter pripadajoˇce verjetnosti: [INFO] nalagam model... [INFO] klasifikacija je trajala 0.039151 sekund [INFO] 1. oznaka: bald eagle, verjetnost: 0.99997 [INFO] 2. oznaka: kite, verjetnost: 3.4535e-05 [INFO] 3. oznaka: vulture, verjetnost: 1.6599e-07 [INFO] 4. oznaka: hornbill, verjetnost: 2.8287e-08 [INFO] 5. oznaka: albatross, verjetnost: 2.4403e-08 92 Poglavje 6. Robustno iskanje objektov Slika se prikaže v novem oknu in oznaka z najvišjo verjetnostjo se izpišeta kot prikazujejo primeri na sliki 6.13. Slika 6.13: Prikaz zaznave objektov z GoogLeNet z izpisom oznake in pripadajoˇce verjetnosti. 6.7 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.2: To je moj odgovor. . 6.7.1 Gradient sivinske slike Naloga 6.1 2 2 2 2 2 Izraˇcunajte druge odvode sivinske slike f ( x , y ) : ∂ f ( x , y ) / ∂ x , ∂ f ( x , y ) / ∂ y , ∂ f (x,y)/ ∂ 2 x ∂ y in ∂ f (x, y)/∂ y∂ x. Prikažite te odvode kot sivinske slike. 6.7.2 Harrisov detektor oglišˇ c Naloga 6.2 Razširite funkcijo cornerHarris() iz vaje 6.4 tako, da bo imela dodaten vhodni parameter iMinDist. Parameter iMinDist naj doloˇca minimalno razdaljo med oglišˇci v slikovnih elementih. Preˇcistite seznam oglišˇc tako, da bodo oglišˇca med seboj oddaljena vsaj za razdaljo iMinDist, pri ˇcemer ohranite oglišˇca z veˇcjim odzivom Harris detektorja. Izrišite sivinsko sliko z vrisanimi detektiranimi oglišˇci, in sicer za vrednost iMinDist = 0 in iMinDist = 20. Naloga 6.3 Uporabite OpenCV funkcijo cornerHarris za izraˇcun odziva Harrisovega detektorja oglišˇc na sliki slika1.jpg: 1 oQH = cornerHarris( iImage, blockSize, ksize, k ) kjer je k obˇcutljivost detektorja oglišˇc (κ), blockSize pa standardna deviacija Gaussove funkcije za glajenje odziva. Funkcija vrne sliko oQH, ki predstavlja odziv detektorja oglišˇc. Preizkusite delovanje funkcije za razliˇcne vrednosti parametrov k in blockSize. Uporabite upragovljanje za doloˇcitev obmoˇcij visokih odzivov. Primerjajte dobljene rezultate s funkcijo , ki ste jo implementirali pri vaji 6.4. 6.7 Naloge in vprašanja 93 Naloga 6.4 Detekcijo oglišˇc obiˇcajno kombiniramo z razliˇcnimi strategijami preˇcišˇcevanja sez-nama oglišˇc, kar implementira naslednja funkcija v knjižnici OpenCV: 1 pts = goodFeaturesToTrack( iImage, maxCorners, qualityLevel, 2 minDistance, None, None, blockSize, 3 useHarrisDetector, k ) kjer vrnjeno Polje pts vsebuje koordinate detektiranih oslonilnih toˇck oz. oglišˇc. Funkcija goodFeaturesToTrack() iz knjižnice OpenCV implementira algoritem za detekcijo robustnih oglišˇc, ki temelji na Shi-Tomasijevem ali Harrisovem detektorju oslonilnih toˇck oziroma oglišˇc, v vhodni sivinski sliki. Izbere oglišˇca z najveˇcjo lastno vrednostjo v izraˇcunu strukturnih tenzorjev, kar zagotavlja robustno detekcijo oglišˇc, ki so optimalni za aplikacijo sledenja, ipd. Kljuˇcni parametri funkcije vkljuˇcujejo maksimalno število toˇck, minimalno kakovost in minimalno evklidsko razdaljo med toˇckami. Algoritem je še posebej uporaben za inicializacijo algoritmov kot je Lukas-Kanade algoritem sledenja (glej poglavje 11) ali za izbor znaˇcilnih toˇck pri strukturi iz gibanja (glej poglavje 12), saj zagotavlja dobro prostorsko porazdelitev stabilnih oslonilnih toˇck (oz. oglišˇc) po celotni vhodni sliki. Preuˇcite dokumentacijo funkcije in ustrezno nastavite parametre tako, da boste dobili seznam oglišˇc kot pri vaji 6.4. Primerjajte rezultat tudi z rezultatom in naloge 6.3. Naloga 6.5 Z drugaˇcno kriterijsko funkcijo, kot jo za analizo lastnih vrednosti λ1 in λ2 matrike M, λ1 > λ2, uporablja Harrisov detektor oglišˇc, lahko išˇcemo, detektiramo ali poudarjamo podolgovate strukture v sliki. Ena od tovrstnih funkcij je linearna anizotropiˇcnost: λ1 − λ2 QLA = , (6.10) λ1 + λ2 + β kjer je β > 0 poljubna konstanta. Napišite funkcijo za poudarjanje podolgovatih struktur v vhodni sivinski sliki iImage: 1 def enhanceLinear( iImage, iSigma, iBeta ): 2 # Tu napišite python kodo funkcije 3 return oQLA kjer je iSigma standardna deviacija Gaussove funkcije za glajenje elementov matrike M, iBeta pa poljubna nenegativna konstanta. Nesortirane lastne vrednosti Harrisovega detektorja lahko dobite z OpenCV funkcijo cornerEigenValsAndVecs. Naložite barvno RGB sliko slika5.png, jo pretvorite v sivinsko sliko in nelinearno skalirajte z vrednostjo γ = 2. Uporabite funkcijo enhanceLinear() za poudarjanje cest na satelitski sliki tako, da doloˇcite optimalne vrednosti parametrov iSigma in iBeta. Prikažite po vašem optimalen odziv oQLA s poudarjenimi podolgovatimi strukturami in izpišite izbrani vrednosti iSigma in iBeta. Z upragovljanjem odziva izlušˇcite masko cest in jih na barvni sliki pobarvajte z rdeˇco barvo. S primerno izbiro parametrov lahko Harrisov detektor uporabimo tudi za zaznavanje okroglih struktur. Na isti sivinski in nelinearno skalirani sliki s funkcijo cornerHarris izlušˇcite obmoˇcja objektov in obmoˇcja brez rastja. Pridobljeni odziv prikažite, njegovo razmejitev pa uporabite na barvni sliki za obarvanje teh obmoˇcij z modro barvo. Ena izmed možnih rešitev je prikazana na sliki 6.14. 6.7.3 Cannyev detektor robov Naloga 6.6 Zgornji in spodnji prag za povezovanje robov pri Canny-jevem detektorju robov lahko avtomatsko izraˇcunamo na podlagi statistiˇcne analize sivin v sliki. Napišite funkcijo za detekcijo robov: 94 Poglavje 6. Robustno iskanje objektov Slika 6.14: Prikaz rezultata filtriranja za poudarjanja podolgovatih struktur v slikah, ki temelji na linearni anizotropiˇcnosti. 1 def canny( iImage, thr_type ): 2 # Tu napišite python kodo funkcije 3 return oEdge ki avtomatsko izraˇcuna spodnji in zgornji prag za povezovanje robov in ju nato uporabi za detekcijo s Cannyjevim detektorjem. Z uporabo vhodne spremenljivke thr_type loˇcite med dvema naˇcinoma doloˇcanja pragov: T L = max (0,(1 − σ)m), TL = TO/2, T H = min (255, (1 + σ )m), (6.11) TH = TO, (6.12) kjer je m mediana sivin v sliki, σ = 1/3 in T O je prag pridobljen z Otsu upragovljanjem. Otsu upragovljanje avtomatsko doloˇci prag, ki najbolje razmeji sliko na dva dela. Funkcijo poišˇcite v knjižnici OpenCV . Na treh lastno poiskanih slikah, ki imajo razliˇcne zahtevnosti doloˇcanja robov, primerjajte oba avtomatska naˇcina izbire z roˇcno izbranima pragovoma, ki najbolje izlušˇcita rob. Ko-mentirajte rezultate in uspešnost avtomatskih postopkov. Slike poimenujte kot canny_###_#.png, kjer ### zamenjajte z zadnjimi tremi številkami vaše vpisne številke in # z zaporednim številom med 1 in 3. Naloga 6.7 Uporabite funkcijo v knjižnici OpenCV za detekcijo robov v vhodni sivinski sliki iImage z uporabo Cannyjevega algoritma: 1 edges = Canny( iImage, threshold1, threshold2 ) kjer praga threshold1 in threshold2 doloˇcata obmoˇcja povezovanja robov. Preizkusite razliˇcne vrednosti pragov na slikah slika1.jpg, slika2.jpg in slika3.jpg. Primerjajte delovanje funkcije v primerjavi z implementacijo iz vaje 6.8. 6.7.4 Zaznavanje krogov s Houghovo preslikavo Naloga 6.8 Na vaji 6.9 smo si pogledali Houghovo transformacijo za detekcijo premic v sliki. Na podoben naˇcin lahko detektiramo tudi okrogle strukture na sliki. Naložite sliko slika4.jpg barvnih kroglic, jo gladite z Gaussovim filtrom iz knjižnice OpenCV z velikosto jedra 3 ×3, ter nato uporabite OpenCV funkcijo HoughCircles za detekcijo središˇc in radijev krožnic. Kot rezultat detekcije na originalno sliko superponirajte narisane krožnice (funkcija circle) detektiranih kroglic. Število detektiranih kroglic tudi izpišite. Na podlagi barvnega tona (iz HSV prostora) središˇca krožnice avtomatsko preštejte število kroglic vsake barve. Opozorilo: funkcija HoughCircles že sama uporabi Cannyjev detektor robov, pragova pa podate kot parametra param1 in param2, zato je 6.7 Naloge in vprašanja 95 vhodna slika v funkcijo sivinska slika in ne slika robov kot je bilo to na vajah. Pravilno izbiro pragov pa vseeno poišˇcite s klicem funkcije Canny in prikazom njenega rezultata. Primerjajte rezultat s sliko 6.15. Slika 6.15: Prikaz rezultata funkcije HoughCircles nad sliko barvnih kroglic slika4.jpg. 6.7.5 Konvolucijske nevronske mreže Naloga 6.9 Dodatno testirajte uspešnost zaznavanja konvolucijskih nevronskih mrež. Poišˇcite 5 slik na katerih je postopek zaznave uspešen in 5 slik kjer je zaznava objekta neuspešna. Anal-izirajte in primerjajte verjetnosti, ki napovedujejo zanesljivost napovedi, med obema množicama slik. Poroˇcajte o ugotovitvah. Slike poimenujte kot cnn_object_detection_group_###_#.png, kjer group zamenjajte glede na množico failed ali success, ### zamenjajte z zadnjimi tremi številkami vaše vpisne številke ter # z ustreznim zaporednim številom med 1 in 5. Naloga 6.10 Skripto iz datoteke deep_learning_with_opencv.py pretvorite v funkcijo: 1 def detect_object_with_cnn( params ): 2 # Tu napišite python kodo funkcije 3 return predict_labels, predict_vals Vhodni parameter params naj bo dictionary parametrov za detekcijo (trenutna spremenljivka args). Funkcija naj vrne seznama petih najverjetnejših opisov objekta slike in pripadajoˇce verjetnosti (po želji lahko oboje vrnete tudi v kakšni drugi obliki, npr. v eni spremeljivki tipa dict). Napisano funkcijo uvozite v glavno skripto, kjer boste izvajali domaˇce naloge. Tam z uporabo for zanke iterirajte skozi seznam poti do vaših 10-ih slik, ustrezno spremenite vhodni dictionary in pokliˇcete funkcijo. V trenutni skripti odstranite zadnji dve vrstici za prikaz slike in ˇcakanje (waitKey), saj sta za namene naloge nepotrebna. 7. Prostorske preslikave in poravnave 7.1 Geometrijske preslikave S prostorskimi ali geometrijskimi preslikavami 2 3 2 3 T : R → R oz. T : R → R preslikamo slikovne elemente 2D slik (x, y) oz. 3D slik (x,y, z) na nove lokacije (u,v) oz. (u,v, w), njihove sivinske vrednosti pa pri tem ohranimo. Na ta naˇcin lahko izvedemo poveˇcavo oz. pomanjšavo (skaliranje), premik (translacijo), zasuk (rotacijo), pa tudi številne druge linearne in nelinearne geometrijske preslikave slik. Poljubno geometrijsko preslikavo za 2D oz. 3D sliko zapišemo kot: (u,v) = T (x,y) oz. (u, v,w) = T (x,y,z) . Najbolj splošna linearna geometrijska preslikava je afina preslikava, ki omogoˇca poljubno skalir-anje, translacijo, rotacijo in strig. Afina preslikava je v 2D doloˇcena s 6, v 3D pa z 12 parametri:       x 11 u       a a 12 a 13 t x u a11 a12 tx x    y 21 v a a 22 a 23 t   y     y 21 v = a a 22 t   y  oz.   =        z 31 32 33    w a a a t z 1 0 0 1 1 | 1 0 0 0 1 1 {z } T | {z } a f ina Ta f ina kjer parametri t x,ty in tz doloˇcajo translacijo v x, y in z smeri slike, parametri ai j pa skaliranje, rotacijo in strig. Matriko za afino preslikavo lahko sestavimo z zaporednim množenjem homogenih matrik posameznih elementarnih preslikav v želenem vrstnem redu, naprimer: Ta f ina = Ttrans Tstrig Trot Tskal . V praksi se pogosto uporabljata toga preslikava, ki jo dobimo z združevanjem rotacijske in transla-cijske elementarne preslikave in podobnostna preslikava, ki jo dobimo z združevanjem toge preslikave in skaliranja. Elementarne preslikave in njihova izražava s homogenimi transforma- cijskim matrikami so prikazane v sliki 7.1 zgoraj. 98 Poglavje 7. Prostorske preslikave in poravnave Slika 7.1: Elementarne preslikave in njihova izražava s homogenimi transformacijskim matrikami (zgoraj) in vpliv interpolacijske in aproksimacijske poravnave (spodaj). 7.2 Poravnava pripadajoˇ cih parov toˇ ck Geometrijska poravnava dveh oblik je proces iskanja optimalne geometrijske preslikave T , ki obliki preslika tako, da se iste strukture oz. znaˇcilnice oblike nahajajo v enakem ali ˇcim bolj »podobnem« položaju. Znaˇcilnice 2D oz. 3D oblik so pogosto kar oblaki toˇck, zato se za mero podobnosti med dvema oblikama pogosto uporablja povpreˇcna kvadratna Evklidska razdalja med pripadajoˇcimi oz. korespondenˇcnimi toˇckami. Na osnovi množice K parov korespondenˇcnih toˇck lahko doloˇcimo preslikavo T tako, da se preslikane toˇcke referenˇcne oblike T (xi, yi) ˇcim bolje prilegajo toˇckam vhodne oblike (ui, vi) in obratno: (ui, vi) ↔ T (xi, yi) oz. (xi, yi ) ↔ T −1(ui, vi ) . Glede na število parov korespondenˇcnih toˇck in vrsto preslikave lahko toˇcke lahko preslikamo tako, da se popolnoma prekrivajo, tj. T (xi, yi) = (ui, vi), kar imenujemo interpolacijska poravnava, ali pa tako, da se toˇcke le približno prekrijejo, tj. T (xi, yi) ≈ (ui, vi), kar imenujemo aproksimacijska poravnava. Prikaz interpolacijske in aproksimacijske poravnave na poravnavo pripadajoˇcih parov toˇck je prikazan na sliki 7.1 spodaj. Afino interpolacijsko poravnavo med 2D oblikami lahko enoliˇcno doloˇcimo, ˇce poznamo natanko tri pare (K = 3) nekolinearnih toˇck (u i, vi) ↔ (xi,yi), poravnavo med 3D oblikami pa ˇce poznamo natanko šest parov (K = 6) nekolinearnih toˇck. V 2D afino interpolacijsko poravnavo T med tremi pari toˇck zapišemo kot:      − 1 a11 a12 tx u1 u2 u3 x1 x2 x3 T =  a21 a22 ty  =  v1 v2 v3   y1 y2 y3  . 0 0 1 1 1 1 1 1 1 7.3 Vaje z rešitvami 99 Afino aproksimacijsko poravnavo lahko med 2D oblikami izvedemo, kadar poznamo veˇc kot tri pare (K > 3), med 3D oblikami pa veˇc kot šest parov (K > 6) korespondenˇcnih toˇck. V tem primeru dobimo predoloˇcen sistem enaˇcb, zato korespondenˇcne toˇcke lahko poravnamo le približno (aproksimacijsko). To storimo tako, da minimiziramo povpreˇcno kvadratno Evklidsko razdaljo med korespondenˇcnimi toˇckami: 1 K T ( ∑ Σ = 2 2 ∥ ( u v ) − , x , y ) ∥ . K i i i i i= 1 Optimalne vrednosti neznanih parametrov preslikave dobimo tako, da odvode povpreˇcne kvadratne Evklidske razdalje 2 Σ po vseh parametrih postavimo na niˇc in dobimo sistem enaˇcb za reševanje. Za 2D afino aproksimacijsko poravnavo dobimo naslednji sistem enaˇcb:       xx xy x 0 0 0 a 11 ux       xy yy y 0 0 0 a 12 uy       x y 1 0 0 0 t u    x    = ,       0 0 0 xx xy x a vx       21       0 0 0 xy yy y a vy       22 0 0 0 x y 1 t y v | {z } | {z } | {z } Pxy t puv kjer elementi s ˇcrto oznaˇcujejo povpreˇcne vrednosti 1 K 1 K vx = u K ∑ = i x oz. x = i i 1 K ∑ x i= i . Zgornji sistem lahko zapišemo v matriˇcni obliki kot P 1 xy uv t = p, kjer vektor t predstavlja vektorski zapis parametrov afine preslikave T . Parametre afine preslikave dobimo z reševanjem sistema t −1 = P p xy uv . Pri poravnavi oblik korespondenˇcne toˇcke obiˇcajno niso vnaprej znane in jih je potrebno doloˇciti v procesu poravnave oblik. Ena od uveljavljenih metod za doloˇcanje korespondenˇcnih toˇck je 1 metoda iterativno najbližje toˇcke, pri kateri v vsakem koraku k doloˇcimo korespondenˇcne toˇcke z iskanjem najbližjih parov toˇck (ui, vi) ↔ (xi , yi) in nato izraˇcunamo interpolacijsko ali aproksimacijsko poravnavo Tk med temi pari toˇck. Korespondenˇcne pare toˇck doloˇcimo tako, da vsaka toˇcka (ui, vi) pripada kveˇcjemu enemu paru (xi, yi). Premiˇcno obliko preslikamo s Tk in ponovimo korak. Postopek ponavljamo dokler je relativna sprememba ∥Tk − I ∥∞ > ε oz. dokler ne izvedemo maksimalno število korakov kmax. ˇ Ce so preslikave Tk linearne, potem lahko preslikavo med oblikama po k-tem koraku doloˇcimo z zaporednim matriˇcnim množenjem vmesnih preslikav: T = Tk Tk−1 Tk−2 · · · T1 . 7.3 Vaje z rešitvami Za izvedbo vaje boste uporabili zbirko 2D oblik v datoteki letala.npy, ki vsebuje seznam tipa list s 30 razliˇcnimi oblikami in ki jih v okolje Python naložite z ukazom numpy.load(). Oblike so podane z (x,y) koordinatami v numpy polju. Pri laboratorijski vaji boste napisali funkcije za 2D afino preslikavo, interpolacijsko in aproksimacijsko poravnavo ter funkcijo za poravnavo oblik z metodo iterativno najbližje toˇcke. Koda vaje je dostopna na Git repozitoriju. Za izvedbo vaje bomo potrebovali naslednje knjižnice, ki jih ustrezno uvozimo: 1 import scipy.ndimage as ni 2 import matplotlib.pyplot as plt 3 import matplotlib.cm as cm 4 import numpy as np 1 ang. iterative closest point (ICP) 100 Poglavje 7. Prostorske preslikave in poravnave 5 import scipy as sp Vaja 7.1 Napišite funkcijo, ki ustvari 3 × 3 matriko poljubne 2D afine preslikave: 1 def transAffine2D( iScale, iTrans, iRot, iShear ): 2 # Tu napišite python kodo funkcije 3 return oMat2D kjer je iScale dvovrstiˇcni vektor s parametroma skale kx, ky, iTrans dvovrstiˇcni vektor s parametroma translacije tx , ty, iRot kot rotacije α v stopinjah in iShear dvovrstiˇcni vektor s parametroma striga gx, gy. Funkcija vrne homogeno matriko oMat2D, ki ima dimenzije 3 × 3. Preverite delovanje funkcije tako, da toˇcke izbrane oblike preslikate s poljubnimi parametri striga, rotacije, translacije in skaliranja, nato pa še s poljubno 2D afino preslikavo. Pri tem poskrbite, da bodo toˇcke zapisane v homogenih koordinatah. Vhodne in preslikane toˇcke oblike prikažite z ukazom matplotlib.pylab.plot(). ■ Funkcijo transAffine2D( iScale, iTrans, iRot, iShear ), ki ustvari poljubno 2D afino preslikavo na osnovi kompozituma štirih elementarnih preslikav (striga, rotacije, translacije in skaliranja) lahko zapišemo: 1 def transAffine2D( iScale=(1, 1), iTrans=(0, 0), iRot=0, iShear=(0, 0) ): 2 """Funkcija za poljubno 2D afino preslikavo""" 3 iRot = iRot * np.pi / 180 4 oMatScale = np.matrix( ((iScale[0],0,0),(0,iScale[1],0),(0,0,1)) ) 5 oMatTrans = np.matrix( ((1,0,iTrans[0]),(0,1,iTrans[1]),(0,0,1)) ) 6 oMatRot = np.matrix( ((np.cos(iRot),-np.sin(iRot),0),\ 7 (np.sin(iRot),np.cos(iRot),0),(0,0,1)) ) 8 oMatShear = np.matrix( ((1,iShear[0],0),(iShear[1],1,0),(0,0,1)) ) 9 # ustvari izhodno matriko 10 oMat2D = oMatTrans * oMatShear * oMatRot * oMatScale 11 return oMat2D Funkcija najprej pretvori kot rotacije iz stopinj v radiane, nato ustvari posamezne transform- acijske matrike v homogenih koordinatah. Vsaka preslikava je predstavljena z lastno matriko: oMatScale za skaliranje, oMatTrans za translacijo, oMatRot za rotacijo in oMatShear za strig. Funkcija nato te matrike združi z množenjem v vrstnem redu, ki najprej uporabi skaliranje, nato rotacijo, sledi strig in na koncu translacija. Konˇcna homogena 3 ×3 matrika oMat2D združuje vse te preslikave v eno samo preslikavo, ki jo lahko uporabimo za transformacijo 2D toˇck ali slik. Kljuˇcna prednost te implementacije v primerjavi s splošno afino matriko je modularnost - vsako elementarno transformacijo lahko nastavimo neodvisno od drugih. Še bolj splošna implementacija bi lahko vkljuˇcevala definicijo vrstnega reda elementarnih transformacij. Preizkus funkcije opravimo z nalaganjem in prikazovanjem originalne in preslikane oblike na naslednji naˇcin: 1 # pripravimo pomožno funkcijo za dodajanje homogene koordinate 2 def addHomo( iPts ): 3 iPts = np.hstack( (iPts, np.ones((iPts.shape[0],1))) ) 4 return iPts 5 6 # naložimo testno obliko (v novejših verzijah je potrebno nastaviti parameter allow_pickle=True) 7 letala = np.load(’letala.npy’, allow_pickle=True) 7.3 Vaje z rešitvami 101 8 let = np.matrix(letala[0]) 9 # dodamo homogeno koordinato 10 let = addHomo( let ) 11 # prikažemo originalno obliko 12 plt.plot(let[::5,0],let[::5,1],’ob’,markersize=5.0) 13 # preslikamo obliko in jo prikažemo 14 oMat2D = transAffine2D( iScale=(1, 1), iTrans=(10, 0), iRot=10, iShear=(0.1, 0) ) 15 let_t = (oMat2D * let.transpose()).transpose() 16 plt.plot(let_t[::5,0],let_t[::5,1],’or’,markersize=5.0) Rezultat prikazuje slika 7.2. Slika 7.2: Prikaz originalne oblike (modra) in s poljubno afino preslikavo preslikane razliˇcice (rdeˇca). Vaja 7.2 Napišite funkcijo, ki med pari korespondenˇcnih toˇck doloˇci 2D afino interpolacijsko poravnavo: 1 def mapAffineInterp2D( iPtsRef, iPtsMov ): 2 # Tu napišite python kodo funkcije 3 return oMat2D kjer je iPtsRef matrika s koordinatami referenˇcnih toˇck, iPtsMov pa matrika s koordinatami premiˇcnih toˇck. Obe matriki sta dimenzij 3 × 2. V funkciji lahko inverz 3× 3 matrike izraˇcunamo analitiˇcno ali z ukazom numpy.linalg.inv(). Funkcija vrne 2D afino preslikavo v obliki homogene matrike oMat2D, ki ima dimenzije 3 × 3. Preverite delovanje funkcije tako, da tri toˇcke izbrane oblike preslikate s poljubnimi parametri 2D afine preslikave in preverite ali funkcija vrne preslikavo v oMat2D, ki je enaka vaši 2D afini preslikavi. ■ 1 def mapAffineInterp2D( iPtsRef, iPtsMov ): 2 """Afina interpolacijska poravnava""" 3 # pretvori v matrike 4 iPtsRef = np.asmatrix( iPtsRef ) 5 iPtsMov = np.asmatrix( iPtsMov ) 6 # po potrebi dodaj homogeno koordinato 7 if iPtsRef.shape[1]!=3: 8 iPtsRef = addHomo(iPtsRef) 9 if iPtsMov.shape[1]!=3: 102 Poglavje 7. Prostorske preslikave in poravnave 10 iPtsMov = addHomo(iPtsMov) 11 # afina interpolacija 12 iPtsRef = iPtsRef.transpose() 13 iPtsMov = iPtsMov.transpose() 14 oMat2D = iPtsMov * np.linalg.pinv( iPtsRef ) 15 return oMat2D 16 17 # preizkus funkcije 18 letala = np.load(’letala.npy’, allow_pickle=True) 19 let = np.matrix(letala[0]) 20 # doloci tri toˇ cke za poravnavo 21 idx = np.floor(rand(3) * let.shape[0]).astype(’uint32’) 22 iPtsRef = let[idx, : ] 23 # preslikaj toˇ cke s poljubno afino preslikavo 24 oMat2D = transAffine2D( iScale=(1, 1), iTrans=(10, 22), iRot=10, iShear=(0.1, 0) ) 25 iPtsMov = (oMat2D * addHomo(iPtsRef).transpose()).transpose() 26 # doloˇ ci matriko preslikave z afino interpolacijo 27 oMat2D_t = mapAffineInterp2D( iPtsRef, iPtsMov ) 28 # izpiši matriki in primerjavo 29 print(’Afina preslikava:\n’, oMat2D) 30 print(’Doloˇ cena z interpolacijsko poravnavo 3 parov toˇ ck:\n’, oMat2D_t) 31 print(’Razlika v elementih matrike:\n’, oMat2D-oMat2D_t) Funkcija vrne naslednji rezultat (lahko se malenkost razlikuje glede na nakljuˇcno izbiro treh korespondenˇcnih toˇck v vrstici 21 gornje Python kode): Afina preslikava: [[ 1.00217257 -0.0751674 10. ] [ 0.17364818 0.98480775 22. ] [ 0. 0. 1. ]] Doloˇ cena z interpolacijsko poravnavo 3 parov toˇ ck: [[ 1.00217257 -0.0751674 10. ] [ 0.17364818 0.98480775 22. ] [ 0. 0. 1. ]] Razlika v elementih matrike: [[ 6.66133815e-16 -1.38777878e-16 5.34683409e-13] [ 9.07607323e-15 1.44328993e-15 -3.77653464e-12] [ 0.00000000e+00 0.00000000e+00 8.88178420e-16]] V splošnem so razlike v elementih matrike razlike zelo majhne, reda 10−16, kar je priˇcakovan red numeriˇcne napake 32-bitnega zapisa z decimalno vejico. Vaja 7.3 Napišite funkcijo, ki med pari korespondenˇcnih toˇck doloˇci 2D afino aproksimacijsko poravnavo: 1 def mapAffineApprox2D( iPtsRef, iPtsMov ): 2 # Tu napišite python kodo funkcije 3 return oMat2D kjer je iPtsRef matrika s koordinatami referenˇcnih toˇck, iPtsMov pa matrika s koordinatami premiˇcnih toˇck. Obe matriki sta dimenzij K × 2, kjer je K ≥ 3. Funkcija vrne 2D afino preslikavo v obliki homogene matrike oMat2D, ki ima dimenzije 3 × 3. Preverite delovanje funkcije tako, 7.3 Vaje z rešitvami 103 da vse toˇcke izbrane oblike preslikate s poljubnimi parametri 2D afine preslikave in preverite ali funkcija vrne preslikavo v oMat2D, ki je enaka vaši 2D afini preslikavi. ■ Funkcija mapAffineApprox2D implementira afino aproksimacijsko poravnavo med dvema množicama 2D toˇck z uporabo minimizacije po metodi najmanjših kvadratov. Algoritem najprej pretvori vhodne toˇcke v matriˇcno obliko in jim doda homogeno koordinato, ˇce ta še ni prisotna. Nato reši sistem linearnih enaˇcb z uporabo normalne enaˇcbe T T − 1 X = BA ( AA ), kjer A predstavlja referenˇcne toˇcke in B premikajoˇce se toˇcke. Ta pristop je matematiˇcno ekvivalenten izraˇcunu psev-doinverza, vendar bolj numeriˇcno stabilen za slabo pogojene probleme. Rezultat je homogena 3 × 3 transformacijska matrika, ki minimizira vsoto kvadratov razdalj med transformiranimi referenˇcnimi toˇckami in ciljnimi toˇckami. Metoda je še posebej primerna za primere, ko je število toˇck veˇcje od minimalno potrebnega za doloˇcitev afine transformacije. Rešitev napišemo: 1 def mapAffineApprox2D( iPtsRef, iPtsMov ): 2 """Afina aproksimacijska poravnava""" 3 # pretvori v matrike 4 iPtsRef = np.asmatrix( iPtsRef ).astype(’float’) 5 iPtsMov = np.asmatrix( iPtsMov ).astype(’float’) 6 # po potrebi dodaj homogeno koordinato 7 if iPtsRef.shape[1]!=3: 8 iPtsRef = addHomo(iPtsRef) 9 if iPtsMov.shape[1]!=3: 10 iPtsMov = addHomo(iPtsMov) 11 # afina aproksimacija (s psevdoinverzom) 12 iPtsRef = iPtsRef.transpose() 13 iPtsMov = iPtsMov.transpose() 14 oMat2D = iPtsMov * iPtsRef.transpose() * np.linalg.inv( iPtsRef * iPtsRef.transpose() ) 15 return oMat2D Delovanje funkcije preverimo na podoben naˇcin kot pri prejšnji vaji 7.2 in dobimo naslednji rezultat: Afina preslikava: [[ 1.00217257 -0.1503348 10. ] [ 0.17364818 1.96961551 22. ] [ 0. 0. 1. ]] Doloˇ cena z aproksimacijsko poravnavo 3 parov toˇ ck: [[ 1.00217257e+00 -1.50334805e-01 1.00000000e+01] [ 1.73648178e-01 1.96961551e+00 2.20000000e+01] [ 2.16840434e-19 0.00000000e+00 1.00000000e+00]] Razlika v elementih matrike: [[-2.88657986e-15 1.67088565e-14 -2.24531505e-12] [-1.42941214e-14 -4.19664303e-14 1.71418435e-11] [-2.16840434e-19 0.00000000e+00 1.77635684e-15]] V splošnem so razlike v elementih matrike razlike zelo majhne, reda 10−16, kar je priˇcakovan red numeriˇcne napake 32-bitnega zapisa z decimalno vejico. Vaja 7.4 Napišite funkcijo, ki poišˇce pare korespondenˇcnih toˇck med dvema množicama toˇck: 1 def findCorrespondingPoints( iPtsRef, iPtsMov ): 2 # Tu napišite python kodo funkcije 104 Poglavje 7. Prostorske preslikave in poravnave 3 return iPtsRef_t, iPtsMov_t kjer je iPtsRef matrika s koordinatami referenˇcnih toˇck, iPtsMov pa matrika s koordinatami premiˇcnih toˇck. Matriki imata dimenziji K R × 2 in KM × 2. Funkcija vrne sortirane koordinate iPtsRef_t in iPtsMov_t tako da istoležeˇce vrstice predstavljajo pare korespondenˇcnih toˇck. ■ Funkcija najprej inicializira matriko razdalj, kjer izraˇcuna evklidsko razdaljo med vsemi pari toˇck v množicah iPtsRef in iPtsMov. Nato iterativno izbira najbližje pare toˇck po principu najmanjše razdalje, pri ˇcemer vsak izbran par izloˇci iz nadaljnjega iskanja, kar zagotavlja bijektivno preslikavo (tj. ena toˇcka pripada natanko eni toˇcki). Ta postopek se ponavlja, dokler niso vsi možni pari porabljeni. Funkcija na koncu vrne le tiste toˇcke, za katere je bilo mogoˇce najti pripadajoˇco toˇck (uporabljamo tudi izraz korespondenco), kar omogoˇca robustno obravnavo primerov, ko množici toˇck nista enako veliki. Rešitev napišemo: 1 def findCorrespondingPoints( iPtsRef, iPtsMov ): 2 """Poišˇ ci korespondence kot najbližje toˇ cke""" 3 # inicializiraj polje indeksov 4 idxPair = -np.ones( (iPtsRef.shape[0],1) ).astype(’int32’) 5 idxDist = np.ones( (iPtsRef.shape[0],iPtsMov.shape[0]) ) 6 for i in range(iPtsRef.shape[0]): 7 for j in range(iPtsMov.shape[0]): 8 idxDist[i,j] = np.sum((iPtsRef[i,:] - iPtsMov[j,:])**2) 9 # doloci bijektivno preslikavo 10 while not np.all(idxDist==np.inf): 11 i, j = np.where(idxDist==np.min(idxDist)) 12 idxPair[i[0]] = j[0] 13 idxDist[i[0],:] = np.inf 14 idxDist[:,j[0]] = np.inf 15 # doloci pare tock 16 idxValid, idxNotValid = np.where(idxPair>=0) 17 idxValid = np.array( idxValid ).flatten() 18 iPtsRef_t = iPtsRef[idxValid,:] 19 iPtsMov_t = iPtsMov[idxPair[idxValid].flatten(),:] 20 return iPtsRef_t, iPtsMov_t 21 22 # preizkus funkcije 23 d = 10 # faktor decimacije toˇ ck 24 let = np.matrix(letala[0]) 25 iPtsRef = let[::d] 26 let = np.matrix(letala[12]) 27 iPtsMov = let[::d] 28 # klic funkcije 29 iPtsRef_t, iPtsMov_t = findCorrespondingPoints( iPtsRef, iPtsMov ) 30 31 # izris oblik 32 plt.plot(iPtsRef_t[:,0], iPtsRef_t[:,1], ’ob’) 33 plt.plot(iPtsMov_t[:,0], iPtsMov_t[:,1], ’or’) 34 35 # izris povezav med pripadajoˇ cimi pari toˇ ck 36 for (ref_pt, mov_pt) in zip(iPtsRef_t, iPtsMov_t): 37 plt.plot([ref_pt[0], mov_pt[0]], 38 [ref_pt[1], mov_pt[1]], 39 ’g-’, linewidth=0.5, alpha=0.7) 7.3 Vaje z rešitvami 105 Rezultat iskanja pripadajoˇcih parov toˇck med dvema oblika prikazuje slika 7.3. Slika 7.3: Prikaz oblike letala pri indeksu 0 (modre pike) in oblike letala pri indeksu 12 (rdeˇce pike) ter pripadajoˇci pari toˇck (zelene ˇcrte). Vaja 7.5 Napišite funkcijo za poravnavo dveh oblik z metodo iterativno najbližje toˇcke (ICP): 1 def alignICP2D( iPtsRef, iPtsMov, iEps, iMaxIter ): 2 # Tu napišite python kodo funkcije 3 return oMat2D, oErr kjer sta iPtsRef in iPtsMov matriki s koordinatami referenˇcnih in premiˇcnih toˇck. Matriki imata dimenziji KR × 2 in KM × 2. iEps in iMaxIter doloˇcata pogoje za zaustavitev postopka, kjer je iEps najmanjša sprememba parametrov preslikave, iMaxIter pa maksimalno število korakov kmax. V funkciji uporabite 2D afino aproksimacijsko poravnavo, funkcija pa naj vrne optimalno 2D afino preslikavo med referenˇcnimi in premiˇcnimi toˇckami v obliki homogene matrike oMat2D in vektor oErr velikosti kmax × 2 1, ki podaja napako Σ med korespondenˇcnimi toˇckami v vsakem koraku metode ICP. Preverite pravilnost delovanja funkcije tako, da vse toˇcke izbrane oblike preslikate s poljub- nimi parametri 2D afine preslikave in preverite ali funkcija vrne preslikavo v oMat2D, ki je podobna dani vaši 2D afini preslikavi in da je konˇcna napaka oErr manjša od zaˇcetne napake in približno enaka 0. Naložite referenˇcne in premiˇcne toˇcke dveh poljubnih, a razliˇcnih oblik iz dane zbirke in preverite, da funkcija obliki ustrezno poravna. ■ Algoritem ICP iterativno izvaja tri kljuˇcne korake: (1) iskanje korespondenˇcnih parov toˇck s funkcijo findCorrespondingPoints, (2) izraˇcun optimalne afine preslikave s funkcijo mapAffine-Approx2D in (3) aplikacija preslikave na premikajoˇce se toˇcke. Postopek se ponavlja dokler napaka poravnave ne pade pod prag iEps ali dokler ne dosežemo najveˇcjega števila iteracij iMaxIter. V vsaki iteraciji algoritem beleži napako poravnave in akumulira transformacijske matrike. Konˇcna transformacija je kompozitum vseh delnih transformacij, kar omogoˇca postopno izboljšanje poravnave. Rešitev lahko napišemo: 1 def alignICP( iPtsRef, iPtsMov, iEps=1e-3, iMaxIter=50 ): 2 """Postopek iterativno najblizje tocke""" 3 # pretvori v polje 4 iPtsRef = np.asarray( iPtsRef ).astype(’float’) 5 iPtsMov = np.asarray( iPtsMov ).astype(’float’) 6 iPtsRef = iPtsRef[:,:2] 7 iPtsMov = iPtsMov[:,:2] 8 # inicializiraj izhodne parametre 106 Poglavje 7. Prostorske preslikave in poravnave 9 curMat = []; oErr = []; iCurIter = 0 10 # zacni iterativni postopek 11 while True: 12 # poisci korespondencne pare tock 13 iPtsRef_t, iPtsMov_t = findCorrespondingPoints( iPtsRef, iPtsMov ) 14 # izracunaj napako 15 oErr.append( np.sqrt(np.sum( (iPtsRef_t - iPtsMov_t)**2)) / iPtsRef_t.shape[0]) 16 # doloci afino aproksimacijsko preslikavo 17 oMat2D = mapAffineApprox2D( iPtsRef_t, iPtsMov_t, iUsePseudoInv=True ) 18 oMat2D = np.linalg.inv(oMat2D) 19 curMat.append( oMat2D ) 20 # posodobi premicne tocke 21 iPtsMov = (oMat2D * addHomo(iPtsMov).transpose()).transpose() 22 iPtsMov = np.asarray( iPtsMov[:,:2] ) 23 # preveri kontrolne parametre 24 iCurIter = iCurIter + 1 25 dMat = np.abs(oMat2D - transAffine2D()) 26 if iCurIter>iMaxIter or np.all(dMat 27 break 28 print(’iter = ’ + str(iCurIter) + ’, err = ’ + str(oErr[-1])) 29 # doloci kompozitum preslikav 30 oMat = transAffine2D() 31 for i in range(len(curMat)): 32 oMat = curMat[i] * oMat 33 return oMat, oErr 34 35 # preizkus funkcije 36 d = 20 # faktor decimacije 37 letala = np.load(’letala.npy’, allow_pickle=True) 38 let = np.matrix(letala[0]) 39 iPtsRef = let[::d] # referenˇ cna oblika 40 let = np.matrix(letala[12]) 41 iPtsMov = let[::d] # premiˇ cna oblika 42 # doloˇ ci matriko preslikave 43 oMat2D_t, oErr = alignICP( iPtsRef, iPtsMov, iEps=1e-12, iMaxIter=50, iVerbose=1 ) 44 # preslikaj premiˇ cne toˇ cke 45 iPtsMov_t = (oMat2D_t * addHomo(iPtsMov).transpose()).transpose() 46 # nariši referenˇ cne, premiˇ cne in preslikane premiˇ cne toˇ cke 47 plt.plot(iPtsMov[:,0],iPtsMov[:,1],’or’) 48 plt.plot(iPtsRef[:,0],iPtsRef[:,1],’ob’) 49 plt.plot(iPtsMov_t[:,0],iPtsMov_t[:,1],’og’) Preizkus funkcije smo opravili na dveh razliˇcnih oblikah letal, tj. oblik letal pri indeksih 0 in 12. Rezultat ICP poravnave premiˇcne oblike na referenˇcno prikazuje slika 7.4, iz katere je razvidno dobro ujemanje referenˇcne in premiˇcne oblike (rdeˇce in zelene toˇcke) po poravnavi z ICP algoritmom. 7.4 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti 7.4 Naloge in vprašanja 107 Slika 7.4: Prikaz oblike letala pri indeksu 0 (modre pike) in oblike letala pri indeksu 12 (rdeˇce pike) ter pripadaoˇci pari toˇck (zelene pike). rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.2: To je moj odgovor. . Naloga 7.1 Izvedite poravnavo vseh 2D oblik z indeksi [1, 29] v datoteki letala.npy na obliko z indeksom 0 z metodo iterativno najbližje toˇcke. Izrišite v eno sliko vse 2D oblike v zaˇcetni legi, v drugo sliko pa vse oblike v poravnani legi. Za vsako poravnavo izraˇcunajte napako pred poravnavo in po njej. V eno sliko narišite histogram vseh napak v zaˇcetni legi, v drugo sliko pa histogram napak v poravnani legi. Izpišite vrednost napake in indeks oblike, pri kateri dobite najveˇcjo in najmanjšo napako v zaˇcetni legi. Obliki tudi izrišite, zraven pa z drugo barvo dorišite še referenˇcno obliko. Enako naredite tudi za oblike po poravnavi. Naloga 7.2 Naložite poljubno obliko iz dane zbirke in naˇcrtajte ustrezno zaporedje 2D linearnih preslikav tako, da se bo oblika rotirala okoli svojega središˇca. Prikažite delovanje preslikave tako, da obliko rotirate od 0 ◦ ◦ − 360 s korakom 5 ter hkrati prikazujete toˇcke oblike v istem prikaznem oknu. Za vsak korak z metodo iterativno najbližje toˇcke poravnajte rotirano obliko v nerotirano obliko (kot 0 ◦). Ali je poravnava uspešna za vse rotacije? Pokažite tako, da za vsak korak izraˇcunate napako kot povpreˇcno razdaljo med istoindeksiranimi toˇckami v referenˇcni in poravnani obliki. Napako prikažite na grafu v odvisnosti od kota rotacije. Naloga 7.3 Toga poravnava, ki se pogosto uporablja v praksi, je vedno aproksimacijska, saj zahtevano minimalno število parov korespondenˇcnih toˇck vodi do predoloˇcenega sistema enaˇcb – dobimo namreˇc veˇc enaˇcb kot pa je neznanih parametrov. Za poravnavo 2D oblik potrebujemo K ≥ 2 korespondenˇcnih toˇck, preslikavo pa doloˇcimo z minimizacijo povpreˇcne kvadratno Evklidske razdalje med toˇckami: 1 K Σ = 2 2 2 ( x cos − y ∑ i α i sinα + tx − ui ) + (xi sin α + yi cos α + ty − vi ) , K i=1 ki jo odvajamo po parametrih tx, ty in α in odvode postavimo na niˇc. Rešitev sistema enaˇcb je: uy − vx − u y + v x α = −atan , ux + vy − u x − v y tx = u − x cos α + y sin α , ty = v − x sin α − y cos α . Napišite funkcijo, ki med pari korespondenˇcnih toˇck doloˇci 2D togo aproksimacijsko poravnavo: 108 Poglavje 7. Prostorske preslikave in poravnave 1 def mapRigid2D( iPtsRef, iPtsMov ): 2 # Tu napišite python kodo funkcije 3 return oMat2D kjer sta iPtsRef in iPtsMov matriki s koordinatami referenˇcnih in premiˇcnih toˇck. Obe matriki imata dimenzije K × 2, pri ˇcemer je K ≥ 2. Funkcija naj vrne 2D togo preslikavo v obliki homogene matrike oMat2D z dimenzijami 3 ×3. Prikažite delovanje funkcije tako, da vse toˇcke izbrane oblike preslikate s poljubnimi parametri 2D toge preslikave in preverite ali funkcija vrne preslikavo v oMat2D, ki je enaka vaši 2D togi preslikavi. Naloga 7.4 Izvedite poravnavo vseh 2D oblik z indeksi [1, 29] v datoteki letala.npy na obliko z indeksom 0 z metodo iterativno najbližje toˇcke, pri tem pa uporabite 2D togo aproksimacijsko poravnavo. Izrišite v eno sliko vse 2D oblike v zaˇcetni legi, v drugo sliko pa vse oblike v poravnani legi. Primerjajte dobljeni rezultat z rezultatom, ki ste ga dobili pri nalogi 7.1. Naloga 7.5 Napišite funkcijo, ki ustvari 4 × 4 matriko poljubne 3D afine preslikave: 1 def transAffine3D( iScale, iTrans, iRot, iShear ): 2 # Tu napišite python kodo funkcije 3 return oMat3D kjer je iScale trivrstiˇcni vektor s parametri skale kx, ky in kz, iTrans trivrstiˇcni vektor s para-metri translacije tx, ty in tz, iRot trivrstiˇcni vektor s koti rotacije α, β in γ v stopinjah in iShear šestvrstiˇcni vektor s parametri striga gxy , gxz, gxy, gzx, gzy, gyz. Funkcija vrne homogeno matriko oMat3D 2 , ki ima dimenzije 4 × 4. Preverite delovanje funkcije na 3D oblikah v datoteki glave.npy tako, da toˇcke izbrane oblike preslikate s poljubnimi parametri striga, rotacije, translacije in skalir-anja, nato pa še s poljubno 3D afino preslikavo. Vhodne in preslikane toˇcke oblike prikažite z dano funkcijo renderSurface.py. Izbrano obliko rotirajte za 180◦ okrog svojega središˇca in okrog poljubne osi. Prikažite glavo pred in po poravnavi. Naloga 7.6 Napišite funkcijo za poravnavo dveh 3D oblik z metodo iterativno najbližje toˇcke (ICP): 1 def alignICP3D( iPtsRef, iPtsMov, iEps, iMaxIter ): 2 # Tu napišite python kodo funkcije 3 return oMat3D, oErr kjer sta iPtsRef in iPtsMov matriki s koordinatami referenˇcnih in premiˇcnih toˇck. Matriki imata dimenziji KR × 3 in KM × 3. iEps in iMaxIter doloˇcata pogoje za zaustavitev postopka, kjer je iEps najmanjša sprememba parametrov preslikave, iMaxIter pa maksimalno število korakov k max. V funkciji uporabite 3D afino aproksimacijsko poravnavo, funkcija pa naj vrne optimalno 3D afino preslikavo med referenˇcnimi in premiˇcnimi toˇckami v obliki homogene matrike oMat3D in vektor oErr 2 , ki podaja napako Σ med korespondenˇcnimi toˇckami v vsakem koraku metode ICP. Preverite pravilnost delovanja funkcije tako, da vse toˇcke izbrane oblike preslikate s poljubnimi parametri 3D afine preslikave in preverite ali funkcija vrne preslikavo v oMat3D, ki je podobna dani vaši 3D afini preslikavi in da je konˇcna napaka oErr manjša od zaˇcetne napake in približno enaka 0. Naložite referenˇcne in premiˇcne toˇcke dveh poljubnih, a razliˇcnih oblik iz dane zbirke glav in preverite, da funkcija obliki ustrezno poravna. 2Vseh pet 3D oblik naložite z ukazom glave = numpy.load(’glave.npy’) in so zapisane v seznamu tipa list, toˇcke prve oblike pa dobite z ukazom x=glave[0]. 7.4 Naloge in vprašanja 109 Izvedite poravnavo vseh 3D oblik [1,4] na obliko z indeksom 0. Izpišite najmanjšo in najveˇcjo vrednost napake oErr pred poravnavno in po njej. Naloga 7.7 Na podlagi enaˇcb v dodatku B implementirajte funkcijo za 3D togo aproksimacijsko poravnavo korespondenˇcnih parov toˇck. Preizkusite delovanje v funkciji alignICP3D( iPtsRef, iPtsMov, iEps, iMaxIter ), pri ˇcemer razširite funkcijo tako, da bo z vhodnim parametrom iTransformType možna izbira preslikave, in sicer ’rigid’ ali ’affine’. Izvedite poravnavo vseh 3D oblik [1,4] na obliko z indeksom 0. Izpišite najmanjšo in najveˇcjo vrednost napake oErr pred poravnavno in po njej. Katera preslikava vrne manjšo napako? Naloga 7.8 3 V pythonu naložite knjižnico pycpd, ki vsebuje poravnavo z metodo koherentnega premika toˇck (CPD; angl. coherent point drift) [14]. Izvedite poravnavo vseh 2D oblik [1,29] v datoteki letala.npy na obliko z indeksom 0 z metodo CPD, enkrat z afino poravnavo (affine_ registration), drugiˇc pa še s togo (rigid_registration). Za vsako od poravnav izrišite v eno sliko vse 2D oblike v poravnani legi. Za obe poravnavi izraˇcunajte porazdelitev napake po poravnavi oblik in ju primerjajte s porazdelitvijo, ki jo dobite pri metodi ICP. Napako izraˇcunajte kot povpreˇcno razdaljo med korespondenˇcnimi toˇckami. 3 https://github.com/siavashk/pycpd 110 Poglavje 7. Prostorske preslikave in poravnave Rezultati nekaterih nalog: Preslikava 2D oblik na referenˇcno obliko (modra ˇcrta) Brez poravnave Z afino aproksimacijo Poljubne afine preslikave 3D oblike II Aplikacije robotskega vida 8 Geometrijska kalibracija kamere . . . 113 8.1 Geometrijski kalibri 8.2 Model preslikave prostor-kamera 8.3 Prileganje modela s slikami kalibra 8.4 Vaje z rešitvami 8.5 Naloge in vprašanja 9 Doloˇ canje poze objektov v prostoru . 127 9.1 Projekcija iz 3D v 2D 9.2 Mera podobnosti 9.3 Optimizacijski postopek 9.4 Vaje z rešitvami 9.5 Naloge in vprašanja 10 Rekonstrukcija 3D oblik . . . . . . . . . . . . . 149 10.1 Fotometriˇ cni stereo in Lambertov model 10.2 Izraˇ cun albeda in normal na površino objekta 10.3 Rekonstrukcija 3D oblike 10.4 Vaje z rešitvami 10.5 Naloge in vprašanja 11 Sledenje in analiza gibanja . . . . . . . . . 161 11.1 Parametrizacija preslikave 11.2 Linearizacija nelinearne kriterijske funkcije 11.3 Zaprto-zanˇ cna posodobitev preslikave 11.4 Piramidna implementacija 11.5 Vaje z rešitvami 11.6 Naloge in vprašanja 12 Lokalizacija in mapiranje okolja . . . . . 175 12.1 Algoritem stereo vida 12.2 Lokalizacija in mapiranje okolja 12.3 Vaje z rešitvami 12.4 Naloge in vprašanja 13 Vizualna kontrola kakovosti . . . . . . . . . 195 13.1 Sistem za razpoznavanje vzorcev 13.2 Vrednotenje sistemov odloˇ canja 13.3 Vaje z rešitvami 13.4 Naloge in vprašanja 8. Geometrijska kalibracija kamere S postopkom geometrijske kalibracije slikovnega sistema zagotovimo enakomerno absolutno velikost slikovnega elementa, ki je sicer v splošnem lahko odvisna od položaja v sliki1 in dodatno popaˇcena zaradi optiˇcnih aberacij, med katerimi so najpogostejše radialne distorzije tipa sodˇcek in blazinica. Po geometrijski kalibraciji slikovnega sistema in preslikavi slike v metriˇcni prostor lahko izvajamo meritve dimenzij, plošˇcin in volumnov objektov na slikah v absolutnih metriˇcnih enotah (npr. mm, mm2 3 , mm). 8.1 Geometrijski kalibri Za geometrijsko kalibracijo 2D slikovnih sistemov uporabljamo ravninske kalibre z enostavn-imi, a dobro definiranimi vzorci, kot so šahovnica, mreža tankih ˇcrt ali diskretno polje toˇck. Geometrijska kalibracija vkljuˇcuje poravnavo vzorcev zajete f (u,v) in referenˇcne g(x, y) slike kalib-racijskega objekta. Uporabimo lahko celotno slikovno informacijo ali pa izlušˇcimo korespondenˇcne pare toˇck referenˇcne in kalibracijske slike ter z njimi doloˇcimo parametre preslikave oz. poravnavo tako, da se preslikane toˇcke referenˇcne slike ˇcim bolje prekrivajo s korespondenˇcnimi toˇckami kalibracijske slike in obratno, kot prikazuje slika 8.1. 8.2 Model preslikave prostor-kamera V praksi se za geometrijsko kalibracijo slikovnih sistemov obiˇcajno uporablja projektivna geomet-rijska preslikava, ki je za preslikavo med dvema ravninama doloˇcena z 8 parametri (a 11, a12, a21, a22, tx, ty, px, py ) in ki jo zapišemo z nehomogeno 3 × 3 matriko, za odpravo popaˇcenj tipa sodˇcek in blazinica pa Brownov model radialnih distorzij, ki je doloˇcen s centrom (uc, vc) in utežmi radialnih funkcij Ki. Enaˇcbe in vpliv omenjenih preslikav je prikazan na sliki 8.2. Geometrijsko kalibracijo izvedemo tako, da toˇcke v metriˇcnem prostoru s projektivno preslikavo preslikamo v prostor slike in jih nato še popaˇcimo z modelom radialnih distorzij. Ker je Brownov model radialnih distorzij nelinearen, je za doloˇcanje parametrov modela radialnih distorzij potrebno uporabiti op- 1 npr. zaradi vpliva projekcije 114 Poglavje 8. Geometrijska kalibracija kamere Slika 8.1: Kalibracija kamere z ravniskimi kalibri z visoko-kontrastnimi vzorci na osnovi zaznave in poravnave korespondenˇcnih oz. pripadajoˇcih parov toˇck. timizacijski postopek, ki minimizira kriterijsko funkcijo, npr. srednjo kvadratno Evklidsko razdaljo E 2 N 2 = ∑ ∥ y ) u , ) ∥ T T ( , − ( med korespondenˇcnimi toˇckami na kalibru. k =1 radialna pro jektivna k k k k x v Slika 8.2: Model geometrijske preslikave za standardne optiˇcne sisteme s kamero je zaporedn projektivna preslikava in radialna distorzija. 8.3 Prileganje modela s slikami kalibra Za doloˇcanje optimalnih parametrov modela radialnih distorij in 2D projektivne preslikave boste uporabili postopek Nelder-Mead simpleksne optimizacije. S simpleksno optimizacijo lahko poišˇcemo lokalni optimum poljubne funkcije veˇc spremenljivk. Simpleks predstavlja geometrijsko strukturo, s pomoˇcjo katere vzorˇcimo parametriˇcni prostor in je za funkcije dveh spremenljivk f (x,y) predstavljen s tremi oglišˇci oz. s trikotnikom. S simpleksno optimizacijo poljubno funkcijo minimiziramo tako, da oglišˇca z najveˇcjo vrednostjo f (x,y) nadomestimo z novimi oglišˇci. Nova oglišˇca lahko doloˇcimo na štiri naˇcine: 1) zrcaljenje, 2) širjenje, 3) krˇcenje in 4) mnogokratno krˇcenje (slika 8.3). Ko oglišˇca nadomestimo dobimo nov simpleks in poišˇcemo pripadajoˇce vrednosti f (x,y) v novih oglišˇcih. Postopek ponavljamo in ga ustavimo, dokler velikost simpleksa ne pade pod neko minimalno vrednost, položaj lokalnega optimuma pa predstavlja oglišˇce z optimalno vrednostjo f (x, y). 8.4 Vaje z rešitvami Pri vaji boste napisali funkcije za popravek radialnih distorzij in 2D projektivno preslikavo. Z doloˇcanjem oglišˇc na sliki kalibracijskega objekta calibration-object.jpg in njihovo pres-likavo v metriˇcni prostor s poravnavo korespondenˇcnih oglišˇc boste izvedli geometrijsko kalibracijo 8.4 Vaje z rešitvami 115 Slika 8.3: Model geometrijske preslikave za standardne optiˇcne sisteme s kamero je zaporedn projektivna preslikava in radialna distorzija. slikovnega sistema. Nato boste sliko kljunastega merila test-object.jpg, ki je bila zajeta z istim slikovnim sistemom kot slika kalibra, preslikali v metriˇcni prostor in z merjenjem dolžine na kljunastem merilu preverili ali je geometrijska kalibracija pravilna. Koda vaje je dostopna na Git repozitoriju. Najprej uvozimo Python knjižnice in naložimo ter prikažemo sliki za izvedbo vaje: 1 import scipy.ndimage as ni 2 import matplotlib.pyplot as plt 3 import matplotlib.cm as cm 4 import numpy as np 5 import scipy as sp 6 from scipy.interpolate import interpn 7 import PIL.Image as im 8 9 from rvlib import * 10 11 iCalImage = loadImage(’calibration-object.jpg’) 12 iTestImage = loadImage(’test-object.jpg’) 13 14 showImage( iCalImage, ’Kalibracijski objekt’ ) 15 showImage( iTestImage, ’Testni objekt’ ) Zajeti sliki sta prikazani na sliki 8.4. Vaja 8.1 Napišite funkcijo, ki preslika vhodne nepopaˇcene koordinate (u, v) v spremenljivkah iCoorU in iCoorV z Brownovim modelom radialnih distorzij poljubnega reda n: 1 def transRadial( iK, iUc, iVc, iCoorU, iCoorV ): 2 # Tu napišite python kodo funkcije 3 return oCoorUd, oCoorVd kjer je iK vrstiˇcni vektor 1 × n parametrov radialnih distorzij, iUc in iVc pa prestavljata (uc,vc) koordinati centra radialnih distorzij. Funkcija vrne preslikane popaˇcene koordinate toˇck v spremenljivkah iCoorUd in iCoorVd. S pomoˇcjo funkcije numpy.meshgrid() ustvarite diskretno mrežo koordinatnih toˇck in jih 116 Poglavje 8. Geometrijska kalibracija kamere (a) (b) Slika 8.4: Za vajo smo z istim slikovnim sistemom zajeli (a) sliko šahovnice kot kalibracijskega objekta in (b) sliko kljunastega merila. preslikajte s poljubnim Brownovim modelom radialnih distorzij prvega reda (n = 1) ter narišite vhodne (u,v) in preslikane toˇcke (ud, vd). Preverite vpliv položaja centra radialnih distorzij na preslikavo koordinatnih toˇck. Katere vrednosti iK predstavljajo distorzije tipa sodˇcek in katere tipa blazinica? Pri katerih vrednostih parametrov bo ta preslikava identiteta? ■ Funkcija transRadial implementira Brownov model radialnih distorzij za popaˇcenje slikovnih koordinat, ki se uporablja v fotogrametriji in raˇcunalniškem vidu za modeliranje geometrijskih napak objektiva. Najprej funkcija centrira vhodne koordinate glede na optiˇcni center (iUc, iVc) in jih normalizira z deljenjem z najveˇcjim odklonom. Nato izraˇcuna radialno razdaljo 2 R = u2 2 n 2i + v in uporabi polinom radialnih distorzij 1 + d d ∑ κ R koeficienti distorzij i = i κ , kjer so 1 i iz vhodnega vektorja iK. Popaˇcene koordinate se nato denormalizirajo in premaknejo nazaj v originalni koordinatni sistem. Rešitev zapišemo: 1 def transRadial( iK, iUc, iVc, iCoorU, iCoorV ): 2 """Funkcija za preslikavo z Brownovim modelom distorzij""" 3 # preveri vhodne podatke 4 iK = np.array( iK ) 5 iCoorU = np.array( iCoorU ) 6 iCoorV = np.array( iCoorV ) 7 if np.size(iCoorU) != np.size(iCoorV): 8 print(’Stevilo U in V koordinat je razlicno!’) 9 # odstej koodinate centra 10 oCoorUd = iCoorU - iUc; oCoorVd = iCoorV - iVc 11 # pripravi izhodne koordinate 12 sUd = np.max( np.abs( oCoorUd ) ) 13 sVd = np.max( np.abs( oCoorVd ) ) 14 oCoorUd = oCoorUd / sUd 15 oCoorVd = oCoorVd / sVd 16 # preracunaj radialna popacenja 17 R2 = oCoorUd**2.0 + oCoorVd**2.0 18 iK = iK.flatten() 19 oCoorRd = np.ones_like( oCoorUd ) 20 for i in range(iK.size): 21 oCoorRd = oCoorRd + iK[i]*(R2**(i+1)) 22 # izracunaj izhodne koordinate 23 oCoorUd = oCoorUd * oCoorRd * sUd + iUc 24 oCoorVd = oCoorVd * oCoorRd * sVd + iVc 25 return oCoorUd, oCoorVd 26 27 # preizkus funkcije: 8.4 Vaje z rešitvami 117 28 # ustvari mrežo vzroˇ cnih toˇ ck slike 29 iCoorU, iCoorV = np.meshgrid(range(iCalImage.shape[1]), 30 range(iCalImage.shape[0]),\ 31 sparse=False, indexing=’xy’) 32 # preslikaj toˇ cke (iK = -1e-1 in +1e-1, sodˇ cek in blazinica) 33 oCoorUd, oCoorVd = transRadial([1e-1], iCalImage.shape[1]/2.0, 34 iCalImage.shape[0]/2.0,\ 35 iCoorU, iCoorV) 36 37 # prikazi originalne in preslikane tocke 38 plt.close( ’all’ ) 39 s = 20 40 plt.plot(iCoorU[::s,::s].flatten(),iCoorV[::s,::s].flatten(), 41 ’ob’,markersize=2.0) 42 plt.plot(oCoorUd[::s,::s].flatten(),oCoorVd[::s,::s].flatten(), 43 ’or’,markersize=2.0) Rezultat funkcije so popaˇcene koordinate (oCoorUd, oCoorVd), ki upoštevajo nelinearne de- formacije, znaˇcilne za širokokotne objektive in objektive ribje oko (ang. fish-eye). Model omogoˇca popravljanje razliˇcnih vrst distorzij, vkljuˇcno z blagimi barvnimi popaˇcenjimi in ekstremnimi popaˇcenji tipa ribje oko. Obliko in stopnjo popaˇcenja lahko opazimo s preslikavo originalnih vzorˇcnih toˇck zajetih slik s poljubno izbranimi vrednostmi κi , kot prikazuje slika 8.5. (a) (b) Slika 8.5: Vzorˇcne toˇcke zajetih slik z (modre toˇcke) in s funkcijo transRadial() preslikanih rdeˇcih toˇck , za radialne distorzije tipa (a) blazinica (κ1 = 0.1) in (b) sodˇcek (κ1 = −0.1). Vaja 8.2 Napišite funkcijo, ki vhodne koordinate (x, y) v spremenljivkah iCoorX in iCoorY preslika s poljubno 2D projektivno preslikavo: 1 def transProjective2D( iPar, iCoorX, iCoorY ): 2 # Tu napišite python kodo funkcije 3 return oCoorU, oCoorV kjer je iPar vrstiˇcni 1 × 8 vektor s parametri (a11, a12, tx , a21, a22, ty, px , py). Funkcija vrne preslikane koordinate toˇck v spremenljivkah oCoorU in oCoorV. Preverite delovanje funkcije tako, da vzorˇcne toˇcke slike test-object.jpg preslikate s poljubnimi parametri projektivne preslikave in s funkcijo za interpolacijo prvega reda scipy. interpolate.interpn() v preslikanih toˇckah poišˇcete nove sivinske vrednosti. Prikažite preslikano sliko. ■ Funkcija transProjective2D implementira 2D projektivno preslikavo (homografijo), ki 118 Poglavje 8. Geometrijska kalibracija kamere omogoˇca transformacijo koordinat med dvema ravninama z uporabo 8-parametrskega modela. Preslikava uporablja projektivno geometrijo, kjer se vhodne koordinate (x,y) preslikajo v izhodne (u, v) po enaˇcbah: a 11x + a x 12 y + t x a 21 + a y t 22 +y u = , v = (8.1) pxx + pyy + 1 pxx + pyy + 1 kjer parametri predstavljajo elemente vektorja iPar. Kljuˇcna znaˇcilnost te preslikave je ohranjanje ravnih ˇcrt, vendar s spremembo njihovega relativnega razmerja in lege, kar omogoˇca modeliranje perspektivnih deformacij. Rešitev zapišemo: 1 def transProjective2D( iPar, iCoorX, iCoorY ): 2 """Funkcija za projektivno preslikavo""" 3 # preveri vhodne podatke 4 iPar = np.asarray( iPar ) 5 iCoorX = np.asarray( iCoorX ) 6 iCoorY = np.asarray( iCoorY ) 7 if np.size(iCoorX) != np.size(iCoorY): 8 print(’Stevilo X in Y koordinat je razlicno!’) 9 # izvedi projektivno preslikavo 10 oDenom = iPar[6] * iCoorX + iPar[7] * iCoorY + 1 11 oCoorU = iPar[0] * iCoorX + iPar[1] * iCoorY + iPar[2] 12 oCoorV = iPar[3] * iCoorX + iPar[4] * iCoorY + iPar[5] 13 # vrni preslikane tocke 14 return oCoorU/oDenom, oCoorV/oDenom 15 16 # preizkus funkcije: 17 # ustvari diskretno mrežo toˇ ck 18 iCoorX, iCoorY = np.meshgrid(range(iCalImage.shape[1]), 19 range(iCalImage.shape[0]),\ 20 sparse=False, indexing=’xy’) 21 # preslikaj toˇ cke 22 iPar = [1, 0, 0, 0, 1, 0, 0, 0.001]; 23 oCoorU, oCoorV = transProjective2D( iPar, iCoorX, iCoorY ) 24 25 # interpolacija slike 1. reda (bilinearna) 26 iCalImageG = colorToGray( iCalImage ) 27 s = 1; dy,dx = iCalImageG.shape 28 oInterpImageG = interpn((np.arange(dy), np.arange(dx)), iCalImageG, (oCoorV[::s,::s],oCoorU[::s,::s]), method = "linear", bounds_error=False) # interpolacija 0. reda 29 30 # prikaz slike z vzroˇ cnimi toˇ ckami 31 plt.imshow(iCalImage, cmap = cm.Greys_r) # prikazi sliko v novem oknu 32 plt.suptitle(’Originalna slika z vzorˇ cnimi toˇ ckami’) # nastavi naslov slike 33 plt.xlabel(’x’); plt.ylabel(’y’) 34 s = 100; plt.plot(oCoorU[::s,::s].flatten(),oCoorV[::s,::s].flatten(), 35 ’or’,markersize=2.0) 36 37 # prikaz prevzorˇ cene (interpolirane) slike 38 showImage( oInterpImageG, ’Slika po projektivni preslikavi’ ) Funkcija uvodoma preveri konsistentnost vhodnih podatkov, nato izvede matriˇcno množenje v homogenih koordinatah in konˇcno deli z normalizacijskim faktorjem oDenom za pretvorbo nazaj v 8.4 Vaje z rešitvami 119 karteziˇcne koordinate. Takšne preslikave so široko uporabljene v raˇcunalniškem vidu za poravnavo slik, panoramsko zlivanje in 3D rekonstrukcijo. Primer projektivne preslikave prikazuje slika 8.6. (a) (b) Slika 8.6: Prikaz (a) originalne slike z vzorˇcnimi toˇckami (rdeˇce) za projektivno preslikavo in (b) z interpolacijo 1. reda (bilinearna) prevzorˇcena slika. Vaja 8.3 Preizkusite delovanje simpleksne optimizacije s pomoˇcjo Pythonove funkcije scipy. optimize.fmin() tako, da poišˇcete minimum Rosenbrockove funkcije funkcije f (x, y) = 100 2 2 2 ( y − x ) + ( 1 − x ), ki je standardni testni primer za nelinearne optimizacijske algoritme. Pri tem pa uporabite zaˇcetni približek x0 = (1.2, 1). Z nastavljanjem dodatnih parametrov v klicu funkcije fmin(func, x0, ...) nastavite prikaz informacij o iteracijaha b c , maksimalno število iteracij , najmanjšo napako funkcije in najmanjši korakd. Preverite, da je minimum f (x, y) v toˇcki (1,1) in opazujte zaporedje operacij simpleksa. ■ a fmin(..., disp = 1, callback = fname), kjer je fname(X) funkcija za oblikovanje izpisa b fmin(..., maxiter = 100) cfmin(..., ftol = 1e-6) d fmin(..., xtol = 1e-6) V kodi implementiramo kot funkcijo izraz 2 2 2 F ( x , y ) = 100 ( y − x ) + ( 1 − x ). To nato podamo funkciji fmin iz knjižnice SciPy, ki uporablja Nelder-Mead simpleksni algoritem za iskanje minimuma, zaˇcenši z zaˇcetno toˇcko (−5,5). Rešitev zapišemo: 1 def F( arg ): 2 x = arg[0]; y = arg[1] 3 return 100*(y-x**2)**2+(1-x)**2 4 5 Nfeval = 1 6 def callbackF(Xi): 7 global Nfeval 8 print(’{0:4d} {1: 3.6f} {2: 3.6f} {3: 3.6f}’.format( 9 Nfeval, Xi[0], Xi[1], F(Xi))) 10 Nfeval += 1 11 12 # uvozi funkcijo za simpleksno optimizacijo 13 from scipy.optimize import fmin 14 15 # definiraj funkcijo za izpis v posamezni iteraciji 16 print(’{0:4s} {1:9s} {2:9s} {3:9s}’.format( 17 ’Iter’, ’ x’, ’ y’, ’f(X)’)) 18 # zaženi optimizacijo 19 xopt = fmin(func=F, x0=np.array([-5,5]), disp=1, 20 xtol=1e-6, ftol=1e-6, maxiter=1000, callback=callbackF) 120 Poglavje 8. Geometrijska kalibracija kamere 21 22 print(’\nOptimalne vrednosti:\n’, xopt) Kljuˇcni elementi klica funkcije fmin so: • funkcija callbackF, ki vsako iteracijo izpiše številko iteracije, trenutne vrednosti parametrov in vrednost ciljne funkcije, • nastavitve za natanˇcnost (xtol=1e-6, ftol=1e-6) in maksimalno število iteracij (1000), • spremljanje števila evalvacij funkcije s globalno spremenljivko Nfeval. Rosenbrockova funkcija predstavlja težaven optimizacijski problem zaradi ozke in dolge doline, kar zahteva robustne optimizacijske metode. Simpleksni algoritem iterativno prilagaja velikost in obliko simpleksa (geometriˇcne figure z n + 1 oglišˇci v n-dimenzionalnem prostoru), da bi našel minimum funkcije. Podatki o posamezni iteraciji in konˇcne optimalne vrednosti se med in po klicu izpišejo: Iter x y f(X) 1 -4.500000 5.375000 22156.812500 2 -4.250000 5.937500 14729.125000 3 -3.125000 6.468750 1103.954102 4 -2.875000 7.031250 167.383789 5 -2.875000 7.031250 167.383789 6 -2.875000 7.031250 167.383789 7 -2.875000 7.031250 167.383789 8 -2.625000 7.031250 15.118164 ... Current function value: 0.000000 Iterations: 157 Function evaluations: 285 Optimalne vrednosti: [1.00000014 1.00000032] Minimum Rosenbrockove funkcije je torej v toˇcki (x,y) = (1, 1), pri ˇcemer je F(1,1) = 0. Vaja 8.4 Napišite funkcijo za izraˇcun srednje kvadratne Evklidske razdalje med toˇckami (u k,vk) v zajeti sliki, ki so preslikane z modelom radialnih distorzij, in korespondenˇcnimi toˇckami (x k,yk) v metriˇcnem prostoru, ki so preslikane s projektivno preslikavo: 1 N T T ( , ) , )∥ ∑ E 2 2 = ∥ y u x − ( v . N radialna pro jektivna k k k k k =1 Funkcija naj kot prvi parameter vzame vrstiˇcni vektor iPar, ki predstavlja parametre 2D projektivne preslikave in modela radialnih distorzij kot (a a t 11 , t 12 , x , a p 21 , a 22 , y ,x, py, uc, uv, K1, K2, . . ., Kn). Ostali parametri funkcije naj bodo N koordinat toˇck (uk, vk) → iCoorU, iCoorV, in N koordinat toˇck (xk,yk) → iCoorX, iCoorY: 1 def geomCalibErr( iPar, iCoorU, iCoorV, iCoorX, iCoorY ): 2 # Tu napišite python kodo funkcije 3 return oErr2 Funkcija naj izraˇcuna napako med preslikanimi toˇckami 2 E, ki jo vrne kot skalarno spremen-ljivko oErr2. Zaˇcetni približek parametrov (a11, a12, tx, a21, a22, ty) doloˇcite s pomoˇcjo funkcije 8.4 Vaje z rešitvami 121 za afino aproksimacijsko preslikavo mapAffineApprox2D() (Vaja 6). Preizkusite delovanje funkcije tako, da naložite sliko calibration-object.jpg in roˇcno oznaˇci-te vsaj 8 oglišˇc na kalibracijskem objektu s pomoˇcjo funkcije matplotlib.pyplot. ginput(). Za potrebe geometrijske kalibracije ustvarite referenˇcno mrežo toˇck, ki sovpada s položajem ogljišˇc na idealni, ravninski sliki šahovnice. Referenˇcne toˇcke naj ležijo v metriˇcnem prostoru, pri tem pa predpostavite, da ima stranica kvadratnega polja šahovnice dolžino 20 mm. Naprimer, oglišˇce v levem zgornjem kotu šahovnice naj ima koordinati (20, 20). Z uporabo simpleksne optimizacije doloˇcite optimalne parametre 2 iPar tako, da bo napaka E minimalna. Prikažite referenˇcne toˇcke in toˇcke oglišˇc na sliki pred in po preslikavi. ■ Pripravimo najprej funkcijo geomCalibTrans tako, da izvaja geometrijsko kalibracijsko pres- likavo s kombiniranjem projektivne transformacije in radialne distorzije. V vektorju parametrov iPar naj najprej uporabi projektivno preslikavo s parametri iPar[0:8] za transformacijo iz origin-alnih koordinat (x, y) v vmesne koordinate, nato pa popravi radialne distorzije s parametri iPar[8:] (vkljuˇcno s koordinatami optiˇcnega centra (uc,vc ) in koeficienti distorzije κi, i = 1, 2,3, . . .). Funk-cijo geomCalibTrans zapišemo: 1 def geomCalibTrans( iPar, iCoorX, iCoorY ): 2 """Funkcija za preslikavo tock (projektivna+radialne distorzije)""" 3 iParProj = iPar[0:8] 4 iParRad = iPar[8:] 5 # preslikava v prostor slike 6 iCoorUt, iCoorVt = transProjective2D( iParProj, iCoorX, iCoorY ) 7 # korekcija radialnih distorzij 8 iCoorUt, iCoorVt = transRadial(iParRad[2:], iParRad[0], iParRad[1], 9 iCoorUt, iCoorVt) 10 # vrni preslikane tocke 11 return iCoorUt, iCoorVt Funkcija geomCalibErr nato izraˇcuna srednjo kvadratno napako med dejanskimi koordinatami (u, v) in preslikanimi koordinatami (ut ,vt ), kar omogoˇca optimizacijo kalibracijskih parametrov. Rešitev zapišemo kot: 1 def geomCalibErr( iPar, iCoorU, iCoorV, iCoorX, iCoorY): 2 """Funkcija za izracun kalibracijske napake""" 3 # preslikaj tocke z dano preslikavo 4 iCoorUt, iCoorVt = geomCalibTrans( iPar, iCoorX, iCoorY ) 5 #izracun napake poravnave 6 oErr2 = np.mean( (iCoorU-iCoorUt)**2 + (iCoorV-iCoorVt)**2 ) 7 # vrni vrednost napake 8 return oErr2 Ta funkcija se nato uporablja za natanˇcno geometrijsko kalibracijo kamer, kjer je potrebno kompenzirati tako perspektivne deformacije kot tudi nelinearne optiˇcne popaˇcitve. Za namen optimizacije je potrebno doloˇciti pripadajoˇce pare toˇck, tj. toˇcke na modelu kalibra in toˇcke v vhodni sliki kalibra. V prvem primeru moram poznati dimenzije kalibra in polj šahovnice; izhodišˇce (0, 0) postavimo v gornji levi kot in v milimetrih zapišemo koordinate toˇck. V vhodni sliki interaktivno z miško naklikamo toˇcke v istem zaporedju in jih shranimo: 1 # koordinate kalibra v metriˇ cnem prostoru v enoti mm 2 iCoorX = np.array([20,140,260,260,260,140,20,20]) 3 iCoorY = np.array([20,20,20,100,180,180,180,100]) 4 # naklikamo pripadajoˇ ce toˇ cke na kalibru 122 Poglavje 8. Geometrijska kalibracija kamere 5 showImage( colorToGray(iCalImage), ’Slika kalibra’ ) 6 pts = plt.ginput(n=8, timeout=-1) 7 pts = np.array(pts) 8 # koordinate v prostoru slike 9 iCoorU = pts[:,0].flatten() 10 iCoorV = pts[:,1].flatten() Koordinate ustrezno preoblikujemo in uporabo funkcijo za poravnavo pripadajoˇcih parov toˇck z afino aproksimacijo mapAffineApprox2D() iz poglavja 7. S funkcijo geomCalibErr() doloˇcimo napako pred in po afini aproksimaciji: 1 # oblikujemo koordinate v matrike (Nx2) 2 ptsUV = np.vstack( (iCoorU, iCoorV) ).transpose() 3 ptsXY = np.vstack( (iCoorX, iCoorY) ).transpose() 4 # doloci zacetni priblizek parametrov 5 oMatA = mapAffineApprox2D( ptsUV, ptsXY ) 6 # preslikava identiteta 7 Uc = iCalImageG.shape[1]/2 8 Vc = iCalImageG.shape[0]/2 9 iParIdent = np.array([1,0,0,0,1,0,0,0,Uc,Vc,0]) 10 # preslikava z afinim približkom 11 Uc = iCalImageG.shape[1]/2 12 Vc = iCalImageG.shape[0]/2 13 iParAffine = np.array([oMatA[0,0],oMatA[0,1],oMatA[0,2],\ 14 oMatA[1,0],oMatA[1,1],oMatA[1,2],\ 15 0,0,Uc,Vc,0]) 16 # doloˇ ci napako z danimi parametri 17 oErr2ident = geomCalibErr( iParIdent, iCoorU, iCoorV, iCoorX, iCoorY ) 18 oErr2affine = geomCalibErr( iParAffine, iCoorU, iCoorV, iCoorX, iCoorY ) 19 print(’Zaˇ cetna napaka: ’, oErr2ident) 20 print(’Napaka po afini aproksimaciji: ’, oErr2affine) V ukazni vrsti dobimo rezultat: Zaˇ cetna napaka: 708764.720110 Napaka po afini aproksimaciji: 3010.379361 Napaka se po afini aproksimaciji bistveno zmanjša, a jo lahko nadalje zmanjšamo z dodajanjem primernih prostostnih stopenj v poravnavo, ki odražajo fizikalni model popaˇcenja, tj. zaporedni perspektivna preslikava in radialna distozija. Za ta namen minimiziramo funkcijo geomCalibErr() s simpleksno optimizacijo, pri ˇcemer kot zaˇcetni približek uporabimo parametre, ki smo jih pridobili z afino aproksimacijo: 1 # nacrtaj funkcijo za izpisovanje vrednosti parametrov 2 Nfeval = 1 3 def callbackF(Xi): 4 global Nfeval 5 np.set_printoptions(formatter={’float’: ’{: 0.3f}’.format}) 6 formattedline = ’%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f \ 7 %8.3e %8.3e %8.3f %8.3f %8.3e’ % ( tuple(Xi[:]) ) 8 print(’{0:4d} {1} {2: 3.6f}’.format(Nfeval, formattedline, F(Xi))) 9 Nfeval += 1 10 11 # uporabi simpleksno optimizacijo 8.4 Vaje z rešitvami 123 12 Nfeval = 1 13 from scipy.optimize import fmin 14 F = lambda x: geomCalibErr( x, iCoorU, iCoorV, iCoorX, iCoorY ) 15 print(’{0:4s} {1:20s} {2:9s}’.format(’Iter’, 16 ’[a11 a12 tx a21 a22 ty px py uc vc k]’, ’f(X)’)) 17 iParOpt = fmin(func=F, x0=iParAffine, maxiter=1000, disp=1, xtol=1e-6, 18 ftol=1e-6, callback=callbackF, maxfun=None ) 19 formattedline1 = ’%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f \ 20 %8.3e %8.3e %8.3f %8.3f %8.3e’ % ( tuple(iParAffine[:]) ) 21 formattedline2 = ’%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f \ 22 %8.3e %8.3e %8.3f %8.3f %8.3e’ % ( tuple(iParOpt[:]) ) 23 24 oErr2opt = geomCalibErr( iParAffine, iCoorU, iCoorV, iCoorX, iCoorY ) 25 print(’Napaka po nelinearni optimizaciji: ’, oErr2ident) 26 print(’Zaˇ cetni parametri (po afini aproks.):\n’,formattedline1) 27 print(’Konˇ cni parametri (po nelin. opt.):\n’,formattedline2) Rezultat se izpiše v ukazni vrstici: --- Iter [a11 a12 tx a21 a22 ty px py uc vc k] f(X) 1 4.816 0.631-55.664 -0.936 4.562 202.175 0.000e+00 , → 0.000e+00 785.000 578.000 0.000e+00 3010.379362 ... 10 4.835 0.636-56.082 -0.943 4.597 203.694 1.796e-05 , →-1.249e-04 790.896 582.341 3.756e-05 2949.440012 ... 20 4.795 0.626-56.008 -0.933 4.544 199.215 5.966e-05 , →-1.287e-04 831.258 546.378 1.459e-04 2577.553598 ... 998 4.305-0.418 67.167-0.742 3.183 242.060 2.078e-04 , →-1.364e-03 1441.124 1709.991 1.520e-02 2.866062 999 4.305-0.419 67.224-0.742 3.184 241.876 2.084e-04 , →-1.364e-03 1446.320 1705.755 1.519e-02 2.865291 --- Napaka po nelinearni optimizaciji: 2.865291 Zaˇ cetni parametri (po afini aproks.): 4.816 0.631-55.664 -0.936 4.562 202.175 0.000e+00 0.000e+00 , → 785.000 578.000 0.000e+00 Konˇ cni parametri (po nelin. opt.): 4.305-0.419 67.224-0.742 3.184 241.876 2.084e-04-1.364e-03 , → 1446.320 1705.755 1.519e-02 Napaka po nelinearni optimizaciji se drastiˇcno zniža iz 3010,379361 na 2,865291, kar kaže na dobro prilaganje modela preslikave za dani problem poravnave. Prikažimo še toˇcke pred in po afini preslikavi in po simpleksni optimizaciji nelinearnege perspektivne preslikave in radialnih distozij na naslednji naˇcin: 1 plt.imshow(iCalImageG, cmap = cm.Greys_r) # prikazi sliko v novem oknu 2 plt.suptitle(’Slika kalibra s toˇ ckami za kalibracijo’) # nastavi naslov slike 3 plt.xlabel(’x’); plt.ylabel(’y’) 4 plt.plot(iCoorU,iCoorV,’om’,markersize=5.0) 5 iCoorUt, iCoorVt = geomCalibTrans( iParIdent, iCoorX, iCoorY ) 6 plt.plot(iCoorUt,iCoorVt,’or’,markersize=5.0) 7 iCoorUt, iCoorVt = geomCalibTrans( iParAffine, iCoorX, iCoorY ) 8 plt.plot(iCoorUt,iCoorVt,’ob’,markersize=5.0) 124 Poglavje 8. Geometrijska kalibracija kamere 9 iCoorUt, iCoorVt = geomCalibTrans( iParOpt, iCoorX, iCoorY ) 10 plt.plot(iCoorUt,iCoorVt,’+g’,markersize=5.0) Rezultat prikazuje slika 8.7, iz katere je razvidno dobro ujemanje toˇck po poravnavi z modelom perspektivne projekcije in radialnih distorzij. (a) (b) Slika 8.7: (a) Slika kalibra z oznaˇcenimi referenˇcnimi toˇckami (vijoliˇcne toˇcke), referenˇcnih toˇck v metriˇcnem prostoru kalibra (rdeˇce toˇcke), ki smo jih nato z afino preslikavo preslikali v modre toˇcke in nato še z zaporednima perspektivno preslikavo in radialnimi distozijami po minimizaciji napake (modre toˇcke nad oz. tik ob vijoliˇcnih toˇckah). (b) Podroˇcje šahovnice preslikano v metriˇcni prostor, pri ˇcemer je velikost posameznega slikovnega elementa 0,2 mm. 8.5 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.1: To je moj odgovor. . Naloga 8.1 Napišite funkcijo, ki za dane optimalne parametre geometrijske kalibracije iPar normalizira zajeto sliko iImage v metriˇcni prostor: 1 def geomCalibImage( iPar, iImage, iCoorX, iCoorY ): 2 # Tu napišite python kodo funkcije 3 return oImage kjer spremenljivki iCoorX in iCoorY predstavljata koordinate toˇck (x,y) v metriˇcnem prostoru, ki jih je potrebno preslikati v prostor slike in doloˇciti pripadajoˇce sivinske vrednosti. Funkcija vrne kalibrirano sliko oImage, katere dimenzije so podane glede na dimenzije vzorˇcnih toˇck (x, y). Preverite delovanje funkcije tako, da vzorˇcne toˇcke (x,y) preslikate z optimalnimi parametri projektivne preslikave in modela radialnih distorzij. Predpostavite, da ima stranica kvadratnega polja kalibracijskega objekta dolžino 20 mm in ustvarite toˇcke (x,y) tako, da bo na izhodni kalibrirani sliki oImage velikost slikovnih elementov enaka 1/5 mm. Izvedite geometrijsko kalibracijo slike calibration-object.jpg tako, da s funkcijo za interpolacijo prvega reda scipy.interpolate. interpn() v preslikanih toˇckah poišˇcete nove sivinske vrednosti. Prikažite preslikano sliko, v kateri morajo biti vzorci v kalibru poravnani z osmi slike. Naloga 8.2 Barvno sliko objekta test-object.jpg, ki je bila zajeta z istim slikovnim sistemom kot slika kalibra, kalibrirajte z referenˇcno geometrijsko kalibracijo tako, da bo velikost slikovnega 8.5 Naloge in vprašanja 125 elementa enaka 1/3 mm. Prikažite kalibrirano barvno sliko in nato na merilu oznaˇcite daljico med kljunoma kljunastega merila ter izraˇcunajte njeno dolžino v mm. Preverite ali dolžina ustreza dolžini, ki je oznaˇcena na kljunastem merilu. Naloga 8.3 Geometrijsko kalibracijo ponovite 3× tako, da najprej 3× roˇcno oznaˇcite oglišˇca na sliki s kalibracijskim objektom calibration-object.jpg. Referenˇcno geometrijsko kalibracijo nato doloˇcite glede na povpreˇcne koordinate teh oglišˇc. Na podlagi referenˇcne geometrijske kalibracije ocenite toˇcnost geometrijske kalibracije v mm z roˇcno oznaˇcenimi oglišˇci. Od ˇcesa zavisi toˇcnost geometrijske kalibracije? Naloga 8.4 Razvijte avtomatski postopek za geometrijsko kalibracijo slikovnega sistema. S po- moˇcjo Harrisovega detektorja oglišˇc iz knjižnice OpenCV (Vaja 6) poišˇcite oglišˇca na sliki kalibra calibration- object.jpg. Nato pa s funkcijo cv2.cornerSubPix() še dodatno izpopolnite položaj oglišˇc. Ustvarite idealno mrežo referenˇcnih oglišˇc in s postopkom ICP (Vaja 5) ter afino preslikavo doloˇcite korespondence z zaznanimi oglišˇci na sliki kalibra. Doloˇcite natanˇcnost avto-matske v primerjavi z referenˇcno geometrijsko kalibracijo v mm. Poizkusite izboljšati natanˇcnost kalibracije še tako, da avtomatsko glede na lastne pogoje zavržete korespondence, ki slabšajo kalibracijo. 126 Poglavje 8. Geometrijska kalibracija kamere Rezultati nekaterih nalog: 9. Doloˇ canje poze objektov v prostoru Prileganje 3D modelov ali slik objekta zanimanja na 2D slike omogoˇca doloˇcanje položaja teh objektov v 3D prostoru. Postopek prileganja 3D modela na 2D sliko imenujemo 3D-2D poravnava. 3D-2D poravnava je proces iskanja optimalne geometrijske preslikave T 3 3 : R → R, ki 3D model oz. sliko V nekega objekta preslika v tako lego, ki je skladna s projekcijo istega objekta na zajeti 2D sliki P. Glavni izziv pri poravnavi 3D in 2D slik je prostorska neskladnost slikovne informacije (3D vs. 2D), ki jo lahko odpravimo na dva naˇcina: 1) s projekcijo 3D informacije v 2D slikovni prostor ali 2) z rekonstrukcijo 3D slike iz razliˇcnih 2D projekcij. Pri vaji boste naˇcrtali postopek poravnave s projekcijo 3D slike v 2D ( 3 2 → P : R R) in maksimizacijo mere podobnosti MP med projekcijo P(V ) in 2D sliko P. Gradniki postopka so prikazani na sliki 9.1 spodaj. 9.1 Projekcija iz 3D v 2D Naˇcin projekcije zavisi od oblike 3D informacije. Pri vaji bo dana 3D CT1 slika, v kateri vsak slikovni element predstavlja atenuacijski koeficient µ (x) zajete 3D scene. Za potrebe poravnave 3D in 2D slik boste s pomoˇcjo 3D CT slike simulirali 2D rentgenske slike, pri katerih se projekcija iz 3D prostora v 2D sliko izraža kot integral atenuacijskega koeficienta vzdolž premice od izvora rentgenskih žarkov R S do 2D slikovne ravnine P = µ (l)dl. Tovrstno projekcijo 3D CT slike l imenujemo digitalno rekonstruirani rentgenski posnetek (DRR 2). Projekcija in pripadajoˇce koliˇcine so prikazane na sliki 9.1 zgoraj. 9.2 Mera podobnosti Prostorsko ujemanje med projekcijo P(V ) in 2D sliko P ovrednotimo z mero podobnosti MP; to je skalarna funkcija, ki zavzame maksimum, ko se položaji korespondenˇcnih struktur v P(V ) in P medsebojno prekrivajo. Mero podobnosti je potrebno smiselno izbrati, tako da je ˇcim manj obˇcutljiva na motilna slikovna neskladja in ˇcim bolj obˇcutljiva na dejanska geometrijska neskladja 1 CT – ang. Computed Tomography 2 DRR – ang. Digitally Reconstructed Radiograph 128 Poglavje 9. Doloˇ canje poze objektov v prostoru Slika 9.1: Zagotavljanje dimenzijskega ujemanja 3D in 2D slik s projekcijo 3D slike na 2D ravnino (zgoraj) in gradniki avtomatskega algoritma 3D-2D poravnave (spodaj). med slikama. Za poravnavo slik se pogosto uporablja 3 medsebojna informacija MI: MI (I ,J) = H(I) + H(J) − H(I,J ) , (9.1) kjer sta H(I) in H(J) entropija referenˇcne I(x,y) in lebdeˇce J(x, y) slike, H(I, J) pa njuna skupna entropija: L−1 H (I) = − p (s p (s ) , ∑ I I ) log I I sI=0 L−1 H (J) = − p ( (9.2) ∑ J s J ) log p J ( s J ) , sJ=0 L−1 L−1 H (I,J) = − p (s ,s ) ∑ ∑ IJ I J log pIJ (sI ,sJ ) , sI =0 sJ=0 kjer spremenljivki sI in sJ oznaˇcujeta soležne, diskretne sivinske vrednosti referenˇcne slike I (x, y) in lebdeˇce slike J(x,y), L pa število diskretnih vrednosti. Verjetnostni porazdelitvi pI(sI) in pJ (sJ) ter 3 MI – ang. Mutual Information 9.3 Optimizacijski postopek 129 skupno porazdelitev pIJ (sI,sJ) dobimo iz pripadajoˇcih normaliziranih histogramov hI (sI ), hJ (sJ) ter hIJ(sI, sJ ): h I (sI) hJ (sJ) hIJ(sI ,sJ ) pI (sI) = , pJ (sJ ) = , pIJ (sI ,sJ ) = , (9.3) N · M N · M N · M kjer N in M predstavljata število elementov vzdolž x in y osi slike. 9.3 Optimizacijski postopek Optimizacija je kljuˇcen del številnih znanstvenih in inženirskih disciplin, saj omogoˇca iskanje optimalnih rešitev za razliˇcne probleme. Optimizacijske metode lahko razdelimo v naslednje kategorije: • Gradientne metode: uporabljajo odvode za iskanje ekstremov funkcij. • Evolucijske in heuristiˇcne metode: temeljijo na simuliranem naravnem izboru. • Kombinatoriˇcne metode: išˇcejo optimalne rešitve v diskretnih prostorih. V Pythonu imamo na voljo veˇc knjižnic za implementacijo optimizacijskih algoritmov: • scipy.optimize: nudi številne metode, kot so gradienti spust (ang. gradient descent), simpleksna metoda Nelder-Mead, BFGS in druge. • cvxpy: orodje za konveksno optimizacijo. • pulp: rešuje linearne optimizacijske probleme. • DEAP: uporablja evolucijske algoritme. 9.3.1 Primer: optimizacija nelinearne funkcije Spodaj je prikazan primer uporabe metode BFGS iz knjižnice scipy.optimize za minimizacijo nelinearne funkcije: 1 import numpy as np 2 from scipy.optimize import minimize 3 4 # Definicija funkcije 5 def objective_function(x): 6 return x**4 - 3*x**3 + 2 7 8 # Zaˇ cetna toˇ cka 9 x0 = np.array([2.0]) 10 11 # Optimizacija z metodo BFGS 12 result = minimize(objective_function, x0, method=’BFGS’) 13 14 # Izpis rezultata 15 print("Optimalna vrednost x:", result.x) 16 print("Minimalna vrednost funkcije:", result.fun) V tem primeru funkcija 4 3 f ( x ) = x − 3 x + 2 vsebuje lokalne ekstreme. Metoda BFGS iterativno prilagaja vrednosti x in poišˇce minimum funkcije. 9.3.2 Optimizacija mere podobnosti za 3D togo preslikavo Tekom poravnave 3D in 2D slike nam izbrana optimizacijska metoda iterativno spreminja parametre p geometrijske preslikave T = T (p) tako, da maksimizira mero podobnosti: p ∗ [ p )], P T = argmax MP V P ( , (9.4) p 130 Poglavje 9. Doloˇ canje poze objektov v prostoru kjer so ∗ p optimalni parametri preslikave T (p). Za poravnavo 3D in 2D slik vretenc boste uporabili togo preslikavo, ki je v 3D definirana s šestimi parametri T p = [ t x y z α , t , t , , β , γ ]. Postopek poravnave 3D in 2D slik je prikazan na gornji sliki. 9.4 Vaje z rešitvami Gradivo za vajo v datoteki vretenca.npz vsebuje dve 3D CT sliki in eno 2D rentgensko sliko. Datoteko naložite z ukazom numpy.load(), z branjem lastnosti files pa lahko preverite imena vsebovanih spremenljivk. Vse spremenljivke so dane v obliki numpy polja. V spremenljivkah ctVol in ct2Vol ter xrayImg so dane pripadajoˇce 3D in 2D matrike sivinskih vrednosti, v spre-menljivkah ctTPos, ct2TPos in xrayTPos geometrijske preslikave koordinatnih sistemov 3D in 2D slik iz referenˇcnega koordinatnega sistema v prvi slikovni element slike z indeksom [0,0,0] oziroma [0,0], v spremenljivki xraySPos pa položaj izvora rentgenskih žarkov v referenˇcnem koordinatnem sistemu. Korak vzorˇcenja 3D in 2D slik je 1 milimeter. Geometrijske razmere in koliˇcine so ponazorjene v gornji sliki. Koda vaje je dostopna na Git repozitoriju. Uvozimo Python knjižnice in pripravimo pomožne funkcije: 1 import matplotlib.pyplot as plt 2 import numpy as np 3 import time 4 import rvlib 5 6 from scipy.optimize import fmin, minimize 7 from scipy.interpolate import interpn 8 from mpl_toolkits.mplot3d import Axes3D 9 10 def preprocess_ct(ct): 11 ct_temp = dict(ct) 12 ct_temp[’img’] = (ct_temp[’img’] - np.median(ct_temp[’img’])).\ 13 astype(’float’) 14 return ct_temp 15 16 def preprocess_Xray(Xray): 17 Xray_temp = dict(Xray) 18 Xray_temp[’img’] = rvlib.windowImage(Xray_temp[’img’], 60.0, 120.0) 19 Xray_temp[’img’] = Xray_temp[’img’].astype(’float’) / \ 20 Xray_temp[’img’].max() * 255.0 21 return Xray_temp Funkciji preprocess_ct() in preprocess_Xray() izvajata predhodno obdelavo medicinskih slikovnih podatkov za CT in rentgenske posnetke. Funkcija preprocess_ct() najprej ustvari kopijo vhodnega slovarja CT slike, nato pa normalizira slikovne vrednosti tako, da odšteje medi-ano vrednost slike (np.median(ct_temp[’img’])), kar centrira podatke okoli niˇcle, in rezultat pretvori v tip float. Funkcija preprocess_Xray() prav tako ustvari kopijo vhodnega slovarja rentgenskega posnetka, nato pa uporabi oknjenje slike (rvlib.windowImage()) z oknom 60-120 HU za izboljšavo kontrasta, nakar slikovne vrednosti normalizira v obmoˇcje 0-255 z deljenjem z maksimalno vrednostjo in množenjem s 255,0, kar omogoˇci primerjavo z drugimi slikami. Obe funkciji ohranjata originalne metapodatke v slovarju, medtem ko modificirata le slikovne podatke, kar je kljuˇcno za nadaljnjo analizo v medicinskih aplikacijah. 9.4 Vaje z rešitvami 131 Vaja 9.1 Za potrebe nadaljnjih nalog za vsako od danih slik ustvarite spremenljivko tipa dict , npr. ct, ct2 in Xray, in pod ustreznim kljuˇcem vnesite sivinske vrednosti slike in pripadajoˇco preslikavo v referenˇcnem koordinatnem sistemu (ctTPos, ct2TPos in xrayTPos), za 2D rentgensko sliko pa še položaj izvora rentgenskih žarkov (xraySPos). ■ Naložimo datoteko vretenca.npz in oblikujemo slovarje tako, da pod kljuˇcem ’img’ shranimo polje sivinski vrednosti slike, pod ’TPos’ koordinatni sistem (preslikavo iz svetovnega koordinat-nega sistema v prvo toˇcko slike), in pod ’SPos’ koordinate izvora rentgenskih žarkov: 1 v = np.load(’vretenca.npz’) 2 # oblikuj v dict 3 Xray = {’img’:v[’xrayImg’], ’TPos’:v[’xrayTPos’], ’SPos’:v[’xraySPos’]} 4 ct = {’img’:v[’ctVol’], ’TPos’:v[’ctTPos’]} 5 ct2 = {’img’:v[’ct2Vol’], ’TPos’:v[’ct2TPos’]} Vaja 9.2 Ustvarite vzorˇcni mreži 2D rentgenske in 3D CT slike s pomoˇcjo ukaza numpy.meshgrid(). Toˇcke na vzorˇcnih mrežah preslikajte v referenˇcni koordinatni sistem s pripadajoˇcimi 3D togimi preslikavami Xray.TPos in ct.TPos. Preslikane toˇcke 3D in 2D vzorˇcnih mrež in položaj izvora rentgenskih žarkov Xray.SPos prikažite s pomoˇcjo ukaza Axes3D.scatter(). Preverite pravilnost dobljene geometrijske postavitve izvora S, 3D in 2D slike v referenˇcnem koordinatnem sistemu s pomoˇcjo slike 9.2. ■ Slika 9.2: Geometrijske postavitve izvora S, 3D in 2D slike v referenˇcnem koordinatnem sistemu. Izvedemo prostorsko vizualizacijo transformacije koordinat med 3D CT sliko (ct) in 2D rentgenskim posnetkom (Xray) tako, da najprej ustvarimo mrežo 3D koordinat (g3) iz dimenzij CT slike z uporabo np.meshgrid, ki jo pretvorimo v homogeni koordinatni sistem z dodajanjem enice v zadnji stolpec. Analogno ustvarimo 2D mrežo koordinat za rentgenski posnetek (g2), ki jo razširimo v homogeni prostor z niˇclo v tretji in enico v ˇcetrti komponenti. Nato izvedemo transformacijo koordinat z uporabo transformacijskih matrik ct[’TPos’] in Xray[’TPos’], ki preslikajo toˇcke v skupni prostorski referenˇcni sistem. Prikažemo še vir rentgenskega žarka (xsp) z vijoliˇcnim krogcem , preslikane 3D toˇcke CT (zeleno) in preslikane 2D toˇcke rentgenskega posnetka (modro), pri ˇcemer se zaradi preglednosti uporablja podvzorˇcenje s faktorji s3 in s2. Rešitev 132 Poglavje 9. Doloˇ canje poze objektov v prostoru zapišemo: 1 s3z,s3y,s3x = ct[’img’].shape 2 g3x,g3y,g3z = np.meshgrid(range(s3x),range(s3y),range(s3z),indexing=’xy’) 3 # ustvari Nx4 matriko, zadnja koordinata homogena 4 g3 = np.vstack((g3x.flatten(),g3y.flatten(),g3z.flatten())).transpose() 5 g3 = np.hstack((g3, np.ones((g3.shape[0],1)))) 6 7 s2y,s2x = Xray[’img’].shape 8 g2x,g2y = np.meshgrid(range(s2x),range(s2y),indexing=’xy’) 9 # ustvari Mx4 matriko, zadnja koordinata homogena 10 g2 = np.vstack((g2x.flatten(),g2y.flatten())).transpose() 11 g2 = np.hstack((g2, np.zeros((g2.shape[0],1)), np.ones((g2.shape[0],1)))) 12 13 g3p = np.dot(g3, ct[’TPos’].transpose()) 14 g2p = np.dot(g2, Xray[’TPos’].transpose()) 15 xsp = Xray[’SPos’].flatten() 16 17 s2 = 50 18 s3 = 30 19 20 plt.close(’all’) 21 fig = plt.figure() 22 ax = fig.add_subplot(111,projection=’3d’) 23 ax.scatter(xsp[0],xsp[1],xsp[2], c=’m’, marker=’o’) 24 ax.scatter(g2p[::s2,0],g2p[::s2,1],g2p[::s2,2], c=’b’, marker=’.’,linewidths=0) 25 ax.scatter(g3p[::s3,0],g3p[::s3,1],g3p[::s3,2], c=’g’, marker=’.’,linewidths=0) 26 plt.show() 3D prikaz omogoˇca analizo relativne geometrije med obema modalitetama, kar je kljuˇcno za primerjavo ali poravnavo slik. Rezultat je prikazan na sliki 9.2 desno. Vaja 9.3 Napišite funkcijo, ki preslika poljubno toˇcko iPos v 3D prostoru na 2D slikovno ravnino glede na izvor rentgenskih žarkov S (xraySPos): 1 def mapPointToPlane( iPos, Xray ): 2 # Tu napišite python kodo funkcije 3 return oPos kjer je iPos matrika N × 3, Xray pa spremenljivka s podatki o 2D rentgenski sliki. Funkcija vrne toˇcke v matriki oPos v obliki matrike N × 3. V ravnino preslikane toˇcke izraˇcunate kot preseˇcišˇca 2D slikovne ravnine s premicami, ki izvirajo v izvoru rentgenskih žarkov in gredo skozi toˇcke iPos. Preverite delovanje funkcije tako, da preslikate koordinate oglišˇc 3D slike na 2D slikovno ravnino in toˇcke vrišete v geometrijsko postavitev iz prejšnje naloge. ■ Funkcija mapPointToPlane() izvaja projekcijo 3D toˇck na ravnino detektorja rentgenskega sistema z uporabo geometrije cone-beam projekcije. Najprej normalizira vhodne koordinate in doloˇci referenˇcno toˇcko (p0) ter normalo (n) na ravnino detektorja z uporabo transformacijske mat-rike Xray[’TPos’]. Projekcijska geometrija temelji na konceptu centralne projekcije, kjer se vsaka 3D toˇcka (iPos) preslika na detektor vzdolž žarka, ki poteka od izvora žarkov (Xray[’SPos’]) skozi obravnavano toˇcko. Kljuˇcni korak je izraˇcun skalirnega faktorja alpha, ki doloˇca preseˇcišˇce tega žarka z ravnino detektorja. Rezultat so 3D koordinate toˇck na detektorju, ohranjene v prostorskem koordinatnem sistemu. Rešitev zapišemo: 9.4 Vaje z rešitvami 133 1 def mapPointToPlane(iPos, Xray): 2 # toˇ cke so v obliki Nx3/4 3 iPos = np.asarray(iPos) 4 if iPos.ndim == 1: 5 iPos = np.reshape(iPos, (1,iPos.size)) 6 iPos = iPos[:,:3] 7 # doloˇ ci izhodišˇ ce na detektorji 8 p0 = np.dot(Xray[’TPos’], np.array([0, 0, 0, 1])) 9 # doloˇ ci normalo na ravnino 10 ex = np.dot(Xray[’TPos’], np.array([1, 0, 0, 1])) 11 ey = np.dot(Xray[’TPos’], np.array([0, 1, 0, 1])) 12 n = np.cross(ex[:-1]-p0[:-1], ey[:-1]-p0[:-1]) 13 n = n / np.sqrt(np.sum(n**2.0)) 14 15 # skrajšaj vse vektorje na dimenzije 1x3 16 p0 = p0[:3]; n = n[:3] 17 s = Xray[’SPos’].reshape((1,3)) 18 19 # koeficient za skaliranje smernega vektorja 20 alpha = np.dot(p0 - s, n) / np.dot(iPos - s, n) 21 22 # doloˇ ci položaj toˇ ck na detektorju 23 oPos = s + alpha.reshape((iPos.shape[0],1)) * (iPos - s) 24 25 return oPos 26 27 # ustvari mrežo tock oglišˇ c 3D slike 28 sz,sy,sx = ct[’img’].shape 29 g3x, g3y, g3z = np.meshgrid((0,sx-1),(0,sy-1),(0,sz-1),indexing=’xy’) 30 g3 = np.vstack((g3x.flatten(),g3y.flatten(),g3z.flatten())).transpose() 31 g3 = np.hstack((g3, np.ones((g3.shape[0],1)))) 32 g3p = np.dot(g3, ct[’TPos’].transpose()) 33 34 # ustvari mrežo tock oglišˇ c 2D slike 35 s2y,s2x = Xray[’img’].shape 36 g2x,g2y = np.meshgrid((0,s2x-1),(0,s2y-1),indexing=’xy’) 37 # ustvari Mx4 matriko, zadnja koordinata homogena 38 g2 = np.vstack((g2x.flatten(),g2y.flatten())).transpose() 39 g2 = np.hstack((g2, np.zeros((g2.shape[0],1)), np.ones((g2.shape[0],1)))) 40 g2p = np.dot(g2, Xray[’TPos’].transpose()) 41 42 # koordinata izvora zarkov 43 xsp = Xray[’SPos’].flatten() 44 45 # tocke 3D oglišˇ c na ravnini detektorja 46 g2proj = mapPointToPlane(g3p[:,:3], Xray) 47 48 # nariši geometrijske razmere 49 s = 1 50 plt.close(’all’) 51 fig = plt.figure() 52 ax = fig.add_subplot(111,projection=’3d’) 53 ax.scatter(xsp[0],xsp[1],xsp[2], c=’m’, marker=’o’) 54 ax.scatter(g2p[::s,0],g2p[::s,1],g2p[::s,2], c=’b’, marker=’.’,linewidths=0) 55 ax.scatter(g3p[::s,0],g3p[::s,1],g3p[::s,2], c=’g’, marker=’.’,linewidths=0) 56 ax.scatter(g2proj[:,0],g2proj[:,1],g2proj[:,2], c=’r’, marker=’o’) 134 Poglavje 9. Doloˇ canje poze objektov v prostoru 57 plt.show() Testna koda vizualizira geometrijske razmere med CT volumom in rentgenskim sistemom. Najprej generira mrežo 3D toˇck (oglišˇca CT volumna) in 2D toˇck (pikslov detektorja), ki jih transformira v skupni koordinatni sistem. Nato uporabi mapPointToPlane() za projekcijo 3D toˇck na ravnino detektorja. Projicirane toˇcke morajo ležati v ravnini. Prikaz na sliki 9.3 omogoˇca preverjanje pravilnosti projekcijske geometrije in transformacijskih matrik. Slika 9.3: Vizualizacija prikazuje: izvor žarkov (vijoliˇcna), detektor (modre toˇcke), CT volumen (zeleno) in projicirane toˇcke (rdeˇce). Vaja 9.4 Napišite funkcijo za stožˇcasto projekcijo 3D CT slike v 2D slikovno ravnino, ki simulira delovanje rentgena (DRR; ang. Digitally Reconstructed Radiograph): 1 def project3DTo2D( ct, Xray, iStep ): 2 # Tu napišite python kodo funkcije 3 return oDRR, oMask kjer sta ct in Xray spremenljivki s podatki o 3D CT in 2D rentgenski sliki, iStep pa je korak vzorˇcenja v milimetrih vzdolž vsake premice od izvora rentgenskih žarkov do preseˇcišˇca z 2D slikovno ravnino. Funkcija vrne 2D DRR sliko v spremenljivki oDRR in 2D masko DRR slike v spremenljivki oMask, ki imata dimenzije 446 × 446 (enako kot xrayImg). 2D maska DRR slike oznaˇcuje tiste toˇcke 2D slikovne ravnine, za katere premica do izvora rentgenskih žarkov seˇce 3D CT sliko. Preizkusite delovanje funkcije s pomoˇcjo danih ct in Xray slik in ustvarite DRR oz. projekcijo 3D CT slike. Preizkusite vpliv dolžine koraka iStep na projicirano sliko. ■ Funkcija project3DTo2D() izvaja stožˇcasto projekcijo 3D CT slike v 2D ravnino rentgenskega detektorja. Postopek se zaˇcne z generiranjem mreže 3D toˇck, ki predstavljajo oglišˇca CT volumna, in njihovo transformacijo v skupni koordinatni sistem z uporabo matrike ct[’TPos’]. Te toˇcke se nato projicirajo na ravnino detektorja s funkcijo mapPointToPlane(), ki uporablja geometrijo centralne projekcije z izvorom žarkov Xray[’SPos’]. Doloˇci se najmanjši omejujoˇci pravokotnik projiciranih toˇck na detektorju, kar omogoˇca optimizacijo raˇcunskega obmoˇcja. Nato se generira mreža vzorˇcnih toˇck vzdolž žarkov, ki potekajo od izvora skozi vsak piksel detektorja, pri ˇcemer se uporabi korak vzorˇcenja iStep za nadzor gostote vzorˇcenja. 9.4 Vaje z rešitvami 135 Vzorˇcne toˇcke v 3D prostoru se nato transformirajo nazaj v koordinatni sistem CT slike z inverzno matriko ct[’TPos’], kjer se z interpolacijsko funkcijo (interpn()) doloˇcijo ustrezne sivinske vrednosti iz CT volumna. Za toˇcke znotraj CT volumna se izvede trilinearna interpolacija, medtem ko se toˇcke zunaj obravnavajo kot ozadje (vrednost 0). Konˇcna 2D projekcija (oDRR) nastane z integracijo (povpreˇcenjem) interpoliranih vrednosti vzdolž vsakega žarka, maska (oMask) pa oznaˇcuje obmoˇcje, kjer je bila projekcija izvedena. Rezultat je simulacija rentgenskega posnetka, ki upošteva geometrijske lastnosti sistema in omogoˇca primerjavo z realnimi rentgenskimi posnetki. Rešitev zapišemo z uporabo matriˇcnih operacij preslikave koordinat, kar omogoˇca uˇcinkovito izvedbo: 1 def project3DTo2D(ct, Xray, iStep): 2 ’’’ 3 Naloga 4: Funkcija za stožˇ casto projekcijo 3D slike v 2D ravnino 4 5 Parameters 6 ---------- 7 ct : dict 8 Podatki o 3D ct sliki in sivinske vrednosti pod kljuˇ ci TPos in img 9 Xray : dict 10 Podatki o 2D Xray sliki in sivinske vrednosti pod kljuˇ ci TPos, SPos in img 11 iStep : float 12 Korak vzorˇ cenja v mm vzdolž Xray žarkov od izvora do 2D detektorja 13 14 Returns 15 --------- 16 oDRR : numpy.ndarray 17 Izhodna iz 3D v 2D prostor preslikana slika, ki simulira rentgen 18 oMask : numpy.ndarray 19 Maska podroˇ cja kamor se preslika 3D slika (1-ospredje, 0-ozadje) 20 ’’’ 21 # ustvari mrežo toˇ ck oglišˇ c 3D slike 22 s3z, s3y, s3x = ct[’img’].shape 23 g3x, g3y, g3z = np.meshgrid((0,s3x-1),(0,s3y-1),(0,s3z-1),indexing=’xy’) 24 g3 = np.vstack((g3x.flatten(),g3y.flatten(),g3z.flatten())).transpose() 25 g3 = np.hstack((g3, np.ones((g3.shape[0],1)))) 26 g3p = np.dot(g3, ct[’TPos’].transpose()) 27 28 # toˇ cke 3D oglišˇ c na ravnini detektorja 29 g2proj = mapPointToPlane(g3p[:,:3], Xray) 30 31 # preslikaj v 2D ravnino 32 g2plane = np.dot(addHomogCoord(g2proj), \ 33 np.linalg.inv(Xray[’TPos’]).transpose()) 34 35 # poišˇ ci najmanjši oˇ crtan pravokotnik 36 xmin = np.floor(np.min(g2plane[:,0])) 37 xmax = np.ceil(np.max(g2plane[:,0])) 38 ymin = np.floor(np.min(g2plane[:,1])) 39 ymax = np.ceil(np.max(g2plane[:,1])) 40 41 # preveri ali so toˇ cke znotraj 2D slike 42 s2y, s2x = Xray[’img’].shape 43 xmin = np.max((0,xmin)); xmin = int(np.min((s2x,xmin))) 44 xmax = np.max((0,xmax)); xmax = int(np.min((s2x,xmax))) 136 Poglavje 9. Doloˇ canje poze objektov v prostoru 45 ymin = np.max((0,ymin)); ymin = int(np.min((s2y,ymin))) 46 ymax = np.max((0,ymax)); ymax = int(np.min((s2y,ymax))) 47 48 # definiraj mrežo toˇ ck v 2D ravnini 49 g2x, g2y = np.meshgrid(range(xmin,xmax), range(ymin,ymax), indexing=’xy’) 50 rectShape = g2x.shape 51 52 # ustvari Mx4 matriko, zadnja koordinata homogena 53 g2 = np.vstack((g2x.flatten(),g2y.flatten())).transpose() 54 g2 = np.hstack((g2, np.zeros((g2.shape[0],1)), np.ones((g2.shape[0],1)))) 55 g2 = np.dot(g2, Xray[’TPos’].transpose()) 56 57 # preberi pozicijo izvora žarkov 58 xsp = Xray[’SPos’].flatten().reshape((1,3)) 59 60 # doloˇ ci minimalno in maksimalno razdaljo za vzorˇ cenje 61 d = np.sqrt(np.sum((g3p[:,:3] - xsp)**2.0, axis=1)) 62 dmin = np.min(d); dmax = np.max(d) 63 64 # doloci vzorˇ cne toˇ cke vzdolž žarkov 65 ds = np.arange(dmin,dmax,iStep) 66 ds = np.reshape(ds, (1,ds.size)) 67 68 # definiraj vzorˇ cne toˇ cke v 3d prostoru 69 vs = g2[:,:3] - xsp 70 vs = vs / np.sqrt(np.sum(vs**2.0, axis=1)).reshape((vs.shape[0],1)) 71 72 Nx1 = (vs.shape[0],1) 73 px = xsp[0,0] + vs[:,0].reshape(Nx1) * ds 74 py = xsp[0,1] + vs[:,1].reshape(Nx1) * ds 75 pz = xsp[0,2] + vs[:,2].reshape(Nx1) * ds 76 77 # preslikava koordinat v prostor 3D slike 78 Tmat = np.linalg.inv(ct[’TPos’]) 79 pxn = Tmat[0,0]*px + Tmat[0,1]*py + Tmat[0,2]*pz + Tmat[0,3] 80 pyn = Tmat[1,0]*px + Tmat[1,1]*py + Tmat[1,2]*pz + Tmat[1,3] 81 pzn = Tmat[2,0]*px + Tmat[2,1]*py + Tmat[2,2]*pz + Tmat[2,3] 82 83 # preveri katere koordinate so znotraj 3D slike 84 idx = np.where((pxn>=0) & (pxn 85 (pyn>=0) & (pyn 86 (pzn>=0) & (pzn 87 88 oRayInterp = np.zeros_like(pxn) 89 pxn = pxn[idx[0],idx[1]] 90 pyn = pyn[idx[0],idx[1]] 91 pzn = pzn[idx[0],idx[1]] 92 93 # izvedi trilinearno interpolacijo 94 oRayInterp_i = interpn((np.arange(s3z),np.arange(s3y),np.arange(s3x)), \ 95 ct[’img’].astype(’float’), \ 96 np.dstack((pzn,pyn,pxn)), \ 97 method=’linear’, bounds_error=False, fill_value=0) 98 99 oRayInterp[idx[0],idx[1]] = oRayInterp_i 100 9.4 Vaje z rešitvami 137 101 # izvedi dejansko operacijo vzdolž žarkov 102 oRayInterp = np.mean(oRayInterp, axis=1).reshape(rectShape) 103 104 # ustvari izhodne spremenljivke 105 oDRR = np.zeros_like(Xray[’img’]).astype(’float’) 106 oMask = np.zeros_like(Xray[’img’]) 107 108 oDRR[ymin:ymax, xmin:xmax] = oRayInterp 109 oMask[ymin:ymax, xmin:xmax] = 255 110 111 return oDRR, oMask 112 113 # preizkus funkcije 114 iStep = 3 # korak vzorcenja vzdolž žarkov v mm 115 116 # predobdelava 3D in 2D slik 117 ct_temp = preprocess_ct(ct) 118 Xray_temp = preprocess_Xray(Xray) 119 120 ts = time.perf_counter() 121 oDRR, oDRRMask = rvlib.project3DTo2D(ct_temp, Xray_temp, iStep) 122 print(’ˇ Cas izraˇ cuna: {:.1f} ms’.format(1000.0*(time.perf_counter()-ts))) 123 124 # tri osi v enem prikaznem oknu 125 f, ax = plt.subplots(1, 3) 126 ax[0].imshow(oDRR, cmap=’gray’) 127 ax[0].set_title(’DRR’) 128 ax[1].imshow(Xray_temp[’img’], cmap=’gray’) 129 ax[1].set_title(’Xray’) 130 ax[2].imshow(2*rvlib.normalizeImage(oDRR, iType=’range’) + 131 rvlib.normalizeImage(Xray_temp[’img’], iType=’range’), cmap=’gray’) 132 ax[2].set_title(’Superpozicija’) 133 plt.show() Slika 9.4 prikazuje rezultat uporabe funkcije, ki se izvede v: ˇ Cas izraˇ cuna: 78.3 ms Slika 9.4: Vizualizacija DRR (levo), rentgenske slike (sredina) in njunega seštevka (desno). 138 Poglavje 9. Doloˇ canje poze objektov v prostoru Vaja 9.5 Napišite funkcijo za 3D togo preslikavo vzorˇcne mreže 3D CT slike okoli njenega središˇca: 1 def rigidTrans( ct, iPar ): 2 # Tu napišite python kodo funkcije 3 return oTrans kjer vhodna spremenljivka ct predstavlja 3D CT sliko, iPar pa šestvrstiˇcni vektor p = [ T t x y z α , t , t , , β , γ ] s parametri 3D toge preslikave, kjer so rotacije definirane glede na center 3D slike. Izhodna spremenljivka oTransnaj predstavlja togo preslikavo 3D CT slike. Razširite funkcijo za 2D projekcijo oz. DRR sliko tako, da bo imela dodatni vhodni parameter iPar, npr. project3DTo2D( ..., iPar ), kjer je iPar je šestvrstiˇcni vektor s parametri 3D toge preslikave 3D CT slike okoli njenega središˇca. Za 3D togo preslikavo 3D CT slikeUporabite funkcijo rigidTrans( ct, iPar ). Prikažite 2D projekcijske slike za razliˇcne vrednosti parametrov preslikave, npr. iPar = [0 20 0 0 0 0], iPar = [0 0 0 0 45 0], iPar = [0 0 0 0 0 90]. ■ Funkcija rigidTransMatrix() generira togo transformacijsko matriko za 3D sliko na podlagi šestih parametrov (iPar), ki vkljuˇcujejo tri translacije (tx, ty, tz) in tri rotacije (alpha, beta, gamma ). Postopek vkljuˇcuje ustvarjanje rotacijske matrike (oRot) in translacijske matrike (oTrans), ter upoštevanje središˇca slike (oCenter in oInvCenter) za pravilno poravnavo transformacije. Funkcija project3DTo2D() nato uporabi to matriko za transformacijo koordinatnega sistema CT slike, kjer kombinira obstojeˇco transformacijsko matriko (ct[’TPos’]) z novo togo transform-acijo, in nato izvede koniˇcno projekcijo 3D slike na 2D ravnino z uporabo knjižniˇcne funkcije rvlib.project3DTo2D, ki upošteva korak vzorˇcenja (iStep) in generira simulirani rentgenski posnetek. Obe funkciji skupaj omogoˇcata projekcijo 3D volumna z možnostjo prilagajanja položaja in orientacije: 1 def rigidTransMatrix(ct, iPar): # iPar = [tx, ty, tz, alpha, beta, gamma] 2 s3z,s3y,s3x = ct[’img’].shape 3 oRot = rvlib.transAffine3D(iTrans=(0,0,0), iRot=(iPar[3],iPar[4],iPar[5])) 4 oTrans = rvlib.transAffine3D(iTrans=(iPar[0],iPar[1],iPar[2]), iRot=(0,0,0)) 5 oCenter = rvlib.transAffine3D(iTrans=(-s3x/2, -s3y/2, -s3z/2), iRot=(0,0,0)) 6 oInvCenter = rvlib.transAffine3D(iTrans=(s3x/2, s3y/2, s3z/2), iRot=(0,0,0)) 7 return np.dot(oTrans, np.dot(oInvCenter, np.dot(oRot, oCenter))) 8 9 def project3DTo2D(ct, Xray, iStep, iPar=[0,0,0,0,0,0]): 10 TPos = np.dot(ct[’TPos’], rigidTransMatrix(ct, iPar)) 11 newCt = {’img’:ct[’img’], ’TPos’:TPos} 12 return rvlib.project3DTo2D(newCt, Xray, iStep) 13 14 # preizkus funkcije 15 iPar = [0, 0, -30, -45, 0, 0] 16 iStep = 3 17 18 # predobdelava 3D in 2D slik 19 ct_temp = preprocess_ct(ct) 20 Xray_temp = preprocess_Xray(Xray) 21 22 ts = time.perf_counter() 23 oDRR, oDRRMask = project3DTo2D(ct_temp, Xray_temp, iStep, iPar) 24 print(’ˇ Cas izraˇ cuna: {:.1f} ms’.format(1000.0*(time.perf_counter()-ts))) 25 9.4 Vaje z rešitvami 139 26 # tri osi v enem prikaznem oknu 27 f, ax = plt.subplots(1, 3) 28 ax[0].imshow(oDRR, cmap=’gray’) 29 ax[0].set_title(’DRR’) 30 ax[1].imshow(Xray_temp[’img’], cmap=’gray’) 31 ax[1].set_title(’Xray’) 32 ax[2].imshow(2*rvlib.normalizeImage(oDRR, iType=’range’) + 33 rvlib.normalizeImage(Xray_temp[’img’], iType=’range’), cmap=’gray’) 34 ax[2].set_title(’Superpozicija’) 35 plt.show() Slika 9.5 prikazuje rezultat uporabe funkcije pri parametrih toge preslikave (t x,ty,tz,α ,β , γ) = [ 0mm,0mm,−30mm,−45 deg,0 deg, 0 deg]. Slika 9.5: Vizualizacija DRR (levo) pri parametrih toge preslikave (tx,ty,tz,α ,β , γ) = [0mm,0mm, −30mm,−45 deg,0 deg, 0 deg], rentgenske slike (sredina) in njunega seštevka (desno). Vaja 9.6 Napišite funkcijo, ki izraˇcuna vrednost medsebojne informacije med dvema slikama: 1 def mutualInformation( iImageI, iImageJ, iBins ): 2 # Tu napišite python kodo funkcije 3 return oMI kjer sta iImageI in iImageJ 2D sivinski sliki, iBins pa število intervalov za izraˇcun histo-grama. Funkcija vrne skalar oMI. Preizkusite delovanje funkcije tako, da izraˇcunate medsebojno informacijo med projicirano sliko in 2D rentgensko sliko, pri tem pa uporabite le sivinske vred-nosti na podroˇcju maske projicirane slike. Preuˇcite vpliv števila intervalov iBins na vrednost medsebojne informacije. Izraˇcunajte in izrišite vrednosti oMI med 2D projekcijami in 2D rentgensko sliko tako, da spreminjate le po en parameter toge preslikave naenkrat. Translacije t x, ty in tz spreminjajte od -20 do 20 milimetrov s korakom 2 milimetra, rotacije ◦ ◦ α , β in γ pa od -20 do 20 s korakom 2 in narišite ter ustrezno oznaˇcite grafe MI(tx), MI(ty), MI(tz), MI(α), MI(β ), MI(γ). ■ Funkcija mutualInformation() izraˇcuna medsebojno informacijo med dvema 2D slikama (iImageI in iImageJ), ki meri statistiˇcno odvisnost med njunimi sivinskimi vrednostmi. Kljuˇcni koraki vkljuˇcujejo: (1) pretvorbo sivinskih vrednosti v indekse histograma z getIndices(), ki upošteva podano obmoˇcje iSpan (min, max) in število predalov iBins; (2) izraˇcun 1D histogramov posameznih slik (hist1D()) in 2D skupnega histograma (hist2D()) z uporabo konvolucijskega 140 Poglavje 9. Doloˇ canje poze objektov v prostoru glajenja za zmanjšanje šuma; (3) normalizacijo histogramov v verjetnostne porazdelitve (pI, pJ, pIJ); ter (4) izraˇcun entropij (HI, HJ, HIJ) in medsebojne informacije kot oMI = HI + HJ - HIJ. Glavni parameter iSpan omogoˇca nastavitev obmoˇcja sivinskih vrednosti, medtem ko iBins doloˇca granularnost histograma. Rešitev zapišemo: 1 def mutualInformation(iImageI, iImageJ, iBins, iSpan=(None, None)): 2 ’’’ 3 Izracunaj medsebojno informacijo med 2D slikama 4 5 Parameters 6 ---------- 7 iImageI : numpy.ndarray 8 Sivinska informacija slike I 9 iImageJ : numpy.ndarray 10 Sivinska informacija slike J 11 iBins : int 12 Stevilo predalov v histogramu 13 iSpan : tuple | list 14 Obmocje vrednosti (min, max) 15 16 Returns 17 --------- 18 oDRR : numpy.ndarray 19 Izhodna iz 3D v 2D prostor preslikana slika, ki simulira rentgen 20 oMask : numpy.ndarray 21 Maska podroˇ cja kamor se preslika 3D slika (1-ospredje, 0-ozadje) 22 ’’’ 23 iImageI = np.asarray(iImageI) 24 iImageJ = np.asarray(iImageJ) 25 26 # funkcija za pretvorbo sivinskih vrednosti v indekse 27 def getIndices(iData, iBins, iSpan): 28 # doloci obmocje sivinskih vrednosti 29 minVal, maxVal = iSpan 30 maxVal = (np.max(iData)+1e-7 if maxVal is None else maxVal) 31 minVal = (np.min(iData) if minVal is None else minVal) 32 # pretvori v indeks polja 33 idx = np.round((iData - minVal) / (maxVal - minVal) * (iBins-1)) 34 idx[idx < 0] = 0 35 idx[idx >= iBins] = iBins-1 36 # vrni indekse 37 return idx.astype(’uint32’) 38 39 # funkcija za izracun 1D histograma 40 def hist1D(iData, iBins, iSpan): 41 # pretvorba sivinskih vrednosti v indekse 42 idx = getIndices(iData, iBins, iSpan) 43 # izracunaj histogram 44 histData = np.zeros((iBins,)) 45 for i in idx: 46 histData[i] += 1 47 # vrni histogram 48 # return histData 49 # vrni glajeni histogram 50 return ni.convolve(histData, np.array([0.2, 0.6, 0.2])) 51 9.4 Vaje z rešitvami 141 52 # funkcija za izracun 2D histograma 53 def hist2D(iData1, iData2, iBins, iSpan): 54 # pretvorba sivinskih vrednosti v indekse 55 idx1 = getIndices(iData1, iBins, iSpan) 56 idx2 = getIndices(iData2, iBins, iSpan) 57 # izracunaj histogram 58 histData = np.zeros((iBins, iBins)) 59 for (i, j) in zip(idx1, idx2): 60 histData[i,j] += 1 61 # vrni histogram 62 # return histData 63 # vrni glajeni histogram 64 return ni.convolve(histData, np.array([ 65 [1, 2, 1], [2, 8, 2], [1, 2, 1]])/20.0) 66 67 # izracunaj histograme slik 68 hI = hist1D(iImageI, iBins, iSpan) 69 hJ = hist1D(iImageJ, iBins, iSpan) 70 hIJ = hist2D(iImageI, iImageJ, iBins, iSpan) 71 72 # normaliziraj histograme v gostote verjetnosti 73 pI = hI / (np.sum(hI) + 1e-7) 74 pJ = hJ / (np.sum(hJ) + 1e-7) 75 pIJ = hIJ / (np.sum(hIJ) + 1e-7) 76 77 # izracunaj entropije 78 HI = np.sum(- pI[pI>0] * np.log(pI[pI>0])) 79 HJ = np.sum(- pJ[pJ>0] * np.log(pJ[pJ>0])) 80 HIJ = np.sum(- pIJ[pIJ>0] * np.log(pIJ[pIJ>0])) 81 82 # izracunaj medsebojno informacijo 83 oMI = HI + HJ - HIJ 84 85 return oMI Rezultat oMI je skalarna vrednost, ki kvantificira podobnost slik, pri ˇcemer višje vrednosti na-kazujejo moˇcnejšo statistiˇcno povezanost. Funkcija je uporabna pri poravnavi slik, kjer se mak-simizacija medsebojne informacije uporablja kot kriterij v numeriˇcni optimizaciji. Testno kodo za izraˇcun vrednosti medsebojne informacije glede na spremembo posameznih parametrov toge preslikave zapišemo kot: 1 # parametri 2 iBins = 32 3 iSpan = [0.0, 255.0] 4 5 # predobdelava 3D in 2D slik 6 ct_temp = preprocess_ct(ct) 7 Xray_temp = preprocess_Xray(Xray) 8 9 par_range = np.arange(-20, 20, 2) 10 oMI_plot = np.zeros_like(par_range).astype(’float’) 11 12 # normalizacija intenzitet v projekciji 13 oDRR, oDRRMask = project3DTo2D(ct_temp, Xray_temp, iStep) 14 ids = np.where(oDRRMask>0) 142 Poglavje 9. Doloˇ canje poze objektov v prostoru 15 xmin, xmax = np.min(ids[1]), np.max(ids[1]) 16 ymin, ymax = np.min(ids[0]), np.max(ids[0]) 17 iTemp = oDRR[ymin:ymax,xmin:xmax] 18 imin, imax = np.min(iTemp), np.max(iTemp) 19 20 for idx in range(len(par_range)): 21 p = par_range[idx] 22 oDRR, oDRRMask = project3DTo2D(ct_temp, Xray_temp, iStep, 23 iPar=[p,0,0,0,0,0]) 24 # poklici funkcijo 25 ids = np.where(oDRRMask>0) 26 xmin, xmax = np.min(ids[1]), np.max(ids[1]) 27 ymin, ymax = np.min(ids[0]), np.max(ids[0]) 28 iImageI = Xray_temp[’img’][ymin:ymax,xmin:xmax] 29 iImageJ = (oDRR[ymin:ymax,xmin:xmax] - imin) / (imax - imin) * 255.0 30 31 oMI = rvlib.mutualInformation(iImageI, iImageJ, iBins, iSpan) 32 oMI_plot[idx] = oMI 33 34 print(’p: {:.1f}, MI={:.2f}’.format(p, oMI)) 35 36 plt.close(’all’) 37 plt.plot(par_range, oMI_plot) 38 plt.suptitle(’MI(t_x)’) 39 plt.xlabel(’t_x’) 40 plt.ylabel(’MI’) Rezultat prikazuje slika 9.6. Pri vrednostih parametrov iPar = [0, 0, 0, 0, 0, 0] sta 3D in 2D sliki ustrezno poravnavi glede na roˇcno referenˇcno poravnavo, kar pomeni, da priˇcakujemo v tej legi maksimum medsebojne informacje, ob vsakem odmiku od te lege pa zmanjšanje vrednosti. Opazimo, da je za parametra tx in ty medsebojna informacija brez izrazitega vrha, kar je posledica tega, da omenjena parametra delno kodirata translacijo v smeri projekcije – premiki v tej smeri komaj opazno skalirajo DRR, zato je obˇcutljivost medsebojne informacije na te premike nizka. Vaja 9.7 Naˇcrtajte avtomatski postopek za poravnavo 3D in 2D slik tako, da ustvarite kriterijsko funkcijo: 1 def criterionFcn( iPar, ct, Xray ): 2 # Tu napišite python kodo funkcije 3 return oMP ki pri podanih parametrih 3D toge preslikave iPar izraˇcuna vrednost medsebojne informacije MI ∗ med DRR in 2D rentgensko sliko. Optimalne vrednosti parametrov p toge preslikave doloˇcite s pomoˇcjo simpleksne ali katere druge metode optimizacije (vaja 8). ■ Kriterijska funkcija criterionFcn() je zasnovana za optimizacijo poravnave med 3D CT sliko (ct) in 2D rentgenskim posnetkom (Xray) z uporabo medsebojne informacije (MI) kot mere podobnosti. Funkcija najprej generira simulirani rentgenski posnetek (DRR) iz 3D volumna s funkcijo project3DTo2D() za dane transformacijske parametre iPar (translacija in rotacija). Nato izraˇcuna medsebojno informacijo med realnim rentgenskim posnetkom in simuliranim DRR-jem z uporabo funkcije mutualInformation(), pri ˇcemer se upošteva le obmoˇcje, kjer je maska oDRRMask aktivna. Vrednosti DRR-ja se normalizirajo v obmoˇcje 0-255, kriterijska funkcija pa 9.4 Vaje z rešitvami 143 (a) (b) (c) (ˇc) (d) (e) Slika 9.6: Potek vrednosti medsebojne informacije glede na spremembo parametrov toge preslikave, in sicer translacije (a) t x, (b) ty, (c) tz, in rotacije (ˇc) α , (d) β in (e) γ. vrne negativno vrednost MI, saj optimizacijski postopek minimizira ciljno funkcijo. Kriterijsko funkcijo zapišemo: 1 def criterionFcn(iPar, ct, Xray): 2 iStep = 3 3 iBins = 32 4 iSpan = [0.0, 255.0] 5 imin = -0.80841250322681901 6 imax = 13.620590454314147 7 8 oDRR, oDRRMask = project3DTo2D(ct, Xray, iStep, iPar) 9 10 ids = np.where(oDRRMask>0) 11 xmin, xmax = np.min(ids[1]), np.max(ids[1]) 12 ymin, ymax = np.min(ids[0]), np.max(ids[0]) 13 iImageI = Xray[’img’][ymin:ymax,xmin:xmax] 14 iImageJ = oDRR[ymin:ymax,xmin:xmax] 15 iImageJ = (oDRR[ymin:ymax,xmin:xmax] - imin) / (imax - imin) * 255.0 16 17 return-rvlib.mutualInformation(iImageI, iImageJ, iBins, iSpan) V eksperimentu za numeriˇcno optimizacijo uporabimo Powellovo metodo za optimizacijo rotacijskega parametra α , kjer vsaka iteracija vkljuˇcuje klic kriterijske funkcije in vizualizacijo trenutnega stanja s primerjavo originalnega rentgenskega posnetka, DRR-ja pred in po poravnavi ter njihove superpozicije. Glavni cilj je maksimizirati medsebojno informacijo, kar pomeni najboljšo možno ujemanje med simuliranim in realnim posnetkom: 1 # definiramo zaˇ cetni odmik 2 iAlphaStart = -8 3 144 Poglavje 9. Doloˇ canje poze objektov v prostoru 4 # predobdelava 3D in 2D slik 5 ct_temp = preprocess_ct(ct) 6 Xray_temp = preprocess_Xray(Xray) 7 8 # definicija funkcije MP(p) 9 oMP = lambda iPar : criterionFcn([0,0,0,float(iPar),0,0], ct_temp, Xray_temp) 10 11 neval = 1 12 def callbackF(iPar): 13 global neval 14 fval = criterionFcn([0,0,0,float(iPar),0,0], ct_temp, Xray_temp) 15 print(’{0:4d} {1: 3.6f} {2: 3.6f}’.format(neval, iPar[0], fval)) 16 neval += 1 17 print(’{0:4s} {1:9s} {2:9s}’.format(’Iter’, ’ Alpha’, ’ MI(Alpha)’)) 18 19 # klic optimizacije 20 ts = time.perf_counter() 21 # simpleksna optimizacija 22 # iAlphaEnd = fmin(func=oMP, x0=iAlphaStart, maxiter=100, xtol=1e-5, ftol=1e-5, disp=1)[0] 23 # method = Nelder-Mead (simpleksna optimizacija), Powell 24 res = minimize(fun=oMP, x0=iAlphaStart, method=’Powell’, tol=1e-5, 25 options={’maxiter’:100, ’maxfev’:1000, ’xtol’:1e-5, 26 ’ftol’:1e-5, ’disp’:True}, callback=callbackF 27 ) 28 iAlphaEnd = float(res.x) 29 print(’ˇ Cas izraˇ cuna: {:.1f} s’.format(time.perf_counter()-ts)) 30 print(’Alpha pred poravnavo: {:.1f} deg\nAlpha po poravnavi: {:.1f} deg’.format( 31 iAlphaStart, iAlphaEnd) 32 ) 33 34 iParStart = [0, 0, 0, iAlphaStart, 0, 0] 35 iParEnd = [0, 0, 0, iAlphaEnd, 0, 0] 36 37 # prikaz rezultatov 38 oDRR_start, oDRRMask = project3DTo2D(ct_temp, Xray_temp, 3, iParStart) 39 oDRR_end, oDRRMask = project3DTo2D(ct_temp, Xray_temp, 3, iParEnd) 40 41 # šest osi v enem prikaznem oknu 42 f, ax = plt.subplots(2, 3) 43 ax[0, 0].imshow(Xray_temp[’img’], cmap=’gray’) 44 ax[0, 0].set_title(’Xray’) 45 ax[1, 0].imshow(Xray_temp[’img’], cmap=’gray’) 46 47 ax[0, 1].imshow(oDRR_start, cmap=’gray’) 48 ax[0, 1].set_title(’DRR pred poravnavo’) 49 50 ax[1, 1].imshow(oDRR_end, cmap=’gray’) 51 ax[1, 1].set_title(’DRR po poravnavi’) 52 53 ax[0, 2].imshow(2*rvlib.normalizeImage(oDRR_start, iType=’range’) + 54 rvlib.normalizeImage(Xray_temp[’img’], iType=’range’), cmap=’gray’) 55 ax[0, 2].set_title(’Superpozicija’) 56 57 ax[1, 2].imshow(2*rvlib.normalizeImage(oDRR_end, iType=’range’) + 58 rvlib.normalizeImage(Xray_temp[’img’], iType=’range’), cmap=’gray’) 9.5 Naloge in vprašanja 145 59 ax[1, 2].set_title(’Superpozicija’) 60 plt.show() Izpis v ukazni vrstici je naslednji: Iter Alpha MI(Alpha) :29: DeprecationWarning: Conversion of an array , → with ndim > 0 to a scalar is deprecated, and will error in future. Ensure , → you extract a single element from your array before performing this , → operation. (Deprecated NumPy 1.25.) oMP = lambda iPar : criterionFcn([0,0,0,float(iPar),0,0], ct_temp, Xray_temp) :34: DeprecationWarning: Conversion of an array , → with ndim > 0 to a scalar is deprecated, and will error in future. Ensure , → you extract a single element from your array before performing this , → operation. (Deprecated NumPy 1.25.) fval = criterionFcn([0,0,0,0,float(iPar),0], ct_temp, Xray_temp) 1 -5.368449 -0.760157 2 -0.591433 -0.778438 3 -0.599876 -0.776393 4 -0.603923 -0.777245 5 -0.086603 -0.766410 6 -0.092069 -0.766163 7 -0.092069 -0.766163 Optimization terminated successfully. Current function value: -0.772473 Iterations: 7 Function evaluations: 204 ˇ Cas izraˇ cuna: 14.0 s Alpha pred poravnavo: -8.0 deg Alpha po poravnavi: -0.1 deg Z metodo optimizacije smo uspešno kompenzirali zaˇcetni premik α = −8 deg v konˇcni odmik α = −0,1 deg, kar je zanemarljivo majhna napaka saj je referenˇcna poravnavo pri α = −0 deg. Prikaz situacije pred in po poravnavi je na sliki 9.7. 9.5 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.1: To je moj odgovor. . Naloga 9.1 Z linearnim oknjenjem 2D rentgenske slike lahko izboljšate kontrast na podroˇcju vretenc. Preslikajte nepredznaˇcene 16-bitne sivinske vrednosti 2D rentgenske slike v nepredznaˇcene 8-bitne sivinske vrednosti na obmoˇcju tako, da se na izhodni sliki poveˇca kontrast na podroˇcju vretenc. Sivinsko preslikano 2D rentgensko sliko uporabite pri vseh naslednjih nalogah! Naloga 9.2 Vsaki od 3D CT slik doloˇcite mediano sivinskih vrednosti in jo odštejte od vseh sivinskih vrednosti. Pred odštevanjem ustrezno pretvorite sivinske vrednosti. Prikažite medialne oz. sredinske 2D rezine v stranskem, ˇcelnem in preˇcnem pogledu. Sivinsko preslikani 3D CT sliki uporabite pri vseh naslednjih nalogah! Naloga 9.3 Izraˇcunajte in izrišite vrednosti oMI med 2D projekcijami in 2D rentgensko sliko tako, da spreminjate le po en parameter toge preslikave naenkrat. Translacije tx, ty in tz spreminjajte od 146 Poglavje 9. Doloˇ canje poze objektov v prostoru Slika 9.7: Vizualizacija DRR (levo), rentgenske slike (sredina) in njunega seštevka (desno) pred (zgoraj) in po poravnavi (zgoraj). -20 do 20 milimetrov s korakom 2 milimetra, rotacije ◦ ◦ α , β in γ pa od -10 do 10 s korakom 1 in narišite ter ustrezno oznaˇcite grafe MI(t x), MI(ty), MI(tz), MI(α ), MI(β ), MI(γ). Naloga 9.4 Obrazložite povezavo med medsebojno informacijo in dejansko podobnostjo 2D rentgenske in 2D projekcijskimi slikami. Katere vrednosti medsebojne informacije, višje ali nižje, odražajo dejansko podobnost med 2D rentgensko in 2D projekcijskimi oz. DRR slikami? Kolikšni sta teoretiˇcno minimalna in maksimalna vrednost medsebojne informacije? Naloga 9.5 ∗ Optimalne vrednosti parametrov p toge preslikave dobimo s klicem simpleksne minimizacije: 1 # definicija funkcije MP(p) 2 oMP = lambda iPar : criterionFcn( iPar, ct, Xray ) 3 # klic simpleksne optimizacije 4 from scipy.optimize import fmin 5 iPar_opt = fmin( func=oMP, x=np.zeros(6,1), maxiter=200, xtol=1e-5, ftol=1e-5, disp=1 ) Naloga 9.6 ∗ Izvedite poravnavo slike ct2 in Xray . Navedite vrednosti optimalnih parametrov p toge preslikave, ki rezultirajo v uspešni poravnavi 3D in 2D slik, in prikažite pripadajoˇco DRR sliko. Uspešnost postopka poravnave lahko preverite kot je prikazano na sliki 9.8. Naloga 9.7 Naˇcrtajte poravnavo 3D CT slike ct2 in 2D rentgenske slike Xray s pomoˇcjo roˇcno doloˇcenih korespondenˇcnih toˇck. Zapišite model preslikave med slikama in parametre preslikave 9.5 Naloge in vprašanja 147 in doloˇcite model kamere, pri ˇcemer uporabite podatke o položaju izvora in legi 2D detektorja. Optimizacijo za iskanje optimalnih parametrov izvedite na veˇc naˇcinov: • Z direktno linearno transformacijo (DLT). • Z nelinearno optimizacijo, pri ˇcemer linearizirate zaprtozanˇcni izraˇcun koraka s p + ∆p in izpeljete enaˇcbe za izraˇcun ∆p. • Kombinacija DLT in nato nelinearna optimizacija. Kateri naˇcin iskanja vrne najboljšo poravnavo 3D in 2D slik? Naloga 9.8 Doloˇcite napako poravnave 3D CT slike ct2 in 2D rentgenske slike Xray iz prejšnjih nalog tako, da kot referenˇcno poravnavo uporabite podatke o legi 3D CT slike ct. Kot referenˇcne toˇcke uporabite oglišˇca 3D slike. Izraˇcunajte napako na dva naˇcina: • Napaka v 3D prostoru (ang. target registration error: TRE) kot srednjo kvadratno napako med korespondenˇcnimi toˇckami oglišˇc 3D slike v referenˇcni legi in trenutni legi. • Napaka v ravnini detektorja (ang. projected registration error: PRE) kot srednjo kvadratno napako med korespondenˇcnimi toˇckami oglišˇc 3D slike v referenˇcni legi in trenutni legi, pri toˇcke predhodno projicirate v 2D ravnino. 148 Poglavje 9. Doloˇ canje poze objektov v prostoru Rezultati nekaterih nalog: Slika 9.8: Poveˇcan prikaz poravnave vretenca pred in po poravnavi CT in rengenskega posnetka. Poza CT je prikazana z vizualizacijo DRR, ki mora ob uspešni poravnavi sovpadati z rentgenskim posnetkom. Ujemanje robov je razvidno iz mešanega prikaza rentgena in DRR. 10. Rekonstrukcija 3D oblik Pri rekonstrukciji 3D oblik želimo s pomoˇcjo ene ali veˇc 2D slik nekega objekta, zajetih iz razliˇcnih pogledov in/ali pod razliˇcnimi osvetlitvami, najti optimalno 3D obliko tega objekta tako, da bo 3D oblika ˇcim bolje sovpadala z zajetimi 2D slikami. Eden od postopkov rekonstrukcije 3D oblik je fotometriˇcni stereo (slika 10.1), pri katerem izlušˇcimo informacijo o obliki na podlagi senc na objektu. Sence ustvarimo z osvetlitvijo objekta iz razliˇcnih smeri (npr. s1,s2,s3), slike pa zajamemo iz enega pogleda (v). Iz zajetih slik dobimo v vsaki toˇcki slike (x,y) oceno normale na površino n, z integriranjem teh normal glede na neko izhodišˇcno toˇcko dobimo rekonstrukcijo 3D oblike. Slika 10.1: Interakcija svetloba-objekt na podlagi senc razkrije obliko objekta (levo), kar za rekonstrukcijo 3D oblike izkorišˇca tehnika fotometriˇcni stereo (desno). 10.1 Fotometriˇ cni stereo in Lambertov model Ce predpostavimo, da pri interakciji svetloba-površina pride le do difuznega odboja, potem lahko ˇ enostavno povežemo svetilnost slikovnega elementa z obliko površine. Uporabimo Lambertov 150 Poglavje 10. Rekonstrukcija 3D oblik model difuzne površine : kc kc I (x, y) = ρi cos θi = ρi s ◦ n , π π kjer je k svetilnost vira, c konstanta optiˇcnega sistema, ρ albedo oz. odbojnost površine. ˇ Ce predpostavimo, da je kc = 1 potem dobimo za j-to svetilo ( j = 1, . . . ,N) enaˇcbo za svetilnost π slikovnega elementa v obliki I j (x, y) = ρi cos θi = ρi s j ◦ n. 10.2 Izraˇ cun albeda in normal na površino objekta Za oceno normale na površino n v toˇcki slike (x,y) potrebujemo vsaj tri svetila (N = 3) iz razliˇcnih smeri (s 1,s2,s3), da lahko zapišemo minimalen sistem linearnih enaˇcb. Sistem enaˇcb za tri Sistem enaˇcb v matriˇcni Rešitev sistema: svetila: obliki: I T    T  −1 1 = ρ s · n I 1 s n ˜ = S I 1 1 I T T 2 ρ = s · n = ⇒ I = s n = ⇒ = ∥n ˜∥ 2     2 ρ ρ 2 I T T · 3 = ρ s n I 3 s n = n ˜/∥n ˜∥ = ∥n ˜∥/ρ 3 3 | {z } | {z } | {z } I(3× S n 1 ) ( 3 × 3 ) ˜(3×1) Z gornjim postopkom dobimo oceno normale n na površino objekta v vsaki toˇcki slike (x,y). Ocena normale je bolj robustna, ˇce uporabimo veˇc osvetlitev objekta iz veˇc med seboj razliˇcnih smeri s j, N > 3. V katerem od j pogledov so lahko nekateri slikovni elementi relativno temni, zato bo ocena normale v teh slikovnih elementih manj zanesljiva, kar rešimo z uteževanjem enaˇcb s svetlostjo I j = I j (x, y) iz slik posameznega pogleda s j. Sistem enaˇcb za N Sistem enaˇcb v matriˇcni Rešitev sistema: svetil: obliki: I 2 T  2   T  · = I 1 ( ρ s n ) I I s 1 1 1 1 1 I = S n ˜ I 2 .. = ⇒  ..  =  .. T T  ρ n = ⇒ S I = SS n ˜ .  .   . 1 T − T T 2 T = I ( ρ s N N · n ˜ = S S S I n ) I I N N N s N | {z } | {z } | {z } n ˜ I (N ×1) S (3×1) (N ×3) 10.3 Rekonstrukcija 3D oblike Sistem enaˇcb za doloˇcanje normale na površino rešimo za vsako toˇcko (x,y) in tako dobimo sliko normal na površino n(x, y), s pomoˇcjo katere lahko na veˇc naˇcinov rekonstruiramo 3D obliko objekta kot sliko povšine oz. globine z (x, y). Najenostavnejši naˇcin je, da integriramo sliko normal n T = [ n x y z 0 , n , n ] po poljubni krivulji od izhodišˇca ( x, y0 ) do izbrane toˇcke (x,y): Z Z x n ( u , y ) n (x, v) y z x x ( x , y ) = du + dv x0 nz(u, y) y0 nz(x,v) Sliko površine z(x,y) naprimer lahko doloˇcimo z analizo odvodov na površino ∂ z/∂ x in ∂ z/∂ y, ki morata biti v vsaki toˇcki (x,y) pravokotna na normalo n (slika 10.2). Iz pripadajoˇcih enaˇcb za sliko velikosti XY zgradimo linearen sistem Mz = p, katerega rešitev je slika površine z(x,y). 10.4 Vaje z rešitvami 151 Matrika M ima dimenzije (2XY × XY ), vektor p pa (2XY × 1). Matrika M je velika, a redka matrika (veˇcina vrednosti je enaka 0), zato za manjšo obremenitev delovnega spomina uporabimo knjižnico scipy.sparse za definicijo redke matrike s funkcijo csc_matrix(), vrednosti pa v matriko vnašamo posamezno z naslavljanjem vrstic in stolpcev. Rešitev sistema dobimo s funkcijo lsqr() v knjižnici scipy.sparse.linalg. Parcialna odvoda na Parcialne odvode izraˇcunamo s konˇcnimi Združimo linearne površino sta vektorja diferencami in za vsako toˇcko (x,y) enaˇcbe za vse toˇcke pravokotna na dobimo dve linearni enaˇcbi: (x, y) in dobimo normalo n: rešitev sistema: zx = (x + 1,y, z(x + 1,y)) − (x,y, z(x,y)) zx = ∂ z/∂ x (1,0,z(x + 1,y) − z(x, y)) Mz = p → z zy = (x,y + 1,z(x,y + 1)) − (x,y,z(x,y)) y = ∂ z / ∂ y M ∈ {0,nz, −nz} ⇒ ⇒ ( 0 , 1 , z ( x , y + 1 ) − z ( x , y )) n ◦ z x = 0 p ∈ { n x , n y } → n x + n z ( z ( x + 1 , y ) − z ( x , y )) = 0 T − 1 T → n ◦ z y = 0 → z = ( M M ) M p → n y + n z ( z ( x , y + 1 ) − z ( x , y )) = 0 Slika 10.2: Rekonstrukcija 3D oblike z analizo parcialnih odvodov, ki naj bodo pravokotni na normalo površine – slednji pogoj omogoˇca zasnovo linearnega predoloˇcenega sistema enaˇcb, katerega rešitev vrne 3D obliko. 10.4 Vaje z rešitvami Gradivo za vajo v owl.zip vsebuje datoteko lights.txt, v kateri so podane smeri svetil glede na objekt, slike objekta owl.k.tif, pri ˇcemer je k = 0, . . . , 11, in pripadajoˇco masko objekta owl.mask.tif. Koda vaje je dostopna na Git repozitoriju. Najprej uvozimo Python knjižnice: 1 import scipy.ndimage as ni 2 import matplotlib.pyplot as plt 3 import matplotlib.cm as cm 4 import numpy as np 5 import scipy as sp 6 from scipy.interpolate import interpn 152 Poglavje 10. Rekonstrukcija 3D oblik 7 from rvlib import * Vaja 10.1 Napišite funkcijo za branje tekstovne datoteke s smermi svetil: 1 def readTxt( iFileName ): 2 # Tu napišite python kodo funkcije 3 return oLightDir kjer parameter iFileName predstavlja ime datoteke za branje (’lights.txt’). Datoteko odprete in zaprete s funkcijama open() in close(), posamezne vrstice pa preberete s funkcijo readline(). Za pretvorbo števil iz besedila v numeriˇcni tip lahko uporabite funkciji split() in float(). Funkcija vrne matriko oLightDir, ki bo imela za dano datoteko ’lights.txt’ dimenzije 12 × 3. ■ Funkcija readTxt(iFileName) naj v Pythonu prebere tekstovno datoteko s podanim imenom iFileName, kjer vsaka vrstica vsebuje tri številske vrednosti (loˇcene s presledki), jih pretvori v matriko s tremi stolpci in jih shrani v numpy array oLightsDir; najprej inicializira prazno polje tipa numpy.array, nato bere datoteko vrstico po vrstico, vsako vrstico razdeli na posamezne številke (kot float zapis), jih preoblikuje v vrstico matrike (1 × 3) in jih vertikalno dodaja v obstojeˇco matriko (np.vstack), dokler ne prebere vseh vrstic, na koncu pa vrne konˇcno matriko smeri svetil v obliki numpy arraya dimenzije N × 3, kjer N predstavlja število prebranih vrstic. Rešitev lahko zapišemo: 1 def readTxt( iFileName ): 2 # naloži smeri svetil 3 oLightsDir = np.array([]) 4 with open( iFileName, "r") as f: 5 while True: 6 line = f.readline() 7 if not line: 8 break 9 # preberi in preoblikuj vrednosti 10 numbers = [float(x) for x in line.split()] 11 numbers = np.array(numbers).reshape( (1,3) ) 12 if oLightsDir.size == 0: 13 oLightsDir = numbers 14 else: 15 oLightsDir = np.vstack( (oLightsDir,np.array(numbers)) ) 16 return oLightsDir 17 18 19 # preizkus funkcije 20 S = readTxt( os.path.join(DATA, "owl/lights.txt" )) 21 # preveri, da so dimenzije 12x3 22 print(S.shape) Izpis v ukazni vrstici je: (12, 3) 10.4 Vaje z rešitvami 153 Vaja 10.2 Ustvarite spremenljivko tipa list in zaporedoma od k=0,...,11 naložite vse dane barvne slike owl.k.tif. Sliki z indeksom k pripada smer svetila v matriki oLightDir z indeksom [k,:]. V spremenljivko numpy.array naložite tudi sliko maske owl.mask.tif. • Izraˇcunajte povpreˇcno barvno sliko, npr. iMeanImage. • Pretvorite vse slike v sivinske slike in jih shranite v spremenljivko tipa numpy.array, npr. iImages z dimenzijami Y × X × 12, kjer sta X,Y dimenziji 2D slike. • Pretvorite sliko maske v sivinsko sliko, npr. iMask. ■ 1 # naloži masko 2 iMask = rvlib.colorToGray( rvlib.loadImage( "./owl/owl.mask.tif" ) ) 3 # naloži barvne slike 4 iImages = [] 5 for i in range(12): 6 iImages.append( rvlib.loadImage( f"./owl/owl.{i}.tif" ) ) 7 # izraˇ cunaj povpreˇ cno sliko 8 iMeanImage = np.mean(np.dstack(iImages).reshape( 9 [*iImages[0].shape[:2], 12, 3]).astype(’float’), axis=2).astype(’uint8’) 10 # pretvori barvne v sivinske slike 11 iImagesG = [colorToGray(iImage) for iImage in iImages] 12 # prikaži masko in povpreˇ cno barvno sliko 13 rvlib.showImage(iMask, ’Slika maske’) 14 rvlib.showImage(iMeanImage, ’Povpreˇ cna barvna slika’) Rezultat je prikazan na sliki 10.3. (a) (b) Slika 10.3: Prikaz (a) povpreˇcne barvne slike preko vseh 12 slik in (b) slika maske podroˇcja zanimanja. Vaja 10.3 Napišite funkcijo, ki izraˇcuna normale na površino objekta n iz treh slik objekta: 1 def computeNormals( iImages, iMask, iLightDir ): 2 # Tu napišite python kodo funkcije 3 return oNormals kjer je iImages matrika dimezij Y × X × 3 (X,Y sta dimenziji 2D slike), iMask je maska 154 Poglavje 10. Rekonstrukcija 3D oblik objekta in iLightDir matrika 3 × 3 s smermi osvetlitve, ki ustrezajo slikam v matriki iImages. Funkcija vrne 3D matriko nenormaliziranih normal ˜ n dimenzij Y × X × 3 v spremenljivki oNormals. Preizkusite delovanje funkcije s poljubnimi tremi vhodnimi slikami (N = 3), pri tem pa pozorno izberite vhodne slike glede na pripadajoˇce smeri osvetlitve s j. Prikažite povpreˇcno barvno sliko iMeanImage in izrišite normale v obliki vektorskega polja s funkcijo quiver() v knjižnici matplotlib.pyplot. Preverite ali so smeri normal smiselne glede na dano sliko 3D oblike. ■ Funkcija computeNormals(iImages, iMask, iLightDir) izraˇcuna normale površine ob- jekta s fotometriˇcno stereo tehniko na podlagi veˇc slik pod razliˇcnimi svetlobnimi pogoji. Najprej preveri število vhodnih svetlobnih smeri ( + iLightDir ) in izraˇcuna psevdoinverz matrike smeri S: ˇce so podane natanko tri smeri in je zato matrika smeri kvadratna uporabi navadno inverzijo ( −1 S), sicer pa psevdoinverz prek metode najmanjših kvadratov ( + T −1 T S = ( S S ) S ). Nato slike (iImages) preoblikuje v 2D matriko (pikslov × slik) in izraˇcuna normale kot linearno kombinacijo intenzitet pikslov s koeficienti iz + S. Piksle zunaj maske (iMask) nastavi na niˇc in normale vrne v obliki 3D matrike (višina × širina × 3), kjer zadnja dimenzija predstavlja komponente normal (nx,ny ,nz) za vsako toˇcko površine. Rešitev zapišemo kot: 1 def computeNormals( iImages, iMask, iLightDir ): 2 """Izracun normal na povrsino objekta iz treh ali vec slik""" 3 if iLightDir.shape[0] == 3: 4 Sinv = np.linalg.inv( iLightDir.transpose() ) 5 else: 6 S = np.dot( iLightDir.transpose(), iLightDir ) 7 Sinv = np.dot( np.linalg.inv(S), iLightDir.transpose() ) 8 Sinv = Sinv.transpose() 9 shapeImages = iImages.shape 10 iImages = iImages.reshape( (iImages.shape[0]*iImages.shape[1],iImages.shape[2])) 11 oNormals = np.dot( iImages, Sinv ) 12 oNormals[iMask.flatten() == 0] = 0 13 oNormals = oNormals.reshape( (shapeImages[0],shapeImages[1],3) ) 14 return oNormals 15 16 # preizkus funkcije 17 idx = [1, 5, 9] # izberemo tri vrednosti med 0 in 11 18 # pripravimo vhodne spremenljivke 19 iImagesG_in = np.dstack( [iImagesG[idx[i]] for i in range(len(idx))] ) 20 iLightsDir_in = S[idx,:] 21 # izraˇ cun normal 22 oNormals = computeNormals( iImagesG_in.astype(’float’), iMask, iLightsDir_in ) 23 # prikažemo komponente normal kot RGB sliko 24 oNormalsRGB = ( oNormals - np.mean(oNormals) ) / np.std(oNormals) 25 thrZ = 6.0 26 oNormalsRGB[oNormalsRGB>thrZ] = thrZ 27 oNormalsRGB[oNormalsRGB<-thrZ] = -thrZ 28 oNormalsRGB = 255.0 * ( oNormalsRGB - np.min(oNormalsRGB) ) / \ 29 ( np.max(oNormalsRGB) - np.min(oNormalsRGB) ) 30 rvlib.showImage(oNormalsRGB.astype(’uint8’), ’Komponente normal kot RGB slika’) 31 # prikažemo normale kot vektorje 32 my, mx = np.where(iMask>0) 33 my = my[::10]; mx= mx[::10] 34 plt.imshow(np.mean(iImagesG_in, axis=2)) 35 plt.xlabel(’x’); plt.ylabel(’y’) 36 gx, gy = np.meshgrid(range(iImagesG_in.shape[1]), range(iImagesG_in.shape[0]), 10.4 Vaje z rešitvami 155 indexing=’xy’) 37 plt.quiver( gx[my,mx], gy[my,mx], \ 38 oNormals[my,mx,0], \ 39 oNormals[my,mx,1], color=’r’,scale=10000) 40 plt.suptitle(’Slika normal’) Rezultat je prikazan na sliki 10.4. Glede na intuicijo lahko iz slik razberemo, da ima model sovice privzdignjen trebušˇcek, torej bodo komponente normal v ˇcelni smeri približno niˇc v središˇcu trebušˇcka in usmerjene navzven v neposredni okolici. (a) (b) Slika 10.4: Prikaz (a) komponent vektorja normale kot RGB vrednosti oz. RGB sliko in (b) povpreˇcna sivinska slika in vrisani vektorji normal (prvi dve komponenti). Vaja 10.4 Napišite funkcijo, ki izraˇcuna albedo oz. odbojnost ρ površine objekta s pomoˇcjo slike nenormaliziranih normal ˜ n: 1 def computeAlbedo( iNormals, iMask ): 2 # Tu napišite python kodo funkcije 3 return oAlbedo kjer je iNormals matrika z dimezijami Y × X × 3 (X,Y sta dimenziji 2D slike, vsaka normala n ˜ pa ima tri komponente), iMask je maska objekta z dimenzijami Y × X . Funkcija vrne 2D matriko oz. sliko albeda ρ z dimenzijami Y × X v spremenljivki oAlbedo. Zagotovite, da bodo vrednosti v oAlbedo na obmoˇcju [0, 1]. Preizkusite delovanje funkcije za sliko normal, ki ste jo izraˇcunali pri prejšnji nalogi. ■ Albedo oz. odbojnost doloˇcimo kot magnitudo vektorja ocenjenih normal na površino: 1 def computeAlbedo( iNormals, iMask ): 2 """Izracunaj albedo iz nenormaliziranih normal""" 3 oAlbedo = np.sqrt( np.sum(iNormals**2.0, axis=2) ) 4 oAlbedo[ iMask == 0 ] = 0 5 oAlbedo = oAlbedo / np.max( oAlbedo ) 6 return oAlbedo 7 8 # preizkus funkcije 9 oAlbedo = computeAlbedo( oNormals, iMask ) 156 Poglavje 10. Rekonstrukcija 3D oblik 10 rvlib.showImage( oAlbedo, ’Albedo’ ) Rezultat prikazuje slika 10.5, pri ˇcemer manjše vrednosti albeda sovpadajo s temnimi barvami modela sovice, veˇcje vrednosti pa s svetlimi barvami. Slika 10.5: Slika parametra albedo oz. odbojnost ρ . Vaja 10.5 Napišite funkcijo, ki rekonstruira 3D obliko objekta oz. vsaki toˇcki na objektu pripiše globino z(x,y) z analizo odvodov na površino in reševanjem linearnega sistema enaˇcb: 1 def computeDepthLinSys( iNormals, iMask ): 2 # Tu napišite python kodo funkcije 3 return oDepth kjer je iNormals matrika dimezij Y × X × 3, iMask je maska objekta z dimenzijami Y × X . Poskrbite, da bodo normale iNormals podane v normalizirani obliki n. Pri tej nalogi rešujete linearni sistem enaˇcb Mz = p, pri ˇcemer matriko M (dimenzije 2XY × XY ) inicializirate z ukazom csc_matrix() v knjižnici scipy.sparse, elemente pa v matriko vpisujte posamezno pri ustreznih indeksih. Rešitev sistema enaˇcb dobite s klicem funkcije lsqr() v knjižnici scipy.sparse.linalg. Vaša funkcija naj vrne 2D matriko z globino z(x,y) dimenzij Y × X v spremenljivki oDepth. Preizkusite delovanje funkcije s sliko normal, ki ste jo izraˇcunali pri prvi nalogi. Prikažite rekonstruirano 3D površino s funkcijo plot_surface() v knjižnici mpl_toolkits.mplot3d in obarvajte površino glede na povpreˇcno barvno sliko iMeanImage. ■ Funkcija computeDepthLinSys(iNormals, iMask) rekonstruira 3D globino iz normal s fotometriˇcne stereo tehnike z reševanjem linearnega sistema parcialnih diferencialnih enaˇcb. Najprej identificira veljavne piksle z masko ( 2N×N iMask ) in pripravi redko matriko M ∈ (kjer je N R število aktivnih toˇck), ki kodira diskretne aproksimacije parcialnih odvodov ∂ z n z ny x − = ∂ in = − za vsako ∂ x n z ∂ y n z toˇcko, kjer (nx, ny,nz) predstavljajo komponente normal. Sistem Mz = v se reši z metodo najmanjših kvadratov (scipy.sparse.linalg.lsqr), kjer z predstavlja neznane vrednosti globine, nato pa se rezultat normalizira in preslika nazaj v 2D prostor, pri ˇcemer ohrani samo vrednosti znotraj maske. Konˇcna globina se vrne kot 2D polje z enakimi dimenzijami kot vhodna maska. Rešitev zapišemo: 1 def computeDepthLinSys( iNormals, iMask ): 2 """Izraˇ cunaj globino z reševanjem linearnega sistema enaˇ cb""" 3 from scipy.sparse import csc_matrix 10.4 Vaje z rešitvami 157 4 from scipy.sparse import linalg as splinalg 5 # doloˇ ci velikosti matrik 6 my, mx = np.where(iMask>0) 7 Ncols = my.size 8 Nrows = 2*Ncols 9 # inicializiraj spremenljivke 10 rcData = [] 11 addToM = lambda x : rcData.append( x ) 12 v = np.zeros( (Nrows,1) ) 13 index = np.zeros_like( iMask ).astype(’uint32’) 14 index[my,mx] = range(mx.size) 15 # sprehodi se preko vseh elementov 16 for i in range(mx.size): 17 nx = iNormals[my[i],mx[i],0] 18 # obrni znak zaradi menjave koordinatnega sistema 19 ny = -iNormals[my[i],mx[i],1] 20 nz = iNormals[my[i],mx[i],2] 21 # parcialni odvod po x 22 if index[my[i],mx[i]+1]>0: 23 addToM( (2*i,i,1) ) 24 addToM( (2*i,index[my[i],mx[i]+1],-1) ) 25 v[ 2*i,0 ] = nx / nz 26 # parcialni odvod po y 27 if index[my[i]+1,mx[i]]>0: 28 addToM( (2*i+1,i,1) ) 29 addToM( (2*i+1,index[my[i]+1,mx[i]],-1) ) 30 v[ 2*i+1,0 ] = ny / nz 31 v[np.isnan(v)] = 0 32 # definiraj redko matriko 33 rows = [rcData[i][0] for i in range(len(rcData))] 34 cols = [rcData[i][1] for i in range(len(rcData))] 35 data = [rcData[i][2] for i in range(len(rcData))] 36 M = csc_matrix((data, (rows, cols)), shape=(Nrows, Ncols)) 37 # reši sistem enaˇ cb 38 b = splinalg.lsqr(M,v.flatten(),show=True) 39 oDepth_t = b[0] - np.min(b[0]) 40 # ustvari izhodno polje 41 oDepth = np.zeros_like( iMask ).astype(’float’) 42 oDepth[my,mx] = oDepth_t 43 return oDepth 44 45 # preizkus funkcije 46 oDepth = computeDepthLinSys( oNormals, iMask ) 47 # prikazi kot globinsko sliko 48 rvlib.showImage(oDepth, ’oDepth’) V ukazni vrstici se izpišejo iteracije reševanja sistema enaˇcb: LSQR Least-squares solution of Ax = b The matrix A has 95314 rows and 47657 columns damp = 0.00000000000000e+00 calc_var = 0 atol = 1.00e-06 conlim = 1.00e+08 btol = 1.00e-06 iter_lim = 95314 Itn x[0] r1norm r2norm Compatible LS Norm A Cond A 158 Poglavje 10. Rekonstrukcija 3D oblik 0 0.00000e+00 1.288e+03 1.288e+03 1.0e+00 8.2e-04 ... 10 -4.60457e+00 1.013e+03 1.013e+03 7.9e-01 1.2e-02 6.4e+00 2.0e , → +01 ... 444 -8.38808e+01 9.861e+02 9.861e+02 7.7e-01 9.2e-06 4.2e+01 4.1e , → +03 445 -8.38773e+01 9.861e+02 9.861e+02 7.7e-01 8.9e-06 4.2e+01 4.1e , → +03 ... istop = 2 r1norm = 9.9e+02 anorm = 4.9e+01 arnorm = 4.8e-02 itn = 604 r2norm = 9.9e+02 acond = 5.6e+03 xnorm = 4.9e+03 Rezultat doloˇcanje 3D oblike je slika globine (ang. depth image), kot prikazuje slika 10.6a ki jo lahko nadalje pretvorimo v 3D trikotniško mrežo, dodamo barvno teksturo (npr. povrpreˇcno barvno sliko iMeanImage) in prikažemo v 3D oseh – smiselnost rekonstrukcije enostavno preverimo na tem prikazu (slika 10.6b). (a) (b) Slika 10.6: Prikaz (a) slike globine in njena (b) upodobitev v obliki 3D površine z originalno barvno teksturo. 10.5 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.1: To je moj odgovor. . Naloga 10.1 Prikažite sliko normal ˜ n kot barvno sliko, pri ˇcemer poskrbite za ustrezno pretvorbo vrednosti komponent normal na obmoˇcje [0, 255]. Naloga 10.2 Popravite funkcijo computeNormals() tako, do bo omogoˇcala izraˇcun normal na površino objekta n iz poljubnega števila slik objekta N. Vhodni parameter iImages naj bo matrika dimezij Y × X × N (X ,Y sta dimenziji 2D slike, N pa število slik z razliˇcno osvetlitvijo), iMask binarna maska objekta z dimenzijami Y × X in iLightDir matrika N × 3 s smermi osvetlitve, ki ustrezajo slikam v matriki iImages. Funkcija vrne 3D matriko nenormaliziranih normal ˜ n dimenzij 10.5 Naloge in vprašanja 159 Y × X × 3 v spremenljivki oNormals. Preizkusite delovanje funkcije z vsemi danimi vhodnimi slikami (N = 12) in pripadajoˇcimi smermi osvetlitve s j . Prikažite povpreˇcno barvno sliko iMeanImage in izrišite normale v obliki vektorskega polja s funkcijo quiver(). Na katerem delu objekta se smeri normal izboljšajo glede na smeri normal, ki ste jih doloˇcili le s tremi slikami? Obrazložite odgovor. Naloga 10.3 Razširite funkcijo computeNormals() tako, da bo omogoˇcala robusten izraˇcun normal na površino objekta n z uteževanjem s sivinsko vrednostjo slik, tj. osnovna enaˇcba za zapis linearnega sistema ima obliko 2 T I = I j ρ ( s · n). Preizkusite delovanje funkcije z vsemi danimi j j vhodnimi slikami (N = 12) in pripadajoˇcimi smermi osvetlitve s j. Prikažite povpreˇcno barvno sliko iMeanImage in izrišite normale v obliki vektorskega polja s funkcijo quiver(). Na katerem delu objekta se smeri normal izboljšajo glede na smeri normal, ki ste jih doloˇciti le s tremi slikami? Obrazložite odgovor. Naloga 10.4 Uporabite posodobljeno funkcijo computeNormals() za rekonstrukcijo 3D površine objekta z vsemi vhodnimi slikami (N = 12) in doloˇcite sliko globine z(x,y) s funkcijo compute DepthLinSys() . Prikažite rekonstruirano 3D površino s funkcijo in jo obarvajte glede na pov-preˇcno barvno sliko iMeanImage. Naloga 10.5 Napišite funkcijo, ki rekonstruira 3D obliko objekta oz. vsaki toˇcki na objektu pripiše globino z(x, y) z integriranjem normal n po poljubni krivulji: 1 def computeDepthIntegrate( iNormals, iMask ): 2 # Tu napišite python kodo funkcije 3 return oDepth kjer je iNormals matrika dimezij X ×Y × 3, iMask je maska objekta z dimenzijami X ×Y . Poskr-bite, da bodo normale iNormals podane v normalizirani obliki n. Funkcija vrne 2D matriko z globino z(x,y) dimenzij X × Y v spremenljivki oDepth. Preizkusite delovanje funkcije s sliko normal, ki ste jo izraˇcunali pri prvi nalogi. Prikažite rekonstruirano 3D površino s funkcijo surf(). 160 Poglavje 10. Rekonstrukcija 3D oblik Rezultati nekaterih nalog: 11. Sledenje in analiza gibanja Na podlagi zaporedja slik zajetih z eno ali veˇc kamerami lahko analiziramo gibanje objektov v 3D prostoru in s to informacijo naˇcrtujemo povratno-zanˇcno vodenje, manipulacijo, sledenje ter izvajamo rekonstrukcijo in detekcijo teh objektov. Postopek analize gibanja obiˇcajno sestavlja veˇc osnovnih postopkov, npr. postopek za robustno zaznavanje objektov zanimanja, iskanje korespondenc med zaporedno zajetimi slikami in poravnava slik. Pri vaji boste naˇcrtali postopek sledenja objekta zanimanja oz. tarˇce z uporabo Lucas-Kanade poravnave slik. 11.1 Parametrizacija preslikave Pri tem postopku za sledenje uporabljamo parametriˇcni model preslikave 2 2 → T : R R med zaporedno zajetima 2D slikama T I k k − ( x ) in I 1 ( x ) ( x = [ x , y ]), kjer je prva slika zajeta ob ˇcasu t(k), druga pa ob t(k − 1) in pri ˇcemer je t(k) > t(k − 1). Parametriˇcni model T (x; p) je npr. translacija, podobnostna, afina ali projektivna preslikava (p ima 2, 3, 6 oz. 8 parametrov). Privzamemo, da se med dvema zaporednima slikama I1 in I2 ohranja svetlost slikovnih elementov I1(x) = I2( T 1 ( x ; p )) + n ( x ) in za mero podobnosti uporabimo vsoto kvadradov razlik med slikama. 11.2 Linearizacija nelinearne kriterijske funkcije Optimalne parametre ⋆ p tako dobimo z minimizacijo izraza p⋆ 2 = arg min [ I ( ( x ; )) − I ( x )] ∑ 2 p 1 T . p x Ker priˇcakujemo majhne premike tarˇce med slikama I1 in I2 ( p → 0) lahko išˇcemo le optimalen korak ⋆ ∆ p v parametrih preslikave ∆ p⋆ 2 = arg min [ I ( ( ; + p − ( x ∑ 2 x p ∆ )) T I1 )] , ∆ p x 1 2 n ( x ) ∼ N ( 0 , σ ) je Gaussov šum. 162 Poglavje 11. Sledenje in analiza gibanja parametre preslikave pa dobimo kot ⋆ ⋆ p ← p + ∆ p . Optimalni korak ∆ p izraˇcunamo brez optimiza-cije tako, da izraz I2(T (x; p + ∆p)) aproksimiramo s Taylorjevo vrsto kot I 2( T (x; p + ∆p)) ≈ I2(T (x; p)) + ∇I2(T (x; p)) ∆ p , ∂ ∂ T p kjer je 2 ∇ I 2( T ( x; p)) = [ Ix , Iy ] gradient slike I2 pri trenutni preslikavi T (x ; p). Izraz ∂ / T ∂ p predstavlja Jacobijevo matriko prvih odvodov in zavisi od izbrane parametriˇcne preslikave T . Razliko med slikama v prvi enaˇcbi lahko zapišemo v matriˇcni obliki kot It + B∆p, kjer je It = I 2(T (p)) − I1; Ik je N × 1 vektor sivinskih vrednosti slike. Izrazi za B pri translaciji in afini preslikavi so podani spodaj. Translacija: Afina preslikava:   x T x + t x 11 a a 12 t T ( x x ; p ) = ( x ; p ) = y y   + t a a t y 21 22 y 1 ∂ ∂ T = = T 1 0 x y 0 0 1 0 ∂ p p ∂ 0 1 0 x 0 y 0 1 ∇I ∂ ∂ T T I 2 x y ∇ = [ , I ] I I y 2 = [ x x,Iy x ,Ix, Iyy , Ix,Iy ∂ ] p ∂ p B = [I x, Iy] ∈ N ×2 N×6 R B = [ I x X , I y X , I x Y , I y Y , I x , I y ] ∈ R 11.3 Zaprto-zanˇ cna posodobitev preslikave Optimalen korak ⋆ ∆ p v parametrih preslikave se nato izraža kot ∆ p⋆ T = arg min ( I t ∆ + B p )(It ∆ + B p) , ∆ p njegovo vrednost pa dobimo tako, da odvajamo gornjo enaˇcbo na ∆p in odvod izenaˇcimo z 0. Dobimo linearen sistem enaˇcb za ⋆ ∆ p, ki ima rešitev ∆ ⋆ −1 T T p = − B B BI t . Postopek Lucas-Kanade izvajamo iterativno tako, da zaporedoma izraˇcunavamo optimalen korak ∆ p⋆ ⋆ in posodabljamo parametre preslikave p = p + ∆ p do nekega maksimalnega števila iteracij i i−1 i max ali dokler ∥p − p ∥ i i − < ε. Potek postopka je prikazan na spodnji sliki. 1 11.4 Piramidna implementacija Postopek Lucas-Kanade konvergira v najbližji lokalni minimum, zato je potrebno za pri veˇcjih premikih tarˇce med slikami uporabiti dober zaˇcetni približek parametrov p. Dober zaˇcetni približek lahko dobimo z uporabo piramidne decimacije tako, da poravnamo sliki najprej pri redkejšem vzorˇcenju, parametre pa nato izboljšamo pri gostejšem vzorˇcenju slike. Gostoto vzorˇcenja obiˇcajno spreminjamo s korakom ×2, glede na spremembo koraka vzorˇcenja pa enako spreminjamo tudi parametre p. Sledenje tarˇci v videu izvedemo tako, da v prvi sliki roˇcno ali avtomatsko doloˇcimo središˇce tarˇce (xc, yc) in velikost pravokotnega okna (w , h) tako, da okno vsebuje celotno tarˇco. ˇ Ce poznamo središˇce tarˇce v sliki I1, potem z Lucas-Kanade postopkom poravnamo sliko I1 na naslednjo zajeto sliko I2 in glede na dobljeno preslikavo T (x; p) posodobimo središˇce tarˇce v sliki I2. Postopek ponavljamo za vse naslednje zaporedne pare zajetih slik {Ik ,Ik } − + 1 , k = 1 , . . . , T 1. 2 ∂ I h i 2 ∂ I ∇ I 2 x y ∂ = , 2 = [I ,I ] x ∂x 11.5 Vaje z rešitvami 163 11.5 Vaje z rešitvami Gradivo za vajo vsebuje videa video1.avi in video2.avi zajeta z nadzorno kamero s pogledom na ploˇcnik iz ptiˇcje perspektive. V Python skripti funkcije.py so pripravljene funkcije, ki jih boste potrebovali za izvedbo vaje, pri kateri boste naˇcrtali postopek za sledenje gibanja oseb, ki se sprehajajo po ploˇcniku. Koda vaje je dostopna na Git repozitoriju. Poleg gradiva boste za nalaganje videov v Python boste potrebovali knjižnico ffmpeg, ki jo najdete na spletnem naslovu https://www.ffmpeg.org/download.html. V Windows okolju si program ffmpeg.exe skopirajte v vašo delovno mapo. V MacOS okolju v ukazni vrstici izvedite ukaz: brew install ffmpeg V Linux okolju, na primer Ubuntu, v ukazni vrstici izvedite: sudo apt update sudo apt install ffmpeg V ukazni vrstici (vsa okolja) pojdite v delovno mapo vaje in preverite delovanje tako, da izvedete: ffmpeg -version Odziv v primeru uspešne namestitve bo približno takole: ffmpeg version 7.1.1 Copyright (c) 2000-2025 the FFmpeg developers ... Nato v Python uvozimo knjižnice: 1 import matplotlib.pyplot as plt 2 import numpy as np 3 import matplotlib.animation as animation 4 5 from rvlib import loadVideo, showVideo, showImage, transformImage, \ 164 Poglavje 11. Sledenje in analiza gibanja 6 drawPathToFrame, imageGradient, transAffine2D, decimateImage2D, \ 7 interpolate1Image2D Vaja 11.1 Okvirje v video datoteki video1.avi naložite v Python spremenljivko numpy.array s pomoˇcjo funkcije loadVideo() in prikažite s funkcijo showVideo() kot prikazuje naslednja skripta: 1 # naloži video 2 oVideo = loadVideo( ’video1.avi’ ) 3 # prikaži video 4 showVideo( oVideo ) 5 # velikost polja videa 6 print(’\nVelikost polja videa:\n’, oVideo.shape) ■ V ukazni vrstici se izpisujejo podatki o branju videa: Berem okvir 1 ... frame= 50 fps=0.0 q=-0.0 size= 59535KiB time=00:00:02.32 bitrate=210220.1 , → kbits/s speed=4.61x ... Berem okvir 178 ... koncano! [out#0/image2pipe @ 0x159732d70] video:215055KiB audio:0KiB subtitle:0KiB other , → streams:0KiB global headers:0KiB muxing overhead: 0.000000% frame= 177 fps= 64 q=-0.0 Lsize= 215055KiB time=00:00:07.08 bitrate=248832.0 , → kbits/s speed=2.56x Velikost polja videa: (576, 720, 177) Iz izpisa je razvidno, da ima slika v videu velikost 576 ×720 slikovnih elementov in da je sestavljena iz 177 okvirjev. Z ukazom ffmpeg -i video1.avi lahko ugotovimo, da se video predvaja s 25 okvirji na sekundo (fps; ang. frames per second). Izris okvirja videa v prikaznem oknu je na sliki 11.1. Slika 11.1: Okvir videa video1.avi pretvorjen v sivinsko sliko. 11.5 Vaje z rešitvami 165 Vaja 11.2 Napišite funkcijo za poravnavo dveh sivinskih slik iImgFix in iImgMov z Lucas-Kanade postopkom: 1 def regLucasKanade( iImgFix, iImgMov, iMaxIter ): 2 # Tu napišite python kodo funkcije 3 return oPar, oImgReg kjer je iImgMov premiˇcna slika, ki jo želimo poravnati na fiksno sliko iImgFix. Parameter iMaxIter predstavlja število korakov Lucas-Kanade postopka. Funkcija vrne dvovrstiˇcni vektor parametrov T oPar s translacijama p = [ t , t ] in preslikano premiˇcno sliko v spremenljivki x y oImgReg. Vhodni sliki iImgFix in iImgMov in izhodna slika oImgReg imajo enake dimenzije. Preizkusite delovanje poravnave na prvi sliki v videu video1.avi tako, da sliko preslikate s parametri T p = [ − 5 , 3 ] in nato poravnate z Lucas-Kanade postopkom. Ocenjeni parametri preslikave oPar morajo biti približno enaki dejanskim parametrom p. ■ Funkcija regLucasKanade() implementira iterativni Lucas-Kanade algoritem za poravnavo slik, ki minimizira razliko med fiksno oz. referenˇcno sliko (iImgFix) in preslikano premiˇcno sliko (iImgMov) z optimizacijo afine transformacije. Algoritem najprej izraˇcuna gradienta Gx in G y gibljive slike, nato v vsaki iteraciji: (1) preslika gibljivo sliko in njene gradiente z aktualnimi parametri p = (tx,ty), (2) izraˇcuna razliko slik It , (3) sestavi Jacobijevo matriko B iz gradientov, (4) reši linearni sistem T T B B ∆p = B It ∆ za posodobitev parametrov p ← p +p z metodo najmanjših kvadratov. Postopek se ponavlja do konˇcnega števila iteracij (iMaxIter), pri ˇcemer se na koncu vrnejo optimizirani parametri in poravnana slika. Rešitev lahko zapišemo: 1 def regLucasKanade(iImgFix, iImgMov, iMaxIter, oPar = (0,0), iVerbose=True): 2 """Postopek poravnave Lucas-Kanade""" 3 # pretvori vhodne slike v numpy polja tipa float 4 iImgType = np.asarray(iImgMov).dtype 5 iImgFix = np.array(iImgFix, dtype=’float’) 6 iImgMov = np.array(iImgMov, dtype=’float’) 7 # doloˇ ci zaˇ cetne parametre preslikave 8 oPar = np.array(oPar) 9 # izracunaj prva odvoda slike 10 Gx, Gy = imageGradient(iImgMov) 11 # v zanki iterativno posodabljaj parametre 12 for i in range(iMaxIter): 13 # doloci preslikavo pri trenutnih parametrih 14 oMat2D = transAffine2D(iTrans=oPar) 15 # preslikaj premicno sliko in sliki odvodov 16 iImgMov_t = transformImage(iImgMov, oMat2D) 17 Gx_t = transformImage(Gx, oMat2D) 18 Gy_t = transformImage(Gy, oMat2D) 19 # izracunaj sliko razlike in sistemsko matriko 20 I_t = iImgMov_t - iImgFix 21 B = np.vstack((Gx_t.flatten(), Gy_t.flatten())).transpose() 22 # reši sistem enaˇ cb 23 invBtB = np.linalg.inv(np.dot(B.transpose(), B)) 24 dp = np.dot(np.dot(invBtB, B.transpose()), I_t.flatten()) 25 # posodobi parametre 26 oPar = oPar + dp.flatten() 27 if iVerbose: print(’iter: %d’ % i, ’, oPar: ’, oPar) 28 # doloˇ ci preslikavo pri konˇ cnih parametrih 29 oMat2D = transAffine2D(iTrans=oPar) 30 # preslikaj premiˇ cno sliko 166 Poglavje 11. Sledenje in analiza gibanja 31 oImgReg = transformImage(iImgMov, oMat2D).astype(iImgType) 32 # vrni rezultat 33 return oPar, oImgReg 34 35 # preizkus funkcije 36 oPar = [0, 1] # doloci parametre sintetiˇ cne afine preslikave 37 iImgFix = oVideo[:,:,0] 38 iImgMov = transformImage(iImgFix, transAffine2D(iTrans = oPar)) # preslikava 39 # klici Lucas-Kanade poravnavo slik 40 oPar, oImgReg = regLucasKanade(iImgFix, iImgMov, 20) 41 print(’parameters: ’, oPar) 42 # prikaz slike razlik pred in po poravnavi 43 showImage(iImgFix.astype(’float’) - iImgMov.astype(’float’), ’Razlika pred poravnavo’) 44 showImage(iImgFix.astype(’float’) - oImgReg.astype(’float’), ’Razlika po poravnavi’) V preizkusu funkcije smo ustvarili afino preslikavo s translacijo (t x,ty) = (0,1) in jo uporabili za preslikavo prvega okvirja slike. Preslikana slika je premiˇcna, originalna slika pa fiksna za namen preizkušanja funkcije. Funkcija v ukazni vrstici izpiše: iter: 0 , oPar: [ 0.000607-0.1348281] iter: 1 , oPar: [ 0.00130562 -0.2682225 ] iter: 2 , oPar: [ 0.00194521 -0.3935767 ] iter: 3 , oPar: [ 0.00244312 -0.50428944] iter: 4 , oPar: [ 0.00275654 -0.59677659] iter: 5 , oPar: [ 0.00289502 -0.67136986] iter: 6 , oPar: [ 0.00289771 -0.73070864] ... iter: 17 , oPar: [ 0.00110536 -0.95931672] iter: 18 , oPar: [ 0.00098431 -0.9652549 ] iter: 19 , oPar: [ 8.75018467e-04 -9.70296550e-01] parameters: [ 8.75018467e-04 -9.70296550e-01] Ocenjeni parametri preslikave po 20 iteracijah so [0, 000875, −0.97029], kar dobro kompenzira prej ustvarjeni premik (t x,ty) = (0,1). V sliki 11.2 je prikazana slika razlik sivinskih vrednosti med fiksno sliko ter premiˇcno pred in po Lukas-Kanade poravnavi, kjer opazimo da poravnava kompenzira razlike v sivinskih vrednostih na celotnem delu slike. Vaja 11.3 Napišite funkcijo za poravnavo dveh sivinskih slik iImgFix in iImgMov s piramidnim Lucas-Kanade postopkom: 1 def regPyramidLK( iImgFix, iImgMov, iMaxIter, iNumScales ): 2 # Tu napišite python kodo funkcije 3 return oPar, oImgReg kjer je iImgMov premiˇcna slika, ki jo želimo poravnati na fiksno sliko iImgFix. Vhodni parameter iNumScales (s) je število zaporednih decimacij vhodnih slik iImgMov in iImgFix s korakom 1:2, ki sestavljajo piramido slik s progresivno manjšim vzorˇcenjem. Lucas-Kanade postopek izvedemo zaporedno na slikah od najmanjšega do najveˇcjega vzorˇcenja, hkrati pa pri prehodu iz enega na drugi nivo posodobimo zaˇcetne parametre kot s s−1 p = 2 × p. Parameter iMaxIter predstavlja število korakov Lucas-Kanade postopka v vsakem nivoju piramide. 11.5 Vaje z rešitvami 167 (a) (b) Slika 11.2: Slika razlik sivinskih vrednosti med fiksno sliko ter premiˇcno (a) pred in (b) po Lukas-Kanade poravnavi. Preizkusite delovanje poravnave na prvi sliki v videu video1.avi tako, da sliko preslikate s parametri T p = [ 50 , − 30 ] in nato poravnate s piramidnim Lucas-Kanade postopkom. Ocenjeni parametri preslikave oPar morajo biti približno enaki dejanskim parametrom p. ■ Funkcija regPyramidLK() implementira piramidno razliˇcico Lucas-Kanade algoritma, ki izvaja poravnavo slik na veˇc nivojih Gaussove piramide. Najprej zgradi piramido slik z uporabo funkcije decimateImage2D, kjer se na vsakem nivoju loˇcljivost zmanjša za faktor 2. Poravnava se zaˇcne na najnižji loˇcljivosti (najmanjša slika v piramidi), kjer se inicializirajo parametri trans-formacije p = (0,0), nato pa se postopek rekurzivno izvaja proti višjim loˇcljivostim, pri ˇcemer se parametri transformacije na vsakem nivoju skalirajo z faktorjem 2 in natanˇcneje prilagodijo z osnovnim Lucas-Kanade algoritmom (regLucasKanade). Ta pristop omogoˇca robustno poravnavo kljub velikim premikom in izboljšano konvergenco algoritma, saj se najprej rešuje groba poravnava na nizkih loˇcljivostih, nato pa le natanˇcne prilagoditve na visokih loˇcljivostih. Rešitev lahko zapišemo: 1 def regPyramidLK(iImgFix, iImgMov, iMaxIter, iNumScales, iVerbose=True): 2 """Piramidna implementacija poravnave Lucas-Kanade""" 3 # pretvori vhodne slike v numpy polja tipa float 4 iImgFix = np.array(iImgFix, dtype=’float’) 5 iImgMov = np.array(iImgMov, dtype=’float’) 6 # pripravi piramido slik 7 iPyramid = [ (iImgFix, iImgMov) ] 8 for i in range(1,iNumScales): 9 # decimiraj fiksno in premicno sliko za faktor 2 10 iImgFix_2 = decimateImage2D(iImgFix, i) 11 iImgMov_2 = decimateImage2D(iImgMov, i) 12 # dodaj v seznam 13 iPyramid.append((iImgFix_2,iImgMov_2)) 14 # doloˇ ci zaˇ cetne parametre preslikave 15 oPar = np.array((0,0)) 16 # izvede poravnavo od najmanjše do najveˇ cje loˇ cljivosti slik 17 for i in range(len(iPyramid)-1,-1,-1): 18 if iVerbose: 19 print(’PORAVNAVA Z DECIMACIJO x{}’ .format(2**i)) 20 # posodobi parametre preslikave na višjo loˇ cljivost 168 Poglavje 11. Sledenje in analiza gibanja 21 oPar = oPar * 2.0 22 # izvedi poravnavo pri trenutni loˇ cljivosti 23 oPar, oImgReg = regLucasKanade(iPyramid[i][0], iPyramid[i][1], \ 24 iMaxIter, oPar, iVerbose=iVerbose) 25 # vrne konˇ cne parametre in poravnano sliko 26 return oPar, oImgReg 27 28 # preizkus funkcije 29 oPar = [0, 1] # doloci parametre sintetiˇ cne afine preslikave 30 iImgFix = oVideo[:,:,0] 31 iImgMov = transformImage(iImgFix, transAffine2D(iTrans = oPar)) 32 # kliˇ ci piramidno Lucas-Kanade poravnavo slik 33 oPar, oImgReg = regPyramidLK(iImgFix, iImgMov, 20, 3) 34 print(’parameters: ’, oPar) 35 # prikaz slike razlik pred in po poravnavi 36 showImage(iImgFix.astype(’float’) - iImgMov.astype(’float’), ’Razlika pred poravnavo’) 37 showImage(iImgFix.astype(’float’) - oImgReg.astype(’float’), ’Razlika po poravnavi’) Podobno kot pri vaji 11.2 smo v preizkusu funkcije smo ustvarili afino preslikavo s translacijo (t x ,ty) = (0, 10) in jo uporabili za preslikavo prvega okvirja slike, nato pa jo poravnali na originalno sliko. Funkcija v ukazni vrstici izpiše: PORAVNAVA Z DECIMACIJO x4 iter: 0 , oPar: [-0.00920315 -0.09918953] iter: 1 , oPar: [-0.01683559 -0.21310131] ... iter: 19 , oPar: [-0.00567002 -2.3217941 ] PORAVNAVA Z DECIMACIJO x2 iter: 0 , oPar: [-0.00909037 -4.70598619] iter: 1 , oPar: [-0.0072928-4.75628114] ... iter: 19 , oPar: [ 1.80525073e-05 -9.99871471e+00] parameters: [ 1.80525073e-05 -9.99871471e+00] Ocenjeni parametri preslikave po 3×20 iteracijah so [0,000081,−9.99871], kar dobro kompenziraj prej ustvarjeni premik (tx,ty) = (0, 10). V sliki 11.3 je prikazana slika razlik sivinskih vrednosti med fiksno sliko ter premiˇcno pred in po Lukas-Kanade poravnavi. Poravnava kompenzira razlike v sivinskih vrednostih na veˇcini dela slike, le gornji rob kaže razlike, in sicer zaradi naˇcin priprave premiˇcne slike s preslikovanjem iz domene originalne slike, kjer je sivinska vrednost privzeto enaka 0. Vaja 11.4 Napišite funkcijo za sledenje izbrani tarˇci v videu s piramidnim Lucas-Kanade postopkom: 1 def trackTargetLK( iVideoMat, iCenterXY, iFrameXY ): 2 # Tu napišite python kodo funkcije 3 return oPathXY kjer je iVideoMat video v matriki z dimenzijami X × Y × T ; X,Y sta prostorski, T pa ˇcasovna os. Vhodna parametra iCenterXY in iFrameXY sta dvovrstiˇcna vektorja, ki ustrezata središˇcu 11.5 Vaje z rešitvami 169 (a) (b) Slika 11.3: Slika razlik sivinskih vrednosti med fiksno sliko ter premiˇcno (a) pred in (b) po piramidni Lukas-Kanade poravnavi. tarˇce (xc, yc) in velikost pravokotnega okna (w,h) tarˇce. Funkcija vrne sled tarˇce v matriki oPathXY dimenzij 2 × T , tj. središˇce tarˇce v vsakem okvirju videa. Preizkusite postopek za sledenje tarˇce na videu video1.avi. V prvem okvirju videa ima tarˇca središˇce T T ( x , y ) = [ 34 , 371 ] in se nahaja v pravokotnem oknu ( w , h ) = [ 40 , 40 ]. Prikažite c c video in vrišite sled tarˇce z uporabo funkcije drawPathToFrame() v knjižnici rvlib tako, kot je prikazano na sliki 11.4. ■ Funkcija trackTargetLK() implementira sledenje tarˇce v video zaporedju z uporabo pir- amidnega Lucas-Kanade algoritma. Algoritem glede na vhodne vrednosti inicializira zaˇcetno pozicijo tarˇce iCenterXY in obmoˇcje interesa velikosti iFrameXY, nato za vsak par zaporednih okvirjev v video zaporedju: (1) vzorˇci obmoˇcje tarˇce v trenutnem in naslednjem okvirju z bilin-earno interpolacijo (interpolate1Image2D), (2) izvede piramidno poravnavo (regPyramidLK) z maksimalno 30 iteracijami na 3 nivojih piramide za doloˇcitev premika ∆p = (∆x,∆y), (3) posodobi pot tarˇce N ×2 oPathXY ∈ R z dodajanjem novih koordinat. 1 def trackTargetLK(iVideoMat, iCenterXY, iFrameXY, iVerbose=True): 2 """Postopek sledenja Lucas-Kanade""" 3 # pretvori vhodni video v numpy polje 4 iVideoMat = np.asarray(iVideoMat) 5 iCenterXY = np.array(iCenterXY) 6 # definiraj izhodno spremenljivko 7 oPathXY = np.array(iCenterXY.flatten()).reshape((1,2)) 8 # definiraj koordinate v tarci 9 gx, gy = np.meshgrid(range(iFrameXY[0]), range(iFrameXY[1])) 10 gx = gx - float(iFrameXY[0]-1)/2.0 11 gy = gy - float(iFrameXY[1]-1)/2.0 12 # zazeni LK preko vseh zaporednih okvirjev 13 for i in range(1,iVideoMat.shape[-1]): 14 # vzorcni tarco v dveh zaporednih okvirjih 15 iImgFix = interpolate1Image2D(iVideoMat[...,i-1], \ 16 gx+oPathXY[-1,0], gy+oPathXY[-1,1]) 17 iImgMov = interpolate1Image2D(iVideoMat[...,i], \ 18 gx+oPathXY[-1,0], gy+oPathXY[-1,1]) 19 # zaženi piramidno LK poravnavo 20 oPar, oImgReg = regPyramidLK(iImgFix, iImgMov, 30, 3, iVerbose=False) 170 Poglavje 11. Sledenje in analiza gibanja 21 # shrani koordinate 22 oPathXY = np.vstack((oPathXY, oPathXY[-1,:] + oPar.flatten())) 23 print(’koordinate tarce: ’, oPathXY[-1,:]) 24 # vrni spremenljivko 25 return oPathXY 26 27 # preizkus funkcije 28 oPathXY = trackTargetLK(oVideo[...,:], (33,370), (40,40)) 29 # prikazi tarˇ co in pot do tarˇ ce v razliˇ cnih okvirjih videa 30 drawPathToFrame(oVideo, oPathXY, iFrame=1, iFrameSize=(40,40)) 31 drawPathToFrame(oVideo, oPathXY, iFrame=100, iFrameSize=(40,40)) 32 drawPathToFrame(oVideo, oPathXY, iFrame=170, iFrameSize=(40,40)) Konˇcni rezultat funkcije trackTargetLK() je matrika poti tarˇce skozi vse okvirje video zaporedja, ki omogoˇca vizualizacijo gibanja tarˇce z uporabo pomožne funkcije drawPathToFrame. Rezultat prikazuje slika 11.4. Tarˇca se pretežno premika od levega do desnega roba slike, kar je razvidno tudi iz izpisanih koordinat toˇck trajektorije tarˇce, ki jih doloˇci funkcija trackTargetLK(): [ 36.55881156 370.14133975] [ 44.07195853 370.7327816 ] [ 52.52228952 371.23034241] [ 61.19684308 371.21558487] [ 69.80159895 371.20814793] [ 77.24933027 371.53277712] [ 84.28658295 372.10005505] [ 92.32973791 373.3278628 ] [100.67993777 374.94910248] [109.45608417 377.45841741] [117.53670261 379.57140207] [124.75423269 380.55816688] [131.91940812 381.01102918] ... [707.31132931 387.65142739] [708.62080031 388.2718046 ] Analiza gibanja Na osnovi analize teh koordinat lahko doloˇcimo tudi dolžino poti tarˇce, hitrost in pospešek tarˇce. Kljuˇcni koraki vkljuˇcujejo pretvorbo koordinat v fizikalne koliˇcine (dolžina, hitrost in pospešek), diskretno aproksimacijo odvodov s konˇcnimi razlikami, loˇcen izraˇcun za x in y komponento ter kombinacijo komponent v skupno dolžino, hitrost in pospešek. Za natanˇcnejše rezultate bi lahko uporabili interpolacijo za zglajenje poti, statistiˇcno obdelavo za zmanjšanje šuma ali Kalmanov filter za boljše sledenje. Hitrost je v tem primeru izražena v pikslih na sekundo (pix/s), za pretvorbo v realne enote pa je potreben kalibracijski faktor (npr. cm/pixel). Dolžina poti Dolžino poti tarˇce izraˇcunamo kot vsoto razdalj med zaporednimi toˇckami: n−1 q L 2 2 = ( x − x ) ∑ i + 1 i + ( y i − + 1 y i ) i=1 11.5 Vaje z rešitvami 171 kjer je: • L- celotna prehojena pot [pix] • (xi, yi)- koordinate tarˇce v i-tem okvirju • n- število meritev Primer izraˇcuna za prve tri toˇcke iz podanih podatkov: P 1 = (36.56,370.14) P 2 = (44.07,370.73) P 3 = (52.52,371.23) Izraˇcunamo delno pot: q L1−2 (44.07 − 2 2 = 36 . 56 ) + ( 370 . 73 − 370 . 14 ) p √ = 2 2 7 . 51 + 0 . 59 = 56.58 + 0.35 = 7.55 pix q L 2 2 2 − 3 = ( 52 . 52 − 44 . 07 ) + ( 371 . 23 − 370 . 73 ) p √ = 2 2 8 . 45 + 0 . 50 = 71.40 + 0.25 = 8.46 pix L1−3 = 7.55 + 8.46 = 16.01 pix Pretvorbo v realne enote lahko izvedemo, ˇce poznamo kalibracijski faktor (npr. 0.1 cm/pix): Lreal = L × kalibracijski faktor = 16.01 × 0.1 = 1.60 cm Hitrost in pospešek Za izraˇcun hitrosti in pospeška tarˇce iz zaporedja koordinat r(t) = (x(t),y(t)) pri znani frekvenci posnetka ( f ps = 25), uporabimo naslednje enaˇcbe: • ˇ ˇ Casovni razmik Cas med posameznimi okvirji: 1 1 ∆t = = = 0.04 s f ps 25 • Hitrost Vektor hitrosti v vsakem ˇcasovnem koraku dobimo kot: x ( t + ∆ t ) − x ( t ) y ( t + ∆ t ) − y ( t ) v(t) = (vx(t), vy(t)) = , ∆ t ∆t Magnitudo hitrosti dobimo kot: q v 2 2 ( t ) = v x ( t ) + v y ( t ) • Pospešek Vektor pospeška dobimo kot: v x ∆ ( t + t ) − v x y ∆ ( t ) v ( t + t ) − v ( t ) a y ( t) = ( ax ( t ) , a y ( t)) = , ∆t ∆t 172 Poglavje 11. Sledenje in analiza gibanja Magnitudo pospeška kot: q a 2 2 ( t ) = a x ( t ) + a y ( t ) Primer izraˇcuna hitrosti za prva dva okvirja za podane koordinate: r1 = (36.56,370.14) r2 = (44.07,370.73) Hitrost: 44.07 − 36.56 vx = = 187.75 pix/s 0.04 370.73 − 370.14 vy = = 14.75 pix/s 0.04 v p 2 2 = 187 . 75 + 14 . 75 ≈ 188.33 pix/s Sledenje tarˇci z Lucas-Kanade postopkom (video1.avi) Slika 11.4: Sledenje tarˇce na videu video1.avi, kjer je bila tarˇca v prvem okvirju levo roˇcno oznaˇcena z zelenim okvirjem, ki pa ga nadalje samodejno premikamo glede na Lukas-Kanade poravnavo med sosednjimi okvirji videa. 11.6 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.1: To je moj odgovor. . Naˇcrtajte postopek za sledenje tarˇce na videu video2.avi. V prvem okvirju videa ima tarˇca središˇce T T ( x , y ) = [ 287 , 415 ] in se nahaja v pravokotnem oknu ( w , h ) = [ 45 , 30 ]. Za ustrezno c c sledenje tarˇci v celotnem videu bo potrebno nadgraditi Lucas-Kanade postopek oz. naˇcrtati nov, bolj robusten postopek sledenja. Pri tem se lahko zgledujete po rešitvah, ki so bile omenjene na predavanjih (namesto kvadratne katera od bolj robustnih razliˇcic Lucas–Kanade kriterijske funkcije, odštevanje ozadja, Kalmanov filter, ipd.). Ideje za nadgradnjo postopka lahko naprimer ˇcrpate tudi iz preteklih laboratorijskih vaj ali iz poljubnih drugih virov. Naloga 11.1 Osnovni postopek Lucas-Kanade, ki smo ga uporabili za sledenje tarˇci v videu video1.avi, ne sledi tarˇci v celotnem videu video2.avi. Prikažite okvir, kjer postopek odpove in obrazložite zakaj. 11.6 Naloge in vprašanja 173 Naloga 11.2 Naˇcrtajte izboljšan postopek za sledenje tarˇce v videu video2.avi. Z besedami na kratko opišite vaš izboljšani postopek sledenja tarˇce. Ocena naloge bo odvisna od inovativnosti vašega postopka sledenja in od tega, kako dolgo in kako natanˇcno vaš postopek sledi tarˇci. Naloga 11.3 Zaženite vaš postopek sledenja na videu video2.avi in doloˇcite pot tarˇce oPathXY. Prikažite video in vrišite sled tarˇce tako, kot je prikazano na sliki 11.5. Sledenje tarˇci (video2.avi) Slika 11.5: Zahtevnejši primer sledenje tarˇci na videu video2.avi, kjer je bila tarˇca v prvem okvirju levo roˇcno oznaˇcena z zelenim okvirjem, ki pa ga nadalje samodejno premikamo glede na robustno Lukas-Kanade poravnavo med sosednjimi okvirji videa. 12. Lokalizacija in mapiranje okolja Ce na robota namestimo ˇ tipalo okolice in ga vozimo po prostoru ter zajemamo meritve okolice, lahko na podlagi teh meritev izvedemo hkratno lokalizacijo robota v prostoru in izgradnjo mape tega prostora (slika 12.1). Numeriˇcne tehnike, ki to omogoˇcajo, oznaˇcujemo pod skupnim imenom SLAM1. Prve tovrstne tehnike so kot meritev okolice uporabljale senzor razdalje (ultrazvoˇcni, laserski (LIDAR), ipd.), v tej vaji pa se bomo posvetili tehnikam, ki temeljijo na vizualni informaciji – sliki zajeti s kamero, ki je na robotu. Te tehnike oznaˇcujemo tudi kot Visual SLAM in zajemajo tudi vizualno odometrijo (VO2 3 ) in 3D rekonstrukcijo na podlagi gibanja (SfM). Slika 12.1: Visual SLAM: primer rekonstrukcije 3D prostora in trajektorije [24]. Pri tej vaji boste na podlagi zaporedja slik zajetih z eno premiˇcno kamero rekonstruirali 3D 1 SLAM: ang. Self Localization And Mapping 2VO: ang. Visual Odometry 3 SfM: ang. Structure from Motion 176 Poglavje 12. Lokalizacija in mapiranje okolja mapo prostora in doloˇcili trajektorijo gibanja kamere v prostoru. S to informacijo lahko kasneje naˇcrtujemo povratno-zanˇcno vodenje robota s kamero tako, da se robot zaveda prostora okoli sebe, npr. aktivno izogiba oviram v prostoru. 12.1 Algoritem stereo vida Osnovni gradniki postopka Visual SLAM so (i) rekonstrukcija 3D scene na podlagi triangulacije, podobno kot pri rekonstrukciji s stereo parom kamer; (ii) ocena esencialne matrike preslikave med dvema slikama; in (iii) analiza esencialne matrike za doloˇcanje premika oz. relativne poze kamere (R, T ). ˇ Ce želimo pridobiti metriˇcno rekonstrukcijo 3D prostora je potrebna tudi kalibracija kamere (intrinziˇcni parametri K) in prisotnost kalibracijskih objektov vsaj v enem paru slik, v nasprotnem primeru bo rekonstrukcija 3D prostora skalirana s poljubno skalo (brez kalibracijskega objekta) ali deformirana s poljubno projektivno preslikavo (brez kalibracije kamere). Triangulacija i i i i T 3D položaja toˇcke P = [ X , Y , Z , 1 ] v prostoru je možna na podlagi dveh projekcij te toˇcke i i i T i i i T p = [ u , v , 1 ] in p = [ u , v , 1 ] na dveh slikah iz dveh pogledov (slika 12.2) 1 1 1 2 2 2 s pripadajoˇcimi projekcijskimi matrikami i M M × P in z dimenzijami 3 4. Za rekonstrukcijo 1 2 zgradimo linearni sistem enaˇcb: i [ p ] × M 1 1 i i · P i = 0 → A · P = 0 , (12.1) [p ]×M 22 pri ˇcemer ima matrika A dimenzije 6 × 4. Notacija [x]× predstavlja pretvorbo vektorja x = [ T x , x , x ] v poševno simetriˇcno matriko: 1 2 3   0 − x 3 x 2 [x]× =  x 3 0 −x1  . (12.2) −x x 21 0 Išˇcemo netrivialno rešitev sistema (12.1), ki minimizira i 2 i ∥ AP ∥ ob pogoju ∥ P∥ = 1. Rešitev dobimo tako, da poišˇcemo najmanjšo lastno vrednost T AA, pripadajoˇci lastni vektor pa predstavlja rešitev za i T P . To lahko storimo z uporabo SVD razcepa matrike A = U Σ V, kjer sta matriki U, V unitarni in Σ diagonalna matrika s singularnimi vrednostmi, tako, da poišˇcemo najmanjšo singularno vrednost in pripadajoˇci stolpec v matriki V . Esencialna matrika E opisuje epipolarno geometrijo kalibriranega stereo sistema kamer tako, da za normalizirane koordinate pripadajoˇcih projekcij ¯ p1 ↔ p ¯2, kjer normaliziramo kot p −1 ¯ = Kp j = 1, 2, velja enaˇcba: j j j p T ¯Ep ¯ , (12.3) = 0 2 1 matrika s strukturo E = [T ]×R pa kodira relativen položaj med kamerama. ˇ Ce normalizirane koordinate nadomestimo z originalnimi dobimo zvezo: ( −1 T −1 T −T −1 T K p ) EK p = 0 → K EK 2 2 p 1 1 ( ) p = 0 → pFp 2 2 1 1 2 1 = 0 , (12.4) kjer je F fundamentalna matrika. Enaˇcba (12.4) velja tudi v primeru, ko sta kameri nekalibrirani, saj vsebuje nenormalizirane koordinate p1 in p2. Enaˇcbi (12.3) in (12.4) predstavljata lastnost epipolarne geometrije, da vektorja ¯ p1 in ¯ p2 (in tudi p1 in p2) ležita v epipolarni ravnini (slika 12.2). Matriki E in F imata dimenzije 3 × 3 in rang 2, zato bo determinanta det E = 0 in det F = 0. 12.1 Algoritem stereo vida 177 Slika 12.2: Rekonstrukcija 3D položaja toˇcke P na podlagi dveh projekcij p 1 in p2. 12.1.1 Ocenjevanje esencialne matrike E Esencialno in fundamentalno matriko doloˇcimo na enak naˇcin z uporabo algoritma 4 osmih toˇck. Za dano množico pripadajoˇcih parov toˇck i i { p ↔ p }i=1,...,N lahko enaˇcbo (12.4) zapišemo kot: 1 2  N 1 T  ( p ⊗ p ) 1 2 ( i i T   p ⊗ p ) vec ( F ) = 0 , kjer je Q = .. → Q · vec 1 (F) = 0 . (12.5) 2   . ( N N T p ⊗ p ) 1 2 Notacija vec(F ) pomeni zapis elementrov matrike F v obliki vektorja. Simbol ⊗ pa predstavlja Kronekerjev produkt, npr.: p i i i i⊤ i i⊤ i⊤ T ⊗ p = u · p v · p 1 · p , (12.6) 1 2 1 2 1 2 2 ki iz dveh trovrstiˇcnih vektorjev (3 × 1) ustvari devetvrstiˇcni vektor (9 × 1). Za rešitev enaˇcbe (12.5) potrebujemo N ≥ 8 pripadajoˇcih parov toˇck. Rešitev dobimo z uporabo SVD razcepa, podobno kot pri triangulaciji, pri ˇcemer pa moramo zagotoviti, da bo detF = 0. To enostavno naredimo tako, da najmanjšo singularno vrednost v Σ = diag({σ j} j =1,2,3) postavimo na 0, npr:     σ 1 0 0 σ 1 0 0 F T T = U 0 0 V → F = U 0 0 V . (12.7)     σ 2 σ 2 0 0 σ 3 0 0 0 Numeriˇcno stabilnost zgoraj opisanega algoritma lahko izboljšamo s središˇcenjem in skaliran- jem koordinat i i { p } in { p } tako, da bo njihova srednja vrednost enaka 0, standardna deviacija pa √ 1 2 2. Za vsako toˇcko i in množico toˇck j = 1,2 naredimo preslikavo: p i √  x  s j 0 − s j µ j 2 i i y i i i ˜ = p − µ p = 0 s j j j , oziroma ˜ j  j − s j µ . j  p → p ˜ = T p j j j j (12.8) ρ j 0 0 1 kjer sta 1 i 1 i 2 µ j = ∑ p N i ρ in = ∥ p µ ) j j ∑ − ∥ N i j j . V naslednjem koraku s središˇcenimi in skaliranimi korespondenˇcnimi toˇckami i i { p ˜ ↔ p ˜ } ocenimo fundamentalno matriko ˜ F po enaˇcbah (12.5) in 1 2 4 ang. eight-point algorithm 178 Poglavje 12. Lokalizacija in mapiranje okolja (12.7). Nato kompenziramo središˇcenje in skaliranje, da dobimo fundamentalno matriko F = T T ˜ FT 1. ˇ Ce poznamo tudi intrinziˇcne parametre kamer (K1 in K2) lahko glede na (12.4) doloˇcimo 2 še esencialno matriko kot T E = KFK 2 1 . 12.1.2 Doloˇ canje poze (R, T ) iz esencialne matrike E Naj bo svetovni koordinatni sistem v kameri j = 1. Potem lahko pripadajoˇco projekcijsko matriko doloˇcimo kot M1 = K1[I |0]. Relativno pozo (R,T ) kamere j = 2 bomo doloˇcili iz esencialne matrike E = [T ]×R, pri ˇcemer obstajajo štiri možne rešitve [4, str. 262]: [R | T 1 T 1 ] = [ UWV|u3] [ T T R 2 T | 2] = [ UW V|u3 ] T (12.9) [R |T UWV u 3 3 ] = [ | −3] [ T T R | 4 T 4 UW ] = [ V| −u ] 3 kjer je:   0 − 1 0 W =  1 0 0  (12.10) 0 0 1 matrika rotacije za 90◦ T in u 3 zadnji stolpec matrike U v SVD razcepu E = U Σ V. Ker je SVD razcep veljaven tudi ˇce pomnožimo matriki T U in V z -1, se lahko zgodi, da bo imela katera od rotacijskih matrik detRi = −1; v tem primeru pomnožimo matriko z -1. Izmed štirih možnih poz bomo izbrali tisto fiziˇcno najbolj smiselno tako, da za vsako od rešitev [ i R | T ] , k = 1 , 2 , 3 , 4 s triangulacijo po enaˇcbi (12.1) doloˇcimo položaj toˇck v 3D prostoru { P} k k k in preštejemo vse toˇcke s pozitivno globino oz. razdaljo od zaslona. Rešitev k∗ z najveˇc takimi toˇckami je konˇcna rešitev (R,T ) = (Rk∗,Tk∗). 12.2 Lokalizacija in mapiranje okolja Za dan par slik bomo najprej ocenili esencialno matriko E na podlagi izloˇcenih korespondenˇcnih parov toˇck i i { p ↔ p }i=1,...,N in znanih intrinziˇcnih parametrov kamer K , j = 1, 2. Nato bomo 1 j 2 z analizo E doloˇcili relativno pozo (R, T ) med kamerama in, glede na kamero j = 1, doloˇcili projekcijski matriki: M1 = K1[I |0] in M2 = K2[R|T ] . (12.11) S tema projekcijskima matrikama in triangulacijo toˇck i i { p ↔ p }i=1,...,N po enaˇcbi (12.1) bomo 1 2 zgradili redek oblak 3D toˇck i P, ki bodo predstavljale mapo prostora. Z relativno pozo (R,T ) kamere j = 2 glede na svetovni koordinatni sistem v kameri j = 1 je doloˇcena tudi lokalizacija kamere v prostoru. Primer lokalizacije in mapiranja okolja na podlagi para slik je prikazan na sliki 12.3. Problem, ki ga pri tej vaji ne obravnavamo je kako poiskati pripadajoˇce pare toˇck med dvema slikama, kar potrebujemo za oceno esencialne matrike E. Obiˇcajno se poslužujemo detektorjev oslonilnih toˇck kot so Harrisov detektor oglišˇc in veˇcnivojskih LoG5 in FAST filtrov, okolico oslonilnih toˇck pa opišemo z deskriptorji SIFT6 7 8 , SURF , ORB, ipd. Pripadajoˇce pare toˇck nato doloˇcimo s primerjavo deskriptorjev in/ali uporabo postopka RANSAC tako, da veˇckrat zaporedoma ocenimo E na podlagi nakljuˇcno izbranih osmih pripadajoˇcih parov toˇck. 5 LoG: ang. Laplacian-of-Gaussian 6 SIFT: ang. Scale Invariant Feature Transform 7 SURF: ang. Speeded-Up Robust Features 8 ORB: ang. Oriented FAST and Rotated BRIEF 12.3 Vaje z rešitvami 179 Slika 12.3: Lokacija kamer in 3D mapa prostora kot oblak toˇck i i P na podlagi pripadajoˇcih toˇck p 1 in i p . 2 12.2.1 Posplošitev na n-pogledov Kako lokalizirati robota in graditi mapo prostora v primeru veˇc kot dveh pogledov? Opisani postopek lahko zaporedno nadaljujemo in dograjujemo mapo trianguliranih toˇck ter posodabljamo lokacijo kamere. Ker lahko iste 3D toˇcke vidimo iz veˇc kot dveh pogledov je smiselno narediti skupinski popravek9 mape prostora in že doloˇcenih poz kamer z nelinearno minimizacijo napake projekcije toˇck i P: arg min i i 2 ∥ M ∑ ∑ j j j j ( R , T ) P − p∥ , (12.12) {R j,Tj } i j pri ˇcemer upoštevamo le veljavne projekcije (toˇcka i mora biti vidna na sliki j). 12.3 Vaje z rešitvami Koda vaje je dostopna na Git repozitoriju. Gradivo za vajo vsebuje sliki 0001.jpg in 0002.jpg iste scene, zajete iz dveh pogledov. Dane so tudi koordinate pripadajoˇcih parov oslonilnih toˇck i i { p ↔ p }i=1,...,N v dveh datotekah matches0001 .txt in matches0002.txt. Obe sliki sta 1 2 bili zajeti z enako kamero, ki je bila kalibrirana. Intrinziˇcni parametri kamere so:   1379 , 74 0 760 , 35 K =  0 1382,08 503,41  . 0 0 1 Uvozimo Python knjižnice: 1 import matplotlib.pyplot as plt 2 from mpl_toolkits.mplot3d import Axes3D 9 ang. bundle adjustment 180 Poglavje 12. Lokalizacija in mapiranje okolja 3 from matplotlib.patches import FancyArrowPatch 4 from mpl_toolkits.mplot3d import proj3d 5 import numpy as np 6 import PIL.Image as im Vaja 12.1 Napišite funkcijo za triangulacijo pripadajoˇcih parov toˇck x1 in x2 v slikah 1 in 2: 1 def linearTriangulation( x1, x2, P1, P2 ): 2 # Tu napišite python kodo funkcije 3 return P kjer so toˇcke x1 in x2 podane v homogenih koordinatah v obliki matrik z dimenzijami 3 × N, N je število parov toˇck. Parametra P1 in P2 predstavljata 3 × 4 projekcijski matriki kamere 1 in 2. Funkcija naj vrne matriko P po enaˇcbi (12.1) trianguliranih toˇck v homogenih koordinatah. Matrika ima dimenzije 4 × N. Preizkusite delovanje triangulacije tako, da ustvarite sintetiˇcni projekcijski matriki P1 in P2 ter sintetiˇcne 3D toˇcke . Nato toˇcke projicirajte v 2D ravnino in z uporabo funkcije P* linearTriangulation() rekonstruirajte toˇcke P. Preverite, da se koordinate sintetiˇcnih in rekonstruiranih toˇck ujemajo. ■ Najprej pripravimo funkcijo cross2matrix() , ki pretvori 3D vektor v antisimetriˇcno (poševno- simetriˇcno) matriko tako, da lahko vektorski produkt izrazimo kot matriˇcno množenje: 1 def cross2matrix(x): 2 ’’’ 3 Antisimetricna matrika na podlagi trivrsticnega vektorja 4 5 Vrne antisimetricno matriko M, ki ustreza 3-vektorju x tako, da 6 velja M*y = cross(x,y) za vse 3-vektorje y. 7 8 Parameters 9 ---------- 10 x : numpy.ndarray 11 Vhodni 3-vektor 12 13 Returns 14 --------- 15 oMat : numpy.ndarray 16 3x3 matrika 17 ’’’ 18 return np.array( 19 ( 20 (0, -x[2], x[1]), 21 (x[2], 0, -x[0]), 22 (-x[1], x[0], 0) 23 ) 24 ) Funkcija linearTriangulation izvede linearno triangulacijo za rekonstrukcijo 3D toˇck iz njihovih projekcij v dveh slikah z uporabo projekcijskih matrik. Za vsako toˇcko sestavi linearni sistem enaˇcb AX = 0, kjer A vsebuje antisimetriˇcne matrike (izraˇcunane s cross2matrix) za 12.3 Vaje z rešitvami 181 projekcijske enaˇcbe x × 1 (P 1X) = 0 in x × 2 (P 2X) = 0. Rešitev sistema se doloˇci z uporabo SVD razcepa, kjer je 3D toˇcka X (v homogenih koordinatah) lastni vektor pri najmanjši singularni vrednosti. Rezultat se dehomogenizira z deljenjem z zadnjo koordinato, kar vrne normalizirane 3D koordinate. Funkcija zahteva, da so vhodne toˇcke x1 in x × 2 v homogenih koordinatah (oblika 3 N), projekcijski matriki P 1 in P 2 pa morata biti velikosti 3 × 4. Rešitev zapišemo: 1 def linearTriangulation(x1, x2, P1, P2): 2 ’’’ 3 Linearna triangulacija 4 5 Parameters: 6 ---------- 7 x1 : numpy.ndarray 8 Homogene koordinate toˇ ck v sliki 1 (3xN) 9 x2 : numpy.ndarray 10 Homogene koordinate toˇ ck v sliki 2 (3xN) 11 P1 : numpy.ndarray 12 Projekcijska matrika kamere 1 (3x4) 13 P2 : numpy.ndarray 14 Projekcijska matrika kamere 2 (3x4) 15 16 Returns 17 --------- 18 X : numpy.ndarray 19 Homogene koordinate 3D toˇ ck (4xN) 20 ’’’ 21 # preveri dimenzije polj 22 assert x1.shape[0] == x2.shape[0], ’Size mismatch of input points’ 23 assert x1.shape[1] == x2.shape[1], ’Size mismatch of input points’ 24 assert x1.shape[0] == 3, ’Arguments x1, x2 should be 3xN matrices (homogeneous coords)’ 25 assert P1.shape == (3, 4), ’Projection matrices should be of size 3x4’ 26 assert P2.shape == (3, 4), ’Projection matrices should be of size 3x4’ 27 28 num_points = x1.shape[1] 29 30 P = np.zeros((4,num_points)) 31 32 # linearni algoritem 33 for j in range(num_points): 34 # sestavi matriko linearnega homogenega sistema enacb 35 A1 = np.dot(cross2matrix(x1[:, j]), P1) 36 A2 = np.dot(cross2matrix(x2[:, j]), P2) 37 A = np.vstack((A1, A2)) 38 39 # resi linearni homogeni sistem enacb 40 _, _, vt = np.linalg.svd(A, full_matrices=False) 41 P[:,j] = vt[-1, :] 42 43 # dehomogeniziraj (P je izrazen v homogenih koordinatah) 44 45 return P / P[-1,:] Preizkus funkcije linearTriangulation je zasnovan tako, da najprej generira N = 10 na- kljuˇcnih 3D toˇck X v homogenih koordinatah (oblika 4 × N), kjer so z-koordinate premaknjene v ob-182 Poglavje 12. Lokalizacija in mapiranje okolja moˇcje [10,15] za bolj realistiˇcno sceno. Nato se definirajo dve projekcijski matriki P 1 in P 2 kamere, pri ˇcemer ima P 2 dodatni premik v x-smeri ( −100), kar simulira stereo konfiguracijo. Projekciji toˇck x 1 in x2 v slikah se izraˇcunata kot x1 = P 1X in x2 = P 2X. Funkcija linearTriangulation nato rekonstruira 3D toˇcke Xest iz projekcij: 1 # test linearne triangulacije 2 N = 10 # stevilo 3D tock 3 X = np.random.randn(4, N) # homogene koordinate 3D tock 4 X[2,:] = X[2,:] * 5 + 10 5 X[3,:] = 1 6 7 # projekcijske matrike kamere 1 in 2 8 P1 = np.array(( 9 (500, 0, 320, 0), 10 (0, 500, 240, 0), 11 (0, 0, 1, 0) 12 ) 13 ) 14 15 P2 = np.array(( 16 (500, 0, 320, -100), 17 (0, 500, 240, 0), 18 (0, 0, 1, 0) 19 ) 20 ) 21 22 # lokacije projiciranih toˇ ck v slikah 1 in 2 23 x1 = np.dot(P1, X) 24 x2 = np.dot(P2, X) 25 26 X_est = linearTriangulation(x1, x2, P1, P2) 27 28 print(’X_est-X=’) 29 print((X_est - X)) Konˇcno se izpiše razlika med originalnimi in rekonstruiranimi toˇckami X − est X, kar omogoˇca preverjanje natanˇcnosti algoritma: X_est-X= [[ 2.17603713e-14 -4.32986980e-14 -3.19744231e-14 1.06581410e-14 2.60902411e-14 -5.99520433e-15 -1.47659662e-14 -1.03250741e-14 -1.44328993e-14 1.82076576e-14] [ 1.22679644e-14 -3.44169138e-14 -2.30926389e-14 8.88178420e-15 1.93040028e-14 -4.55191440e-15 -8.65973959e-15 -4.44089210e-15 -9.99200722e-15 6.43929354e-15] [-6.03961325e-14 7.63833441e-14 7.63833441e-14 -5.32907052e-15 -3.28626015e-14 9.76996262e-15 4.26325641e-14 1.77635684e-15 3.19744231e-14 7.10542736e-15] [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00]] Razlike v rekonstruiranah koordinatah X_est, na podlagi dveh 2D projekcij, in dejanskimi koordinatami −16 X so reda 10, kar je na nivoju numeriˇcne napake za zapis z decimalno vejico tipa float32 . 12.3 Vaje z rešitvami 183 Vaja 12.2 Napišite funkcijo za oceno esencialne matrike E z algoritmom osmih toˇck (enaˇcbe (12.5, 12.7, 12.8)): 1 def estimateEssentialMatrix( x1, x2, K1, K2 ): 2 # Tu napišite python kodo funkcije 3 return E kjer so pripadajoˇci pari toˇck x1 in x2 v slikah 1 in 2 podani v homogenih koordinatah v obliki matrik z dimenzijami 3 × N, N je število parov toˇck. Parametra K1 in K2 predstavljata 3 × 4 matriki intrinziˇcnih parametrov kamer 1 in 2. Preizkusite delovanje z uporabo sintetiˇcnih podatkov, ki ste jih ustvarili pri prvi nalogi. Preverite, kakšen vpliv ima na oceno dodajanje Gaussovega šuma projiciranim toˇckam (s tem simulirate napako detektorjev oslonilnih toˇck). Ocenite algebrsko napako z izraˇcunom epipolarne omejitve v enaˇcbi (12.3) in geometrijske napake kot srednjo kvadratno razdaljo toˇck do epipolarne ˇcrte: 1 " # 2 N 1 ∑ d 2 i i 2 i i ( p , ℓ ) + d ( p , ℓ ) , N ⊥ 1 1 ⊥ 2 2 i=1 kjer sta i T i i i ℓ = F p in ℓ = F p epipolarni ˇcrti v slikah 1 in 2, d⊥ (p, ℓ) pa meri razdaljo med 2 2 1 1 toˇcko in premico v slikovni ravnini. Preverite vpliv Gaussovega šuma na algoritem osmih toˇck z in brez uporabe središˇcenja in skaliranja koordinat po enaˇcbi (12.8). ■ Najprej pripravimo funkcijo normalise2dpts(), ki izvede normalizacijo 2D homogenih toˇck tako, da prestavi njihov centroid v izhodišˇce koordinatnega sistema in skalira toˇcke tako, da je √ njihova povpreˇcna razdalja od izhodišˇca enaka 2. Najprej izraˇcuna centroid µ toˇck in povpreˇcno standardno deviacijo σ . Nato konstruira transformacijsko matriko T velikosti 3 × 3, ki vsebuje √ skalirni faktor s = 2/σ in translacijske komponente za premik centrioda v izhodišˇce. Kot izhod vrne transformirane toˇcke T · pts in uporabljeno transformacijsko matriko T . Rešitev zapišemo: 1 def normalise2dpts(pts): 2 ’’’ 3 Normalizacija 2D homogenih toˇ ck 4 5 Funkcija normalizira vsak set toˇ ck tako, da bo izhodišˇ ce v centroidu in 6 povpreˇ cna razdalja do toˇ ck od izhodišˇ ca sqrt(2) 7 8 Parameters: 9 ---------- 10 pts : numpy.ndarray 11 Polje homogenih koordinat 2D toˇ ck v sliki 1 (3xN) 12 13 Returns 14 --------- 15 new_pts : numpy.ndarray 16 Polje preslikanih homogenih koordinat 2D toˇ ck v sliki 1 (3xN) 17 T : numpy.ndarray 18 Transformacijska matrika 3x3: newpts = T * pts 19 ’’’ 20 num_points = pts.shape[-1] 21 184 Poglavje 12. Lokalizacija in mapiranje okolja 22 # centroid 23 mu = np.mean(pts[:2,:], axis=-1).reshape((2,1)) 24 # povpreˇ cna standardna deviacija 25 sigma = np.mean(np.sqrt(np.sum((pts[:2,:] - mu)**2.0, axis=0))) 26 s = np.sqrt(2) / sigma 27 28 T = np.array( 29 ( 30 (s, 0, -s * mu[0][0]), 31 (0, s, -s * mu[1][0]), 32 (0, 0, 1) 33 ) 34 ) 35 36 return np.dot(T, pts), T Ta normalizacija izboljša numeriˇcno stabilnost pri kasnejših izraˇcunih. Algoritem 8-toˇck za oceno fundamentalne matrike F rešuje linearni sistem enaˇcb Af = 0, kjer f vsebuje elemente funda-mentalne matrike. Za vsak par toˇck (x1 i,x2i ) zgradi vrstico matrike A z uporabo Kroneckerjevega produkta kron T ( x 1 , x 2 ) . Rešitev sistema se najde s SVD razcepom (dodatek A), kjer je F ob-i i likovana iz zadnjega stolpca matrike T V . Da zagotovi rang 2, funkcija izvede dodatni SVD razcep in nastavi najmanjšo singularno vrednost na 0. Ta postopek zagotovi, da fundamentalna matrika ustreza epipolarnim omejitvam T x 2 Fx1 = 0. Rešitev zapišemo: 1 def fundamentalEightPoint(x1, x2): 2 ’’’ 3 Algoritem 8-toˇ ck za oceno fundamentalne matrike F 4 5 Rang 3x3 matrike F je v splošnem enak 2, kar ta funkcija zagotavlja preko 6 nastavljanja singularnih vrednosti SVD razcepa. Funkcija ne vkljuˇ cuje normalizacije. 7 8 Referenca: "Multiple View Geometry" (Hartley & Zisserman 2000), Sect. 10.1 page 262. 9 10 Parameters: 11 ---------- 12 x1 : numpy.ndarray 13 Homogene 2D koordinate toˇ ck v sliki 1 (3xN) 14 x2 : numpy.ndarray 15 Homogene 2D koordinate toˇ ck v sliki 2 (3xN) 16 17 Returns 18 --------- 19 F : numpy.ndarray 20 Fundamentalna matrika (3x3) 21 ’’’ 22 # preveri dimenzije vhodnih spremenljivk 23 assert x1.shape == x2.shape, ’Vhodni polji koordinat nista enake velikosti’ 24 assert x1.shape[0], ’Vhodne spremenljivke niso 2D toˇ cke’ 25 assert x1.shape[-1] >= 8, ’Nezadostno število toˇ ck za izraˇ cun fundamentalne matrike (potrebujemo >=8)’ 26 27 num_points = x1.shape[-1] 28 12.3 Vaje z rešitvami 185 29 # izraˇ cunaj matriko A linearnega homogenega sistema, rešitev katere je vektor 30 # z elementi fundamentalne matrike 31 A = np.zeros((num_points, 9)) 32 for i in range(num_points): 33 A[i, :] = np.kron(x1[:, i], x2[:, i]).T 34 35 # reši linearni homogeni sistem enaˇ cb kot A*f=0 36 # korespondence x1<->x2 so eksaktne == rank(A)=8, torej rešitev obstaja 37 # ˇ ce so meritve koordinat x1, x2 obremenjene s šumom, potem je rank(A)=9, torej ne obstaja 38 # eksaktna rešitev in moramo poiskati rešitev z minimizacijo srednje kvadratne napake 39 _, _, Vt = np.linalg.svd(A, full_matrices=False) 40 F = np.reshape(Vt[-1, :], (3, 3)).T 41 42 # vsili det(F)=0 s projekcijo ocenjene matrike F na množico 3x3 singularnih matrik 43 u, s, vt = np.linalg.svd(F) 44 s[2] = 0 45 return np.dot(u, np.dot(np.diag(s), vt)) Izboljšana razliˇcica algoritma 8-toˇck vkljuˇcuje normalizacijo toˇck pred izraˇcunom fundament- alne matrike. Najprej uporabi funkcijo normalise2dpts za normalizacijo toˇck v obeh slikah, nato izraˇcuna fundamentalno matriko F na normaliziranih toˇckah z osnovnim algoritmom 8-toˇck. Na koncu kompenzira normalizacijo z uporabo transformacijskih matrik T T 1 in T 2 kot T 2 FT 1. Rešitev zapišemo: 1 def fundamentalEightPoint_normalized(x1, x2): 2 ’’’ 3 Oceni fundamentalno matriko glede na dane pripadajoˇ ce pare toˇ ck in 4 projekcijsko matriko kamere K (intrinziˇ cni parametri) 5 6 Parameters: 7 ---------- 8 x1 : numpy.ndarray 9 Homogene 2D koordinate toˇ ck v sliki 1 (3xN) 10 x2 : numpy.ndarray 11 Homogene 2D koordinate toˇ ck v sliki 2 (3xN) 12 13 Returns 14 --------- 15 F : numpy.ndarray 16 Fundamentalna matrika (3x3) 17 ’’’ 18 # normaliziraj vsak set toˇ ck tako, da bo izhodišˇ ce v centroidu in 19 # povpreˇ cna razdalja do toˇ ck od izhodišˇ ca sqrt(2) 20 x1_nh, T1 = normalise2dpts(x1) 21 x2_nh, T2 = normalise2dpts(x2) 22 23 # linearna rešitev 24 F = fundamentalEightPoint(x1_nh, x2_nh) 25 26 # kompenzacija normalizacije 186 Poglavje 12. Lokalizacija in mapiranje okolja 27 return np.dot(T2.T, np.dot(F, T1)) V primeru, da poznamo intrinziˇcne parametre kamer K1 in K2, lahko ocenimo esencialno matriko E iz parov toˇck. Postopamo tako, da najprej ocenimo fundamentalno matriko F z normaliz-iranim algoritmom 8-toˇck. Nato esencialno matriko izraˇcunamo kot T E = K 2FK 1, kar predstavlja transformacijo iz prostora normaliziranih slikovnih koordinat v prostor esencialne geometrije. Esencialna matrika E kodira relativno rotacijo in translacijo med kamerama in se uporablja za rekonstrukcijo 3D scene iz veˇc pogledov. Rešitev zapišemo: 1 def estimateEssentialMatrix(x1, x2, K1, K2): 2 ’’’ 3 Oceni esencialno matriko glede na dane pripadajoˇ ce pare toˇ ck in 4 projekcijsko matriko kamere K (intrinziˇ cni parametri) 5 6 Parameters: 7 ---------- 8 x1 : numpy.ndarray 9 Homogene 2D koordinate toˇ ck v sliki 1 (3xN) 10 x2 : numpy.ndarray 11 Homogene 2D koordinate toˇ ck v sliki 2 (3xN) 12 K1 : numpy.ndarray 13 Projekcijska matrika kamere 1 (3x4) 14 K2 : numpy.ndarray 15 Projekcijska matrika kamere 2 (3x4) 16 17 Returns 18 --------- 19 E : numpy.ndarray 20 Esencialna matrika (3x3) 21 ’’’ 22 # oceni fundamendalno matriko K2^-T * F * K1^-1 23 F = fundamentalEightPoint_normalized(x1, x2) 24 25 # izraˇ cunaj esencialno matriko iz ocenjene fundamentalne matrike 26 return np.dot(K2.T, np.dot(F, K1)) Funkcija distPoint2EpipolarLine() izraˇcuna povpreˇcno razdaljo med toˇckami in njihovimi epipolarnimi ˇcrtami, kar služi kot mera napake pri oceni fundamentalne matrike F . Najprej združi vse toˇcke iz obeh slik (x1 in x2) v homogenem formatu, nato pa izraˇcuna epipolarne ˇcrte za vsako toˇcko: za toˇcke v prvi sliki uporabi T Fx2, za toˇcke v drugi sliki pa Fx1. Razdaljo med toˇcko in epipolarno ˇcrto izraˇcuna po formuli za razdaljo toˇcke do premice v homogenih koordinatah |ax+by+c| √ , kjer so a, b in c koeficienti epipolarne ˇcrte. Konˇcni rezultat je povpreˇcna 2 2 a + b kvadratna razdalja, normalizirana s številom toˇck, kar omogoˇca kvantificiranje napake pri ujemanju epipolarnih omejitev T x 2 Fx1 = 0. Rešitev zapišemo: 1 def distPoint2EpipolarLine(F, x1, x2): 2 ’’’ 3 Izracunaj razdaljo med tocko in epipolarno crto 4 5 Parameters: 6 ---------- 7 F : numpy.ndarray 8 Fundamentalna matrika 12.3 Vaje z rešitvami 187 9 x1 : numpy.ndarray 10 Homogene 2D koordinate toˇ ck v sliki 1 (3xN) 11 x2 : numpy.ndarray 12 Homogene 2D koordinate toˇ ck v sliki 2 (3xN) 13 14 Returns 15 --------- 16 dist2 : float 17 vsota kvadriranih razdalj od tock do epipolarne crte, normalizirana s stevilom koordinat tock 18 ’’’ 19 num_points = x1.shape[-1] 20 21 homog_points = np.hstack((x1, x2)) 22 epi_lines = np.hstack((np.dot(F.T, x2), np.dot(F, x1))) 23 24 denom = epi_lines[0,:]**2.0 + epi_lines[1,:]**2.0 25 return np.sqrt( 26 np.sum( 27 (np.sum(epi_lines*homog_points, axis=0)**2.0) / denom 28 ) / num_points 29 ) Testna koda ocenjuje delovanje funkcij fundamentalEightPoint() in fundamentalEight Point_normalized() na simuliranih podatkih. Najprej generira N = 40 nakljuˇcnih 3D toˇck X v homogenih koordinatah, ki jih postavi v realistiˇcen obmoˇcje (z-koordinate med 5 in 15). Dve kameri s projekcijskima matrikama P1 in P2 (druga kamera premaknjena za 100 enot v x-smeri) projicirata 3D toˇcke v dve sliki, kar da toˇcke x1 in x2. Test primerja tri scenarije: (1) idealne korespondence brez šuma, (2) koordinate z dodanim Gaussovim šumom (σ = 0.1), in (3) normalizirane koordinate s šumom. Za vsak scenarij izraˇcuna fundamentalno matriko F in ovrednoti njeno natanˇcnost z dvema metrikama: algebrajsko napako (norma epipolarne omejitve T x Fx ) in geometriˇcno napako 2 1 (povpreˇcna razdalja toˇck do epipolarnih ˇcrt). Testno kodo zapišemo: 1 # test algoritma 8-toˇ ck 2 N = 40 # stevilo 3D toˇ ck 3 X = np.random.randn(4,N) # homogene koordinate 3D toˇ ck 4 5 # postavitev simulirane scene z eksaktnimi korespondencami 6 X[2,:] = X[2,:] * 5 + 10 7 X[3,:] = 1 8 9 # projekcijske matrike kamere 1 in 2 10 P1 = np.array(( 11 (500, 0, 320, 0), 12 (0, 500, 240, 0), 13 (0, 0, 1, 0) 14 ) 15 ) 16 P2 = np.array(( 17 (500, 0, 320, -100), 18 (0, 500, 240, 0), 19 (0, 0, 1, 0) 20 ) 21 ) 188 Poglavje 12. Lokalizacija in mapiranje okolja 22 23 # lokacije projiciranih toˇ ck v slikah 1 in 2 24 x1 = np.dot(P1, X) 25 x2 = np.dot(P2, X) 26 27 sigma = 1e-1 28 noisy_x1 = x1 + sigma * np.random.randn(x1.shape[0], x1.shape[1], x1.shape[2]) 29 noisy_x2 = x2 + sigma * np.random.randn(x1.shape[0], x1.shape[1], x1.shape[2]) 30 31 # ocena fundamentalne matrik z algoritmom 8-toˇ ck 32 F = fundamentalEightPoint(x1, x2) 33 34 # preveri epipolarno omejitev x2(i).’ * F * x1(i) = 0 za vse toˇ cke i. 35 err_algebraic = np.linalg.norm(np.sum(x2*np.dot(F, x1), axis=0)) / np.sqrt(N) 36 err_dist_epi_line = distPoint2EpipolarLine(F, x1, x2) 37 38 print(’Eksaktne korespondence’) 39 print(’Algebrajska napaka: ’, err_algebraic) 40 print(’Geometriˇ cna napaka: ’, err_dist_epi_line, ’ px’) 41 42 % # test algoritma 8-toˇ ck s pošumljenimi koordinatami toˇ ck 43 F = fundamentalEightPoint(noisy_x1, noisy_x2) 44 45 # preveri epipolarno omejitev x2(i).’ * F * x1(i) = 0 za vse toˇ cke i. 46 err_algebraic = np.linalg.norm(np.sum(noisy_x2*np.dot(F, noisy_x1), axis=0)) / np.sqrt(N) 47 err_dist_epi_line = distPoint2EpipolarLine(F, noisy_x1, noisy_x2) 48 49 print(f’\nKorespondence z Gaussovim šumom (sigma={sigma}) na koordinatah, algoritem 8-toˇ ck’) 50 print(’Algebrajska napaka: ’, err_algebraic) 51 print(’Geometriˇ cna napaka: ’, err_dist_epi_line, ’ px’) 52 53 # test normaliziranega algoritma 8-toˇ ck s pošumljenimi koordinatami toˇ ck 54 Fn = fundamentalEightPoint_normalized(noisy_x1, noisy_x2) 55 56 err_algebraic = np.linalg.norm(np.sum(noisy_x2*np.dot(Fn, noisy_x1), axis=0)) / np.sqrt(N) 57 err_dist_epi_line = distPoint2EpipolarLine(Fn, noisy_x1, noisy_x2) 58 59 print(f’\nKorespondence z Gaussovim šumom (sigma={sigma}) na koordinatah, normaliziran algoritem 8-toˇ ck’) 60 print(’Algebrajska napaka: ’, err_algebraic) 61 print(’Geometriˇ cna napaka: ’, err_dist_epi_line, ’ px’) Testna koda vrne naslednji rezultat: Eksaktne korespondence Algebrajska napaka: 2.5228702197057964e-09 Geometriˇ cna napaka: 4.069852295117214e-10 px Korespondence z Gaussovim šumom (sigma=0.1) na koordinatah, algoritem 8-toˇ ck Algebrajska napaka: 31.75717454596762 Geometriˇ cna napaka: 622.2465781038167 px 12.3 Vaje z rešitvami 189 Korespondence z Gaussovim šumom (sigma=0.1) na koordinatah, normaliziran , → algoritem 8-toˇ ck Algebrajska napaka: 0.0046674954809248805 Geometriˇ cna napaka: 38.303436435518336 px Rezultati prikazujejo, kako bistveno normalizacija pred izvedbo algoritma 8-toˇck izboljša robustnost algoritma ob prisotnosti šuma, kar je kljuˇcno za praktiˇcne aplikacije raˇcunalniškega vida. Opazimo tudi, da je geometriˇcna napaka v primerjavi z algebrajsko bistveno bolj obˇcutljiva na napake prileganja z algoritmom 8-toˇck. Algebrajsko napako namreˇc direktno minimizira algoritem 8-toˇck, medtem ko je geometriˇcna napaka nepristranska ocena. Vaja 12.3 Napišite funkcijo za oceno poze na podlagi esencialne matrike E: 1 def decomposeEssentialMatrix( E ): 2 # Tu napišite python kodo funkcije 3 return R, u3 kjer je E vhodna 3 × 3 esencialna matrika. Po enaˇcbi (12.9) naj funkcija ustvari in vrne štiri možne rešitve poze T ( R , T ) , k = 1 , 2 , 3 , 4, pri ˇcemer vrne dve možni rotacijski matriki UWV in k k UW T T V kot matriko 3 × 3 × 2 v spremenljivki R in translacijo kot 3 × 1 vektor v spremenljiki u3. Zagotovite, da imajo rotacijske matrike determinanto enako 1 in vektor u3 normo enako 1. ■ Funkcija decomposeEssentialMatrix() razcepi esencialno matriko E na možne rotacije in translacije med kamerama. Najprej izvede singularni razcep (SVD) matrike T E = U ΣV , kjer je translacijski vektor T u zadnji stolpec matrike U . Rotacijske matrike se izraˇcunajo kot R = UWV 3 1 in T T R 2 = UW V , kjer je W preddefinirana matrika oblike:   0 − 1 0 W = 1 0 0 . 0 0 1 Funkcija zagotovi, da imajo rotacijske matrike determinanto +1 (pravilna orientacija), in normal-izira translacijski vektor na enotsko dolžino. Rezultat sta dve možni rotaciji (3 × 3 × 2) in en translacijski vektor u3. Rešitev zapišemo: 1 def decomposeEssentialMatrix(E): 2 ’’’ 3 Za dano esencialno matriko izraˇ cunaj gibanje kamere, tj. R in T tako, da bo E ~ T_x R 4 function [R,u3] = decomposeEssentialMatrix(E) 5 6 Parameters: 7 ---------- 8 E : numpy.ndarray 9 Esencialna matrika (3x3) 10 11 Returns 12 --------- 13 R : numpy.array 14 Polje (3x3x2) dveh moznih rotacij 15 u3 : numpy.array 16 Vektor s translacijo 17 ’’’ 18 U, _, Vt = np.linalg.svd(E) 190 Poglavje 12. Lokalizacija in mapiranje okolja 19 20 # translacija 21 u3 = U[:,2] 22 23 # rotacija 24 R = np.zeros((3,3,2)) 25 W = np.array(((0, -1, 0), (1, 0, 0), (0, 0, 1))) 26 R[:,:,0] = np.dot(U, np.dot(W, Vt)) 27 R[:,:,1] = np.dot(U, np.dot(W.T, Vt)) 28 29 if np.linalg.det(R[:,:,0])<0: 30 R[:,:,0] = -R[:,:,0] 31 32 if np.linalg.det(R[:,:,1])<0: 33 R[:,:,1] = -R[:,:,1] 34 35 if np.linalg.norm(u3) != 0: 36 u3 = u3 / np.linalg.norm(u3) 37 38 return R, u3 Funkcijo bomo preizkusili pri naslednji vaji 12.4. Vaja 12.4 Napišite funkcijo za doloˇcanje konˇcne poze (R,T ): 1 def disambiguateRelativePose( R, u3, x1, x2, K1, K2 ): 2 # Tu napišite python kodo funkcije 3 return R, u3 kjer so vhodni parametri R možni rotacijski matriki, u3 možna translacija (±1), x1 in x2 toˇcke podane v homogenih koordinatah v obliki matrik z dimenzijami 3 × N (N je število parov toˇck), K1 in K2 pa predstavljata 3 × 4 matriki intrinziˇcnih parametrov kamer 1 in 2. S triangulacijo doloˇcite položaj toˇck v prostoru i P in preštejte koliko toˇck ima pozitivno razdaljo od zaslona ene in druge kamere – rotacija in translacija pri kateri je število teh toˇck maksimalno naj bo izhodna poza (R, T ). Preizkusite funkciji z danimi pripadajoˇcimi pari toˇck na danih slikah in danimi intrinziˇcnimi parametri kamere K, ki je enaka za obe sliki. S triangulacijo (12.1) rekonstruirajte 3D položaj danih pripadajoˇcih parov toˇck in jih narišite v 3D sceno. Vrišite tudi 3D položaj prve in druge kamere. Rezultat primerjajte s sliko 12.3. ■ Funkcija disambiguateRelativePose() izbere pravilno kombinacijo rotacije in translacije izmed štirih možnosti (dve rotaciji × dve smeri translacije) tako, da rekonstruirane 3D toˇcke ležijo pred obema kamerama. Za vsako kombinacijo sestavi projekcijsko matriko druge kamere M 1 = K1[R|t] in uporabi linearno triangulacijo za rekonstrukcijo 3D toˇck PC0 v koordinatnem sistemu prve kamere. Nato preveri, koliko toˇck ima pozitivno globino (z-koordinato) v obeh kamerah (PC0 in PC1 = RPC0 + t). Kombinacija z najveˇcjim številom toˇck pred kamerami se izbere kot konˇcna rešitev. Ta postopek rešuje dvoumnost v interpretaciji esencialne matrike in zagotavlja fizikalno smiselno rekonstrukcijo. Rešitev zapišemo: 1 def disambiguateRelativePose(Rots, u3, points0_h, points1_h, K0, K1): 2 ’’’ 3 Poišˇ ci pravilno relativno pozo kamere (med štirimi možnimi) in vrni tisto, pri 12.3 Vaje z rešitvami 191 4 kateri toˇ cke ležijo pred slikovno ravnino (s pozitivno globino) 5 6 Parameters: 7 ---------- 8 Rots : numpy.array 9 Polje (3x3x2) dveh moznih rotacij 10 u3 : numpy.array 11 Vektor s translacijo 12 x1 : numpy.ndarray 13 Homogene koordinate toˇ ck v sliki 1 (3xN) 14 x2 : numpy.ndarray 15 Homogene koordinate toˇ ck v sliki 2 (3xN) 16 K1 : numpy.ndarray 17 Kalibracijska matrika kamere 1 (3x3) 18 K2 : numpy.ndarray 19 Kalibracijska matrika kamere 2 (3x3) 20 21 Returns 22 --------- 23 R : numpy.array 24 Polje (3x3) rotacije 25 T : numpy.array 26 Vektor s translacijo 27 28 kjer [R|t] = T_C1_C0 = T_C1_W predstavlja transformacijo toˇ ck iz 29 svetovnega koordinatnega sistema (enak tistemu od kamere 1) v kamero 2 30 ’’’ 31 # projekcijska matrika kamere 1 32 M0 = np.dot(K0, np.eye(3,4)) 33 34 total_points_in_front_best = 0 35 for iRot in (0,1): 36 R_C1_C0_test = Rots[:, :, iRot] 37 38 for iSignT in (1,2): 39 T_C1_C0_test = (u3 * (-1)**iSignT).reshape((3, 1)) 40 41 M1 = np.dot(K1, np.hstack((R_C1_C0_test, T_C1_C0_test))) 42 43 P_C0 = linearTriangulation(points0_h,points1_h, M0, M1) 44 45 # projekcija v obe kameri 46 P_C1 = np.dot(np.hstack((R_C1_C0_test, T_C1_C0_test)), P_C0) 47 48 num_points_in_front0 = np.sum(P_C0[2, :] > 0) 49 num_points_in_front1 = np.sum(P_C1[2, :] > 0) 50 total_points_in_front = num_points_in_front0 + num_points_in_front1 51 52 if (total_points_in_front > total_points_in_front_best): 53 # shrani rotacijo, ki vrne najveˇ cje število toˇ ck pred obema kamerama 54 R = R_C1_C0_test 55 T = T_C1_C0_test 56 total_points_in_front_best = total_points_in_front 57 192 Poglavje 12. Lokalizacija in mapiranje okolja 58 return R, T Testna koda implementira celoten potek strukturne rekonstrukcije scene iz dveh slik. Najprej naloži dve vhodni sliki (0001.jpg in 0002.jpg) ter kalibracijsko matriko K kamere, ki vsebuje intrinziˇcne parametre (gorišˇcne razdalje in optiˇcno središˇce). Ujemanja toˇck med slikama se naložijo iz datotek matches0001.txt in matches0002.txt ter pretvorijo v homogene koordinate. Postopek rekonstrukcije vkljuˇcuje naslednje kljuˇcne korake: (1) oceno esencialne matrike E z normaliziranim algoritmom 8-toˇck, (2) razcep esencialne matrike na možne rotacije in translacije s funkcijo decomposeEssentialMatrix(), (3) razrešitev dvoumnosti relativne poze s funkcijo disambiguateRelativePose() , ki izbere tisto kombinacijo rotacije R in translacije t, kjer veˇcina rekonstruiranih toˇck leži pred obema kamerama, in (4) linearno triangulacijo 3D toˇck. Rezultati se vizualizirajo v 3D prostoru, kjer so prikazane rekonstruirane toˇcke scene ter koordinatni sistemi obeh kamer (rdeˇca za prvo kamero, modra za drugo), skupaj z ustreznimi 2D prikazi slik z oznaˇcenimi ujemanji. Vizualizacijski del uporablja razred Arrow3D za prikaz 3D koordinatnih sistemov kamer, ki deduje od razreda FancyArrowPatch in implementira metode za pravilno projiciranje v 2D prostor. Funkcija plotCoordinateFrame() riše koordinatne osi kot 3D pušˇcice. 3D toˇcke scene se prikažejo s funkcijo scatter(), medtem ko se 2D prikazi originalnih slik z oznaˇcenimi ujemanji pripravijo z imshow() in plot(). Celoten postopek omogoˇca preverjanje kakovosti rekonstruk-cije in pravilnosti delovanja vseh vkljuˇcenih funkcij, od ocene esencialne matrike do konˇcne triangulacije in vizualizacije. Testno kodo zapišemo: 1 img_1 = np.array(im.open(’0001.jpg’)) 2 img_2 = np.array(im.open(’0002.jpg’)) 3 4 K = np.array( 5 ( 6 (1379.74, 0, 760.35), 7 (0, 1382.08, 503.41), 8 (0, 0, 1) 9 ) 10 ) 11 12 p1 = np.genfromtxt(’matches0001.txt’) 13 p2 = np.genfromtxt(’matches0002.txt’) 14 15 p1 = np.vstack((p1, np.ones((1, p1.shape[-1])))) 16 p2 = np.vstack((p2, np.ones((1, p2.shape[-1])))) 17 18 # oceni esencialno matriko z algoritmom 8-toˇ ck 19 E = estimateEssentialMatrix(p1, p2, K, K) 20 21 # izloˇ ci relativen položaj kamere (R, T) iz esencialne matrike 22 Rots, u3 = decomposeEssentialMatrix(E) 23 24 # najdi rešitev med 4-imi moznimi 25 R_C2_W, T_C2_W = disambiguateRelativePose(Rots, u3, p1, p2, K, K) 26 27 # izvedi triangulacijo oblaka toˇ ck s konˇ cno preslikavo (R, T) 28 M1 = np.dot(K, np.eye(3, 4)) 29 M2 = np.dot(K, np.hstack((R_C2_W, T_C2_W))) 30 P = linearTriangulation(p1, p2, M1, M2) 12.3 Vaje z rešitvami 193 31 32 # vizualiziraj 3d sceno 33 class Arrow3D(FancyArrowPatch): 34 def __init__(self, xs, ys, zs, *args, **kwargs): 35 super().__init__((0, 0), (0, 0), *args, **kwargs) 36 self._verts3d = xs, ys, zs 37 38 def do_3d_projection(self, renderer=None): 39 xs3d, ys3d, zs3d = self._verts3d 40 xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M) 41 self.set_positions((xs[0], ys[0]), (xs[1], ys[1])) 42 return np.min(zs) # Return the zorder value 43 44 def draw(self, renderer): 45 super().draw(renderer) 46 47 def plotCoordinateFrame(ax, rotation, origin, color): 48 for i in range(3): 49 lines = [[float(origin[j]), float(origin[j] + rotation[j,i])] for j in range(3)] 50 a = Arrow3D(lines[0], lines[1], lines[2], mutation_scale=20, 51 lw=2, arrowstyle="-|>", color=color) 52 ax.add_artist(a) 53 54 fig = plt.figure() 55 ax = fig.add_subplot(111, projection=’3d’) 56 57 # nariši toˇ cke okolja 58 ax.scatter(P[0, :], P[1, :], P[2, :]) 59 60 # nariši polozaj kamer 61 plotCoordinateFrame(ax, np.eye(3), np.zeros((3,1)), ’r’) 62 center_cam2_W = -np.dot(R_C2_W.T, T_C2_W) 63 plotCoordinateFrame(ax, R_C2_W.T, center_cam2_W, ’b’) 64 ax.scatter(0, 0, 0, c=’r’) 65 ax.scatter(center_cam2_W[0], center_cam2_W[1], center_cam2_W[2], c=’b’) 66 67 ax.text(0, 0, 0, ’Kamera 1’) 68 # ax.text(center_cam2_W[0], center_cam2_W[1], center_cam2_W[2], ’Kamera 2’) 69 70 ax.set_xlabel(’x’) 71 ax.set_ylabel(’y’) 72 ax.set_zlabel(’z’) 73 ax.set_title(’Mapa okolja in polozaj kamer’) 74 75 plt.axis(’equal’) 76 plt.show() 77 78 plt.figure() 79 plt.subplot(1,2,1) 80 plt.imshow(img_1) 81 plt.axis(’off’) 82 plt.plot(p1[0, :], p1[1, :], marker=’.’, lw=0, ms=8, mfc=None, c=’y’,mew=1, mec=’y’) 83 plt.subplot(1,2,2) 84 plt.imshow(img_2) 194 Poglavje 12. Lokalizacija in mapiranje okolja 85 plt.axis(’off’) 86 plt.plot(p2[0, :], p2[1, :], marker=’.’, lw=0, ms=8, mfc=None, c=’y’, mew=1, mec=’y’) 87 plt.plot() Rešitev je prikazana na sliki 12.3. 12.4 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.1: To je moj odgovor. . Naloga 12.1 Preizkusite delovanje algoritmov na slikah, ki jih zajamete z lastnim fotoaparatom. Pred tem kalibrirajte fotoaparat tako, da veˇckrat slikate šahovnico in nato doloˇcite intrinziˇcne para- metre kamere s funkcijami v knjižnici OpenCV (dodatne naloge pri vaji 8). Nastavitev fotoaparata nato ne spreminjajte (enak zoom in fokus – izklopite avtofokus). Zajemite veˇc slik prostora in rekonstruirajte 3D okolje kot oblak toˇck. Ali dobite smiselno rešitev za lokalizacijo (položaj kamere med zajemom) in okolja? Naloga 12.2 Rešitev, ki ste jo dobili lahko še izboljšate s skupinskim popravkom (ang. bundle adjustment), pri ˇcemer se lahko zgledujete po naslednji rešitvi: http://scipy-cookbook.readthedocs.io/items/bundle_adjustment.html 13. Vizualna kontrola kakovosti Vizualna kontrola kakovosti je proces razpoznavanja vzorcev na podlagi vizualne oz. slikovne informacije, ki je namenjen podpori pri odloˇcanju o ustreznosti oz. neustreznosti objektov zanim-anja. Interpretacija surovih slikovnih podatkov je pogosto zelo zamudna, naporna in vˇcasih tudi zelo zahtevna naloga, zato je smiselno razvijati raˇcunalniške postopke za podporo pri odloˇcanju, ki temeljijo na avtomatski analizi, opisovanju in razpoznavanju opazovanih objektov. Splošni sistem za razpoznavanje vzorcev lahko obravnavamo kot zaporedje šestih osnovnih funkcionalnih pod-sistemov: zajemanje podatkov, obdelava in obnova zajetih podatkov, razgradnja obnovljenih podatkov, izloˇcanje znaˇcilnic, razvršˇcanje ter vrednotenje razvršˇcanja (slika 13.1). Pri vaji se bomo spoznali z osnovno razgradnjo slik, izloˇcanjem znaˇcilnic in razvršˇcanjem za problem vizualne kontrole kakovosti farmacevtskih tablet. 13.1 Sistem za razpoznavanje vzorcev 13.1.1 Razgradnja objektov zanimanja Razgradnja1 združuje postopke, s katerimi sliko razdelimo na osnovna podroˇcja oziroma objekte. Razgradnjo bomo izvedli z upragovanja slike z osvetlitvijo iz ozadja in z oznaˇcevanjem objektov. Pri oznaˇcevanju objektov vsakemu (binarnemu) objektu na sliki priredimo lastno oznako oz. kodo. Primer izvajanja upragovanja: 1 import cv2 2 import numpy as np 3 4 # Naložimo sliko v sivinski lestvici 5 image = cv2.imread("tablet.png", cv2.IMREAD_GRAYSCALE) 6 7 # Nastavimo prag 8 _, thresholded = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY) 9 10 # Prikaz rezultatov 1 razgradnja: ang. image segmentation 196 Poglavje 13. Vizualna kontrola kakovosti Slika 13.1: Sistem za razpoznavanje vzorcev (levo) in izziv analize slik tablet na vaji (desno). 11 cv2.imshow("Thresholded Image", thresholded) 12 cv2.waitKey(0) 13 cv2.destroyAllWindows() 13.1.2 Analiza objektov Analizo objektov v razgrajeni sliki izvedemo z izloˇcanjem znaˇcilnic oz. kvantitativnimi opisi lastnosti posameznih podroˇcij slike. Posamezno podroˇcje slike lahko predstavimo z zunanjimi last-nostmi (mejami podroˇcja) in/ali notranjimi lastnostmi (slikovnimi elementi podroˇcja). Zunanje lastnosti omogoˇcajo predstavitev in opisovanje podroˇcij z izloˇcanjem znaˇcilnic iz mej podroˇcij, npr. obseg, usmeritev, najveˇcja dolžina, gladkost meje, itd. Notranje lastnosti po drugi strani omogoˇcajo predstavitev in opisovanje pojavnosti podroˇcij, kot so npr. homogenost, barva, tekstura, itd. Zunanje lastnosti podroˇ cja Najosnovnejša znaˇcilnica zunanje lastnosti podroˇcja je njegova površina A, ki je doloˇcena s številom slikovnih elementov podroˇcja. Meje podroˇcja v binarni sliki (0 – ozadje, 1 - objekt) lahko doloˇcimo s Moorovim postopkom sledenja mej (slika 13.2). Obseg P podroˇcja je enak dolžini njegove meje. Opis oblike podroˇcja, ki je neodvisen od velikosti in orientacije objekta je krožno razmerje: K = (13.1) 2 P 4π A ki ima vrednost 1 za podroˇcja v obliki kroga, sicer pa je manjše od 1. Notranje lastnosti podroˇ cja Notranje lastnosti podroˇcja najbolj preprosto opišemo s statistiˇcnimi momenti sivinskih vrednosti podroˇcij slike. Naj bo p(z) verjetnostna porazdelitev sivinskih vrednosti znotraj posameznih 13.1 Sistem za razpoznavanje vzorcev 197 Mejo predstavimo kot zaporedje mejnih toˇck v smeri urinega kazalca: 1. Doloˇcimo zaˇcetno toˇcko b0 kot najvišjo in najbolj levo toˇcko z vrednostjo 1 ter oznaˇcimo s c0 sosednjo toˇcko na levi, ki ima vrednost 0. Preišˇcemo 8 sosednjih toˇck zaˇcetne toˇcke b 0, tako da zaˇcnemo v toˇcki c0 in nadaljujemo v smeri urinega kazalca. Z b1 oznaˇcimo prvo sosednjo toˇcko z vrednostjo 1, s c 1 pa toˇcko z vrednostjo 0 tik pred b1 v zaporedju sosednjih toˇck. Zapomnimo si b0 in b1. 2. Naj bo b = b1 in c = c1. 3. Oznaˇcimo 8 sosednjih toˇck n 1, n2, . . . , n8 toˇcke b, ki se zaˇcnejo v toˇcki c in se nadaljujejo v smeri urinega kazalca, ter poišˇcemo prvo toˇcko n z vrednostjo 1. k 4. Naj bo b = n in c = n . k k − 1 5. Ponavljamo koraka 3 in 4 dokler ne pridemo do zaˇcetne toˇcke b = b 0 in za njo najdemo isto naslednjo toˇcko b1. Po konˇcanem postopku zaporedje toˇck b i predstavlja zaporedje zunanjih mejnih toˇck objekta v smeri urinega kazalca. Slika 13.2: Moorov postopek za sledenje in doloˇcanje meje objekta. podroˇcij, ki jo ocenimo z normalizacijo histograma sivinskih vrednosti znotraj posameznih podroˇcij slike. Centralni moment n-te stopnje izraˇcunamo kot: L−1 L−1 µn n = ( z m ) p(z ∑ ∑ i i i i − ) , kjer povpreˇcje m izraˇcunamo kot m = z p ( z) (13.2) i=0 i=0 Niˇcti moment 2 µ0 je vedno enak 1, prvi moment pa 0. Drugi moment predstavlja varianco σ , ki je znaˇcilnica razpršenosti oz. kontrasta sivinskih vrednosti, in ga lahko uporabimo za opis gladkosti podroˇcja: G = (13.3) 2 1 1 + σ Ta ima vrednost 1 na popolnoma homogenih podroˇcjih in se približuje 0 na podroˇcjih z veliko varianco. Teksturo objekta lahko opišemo tudi z osnovnimi komponentami, naprimer tako, da izraˇcunamo gradient slike T ∇I = [ g x y ∇ g ] in za podroˇcje objekta sestavimo N × 2 matriko gradientovI ter doloˇcimo kovarianˇcno matriko gradientov T × C = ( ∇ I ) ∇ I z dimenzijami 2 2. Nato doloˇcimo lastni vrednosti matrike C → λ1, λ2, ki kodirata lastnosti teskture na objektu. Za objekte, kjer je tekstura izrazito orientirana v doloˇceno smer, lahko izpeljemo znaˇcilnico: min {λ1, λ2} R = 1 − . (13.4) max{λ1, λ2} 198 Poglavje 13. Vizualna kontrola kakovosti Znaˇcilnica R bo imela vrednost 0, ˇce bo teksura orientirana enakomerno v vse smeri, oziroma vrednost 1, ˇce bo izrazito orientirana v eno smer. 13.1.3 Razvršˇ canje Razvršˇcanje (klasifikacija) je kljuˇcni korak v vizualni kontroli kakovosti, saj omogoˇca avtomatsko odloˇcanje o ustreznosti ali neustreznosti objektov na podlagi njihovih znaˇcilnic. Obstajajo razliˇcne metode razvršˇcanja, od enostavnih pragovnih metod do naprednih algoritmov strojnega uˇcenja. Spodaj so opisane nekatere izmed najpogosteje uporabljenih metod in njihove implementacije v Python knjižnicah. • Pragovna metoda (ang. thresholding): Pragovna metoda je ena najpreprostejših tehnik razvršˇcanja, kjer vrednost doloˇcene znaˇcilnice (npr. povpreˇcna intenziteta sivinske slike) primerjamo s prednastavljeno mejno vrednostjo. ˇ Ce je vrednost nad pragom, je objekt razvršˇcen v eno kategorijo, sicer v drugo. • K-najbližjih sosedov (KNN; ang. K-nearest neighbor): KNN je preprost nadzorovan uˇcni algoritem, ki nove vzorce razvršˇca glede na razdaljo do k najbližjih že znanih primerov v uˇcnem sklopu. Primer uporabe: 1 from sklearn.neighbors import KNeighborsClassifier 2 import numpy as np 3 4 # Uˇ cni podatki (primeri znaˇ cilnic) 5 X_train = np.array([[1, 2], [2, 3], [3, 1], [5, 5], [6, 7]]) 6 y_train = np.array([0, 0, 0, 1, 1]) # Razredi 7 8 # Novi podatki za razvršˇ canje 9 X_test = np.array([[4, 4]]) 10 11 # Ustvarimo in nauˇ cimo model 12 knn = KNeighborsClassifier(n_neighbors=3) 13 knn.fit(X_train, y_train) 14 15 # Napoved 16 prediction = knn.predict(X_test) 17 print("Razred:", prediction) • Nakljuˇcni gozd (RF; ang. Random Forest): Nakljuˇcni gozd je ansambelski model, ki uporablja veˇc odloˇcilnih dreves in glasovanje za konˇcno napoved. Primer uporabe: 1 from sklearn.ensemble import RandomForestClassifier 2 3 # Ustvarimo model 4 rf = RandomForestClassifier(n_estimators=100, random_state=42) 5 6 # Uˇ cenje modela 7 rf.fit(X_train, y_train) 8 9 # Napoved 10 prediction = rf.predict(X_test) 11 print("Razred:", prediction) 13.2 Vrednotenje sistemov odloˇ canja 199 • XGBoost (ang. Extreme Gradient Boosting): XGBoost je optimizirana razliˇcica gradientnega ojaˇcevanja, ki omogoˇca hitro in uˇcinkovito razvršˇcanje. Primer uporabe: 1 import xgboost as xgb 2 3 # Ustvarimo model in ga nauˇ cimo 4 xgb_model = xgb.XGBClassifier(use_label_encoder=False, eval_metric="logloss") 5 xgb_model.fit(X_train, y_train) 6 7 # Napoved 8 prediction = xgb_model.predict(X_test) 9 print("Razred:", prediction) • Metoda podpornih vektorjev (SVC; ang. Support Vector Machine): SVC je model, ki išˇce optimalno mejo za loˇcevanje razredov v veˇcdimenzionalnem prostoru, pri ˇcemer optimalna meja temelji na identifikaciji vzorcev ob meji tako, da so ti vzorci (reˇcemo jim tudi podporni vektorji) hkrati ˇcimbolj odddaljeni od optimalne meje. Primer uporabe: 1 from sklearn.svm import SVC 2 3 # Ustvarimo model 4 svc = SVC(kernel="linear") 5 6 # Uˇ cenje modela 7 svc.fit(X_train, y_train) 8 9 # Napoved 10 prediction = svc.predict(X_test) 11 print("Razred:", prediction) Vsaka od zgornjih metod ima svoje prednosti in slabosti. Pragovne metode so enostavne, vendar ne fleksibilne. KNN je enostaven, a poˇcasen pri velikih podatkovnih zbirkah. Random Forest in XGBoost sta robustna modela, primerna za kompleksne podatke, medtem ko SVC deluje dobro pri dobro loˇcljivih podatkih. Izbira metode je odvisna od znaˇcilnic vhodnih podatkov in zahtev aplikacije. 13.2 Vrednotenje sistemov odloˇ canja Pri ocenjevanju uˇcinkovitosti sistemov odloˇcanja uporabljamo razliˇcne metrike, ki nam pomagajo razumeti, kako dobro model loˇcuje med pozitivnimi in negativnimi primeri. Nekatere kljuˇcne metrike vkljuˇcujejo obˇcutljivost (senzitivnost), specifiˇcnost, ter ROC krivuljo in AUC metriko. 13.2.1 Obˇ cutljivost in specifiˇ cnost Obˇcutljivost (ang.sensitivity ali recall) meri, koliko dejanskih pozitivnih primerov je pravilno prepoznanih kot pozitivni: TP Obˇcutljivost = (13.5) TP + FN kjer je: • TP (ang. True Positive) – pravilno prepoznani pozitivni primeri, 200 Poglavje 13. Vizualna kontrola kakovosti • FN (ang. False Negative) – napaˇcno prepoznani negativni primeri. Specifiˇcnost (ang. specificity) meri, koliko dejanskih negativnih primerov je pravilno oznaˇcenih kot negativni: TN Specifiˇcnost = (13.6) TN + FP kjer je: • TN (ang. True Negative) – pravilno prepoznani negativni primeri, • FP (ang. False Positive) – napaˇcno prepoznani pozitivni primeri. 13.2.2 ROC krivulja in AUC metrika ROC (ang. Receiver Operating Characteristic) krivulja prikazuje razmerje med obˇcutljivostjo in nespecifiˇcnostjo (tj. 1 - specifiˇcnost) pri razliˇcnih pragovnih vrednostih. Krivulja nam omogoˇca vizualno analizo delovanja razvršˇcevalnika. AUC (ang. Area Under the Curve) metrika meri površino pod ROC krivuljo in predstavlja splošno zmogljivost modela: 0,5 ≤ AUC ≤ 1 (13.7) kjer vrednost 1 pomeni popolno loˇcevanje pozitivnih in negativnih primerov, vrednost 0.5 pa pomeni nakljuˇcno napovedovanje. ROC krivuljo in AUC vrednost lahko izrišemo in izraˇcunamo v Pythonu s knjižnico sklearn: 1 from sklearn.metrics import roc_curve, auc 2 import matplotlib.pyplot as plt 3 4 # Primer podatkov: resniˇ cne oznake in napovedane verjetnosti 5 y_true = [0, 0, 1, 1] 6 y_scores = [0.1, 0.4, 0.35, 0.8] 7 8 # Izraˇ cun ROC krivulje 9 fpr, tpr, _ = roc_curve(y_true, y_scores) 10 roc_auc = auc(fpr, tpr) 11 12 # Prikaz ROC krivulje 13 plt.figure() 14 plt.plot(fpr, tpr, color="darkorange", lw=2, label="ROC curve (area = %0.2f)" % roc_auc) 15 plt.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--") 16 plt.xlabel("1 - Specifiˇ cnost") 17 plt.ylabel("Obˇ cutljivost") 18 plt.title("ROC krivulja") 19 plt.legend(loc="lower right") 20 plt.show() Te metrike so kljuˇcne za vrednotenje sistemov odloˇcanja, še posebej na podroˇcjih, kot so medicinska diagnostika, varnostni sistemi in strojno uˇcenje. 13.3 Vaje z rešitvami Koda vaje je dostopna na Git repozitoriju. Gradivo za vajo vsebuje barvni sliki osvetlitev-ozadje.jpg in osvetlitev-ospredje.jpg, ki sta bili zajeti z osvetlitvijo objektov zanimanja (farmacevtskih 13.3 Vaje z rešitvami 201 tablet) iz ozadja in ospredja. Na slikah so farmacevtske tablete razliˇcnih oblik, barv, tekstur, nekatere tablete pa imajo tudi zarezo. Najprej uvozimo osnovne Python knjižnice: 1 import PIL.Image as im 2 import scipy.ndimage as ni 3 import matplotlib.pyplot as plt 4 import matplotlib.cm as cm 5 import numpy as np 6 import os.path 7 import sys 8 9 from rvlib import imageGradient, loadImage, showImage, toGray Vaja 13.1 Naložite sivinsko sliko osvetlitev-ozadje.jpg in jo z upragovljanjem pretvorite v binarno sliko tako, da doloˇcite optimalen prag t, tj. sivinsko vrednost, ki podroˇcje tablet loˇci od ozadja. Optimalni prag t doloˇcite s pomoˇcjo histograma sivinske slike. ■ Za izraˇcun histograma si pripravimo dve funkciji. Prva je funkcija getIndices(), ki naj pretvori vrednosti sivinskih slikovnih elementov (pikslov) v indekse histograma. Deluje naj tako, da normalizira vhodne vrednosti glede na dano obmoˇcje (iMinVal, iMaxVal) in jih preslika na število predalˇckov (iBins). Normalizacija poteka po formuli: iData − iMinVal idx = round × (iBins − 1) , iMaxVal − iMinVal pri ˇcemer se rezultat zaokroži na cela števila in pretvori v 32-bitni nepredznaˇceni celoštevilski tip (uint32). Primer uporabe: ˇce je iData = [10, 20, 30], iBins = 4, iMinVal = 0 in iMaxVal = 40, potem je izraˇcun: [ 10 , 20 , 30 ] idx = round × 3 = [1,2,2]. 40 Druga funkcija, hist1D(iData, iBins, iMinVal=None, iMaxVal=None), naj izraˇcuna 1D histogram sivinskih vrednosti slike z uporabo funkcije getIndices. ˇ Ce obmoˇcje vrednosti (iMinVal in iMaxVal) ni podano, se avtomatsko doloˇcita kot minimum in maksimum vhodnih podatkov, pri ˇcemer se pri iMaxVal doda majhna vrednost (1e-7), da se zagotovi, da je veˇcji od najveˇcje vrednosti. Nato se vrednosti pretvorijo v indekse, inicializira se niˇcelno polje velikosti iBins , in z iteracijo po indeksih se ustrezni predalˇcki inkrementirajo. Izhod je polje histData, kjer vsak element predstavlja število pikslov v ustreznem predalˇcku. Rešitev zapišemo: 1 # funkcija za pretvorbo sivinskih vrednosti v indekse 2 def getIndices( iData, iBins, iMinVal, iMaxVal ): 3 # pretvori v indeks polja 4 idx = np.round( (iData - iMinVal) / (iMaxVal - iMinVal) * (iBins-1) ) 5 # vrni indekse 6 return idx.astype(’uint32’) 7 8 # funkcija za izracun 1D histograma 9 def hist1D( iData, iBins, iMinVal=None, iMaxVal=None ): 10 # doloci obmocje sivinskih vrednosti 11 if iMinVal==None: iMinVal = np.min(iData) 202 Poglavje 13. Vizualna kontrola kakovosti 12 if iMaxVal==None: iMaxVal = np.max(iData)+1e-7 13 # pretvorba sivinskih vrednosti v indekse 14 idx = getIndices( iData, iBins, iMinVal, iMaxVal ) 15 # izracunaj histogram 16 histData = np.zeros((iBins,)) 17 for i in idx: 18 histData[i] += 1.0 19 # vrni histogram 20 return histData 21 22 # nalaganje slik 23 imgB = loadImage(’osvetlitev-ozadje.jpg’) 24 imgF = loadImage(’osvetlitev-ospredje.jpg’) 25 26 # prikaži histogram indoloˇ ci optimalen prag 27 imgBG = toGray( imgB ) 28 plt.hist( imgBG.flatten(), 256 ) 29 iThr = 100 # izbran prag 30 # prikaz upragovljenje slike z osvetlivijo iz ozadja 31 imgT = 255 * (imgBG < iThr).astype(’uint8’) 32 showImage( imgT ) Testna koda demonstrira analizo histograma sivinskih vrednosti dveh slik (ozadje in ospredje) za doloˇcitev optimalnega praga (iThr). Sliki se naložita s funkcijo loadImage, ozadje se pretvori v sivinsko sliko z toGray, histogram se prikaže z plt.hist (256 predalˇckov), prag (iThr, npr. 100) se roˇcno nastavi, in ustvari se binarna slika, kjer so piksli pod pragom beli (255), ostali pa ˇcrni (0). Konˇcni rezultat se prikaže s showImage. Skupaj funkciji getIndices in hist1D omogoˇcata analizo porazdelitve sivinskih vrednosti, kar je kljuˇcno za segmentacijo slike, na primer loˇcevanje ozadja in ospredja. Rezultat loˇcevanja tablet od ozadja je prikazan na sliki 13.3. (a) (b) Slika 13.3: Prikaz (a) histograma slike osvetlitev-ozadje.jpg in (b) binarna slika ustvarjena s pragom iThr=100. Vaja 13.2 Na osnovi upragovljene slike ozadja s preizkusite vpliv funkcij za morfološko filtrir-anje erosion(), dilation(), opening() in closing() v Python knjižnici skimage.morphology. Jedro za filtriranje doloˇcite s funkcijo disk(). Po filtriranju lahko vsakemu objektu pripišite unikatno oznako s funkcijo label() v knjižn- ici skimage.measure. Koliko objektov oz. tablet je na sliki? Preverite vpliv morfološkega 13.3 Vaje z rešitvami 203 filtriranja na število zaznanih tablet v sliki. ■ Morfološke operacije nad binarno sliko 13.3b izvedemo kot: 1 from skimage.morphology import erosion, dilation, opening, closing 2 from skimage.morphology import disk 3 from skimage.measure import label 4 # poisci vse objekte na sliki 5 6 # erozija 7 imgE = erosion(imgT, disk(3)) #3-> velikost strukturnega elementa 8 showImage(imgE, ’Erozija’) 9 # dilacija 10 imgD = dilation(imgT, disk(3)) 11 showImage(imgD, ’Dilacija’) 12 # erozija-dilacija 13 imgO = opening(imgT, disk(3)) 14 showImage(imgO, ’Odpiranje (erozija+dilacija)’) 15 # dilacija-erozija 16 imgC = closing(imgT, disk(3)) 17 showImage(imgC, ’Zapiranje (dilacija+erozija)’) 18 # oznacevanje objektov 19 iLabel = label( imgO, neighbors=8 ) 20 numL = np.max( iLabel ) 21 msg = ’Najdeno stevilo objektov je %d.’ % numL 22 # prikazi sliko objektov 23 showImage( iLabel.astype(’uint8’) ) Rezultat prikazuje slika 13.4. Za nadaljnjo uporabo v analizi objektov izberemo sliko 13.4c, pridobljeno z operacijo odpiranja. Nato izvedemo še oznaˇcevanje objektov s funkcijo label(): 1 # oznaˇ cevanje objektov 2 iLabel = label( imgO, connectivity=2 ) 3 numL = np.max( iLabel ) 4 # prikaži sliko objektov 5 showImage( iLabel.astype(’uint8’), ’Oznaˇ ceni objekti’ ) 6 print(f’Najdeno stevilo objektov je {numL}.’) Rezultat prikazuje slika 13.5, testna koda pa vrne naslednji izpis: Najdeno število objektov je 152. Vaja 13.3 Napišite funkcijo, ki doloˇci koordinate meje posameznega podroˇcja na 2D sliki: 1 def trackContour( iLabel, iObject ): 2 # Tu napišite python kodo funkcije 3 return oContour kjer je iLabel slika oznaˇcenih objektov, iObject pa je oznaka objekta, katerega mejo želimo doloˇciti. Funkcija vrne spremenljivko oContour v obliki matrike 2 × M z zaporedjem koordinat meje T [ x y ], i = 1, . . . ,M. i i Doloˇcite meje vseh oznaˇcenih objektov iz vaje 13.2. Prikažite sliko osvetlitev-ospredje.jpg 204 Poglavje 13. Vizualna kontrola kakovosti (a) (b) (c) (d) Slika 13.4: Morfološke operacije nad binarno sliko 13.3b: (a) erozija, (b) dilacija, (c) odpiranje in (d) zapiranje. Slika 13.5: Slika oznaˇcenih objektov, kjer posamezna sivina predstavlja oznako objekta. Skupaj je bilo v sliki najdenih 152 objektov. ter vanjo vrišite meje oznaˇcenih objektov kot je prikazano na sliki 13.6 levo. ■ Funkcija trackContour sledi konturi objekta v binarni maski z uporabo algoritma sledenja robu (ang. boundary tracking). Najprej preveri velikost objekta in zavrne objekte z manj kot 10 piksli. Nato poišˇce zaˇcetno toˇcko konture kot zgornjo levo toˇcko objekta in iterativno sledi robu s pomožno funkcijo findNextPoint, ki preverja 8-sosednjih pikslov v nasprotni smeri urinega 13.3 Vaje z rešitvami 205 kazalca, zaˇcenši od prejšnjega odmika (iOffset). Vsaka najdena toˇcka se doda v izhodno polje oContour, dokler se ne zapre zanka (ko naslednja toˇcka ustreza prvi toˇcki konture). Funkcija findNextPoint za iskanje naslednje toˇcke konture na podlagi trenutne pozicije in prejšnjega odmika, kar zagotavlja dosledno sledenje robu objekta. Rezultat je 2D polje toˇck konture v obliki polja 2 × N, kjer N predstavlja število toˇck na konturi. Rešitev zapišemo: 1 def findNextPoint( iObjectMask, iCurPoint, iOffset ): 2 """Funkcija za iskanje naslednje toˇ cke""" 3 # preberi velikost slike 4 dy, dx = iObjectMask.shape 5 # preberi trenuten polozaj 6 x, y = iCurPoint 7 # definiraj zaporedje odmikov 8 lOffsets = np.array( ( (-1,-1,0,1,1,1,0,-1), (0,-1,-1,-1,0,1,1,1) ) ) 9 sx = lOffsets[0,:]; sy = lOffsets[1,:] 10 lOffsetIdx = np.where( np.sum( np.abs( lOffsets - iOffset ), axis=0 ) == 0 )[0] 11 for i in range(len(sx)): 12 j = (i + lOffsetIdx) % len(sx) 13 if (x+sx[j]) and (x+sx[j])>=0 and \ 14 (y+sy[j]) and (y+sy[j])>=0: 15 if iObjectMask[y+sy[j], x+sx[j]]: 16 # doloci koordinate naslednje tocke konture 17 oNextPoint = np.array( (x+sx[j], y+sy[j]) ) 18 # doloci koordinate prejsnje tocke zunaj konture 19 oPrevPoint = np.array( (x+sx[j-1], y+sy[j-1]) ) 20 return oNextPoint, oPrevPoint-oNextPoint 21 return iCurPoint, iOffset 22 23 def trackContour( iLabel, iObject ): 24 """Funkcija za doloˇ canje konture objekta""" 25 # definiraj izhodno spremenljivko 26 oContour = np.array( [] ) 27 # preveri velikost objekta in preskoˇ ci objekte 28 # po velikosti manjše od 10 slikovnih elementov 29 iObjectMask = iLabel == iObject 30 y, x = np.where( iObjectMask ) 31 if x.size < 10: 32 print(f’Objekt {iObject} ima velikost {x.size} < 10’) 33 return oContour 34 # poišˇ ci zacetno toˇ cko na konturi kot zgornjo-levo toˇ cko 35 idy = np.where( y == np.min(y) )[0] 36 idx = np.where( x[idy] == np.min(x[idy]) )[0] 37 oContour = np.array( (x[idy[idx]], y[idy[idx]]) ).reshape( (2,1) ) 38 # iterativno išˇ ci toˇ cke na konturi 39 iOffset = np.array((-1,0)).reshape((2,1)) 40 while True: 41 # trenutna toˇ cka 42 oCurPoint = oContour[:,-1] 43 # naslednja toˇ cka 44 oNextPoint, iOffset = findNextPoint( iObjectMask, oCurPoint, iOffset ) 45 # preveri ali je enaka prejšnji ali prvi toˇ cki 46 if np.all( oCurPoint == oNextPoint ) or \ 47 np.all( oNextPoint.flatten() == oContour[:,0] ): 48 # da, prekini zanko 49 break 206 Poglavje 13. Vizualna kontrola kakovosti 50 # dodaj tocko na konturo 51 oContour = np.hstack( (oContour, oNextPoint) ) 52 # vrni konturo 53 return oContour S funkcijo trackContour() na sliki oznaˇcenih objektov 13.5 doloˇcimo konturo vsakemu objektu posebej in konture nato narišemo v sliko kot: 1 def plotContour( oContour ): 2 """Prikaz konture na sliki""" 3 plt.plot(oContour[0,:], oContour[1,:],’-g’) 4 plt.plot(oContour[0,(-1,0)], oContour[1,(-1,0)],’-g’) 5 6 oContours = [] 7 for iObject in range(0,numL): 8 x,y = np.where( iLabel == (iObject+1) ) 9 # preskoˇ ci objekte po velikosti manjše od 10 slikovnih elementov 10 if x.size > 10: 11 # z uporabo Moorovega algoritma 12 oContours.append( trackContour( iLabel, iObject+1 ) ) 13 14 # prikaži barvno sliko z osvetlitvijo iz ospredja in obriši konture 15 showImage( imgF ) 16 for i in range(len(oContours)): 17 plotContour( oContours[i] ) Rezultat prikazuje slika 13.6 levo, kjer so konture objektov oznaˇcene z zeleno barvo. Slika 13.6: Prikaz mej posameznih objektov in razvršˇcanje z upragovanjem znaˇcilnice K (enaˇcba (13.1). Vaja 13.4 Napišite funkcijo, ki izloˇci znaˇcilnice oznaˇcenih objektov na 2D sliki: 1 def extractFeatures( iImage, iLabel ): 2 # Tu napišite python kodo funkcije 3 return oFeatures kjer je iImage sivinska slika z osvetlitvijo iz ospredja, iLabel slika oznaˇcenih objektov, katerih znaˇcilnice želimo izraˇcunati. Funkcija vrne spremenljivko oFeatures v obliki vektorja z zn-aˇcilnicami T [ A , P , K , µ 2 , G , R ], ki ima dimenzije 6 × N .Znaˇcilnice notranjih lastnosti objektov ob j doloˇcite na erodirani maski objektov, ki jo dobite z ukazom iMask = erosion(iLabel, disk(3)). 13.3 Vaje z rešitvami 207 Narišite 1D histograme posameznih znaˇcilnic za vse objekte v sliki. Razmislite katera oz. katere od znaˇcilnic bi bile primerne za zaznavo okroglih tablet in tablet z zarezo? ■ Funkcija extractFeaturesSingle() izraˇcuna geometrijske in teksturne znaˇcilnice objekta na podlagi vhodne slike (iImage) in binarne maske (iMask). Najprej doloˇci konturo objekta s funkcijo trackContour() in izraˇcuna osnovne geometrijske znaˇcilnice: površino objekta (A), obseg konture ( 2 P ) ter krožnost ( K = 4 π A / P). Nato uporabi morfološko erozijo za odstranitev robov objekta in izraˇcuna teksturne znaˇcilnice iz sivinske slike: normaliziran histogram (p), srednjo vrednost (m), varianco (µ2), mero homogenosti (G = 1/(1 + µ2)) ter mero strukture gradientov (R), ki temelji na lastnih vrednostih kovarianˇcne matrike gradientov slike. Vse znaˇcilnice združi v izhodni vektor oFeatures v obliki (A, P , K, µ 2, G,R), ki opisuje lastnosti objekta. Funkcija naj zavrne premajhne objekte (manj kot 10 pikslov) z vrnitvijo None. Rešitev zapišemo kot: 1 from skimage.morphology import erosion 2 from skimage.morphology import disk 3 4 def extractFeaturesSingle( iImage, iMask ): 5 """Funkcija za doloˇ canje znaˇ cilnic objekta""" 6 # izraˇ cun dolžine konture 7 def lengthContour( oContour ): 8 dC = np.diff( np.hstack( (oContour, oContour[:,0].reshape((2,1))) ), axis=-1 ) 9 return np.sum( np.sqrt(np.sum(dC**2.0,axis=0)) ) 10 # doloˇ ci konturo objekta 11 iMask = iMask.astype(’uint8’) 12 oContour = trackContour( iMask, 1 ) 13 # doloˇ ci zunanje znaˇ cilnice podroˇ cja 14 A = np.count_nonzero( iMask ) 15 if A < 10: 16 print(f’Objekt {iObject} ima velikost {A} < 10’) 17 return None 18 P = lengthContour( oContour ) 19 K = 4.0 * np.pi * A / (P**2.0) 20 # odstrani sivinsko informacijo na robu z erozijo 21 iMask = erosion( iMask, disk(3) ) 22 # izlušˇ ci informacijo 23 iImageG = toGray( iImage ) 24 oGx, oGy = imageGradient( iImageG ) 25 y, x = np.where( iMask>0 ) 26 # doloˇ ci notranje znaˇ cilnice podroˇ cja 27 p = hist1D( iImageG[y, x].flatten(), iBins=256, iMinVal=0, iMaxVal=255 ) 28 p = p / ( np.sum( p ) + 1e-7 ) 29 m = np.sum( np.array( range(256) ) * p ) 30 mu2 = np.sum( np.array( range(256) - m )**2.0 * p ) 31 G = 1.0 / ( 1.0 + mu2 ) 32 Gxy = np.vstack( ( oGx[y, x].flatten(),oGy[y, x].flatten() ) ) 33 Eigvals = np.linalg.eigvals( np.dot( Gxy, Gxy.transpose() ) ) 34 R = 1 - np.min( Eigvals ) / np.max( Eigvals ) 35 # sestavi vektor znaˇ cilnic in ga vrni 36 oFeatures = np.array( (A,P,K,mu2,G,R) ) 37 return oFeatures 38 39 # preizkus funkcije 40 iObject = 3 # oznaka objekta 41 oFeatures = extractFeaturesSingle( imgF, iLabel==(iObject+1) ) # izraˇ cun znaˇ cilnic 208 Poglavje 13. Vizualna kontrola kakovosti 42 print(’features =’,oFeatures) Vektor znaˇcilnic za objekt z oznako iObject = 3 v sliki 13.5 je: features = [3.81800000e+03 2.27965512e+02 9.23224857e-01 3.94994389e+01 , → 2.46917001e-02 2.21304642e-01] Funkcijo extractFeaturesSingle() uporabimo za izraˇcun znaˇcilnic na posameznih objektih in potem združimo rezultat v matriko dimenzij M × 6, kjer je M število objektov, vsak s šestimi znaˇcilnicami. Rešitev zapišemo: 1 def extractFeatures( iImage, iLabel ): 2 """Funkcija za doloˇ canje znaˇ cilnic vseh objektov v sliki""" 3 # pripravi vhodne podatke 4 iImage = np.asarray( iImage ) 5 iLabel = np.asarray( iLabel ) 6 numL = np.max( iLabel ) 7 # doloci znacilnice za vsak objekt 8 oFeatures = np.array( () ) 9 for iObject in range(numL): 10 # izpis poteka izraˇ cuna znaˇ cilnic 11 if iObject==0: 12 oFeatures = extractFeaturesSingle( iImage, iLabel == (iObject+1) ).reshape( (1,6) ) 13 else: 14 oFeaturesSingle = extractFeaturesSingle( iImage, iLabel == (iObject+1) ) 15 if oFeaturesSingle is not None: 16 oFeaturesSingle = oFeaturesSingle.reshape( (1,6) ) 17 oFeatures = np.vstack( (oFeatures, oFeaturesSingle)) 18 # konˇ caj in vrni znaˇ cilnice 19 return oFeatures Rezultat prikazuje slika 13.7. Za razvršˇcanje oz. zaznavanje okroglih tablet je primerna znaˇcilnica kompaktnosti oblike K, medtem ko je za zaznavanje tablet z zarezo primerna znaˇcilnica izrazitosti orientacije R. Vaja 13.5 T Na osnovi slik oznaˇcenih objektov iLabel in znaˇcilnic [ A , P , K , µ 2 , G , R ] teh ob-jektov naˇcrtajte binarne razvršˇcevalnike, ki bodo odgovorili na naslednja vprašanja: • Koliko tablet je na sliki? • Koliko tablet je okroglih tablet? • Koliko tablet je okroglih tablet z zarezo? Razmislite na kakšen naˇcin bi vrednotili zanesljivost delovanja tega procesa razvršˇcanja. ■ Za odgovor na vprašanja bomo na podlagi opazovanja porazdelitev in pomena znaˇcilnic v sliki 13.7 ustvarili enostavne (hierarˇcne) binarne odloˇcitvene funkcije in tako odgovorili na postavljena vprašanja. Koliko tablet je na sliki? Privzamemo, da je pri trenutni postavitvi posamezna tableta velika vsaj 500 piklov po površini (znaˇcilnica A), kar dosežemo z upragovanjem po tej znaˇcilnici: 1 idxTablete = np.where( (oFeatures[:,0]>500) )[0] 2 #idxTablete = np.where( (oFeatures[:,0]>500) * (oFeatures[:,0]<12e+3) )[0] 3 print(’fNa sliki je {idxTablete.size} tablet.’) 4 # prikažemo barvno sliko z oznaˇ cenimi tabletami 13.3 Vaje z rešitvami 209 (a) (b) (c) (ˇc) (d) (e) Slika 13.7: Histogrami znaˇcilnic vseh objektov v sliki 13.5: (a) površina objekta A, (b) obseg objekta 2 P , (c) kompaktnost objekta K , (ˇc) varianca intenzitete µ 2 = σ , (d) gladkost sivinskih vrednosti G in (e) izrazitost orientacije R. Opisi znaˇcilnic so podani v podpoglavju 13.1.2. 5 plt.imshow(imgF) 6 plt.suptitle(’Slika tablet’) 7 plt.xlabel(’Koordinata x’) 8 plt.ylabel(’Koordinata y’) 9 for i in range(idxTablete.size): 10 plotContour( oContours[idxTablete[i]] ) 11 plt.show() Objekti, ki ustrezajo imajo oznaˇcene konture v sliki 13.8a. Dobimo odgovor: Na sliki je 148 tablet. Koliko tablet je okroglih tablet? Za vsak objekt, ki smo ga oznaˇcili kot tableta nadalje preverimo še znaˇcilnico kompaktnosti oblike K. Prag postavimo na K > 0,9: 1 idxTableteOkrogle = np.where( (oFeatures[idxTablete,2]>0.9) )[0] 2 print(f’Na sliki je {idxTableteOkrogle.size} okroglih tablet.’) 3 # prikažemo barvno sliko z oznaˇ cenimi tabletami 4 plt.imshow(imgF) 5 plt.suptitle(’Slika okroglih tablet’) 6 plt.xlabel(’Koordinata x’) 7 plt.ylabel(’Koordinata y’) 8 for i in range(idxTableteOkrogle.size): 9 plotContour( oContours[idxTablete[idxTableteOkrogle[i]]] ) 10 plt.show() Objekti, ki ustrezajo imajo oznaˇcene konture v sliki 13.8b. Dobimo odgovor: 210 Poglavje 13. Vizualna kontrola kakovosti Na sliki je 99 okroglih tablet. Koliko tablet je okroglih tablet z zarezo? Za vsak objekt, ki smo ga oznaˇcili kot okrogla tableta nadalje preverimo še znaˇcilnico izrazitosti orientacije teksture R. Prag postavimo na R > 0,4: 1 idxTableteOkrogleZareza = np.where( (oFeatures[idxTablete[idxTableteOkrogle],5]>0.4) )[0] 2 print(f’Na sliki je {idxTableteOkrogleZareza.size} okroglih tablet z zarezo.’) 3 # prikažemo barvno sliko z oznaˇ cenimi tabletami 4 plt.imshow(imgF) 5 plt.suptitle(’Slika okroglih tablet’) 6 plt.xlabel(’Koordinata x’) 7 plt.ylabel(’Koordinata y’) 8 for i in range(idxTableteOkrogleZareza.size): 9 plotContour( oContours[idxTablete[idxTableteOkrogle[idxTableteOkrogleZareza[i]]]] ) 10 plt.show() Objekti, ki ustrezajo imajo oznaˇcene konture v sliki 13.8c. Dobimo odgovor: Na sliki je 10 okroglih tablet z zarezo. Zanesljivost razvršˇcanja lahko ovrednotimo tako, da preštejemo (za posamezno vprašanje oz. kategorijo) števimo pravilno zaznanih tablet (TP) in število tistih, ki smo jih napaˇcno oznaˇcili (loˇceno lažno oznaˇcene in neoznaˇcene; FP in FN). Nato lahko ovrednotimo obˇcutljivost, specifiˇcnost in toˇcnost razvršˇcanja kot velevajo enaˇcbe v podpoglavju 13.2. 13.4 Naloge in vprašanja Poroˇcilo naloge pripravite v obliki Python skripte, ki naj izvede zahtevane izraˇcune oz. klice funkcij in izriše pripadajoˇce slike oz. grafe. Vaša dolžnost je, da v skripti uvozite ustrezne Python knjižnice in dodate morebitne podporne skripte. Skripta se mora izvesti in poustvariti rezultate, kot se zahtevajo v nalogah. Na vprašanja odgovorite v obliki komentarja, naprimer # Naloga X.1: To je moj odgovor. . Naloga 13.1 Za namen vrednotenja oznaˇcite vse objekte v sliki v naslednje razrede: • okrogle / podolgovate tablete (pripadajoˇca znaˇcka razreda 1 in 2) • tablete z zarezo / brez zareze (pripadajoˇca znaˇcka razreda 4 in 8) Znaˇcke razredov so doloˇcene tako, da jih lahko predstavimo z eno nepredznaˇceno 8–bitno sliko s po eno znaˇcko na bit (v tem primeru koristimo prve štiri bite). Ustvarite tako referenˇcno masko objektov. Doloˇcite obˇcutljivost in specifiˇcnost razvršˇcanja objektov z razvršˇcevalnikom, ki smo ga razvili pri nalogi 5. Naloga 13.2 2 Vrednotite kakovost razvršˇcanja z uporabo ROC krivulja, pri ˇcemer lahko uporabite implementacijo v Python knjižnici sklearn, in sicer funkcijo sklearn.metrics.roc_curve. Kakovost razvršˇcanja lahko opredelite z mero površine pod ROC krivuljo (AUC 3) z uporabo funkcije sklearn.metrics.auc. 2 ROC: ang. Receiver Operating Characteristics 3AUC: ang. Area Under the (ROC) Curve 13.4 Naloge in vprašanja 211 (a) (b) (c) Slika 13.8: Oznaˇceni objekti, ki odgovorijo na postavljno vprašanje pri vaji 13.5: (a) objekt je tableta, (b) objekt je okrogla tableta in (c) objekt je okrogla tableta z zarezo. Naloga 13.3 Uporabite referenˇcno masko objektov za uˇcenje razvršˇcevalnika in vrednotite njegovo kakovost tako, da doloˇcite optimalno obˇcutljivost in specifiˇcnost ter AUC. Preizkusite naslednje razvršˇcevalnike: • Nakljuˇcna drevesa (RF 4): uporabite implementacijo RandomForestClassifier v knjižnici sklearn.ensemble (povezava na dokumentacijo) • Podporne vektorje (SVM 5): uporabite implementacijo svm v knjižnici sklearn (povezava na dokumentacijo) • K najbližjih sosedov (k–NN 6): uporabite implementacijo NearestNeighbors v knjižnici sklearn.neighbors (povezava na dokumentacijo) Za vrednotenje razvšˇcevalnika je smiselno uporabiti križno validacijo, tj. uˇciti na delu pod-atkov in testirati na preostanku in nato še obratno. Delitev podatkov lahko naredite s funkcijo train_test_split() (povezava na dokumentacijo) v knjižnici sklearn.model_selection. Konˇcne rezultate nato dobite s povpreˇcenjem rezultatov pridobljenih na posameznih delih podatkov. 4 RF: ang. Random Forests 5 SVM: ang. Support Vector Machines 6 k–NN: ang. k Nearest Neighbors III Dodatek A Singularni razcep . . . . . . . . . . . . . . . . . . . 215 A.1 Matematiˇ cna formulacija A.2 Povezava z lastnimi vrednostmi in lastnimi vektorji A.3 Lastnosti singularnega razcepa A.4 Geometrijska interpretacija A.5 Reševanje predoloˇ cenih sistemov enaˇ cb B Aproksimacijska 3D toga preslikava . 219 B.1 Toga preslikava v 3D B.2 Izpeljava aproksimacijske preslikave B.3 Aproksimacijska preslikava v raˇ cunskih korakih C Razvojna okolja . . . . . . . . . . . . . . . . . . . . 225 C.1 Osnovna namestitev in konfiguracija Pythona C.2 Upravljanje Python razvojnih okolij s pip C.3 Upravljanje Python razvojnih okolij s conda C.4 Uporaba Docker virtualnega okolja C.5 Uporaba Docker mape C.6 Uporaba spletnih razvojnih okolij: Google Colab C.7 Lokalno razvojno okolje: Visual Studio Code D Vodenje razliˇ cic kode z Git . . . . . . . . . . 245 D.1 Kaj je Git in zakaj ga uporabljamo? D.2 Namestitev Gita D.3 Osnovne nastavitve D.4 Ustvarjanje in kloniranje repozitorija D.5 Osnovni Git ukazi D.6 Uporaba vejitev D.7 Povezava z oddaljenim repozitorijem D.8 Pogoste napake in koristni namigi D.9 Zakljuˇ cek D.10 Dobra praksa priprave Git repozitorija D.11 Zakljuˇ cek Bibliografija . . . . . . . . . . . . . . . . . . . . . . . . 255 Knjige Clanki ˇ Konferenˇ cni ˇ clanki Razno O avtorju . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 A. Singularni razcep Singularni razcep (SVD; ang. Singular value decomposition) [8, str. 73–74] je ena temeljnih matrikovnih faktorizacij v linearni algebri. Ta faktorizacija je vsestransko uporabno orodje v numeriˇcni linearni algebri, za analizo realnih ali kompleksnih matrik pri številnih numeriˇcnih metodah v statistiki in obdelavi signalov in slik. Na primer za reševanje predoloˇcenih sistemov enaˇcb, za stiskanje podatkov in zmanjšanje dimenzionalnosti z analizo glavnih komponent (PCA; ang. Principal Component Analysis), ipd. A.1 Matematiˇ cna formulacija Faktorizacija SVD omogoˇca razgradnjo poljubne matrike v produkt treh posebnih matrik. Za poljubno matriko m ×n A ∈ R , m > n, lahko zapišemo njen singularni razcep kot: A T = U Σ V (A.1) kjer so: • m×m U ∈ R – ortogonalna matrika, katere stolpci so levi singularni vektorji matrike A, • m×n Σ ∈ R – diagonalna matrika, katere diagonalni elementi so singularne vrednosti σi ≥ 0, • n×n V ∈ – ortogonalna matrika, katere stolpci so desni singularni vektorji matrike A R . Matrika Σ je praviloma zapisana v obliki:   σ1 0 · · · 0   0 σ · · · 0 Σ =   , (A.2)  2 .. ..  .   . . .. ... 0 0 · · · σr kjer so σi singularne vrednosti matrike A, urejene v padajoˇcem vrstnem redu (σ ≥ ≥ · · · ≥ 1 σ 2 σr ≥ 0). Stolpci matrik U in V so ortonormalni vektorji (ki so hkrati ortogonalni in enotski): 216 Poglavje A. Singularni razcep A U VT S Slika A.1: Kanoniˇcni diagram SVD-razcepa matrike A. Stolpci matrike U so ortonormalni levi singularni vektorji, T Σ je diagonalna matrika singularnih vrednosti, vrstice matrike V pa so ortonor-malni desni singularni vektorji. Obmoˇcja oznaˇcena s ˇcrtkano ˇcrto predstavljajo dopolnitev matrik (ang. padding). U = [u1, u2, . . . ,um], V = [v1,v2, . . . , vn], (A.3) kar pomeni, da veljata naslednji lastnosti: U T T U = I , VV = I . (A.4) m n To pomeni, da stolpci U in V tvorijo ortonormalne baze za vhodni in izhodni prostor matrike A. A.2 Povezava z lastnimi vrednostmi in lastnimi vektorji Singularne vrednosti σi matrike A so povezane z njenimi lastnimi vrednostmi. Za vsako singularno vrednost σ i obstajata enotska vektorja ui in vi, za katera velja: Av T i σ = i i i σ u , A u = v , i = 1, . . . ,n. (A.5) i i A.3 Lastnosti singularnega razcepa Kanoniˇcni diagram SVD-razcepa matrike A je prikazan na sliki A.1. • Lastne vrednosti matrike T 2 2 2 T A A so σ , σ , . . . , σ . Lastni vektorji matrike A A so desni 1 2 n singularni vektorji v v 1 , v 2 , . . . ,n. • Lastne vrednosti matrike T 2 2 2 − AA so σ , σ , . . . , σ , 0 , . . . , 0 (slednjih mn je niˇcelnih, ˇce m > n). 1 2 n Lastni vektorji matrike T AA so levi singularni vektorji u1 , u2, . . . , u . m • ˇ T Ce je A = A (torej simetriˇcna matrika), potem jo lahko diagonaliziramo kot: A T T = U DU , UU = I. (A.6) V tem primeru je singularni razcep: A T = U Σ V , σi = |λi|, vi λ = sign ( )u , (A.7) i i kjer so λi lastne vrednosti matrike A, pri ˇcemer velja sign(0) = 1. A.4 Geometrijska interpretacija 217 σ1 σ2 v ® Av v1 u1 v 2 u 2 σ T v 1 1 A = . . . u σ T 2 v 2 1 u 2 u n . . . . . . . . . σ T v n n Slika A.2: Geometrijska interpretacija SVD za kvadratne matrike. A.4 Geometrijska interpretacija Ce je matrika ˇ A kvadratna, torej dimenzije m × m, in ima pozitivno determinanto, potem so tudi matrike T U , V in Σ dimenzije m × m. V tem primeru imajo te matrike naslednje pomene: • T U in V sta ortogonalni matriki, ki predstavljata rotaciji, • Σ je diagonalna matrika, ki predstavlja skaliranje vzdolž posameznih osi. To pomeni, da lahko izraz T A = U Σ V razumemo kot zaporedje treh geometrijskih transformacij: 1. rotacija v prostoru vhodnih podatkov (množenje z T V ), 2. skaliranje vzdolž novih koordinatnih osi (množenje z Σ), 3. rotacija v konˇcni koordinatni sistem (množenje z U ). Ta interpretacija SVD omogoˇca intuitivno razumevanje, kako matrika A transformira vektorje v prostoru – najprej jih rotira, nato skalira (raztegne ali skrˇci) vzdolž glavnih osi in na koncu ponovno rotira. A.5 Reševanje predoloˇ cenih sistemov enaˇ cb Predoloˇcen sistem linearnih enaˇcb ima obliko: Ax m ×n n = b , A ∈ R , b ∈ R , m > n. (A.8) Ker sistem nima nujno eksaktne rešitve, išˇcemo rešitev po metodi najmanjših kvadratov (ang. least squares ), ki minimizira normo ostanka ∥Ax − b∥2. ˇ Ce rešitev za optimalne parametre x zapišemo z Moore-Penrose psevdoinverzom + + T A [3] in upoštevamo singularni razcep A = V Σ U dobimo: x T −1 T + T = ( A A ) A b = V Σ U b, (A.9) kjer je + Σ psevdoinverzna matrika matrike Σ , ki ga je možno enostavno izraˇcunati tako, da vzamemo elemente na diagonali matrike Σ in jih zamenjamo z njihovimi inverznimi vrednostmi:   1 / σ 0 · · · 0 1   0 1 / σ · · · 2 0 Σ = +   , (A.10) .. .. .. . ..   . . .   0 0 · · · 1/σr pri ˇcemer je 1/σi definirano le za neniˇcelne singularne vrednosti. Faktorizacija SVD omogoˇca stabilne rešitve tudi v primerih, ko je matrika T A A slabo pogojena ali singularna. B. Aproksimacijska 3D toga preslikava B.1 Toga preslikava v 3D Toga preslikava v 3D ima 6 prostih parametrov, tri translacije T t = [ t , t , , t ] in vzdolž 3D slike x y z x,y,z in in rotacije α (okoli osi x), β (okoli osi y) in γ (okoli osi z). Preslikovanje koordinat toˇcke T p = [ x , y , z ] lahko zapišemo z matriˇcnimi operacijami tako, da rotacijski del izrazimo z i i i i Eulerjevo 3 × 3 rotacijsko matriko: R = Rz(γ)Ry(β )Rx(α ) (B.1) kjer so posamezne 3 × 3 rotacijske matrike:   1 0 0 Rx(α) = 0 cosα − sinα  (B.2) 0 sin α cos α   cos β 0 sin β Ry(β ) =  0 1 0  (B.3) − sin β 0 cos β   cos γ − sin γ 0 Rz(γ) = sin γ cos γ 0 (B.4) 0 0 1 Celotna kompozitna 3 × 3 rotacijska matrika je:   cos β cos γ sin α sin β cos γ − cos α sin γ cos α sin β cos γ + sin α sin γ R =  cosβ sin γ sin α sin β sin γ + cosα cosγ cos α sin β sin γ − sinα cos γ (B.5) − sinβ sin α cosβ cos α cos β Rotacije se izvajajo v zaporedju z-y-x (Tait-Bryanovi koti), zato se matrik množijo v obratnem vrstnem redu kot rotacije. Koti α , β , in γ morajo biti izraženi v radianih. Rotacijska matrika 220 Poglavje B. Aproksimacijska 3D toga preslikava R T ohranja ortonormiranost ( R R = I). Ta predstavitev rotacije v 3D je še posebej uporabna pri obdelavi 3D slik in raˇcunalniškem vidu, kjer je potrebno opisati orientacijo objekta v prostoru. B.1.1 Homogena 3D toga preslikava Homogena 3D toga preslikava, ki vkljuˇcuje rotacijo R (3×3 matrika) in translacijo t (3×1 vektor), je podana s 4 × 4 matriko:   r 11 r 12 r 13 t x R t  y 21 r r 22 r 23 t T  = = (B.6) 0T   1  z 31 r r 32 r 33 t  0 0 0 1 kjer je T T 0 = [ 0 , 0 , 0 ] niˇcelni vektor. Preslikava 3D toˇcke p = [ x , y , z , 1 ] v homogenih i i i i koordinatah:     x i 11 r x i 12 + r y i 13 + r z + t i x r x y R t y + r r t + z + p ′  i  21 i 22 i 23 i y = Tp = = (B.7) i     T 0 1  i  31 z r x i + r32 yi + r33 zi + tz  1 1 Inverzna transformacija: T T − R R t T− 1 = (B.8) 0T 1 Kljuˇcne znaˇcilnosti homogene 3D toge preslikave so, da je kombinacija rotacije in translacije predstavljena v eni 4 × 4 matriki, pri ˇcemer uporaba homogenih koordinat omogoˇca zapis v matriˇcni obliki. Toga 3D preslikava ohranja razdalje med toˇckami (togost) in kote med vektorji. Inverz 3D toge preslikave se enostavno izraˇcuna zaradi lastnosti ortonormiranih matrik in je enak inverzu matrike dane homogene 3D toge preslikave. B.2 Izpeljava aproksimacijske preslikave Problem doloˇcanja aproksimacijske 3D toge preslikave v literaturi poznamo tudi pod izrazom prob-lem absolutne orientacije. Problem absolutne orientacije sega v zgodnje obdobje raˇcunalniškega vida in fotogrametrije, ko je bilo potrebno najti optimalno preslikavo med dvema množicama 3D toˇck. Klasiˇcno rešitev sta leta 1987 neodvisno razvila Horn in Arun ter skupina soavtorjev [13], ki so predlagali zaprto obliko rešitve z uporabo razcepa po singularnih vrednostih (SVD; poglavje A). Zasnova rešitve temelji na elegantni matematiˇcni lastnosti, da se optimalna rotacijska matrika dobi z maksimizacijo sledi (ang. trace) kovarianˇcne matrike med koordinatami dveh množic 3D toˇck. Ta pristop omogoˇca uˇcinkovito izraˇcunavanje transformacijskih parametrov brez iterativnih postopkov, kar je kljuˇcnega pomena za aplikacije, ki naj teˇcejo v realnem ˇcasu; na primer, poravnava toˇckovnih oblakov, registracija medicinskih slik in robotsko vodenje. Metoda ohranja svojo aktualnost še danes zaradi svoje numeriˇcne stabilnosti in raˇcunske uˇcinkovitosti. Problem absolutne orientacije v kontekstu poravnave 3D kontrolnih toˇck rešujemo z iskanjem optimalne rotacijske matrike R, translacijskega vektorja t in skalirnega faktorja s, ki preslikajo množico toˇck {pi} v množico toˇck {qi}. Naj imamo dve množici 3D toˇck {pi} in {qi} za i = 1, . . . , n. Iskana preslikava naj bo: B.2 Izpeljava aproksimacijske preslikave 221 qi = sRpi + t + ei (B.9) kjer je e i residualna napaka pri poravnavi. Cilj je minimizirati vsoto kvadratov napak: n n ∑ ∥ 2 2 e min ∥ = ∥ q ( Rp ∑ i i i − s + t)∥ → (B.10) i =1 i=1 Najprej eliminiramo translacijo t s centriranjem toˇck: p ¯ = p ∑i, q ¯ = q ∑i (B.11) n n i 1 n n 1 =1 i=1 Definiramo centrirane toˇcke kot: p ′ ′ i i = p − p ¯ , q − i i = q q ¯ (B.12) Nadalje rešujemo neodvisno od translacije problem rotacije in skaliranja: Minimizacijski problem postane: n ∑ ∥ ′ ′ 2 q − s Rp ∥ → (B.13) min i i i =1 Razvijemo kvadratno obliko: ∑ ∥ ′ 2 ′T ′ 2 ′ 2 q ∥ − 2 s q Rp + s ∥ p ∥ → (B.14) ∑ ∑ i i i i min Optimalno skaliranje doloˇcimo z odvajanjem enaˇcbe (B.14) po s in iskanjem niˇcelne toˇcke: ∑ q′T ′ Rp s i i = (B.15) ∑ p ∥ ′ 2 ∥ i Dobljen rezultat v enaˇcbi (B.15) vstavimo s nazaj in dobimo izraz za minimizacijo, kjer je edina spremenljivka R: ′ 2 ∑ q ( ′T ′ 2 Rp ) ∑ ∥ i i q ∥ − → i ′ 2 ∥ min (B.16) ∑ p ∥ i Problem minimizacije v enaˇcbi (B.16) je ekvivalenten maksimizaciji: ∑ q′T ′ T R ) → max Rp = tr ( H (B.17) i i kjer je ′ ′T T H = ∑ q p . Rešitev dobimo z uporabo SVD (poglavje A). Razcep H = U ΣV po i i singularnih vrednostih uporabimo, da zapišemo optimalno rotacijo: R T = VU (B.18) 222 Poglavje B. Aproksimacijska 3D toga preslikava Ce det ˇ(R) = −1, popravimo rešitev z zrcaljenjem: V′ ′ T = V · diag ( 1 , 1 , . . . , 1 , − 1 ) , R = V U (B.19) Konˇcna rešitev, s skaliranjem in translacijo je podana kot: s = , t = q ¯ − sRp ¯ (B.20) ′ 2 ∑ tr(Σ) ∥p ∥ i B.3 Aproksimacijska preslikava v raˇ cunskih korakih Za implementacijo aproksimacijske preslikave uporabimo naslednjih 7 (podobnostna preslikava) oziroma 6 (toga preslikava, brez koraka 6.) raˇcunskih korakov: 1. Izraˇcun težišˇc: p ¯ = p , q ∑ i ¯ = q (B.21) ∑ i n n i 1 n n 1 =1 i=1 2. Centriranje toˇck: p ′ ′ = p − p ¯ , q = q − q ¯ (B.22) i i i i 3. Izraˇcun kovarianˇcne matrike: n H ′ ′T = q p ∑ i i (B.23) i=1 4. Razcep po singularnih vrednostih (SVD): H T = U ΣV (B.24) 5. Doloˇcitev rotacijske matrike: R T = VU (B.25) Ce det ˇ(R) = −1, potem matriko popravimo: V′ ′ T = V · diag ( 1 , 1 , . . . , 1 , − 1 ) , R = V U (B.26) 6. Izraˇcun skalirnega faktorja (izpustimo, ˇce išˇcemo le togo preslikavo): ∑ i q n ′T ′ Rp s =1 i i = (B.27) ∑ n ′ 2 ∥ i p ∥ = 1 i 7. Doloˇcitev translacije (uporabimo s=1, ˇce išˇcemo le togo preslikavo): t = q ¯ − sRp ¯ (B.28) V koraku 5. dobimo rotacijsko matriko R, v koraku 7. pa translacijski vektor t. Toga preslikava toˇck je nato podana z: qi = Rpi + t (B.29) B.3 Aproksimacijska preslikava v raˇ cunskih korakih 223 Ce v gornjih korakih doloˇcimo tudi parameter skaliranja ˇ s, potem lahko zapišemo podobnostno preslikavo (toga preslikava + skaliranje) toˇck kot: qi = sRpi + t (B.30) C. Razvojna okolja Python je eden najbolj priljubljenih programskih jezikov na svetu, ki se zaradi svoje enostavnosti, berljivosti in širokega nabora uporabnih in uˇcinkovitih knjižnic pogosto uporablja v razvoju pro-gramske opreme, podatkovne analize in posebej na podroˇcju raˇcunalniškega vida. Po podatkih TIOBE Indexa1, ki meri priljubljenost programskih jezikov, je Python v zadnjih letih redno med prvimi tremi, pogosto celo na prvem mestu, kar kaže na njegovo široko sprejetost v industriji in akademskih krogih [28]. Na podroˇcju raˇcunalniškega vida je Python še posebej privlaˇcen zaradi bogatega ekosistema knjižnic, kot so OpenCV, PyTorch in TensorFlow, ki olajšajo obdelavo slik, globoko uˇcenje in analizo vizualnih podatkov. Razvojni proces v Pythonu zahteva uˇcinkovita orodja, ki omogoˇcajo hitro namestitev, konfig- uracijo in upravljanje knjižnic, saj je uspeh projekta odvisen od stabilnega in reproducibilnega razvojnega okolja. Brez pravilno nastavljenih orodij lahko pride do težav, kot so nezdružljive verzije knjižnic, težave s so-odvisnostmi knjižnic ali pomanjkanje zmogljivosti za zahtevne naloge, kot je uˇcenje in poganjanje globokih modelov za raˇcunalniški vid. V tem poglavju bomo pregledali kljuˇcna razvojna orodja, ki so uporabna na vseh operacijskih sistemih (Windows, MacOS in Linux) in so posebej koristna za razvoj na podroˇcju raˇcunalniškega vida. Obravnavali bomo naslednja orodja: • Google Colab: Spletno okolje za Python, ki ponuja brezplaˇcen dostop do grafiˇcnih in tenzorskih procesnih enot (GPU; ang. Graphic Processing Unit; TPU; ang. Tensor Processing Unit), je idealno za hitre teste in medsebojno deljenje projektov. • Visual Studio Code (VS Code): Moˇcno lokalno razvojno okolje s podporo za Conda in Docker kontejnerje, ki omogoˇca prilagodljivo delo na projektih. • Dodatna orodja: Jupyter Notebook za interaktivni razvoj, virtualna okolja (venv) za izolacijo projektov, ter kljuˇcne knjižnica za raˇcunalniški vid, kot je OpenCV. Za dodatne vire in literaturo glede jezika Python priporoˇcamo “Uvod v programski jezik Python” avtorja Mirana Bürmena [2], ki podaja pregled osnovne sintakse jezika, pregled temeljnih knjižnic kot sta numpy in matplotlib in primere njune uporabe. Za podroˇcje raˇcunalniškega (in robotskega) vida priporoˇcam knjigo “Programming Computer Vision with Python” avtorja 1(TIOBE Index: https://www.tiobe.com/tiobe-index/) 226 Poglavje C. Razvojna okolja Jan Erik Solem [7], ki ponuja poglobljen uvod v raˇcunalniški vid s praktiˇcnimi primeri v jeziku Python. Uporabni spletni viri so uradna dokumentacija Pythona (https://docs.python.org/) in vadnice na spletnih straneh, kot so Real Python (https://realpython.com/) in PyImageSearch (https://www.pyimagesearch.com/). C.1 Osnovna namestitev in konfiguracija Pythona Za zaˇcetek dela s Pythonom je potrebna pravilna namestitev in konfiguracija, ki zagotavlja stabilno in funkcionalno razvojno okolje. V tem podpoglavju bomo podali navodila za namestitev Pythona, uporabo upravljalnika paketov pip in nastavitev poti (PATH) na razliˇcnih operacijskih sistemih, ter pokazali, kako preveriti namestitev. C.1.1 Namestitev Pythona Python lahko namestite na dva naˇcina: preko uradne strani https://www.python.org/downloads/ ali z uporabo Anaconda distribucije, ki je posebej priporoˇcljiva za razvoj na podroˇcju raˇcunalniškega vida, saj vkljuˇcuje mnoge priljubljene knjižnice, kot so NumPy, Matplotlib in SciPy, ter upravljalnik paketov Conda. • Namestitev preko python.org: Obišˇcite https://www.python.org/downloads/ in pren- esite izbrano verzijo Pythona (npr. 3.9 ali novejšo). Med namestitvijo na Windows in macOS izberite možnost “Add Python to PATH”, kar olajša dostop do Pythona iz terminala oziroma ukazne vrstice. Na Linuxu obiˇcajno uporabite upravljalnik paketov sistema, npr.: $ sudo apt-get update $ sudo apt-get install python3 python3-pip # Na Debian/Ubuntu • Namestitev s Conda: Namestitev Conda na posamezne operacijske sisteme je opisna na https://docs.conda.io → Install. Prenesite Miniconda z https://docs.conda.io/ en/latest/miniconda.html ali Anaconda z https://www.anaconda.com/products/ distribution . Namestite ga z privzetimi nastavitvami. Na primer, v Linux okolju izvedete namestitev z naslednjimi ukazi v ukaznem oknu: wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh Anaconda vkljuˇcuje Python, Conda in veˇcino potrebnih knjižnic za raˇcunalniški vid, kar prihrani ˇcas pri roˇcni namestitvi, medtem, ko je potrebno pri Miniconda roˇcno namestiti knjižnice. Za prikaz delovanja rešitev vaj v tem priroˇcniku je bila uporabljena razliˇcica “Python 3.12.7”, na MacOS sistemu, z namestivijo preko python.org. C.1.2 Preverjanje namestitve Poleg ukaza python potrebujete upravljalnik paketov pip, ki je standardno orodje za namestitev Python knjižnic. Po namestitvi Pythona ali Anaconde preverite, ali je pip na voljo, in nastavite PATH, da lahko izvajate ukaze iz katere koli mape. • Na Windows: Po namestitvi Pythona preko python.org preverite, ali je PATH nastavljen. Odprite Command Prompt in vnesite: C:\> python --version Python 3.9.7 C:\> pip --version pip 21.2.3 from C:\Python39\Lib\site-packages\pip (python 3.9) C.2 Upravljanje Python razvojnih okolij s pip 227 Ce ukazi ne delujejo, morate roˇcno dodati pot do Pythona (npr. ˇ C:\Python39\;C:\Python39 \Scripts\) v sistemsko spremenljivko PATH preko nastavitev sistema. • Na macOS in Linux: Terminal obiˇcajno že vkljuˇcuje Python in pip, ker sta namešˇcena kot del operacijskega sistema. Preverite z: $ python3 --version Python 3.9.7 $ pip3 --version pip 21.2.3 from /usr/local/lib/python3.9/site-packages/pip (python 3.9) Ce uporabljate Anaconda, uporabite Conda namesto ˇ pip za veˇcjo stabilnost: $ conda --version conda 23.7.4 Nastavitev PATH na macOS/Linux ni potrebna, ˇce ste namestili Python v standardno lokacijo ali ˇce uporabljate Anaconda. C.2 Upravljanje Python razvojnih okolij s pip V tem podpoglavju bomo podrobno opisali, kako uporabljati pip, namen, vsebino in pripravo datoteke requirements.txt, izbiro specifiˇcnih verzij knjižnic, razreševanje konfliktov med odvis-nostmi in druge pomembne vidike. pip omogoˇca namestitev Python paketov iz PyPI (ang. Python Package Index). Osnovni ukaz za namestitev paketa je pip install . Primer namestitve knjižnice numpy: $ pip install numpy Collecting numpy Downloading numpy-1.21.0-cp39-cp39-win_amd64.whl (13.0 MB) |••••••••••••••••••••••••••••••••| 13.0 MB 2.5 MB/s Installing collected packages: numpy Successfully installed numpy-1.21.0 Za posodabljanje paketa uporabite: $ pip install --upgrade numpy Collecting numpy Downloading numpy-1.22.0-cp39-cp39-win_amd64.whl (14.1 MB) |••••••••••••••••••••••••••••••••| 14.1 MB 3.0 MB/s Installing collected packages: numpy Attempting uninstall: numpy Found existing installation: numpy 1.21.0 Uninstalling numpy-1.21.0: Successfully uninstalled numpy-1.21.0 Successfully installed numpy-1.22.0 Za odstranitev paketa uporabite: $ pip uninstall numpy Found existing installation: numpy 1.22.0 Uninstalling numpy-1.22.0: Would remove: c:\python39\lib\site-packages\numpy-1.22.0.dist-info\* c:\python39\lib\site-packages\numpy\* Proceed (y/n)? y Successfully uninstalled numpy-1.22.0 228 Poglavje C. Razvojna okolja C.2.1 Namen in uporaba requirements.txt Datoteka requirements.txt je kljuˇcna za dokumentiranje in reproduciranje odvisnosti projekta. Vsebuje seznam programskih knjižnic z njihovimi verzijami, ki jih projekt zahteva. Primer vsebine requirements.txt: numpy==1.21.0 pandas>=1.3.5 opencv-python<4.6 Za namestitev vseh paketov, navedenih v requirements.txt, uporabite: $ pip install -r requirements.txt Collecting numpy==1.21.0 Downloading numpy-1.21.0-cp39-cp39-win_amd64.whl (13.0 MB) |••••••••••••••••••••••••••••••••| 13.0 MB 2.5 MB/s Collecting pandas>=1.3.5 Downloading pandas-1.3.5-cp39-cp39-win_amd64.whl (10.2 MB) |••••••••••••••••••••••••••••••••| 10.2 MB 3.0 MB/s Collecting opencv-python<4.6 Downloading opencv-python-4.5.5.64-cp39-cp39-win_amd64.whl (35.4 MB) |••••••••••••••••••••••••••••••••| 35.4 MB 2.1 MB/s Installing collected packages: numpy, pandas, opencv-python Successfully installed numpy-1.21.0 pandas-1.3.5 opencv-python-4.5.5.64 Za ustvarjanje requirements.txt iz trenutno namešˇcenih paketov uporabite: $ pip freeze > requirements.txt Ta ukaz ustvari datoteko z vsemi namešˇcenimi paketi in njihovimi verzijami. Za pripravo ustreznega razvojnega Python okolja za izvedbo vaj v tem priroˇcniku je v javno dostopnem Git repozitoriju dodana datoteka requirements.txt, ki vkljuˇcuje skupno 52 Py-thon knjižnic s specifiˇcnimi verzijami. Veˇcina od teh 52 knjižnic predstavlja so-odvisnosti od minimalnega nabora knjižnic, ki jih potrebujete za izvedbo vaj. Minimalni nabor knjižnic lahko naložite z naslednjim ukazom: pip install numpy scipy scikit-image scikit-learn pandas pillow opencv-python , → opencv-contrib-python matplotlib Orodje pip (ali conda) bo nato naložilo še vse ostale so-odvisne knjižnice. C.2.2 Izbor verzij knjižnic Pri izbiri specifiˇcnih verzij je pomembno zagotoviti skladnost in stabilnost. Lahko specificirate: • Toˇcno verzijo: ==1.21.0 (npr. numpy==1.21.0). • Minimalno verzijo: >=1.21.0 (npr. pandas>=1.3.5). • Maksimalno verzijo: <4.6 (npr. opencv-python<4.6). Ce želite preveriti, katere verzije so na voljo, uporabite: ˇ $ pip search numpy numpy (1.22.0) - NumPy: multidimensional array processing for numbers, strings, , → records, and objects. INSTALLED: 1.21.0 LATEST: 1.22.0 Konflikti se pojavijo, ko razliˇcni paketi zahtevajo nezdružljive verzije istih knjižnic. Na primer, ˇce en paket zahteva numpy==1.20.0, drugi pa numpy==1.22.0, pip lahko vrne napako: C.3 Upravljanje Python razvojnih okolij s conda 229 $ pip install package1 package2 ERROR: Cannot install package1 and package2 because these package versions have , → conflicting dependencies. The conflict is caused by: package1 1.0 depends on numpy==1.20.0 package2 2.0 depends on numpy==1.22.0 Za razreševanje roˇcno uredite requirements.txt in izberite kompatibilno verzijo (npr. na- jnovejšo stabilno verzijo, ki jo podpirata oba paketa). Nato uporabite pip install -no-cache-dir za ponovno preverjanje ali pipdeptree za analizo odvisnosti: $ pip install pipdeptree $ pipdeptree numpy==1.21.0 - package1 [requires numpy==1.20.0] - package2 [requires numpy==1.22.0] Prav zaradi morebitnih razliˇcnih zahtev glede verzij knjižnic je smiselno za posamezen Python projekt ustvariti lastno razvojno okolje in namestiti ustrezne pakete. Za ta namen predlagamo uporabo virtualnega okolja (venv), ki je loˇceno od sistemske Python namestitve (nujno na MacOS in Linux). Kljuˇcni ukazi za ustvarjanje in aktivacijo virtualnega okolja so: $ python -m venv myenv $ source myenv/bin/activate # Na Linux/macOS $ myenv\Scripts\activate # Na Windows (myenv) $ pip install opencv-python numpy Alternativno lahko uporabite Conda za loˇceno upravljanje Python namestitve in odvisnosti. V življenskem ciklu razvoja in posodabljanja programske kode Python redno posodabljajte verzije knjižnic pip install -upgrade pip. ˇ Ce se sreˇcujete s težavami, preverite dokumentacijo PyPI (https://pypi.org) ali forum, kot je Stack Overflow (https://stackoverflow.com/) ali povprašajte za nasvet klepetalnega bota (https:\chatgpt.com ali https:\https://chat. deepseek.com/ ). S temi koraki lahko uˇcinkovito upravljate odvisnosti v Python projektih, kar je kljuˇcno za stabilnost in reproducibilnost vašega dela. C.3 Upravljanje Python razvojnih okolij s conda Ukaz conda uporabljamo podobno kot ukaz pip, vendar omogoˇca veˇc funkcij v enem: (i) ustvar-janje in manipuliranje virtualnih okolij, (ii) upravljanje z verzijami Python knjižic, (iii) samodejno ali roˇcno razreševanje konfliktov med so-odvisnostmi knjižnic, ipd. V terminalu oziroma ukazni vrstici ustvarite novo virtualno okolje in naložite Python knjižnice kot: $ conda create -n myenv python=3.9 Collecting package metadata (current_repodata.json): done Solving environment: done ## Package Plan ## environment location: C:\Users\YourUser\miniconda3\envs\myenv Proceed ([y]/n)? y Preparing transaction: done Verifying transaction: done 230 Poglavje C. Razvojna okolja Executing transaction: done # # To activate this environment, use: # # $ conda activate myenv # # To deactivate an active environment, use: # # $ conda deactivate # Nato aktivirajte okolje z: $ conda activate myenv (myenv) $ Namestite kljuˇcne knjižnice za robotski vid: (myenv) $ conda install opencv numpy pytorch torchvision -c pytorch Collecting package metadata (current_repodata.json): done Solving environment: done ## Package Plan ## The following packages will be downloaded: package | build ---------------------------|----------------- opencv-4.5.5 | py39h4f2741e_0 14.2 MB conda-forge numpy-1.21.0 | py39h2e03b76_0 5.5 MB conda-forge pytorch-1.10.0 |py3.9_cuda11.1_cudnn8_0 609.9 MB pytorch torchvision-0.11.0 | py39_cu111 6.8 MB pytorch ------------------------------------------------------------ Total: 636.4 MB Proceed ([y]/n)? y Downloading and Extracting Packages ... Preparing transaction: done Verifying transaction: done Executing transaction: done Shranite requirements.txt za dokumentirnje so-odvisnosti knjižnic: (myenv) $ pip freeze > requirements.txt Ta datoteka bo vsebovala seznam vseh namešˇcenih paketov, na primer: numpy==1.21.0 opencv-python==4.5.5.64 pytorch==1.10.0 torchvision==0.11.0 C.4 Uporaba Docker virtualnega okolja Docker kontejnerji (ang. Docker containers) omogoˇcajo izolacijo in reproducibilnost razvojnega okolja, ne glede na operacijski sistem in Python razliˇcico oziroma distribucijo. Kontejnerji so C.4 Uporaba Docker virtualnega okolja 231 izolirana virtualna okolja, ki vsebujejo vse potrebno za poganjanje aplikacije (kodo, knjižnice). Uporabni so za zagotavljanje, da projekt deluje enako na razliˇcnih operacijskih sistemih in da je med njimi enostavno prenosljiv. Namestitev Dockerja izvedete tako, da prenesete Docker Desktop z https://www.docker. com/products/docker-desktop za Windows/macOS ali Docker CE za Linux. Sledite navodilom za namestitev. C.4.1 Razumevanje osnovnih Docker konceptov Poleg Docker kontejnerjev, ki predstavljajo dinamiˇcno razvojno okolje poznamo še “Docker slike” (ang. Docker images), ki predstavljajo zamrznjeno razvojno okolje. Za trajno hrambo podatkov uporabljamo “Docker mape” (ang. Docker volume). Vsaka Docker slika ob zagonu z izbranimi parametri postane “docker kontejner”, ki lahko (ni pa nujno) za hrambo podatkov uporablja “docker mapo”. Razlaga konceptov treh omenjenih gradnikov je podana v nadaljevanju: Docker slika : • Opis: Nespremenljiva predloga z navodili za ustvarjanje kontejnerjev • Analogija: Kot ISO datoteka za virtualni operacijski sistem • Znaˇcilnosti: – Sestavljena iz veˇc slojev (ang. layers) – Vsebuje vse potrebne odvisnosti in nastavitve – Ustvarja se z Dockerfile ali docker commit • Primer ukaza: docker build -t moja-slika . Docker kontejner : • Opis: Izvedljiva instanca Docker slike • Analogija: Kot zagon programa iz ISO datoteke • Znaˇcilnosti: – Izolirano okolje z lastnim procesnim prostorom – Lahko se ustvari, zaustavi, odstrani ali ponovno zažene – Volatilen – spremembe se privzeto ne shranijo po odstranitvi • Primer ukaza: docker run -d --name moj-kontejner moja-slika Docker mapa : • Opis: Mehanizem za trajno shranjevanje podatkov • Analogija: Zunanji disk za kontejner • Znaˇcilnosti: – Ohranja podatke neodvisno od življenjskega cikla kontejnerja – Lahko se uporablja za deljenje podatkov med kontejnerji – Obstajajo tri vrste: imenovani (named), anonimni in gostiteljevi (host) • Primer ukaza: docker volume create moj-volume 232 Poglavje C. Razvojna okolja Primerjalna tabela Koncept Namen Trajnost Primer uporabe Docker slika Predloga za kontejner Trajna Osnovna Python slika Docker kontejner Izvedba aplikacije Volatilna Delujoˇca spletna aplikacija Docker mapa Shranjevanje podatkov Trajna Podatkovna baza • Povezava med koncepti: Slika → Kontejner + Volume = Delujoˇca aplikacija s trajnimi podatki. • Pomembno: Kontejnerji so “žive oziroma delujoˇce” slike, volumni pa omogoˇcajo trajnost podatkov. C.4.2 Ustvarjanje virtualnega okolja Recept za pripravo virtualnega okolja se pripravi z zapisom zaporedja ukazov v ukazni vrstici, kot je navedeno v poglavjih C.1, C.2 in C.3. Te ukaze zapišemo v datoteko, ki jo obiˇcajno poimenujemo Dockerfile . Pomen osnovnih Docker ukazov Docker uporablja posebne ukaze v Dockerfile za definiranje postopka gradnje kontejnerja. Spodaj je razlaga kljuˇcnih ukazov: FROM • Namen: Doloˇci osnovno sliko (base image) za kontejner • Sintaksa: FROM [:] • Primer: FROM python:3.9-slim • Podrobnosti: – Vedno mora biti prvi ukaz v Dockerfile – Oznaka (ang. tag) doloˇca verzijo (npr. 3.9, latest) – Uradne slike najdemo na https://hub.docker.com WORKDIR • Namen: Nastavi delovni imenik znotraj kontejnerja • Sintaksa: WORKDIR /path/to/directory • Primer: WORKDIR /app • Podrobnosti: – Ustvari imenik, ˇce ne obstaja – Vpliva na vse naslednje ukaze (COPY, RUN, itd.) – Alternativa ukazu cd v terminalu COPY • Namen: Kopira datoteke iz gostitelja v kontejner • Sintaksa: COPY • Primer: COPY requirements.txt . COPY src/ ./src/ • Podrobnosti: – Relativne poti so glede na WORKDIR C.4 Uporaba Docker virtualnega okolja 233 – Podpira vzorce (npr. *.py kopira vse datoteke z navedeno konˇcnico) – Za veˇcjo fleksibilnost obstaja tudi ADD RUN • Namen: Izvede ukaze med gradnjo slike • Sintaksa: RUN • Primer: RUN apt-get update && apt-get install -y git RUN pip install -r requirements.txt • Podrobnosti: – Vsak RUN ustvari nov sloj (layer) v sliki – Priporoˇcljivo je združevati povezane ukaze z && – Izvaja se v privzetem terminalu (/bin/sh) CMD • Namen: Doloˇci privzeti ukaz za zagon kontejnerja • Sintaksa: – CMD command param1 param2 (format ukazne vrstice) – CMD ["executable","param1","param2"] (strukturiran format ukazne vrstice, kjer so ukazi ali parametri navedeni loˇceno z vejicami) • Primer: CMD ["python", "app.py"] • Podrobnosti: – Lahko ga prepišemo z argumenti pri docker run – V Dockerfile je dovoljen samo en CMD – ˇ Ce ni podan, se uporabi CMD iz osnovne slike ENTRYPOINT • Namen: Doloˇci fiksni ukaz, ki se izvede ob zagonu • Sintaksa: – ENTRYPOINT ["executable","param1"] (exec forma) – ENTRYPOINT command param1 (shell forma) • Primer: ENTRYPOINT ["python"] CMD ["app.py"] • Podrobnosti: – Argumenti docker run se dodajo k ENTRYPOINT – ˇ Ce ni podan CMD, morajo biti vsi argumenti podani ob zagonu – Uporaben za "wrappere" okoli aplikacij Primerjalna tabela: CMD vs ENTRYPOINT Lastnost CMD ENTRYPOINT Namen Privzeti parametri Fiksni izvedljivi ukaz Prepisovanje Da (z argumenti docker run) Ne (le dodajanje argumentov) Priporoˇcena uporaba Spremenljivi parametri Fiksna osnova ukaza Primer kombinacije ENTRYPOINT in CMD v Dockerfile: 234 Poglavje C. Razvojna okolja ENTRYPOINT ["python"] CMD ["--help"] # Privzeto pokaže help, ˇ ce ni argumentov v docker run Na primer, ustvarite datoteko Dockerfile v korenu projekta in zapišite ukaze: FROM python:3.9-slim WORKDIR /app COPY /pot/do/moj_program.py . COPY requirements.txt . RUN pip install -r requirements.txt CMD ["python", "moj_program.py"] Ukaz FROM python:3.9-slim doloˇca osnovno Python 3.9 Docker sliko, optimizirano za ma- jhno velikost (glej C.4.2). Nato WORKDIR /app nastavi delovni imenik v kontejnerju na /app. Z ukazoma COPY /pot/do/moj_program.py . in COPY requirements.txt . kopiramo pro-gramsko kodo in seznam odvisnosti v kontejner. Ukaz RUN pip install -r requirements.txt namesti vse potrebne Python pakete iz datoteke requirements.txt. Nazadnje ukaz CMD ["python", "moj_program.py"] doloˇci privzeti ukaz za zagon programa ob inicializaciji kontejnerja. Ta datoteka Dockerfile naj z ustreznim zaporedjem ukazov, kot bi jih izvedli v terminali oziroma ukazni vrstici, ustvari optimizirano okolje za izvajanje Python aplikacije z vsemi potrebnimi Python knjižnicami in vašimi programi. Nato lahko zgradite Docker sliko z: $ docker build -t myimage . Sending build context to Docker daemon 2.048kB Step 1/4 : FROM python:3.9-slim ---> 48e5f4513959 Step 2/4 : WORKDIR /app ---> Using cache ---> 6d7f8b3a9a0c Step 3/4 : COPY requirements.txt . ---> Using cache ---> 9a3b2c1d8e7f Step 4/4 : RUN pip install -r requirements.txt ---> Running in 4b5c6d7e8f9a Collecting numpy==1.21.0 (from -r requirements.txt (line 1)) Downloading numpy-1.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64. , → whl (15.8 MB) |••••••••••••••••••••••••••••••••| 15.8 MB 3.2 MB/s ... Successfully built myimage in izvedete zagon kontejnerja: $ docker run -it myimage Python 3.9.7 (default, Sep 16 2021, 08:50:19) [GCC 10.2.1 20210110] on linux Type "help", "copyright", "credits" or "license" for more information. >>> Glavne nastavitve za zagon kontejnerjev z ukazom docker run vkljuˇcujejo:-i (interaktivni naˇcin, omogoˇca vnos preko STDIN),-t (alocira psevd-terminal za interaktivne seje, obiˇcajno uporabljen skupaj z-i kot-it),-d (detached naˇcin, poganja kontejner v ozadju),-rm (samodejno C.5 Uporaba Docker mape 235 odstrani kontejner po izhodu),-p (preslikanje vrat, npr.-p 8080:80),-v (montiranje volumnov, npr.-v /host:/container), in-name (doloˇcitev imena kontejnerja). Te opcije omogoˇcajo fleksibilno upravljanje vedenja kontejnerjev med zagonom. Kljuˇcna prednost, ko enkrat preverimo delovanje para Docker slike in kontejnerja, je reproducibilnost (ista slika oz. kontejner deluje povsod), enostavno deljenje okolja z drugimi razvijalci in izolacija od lokalnega sistema. Uporaba vnaprej pripravljenih Docker slik Uporabite ali prilagodite lahko razliˇcne osnovne Docker slike, ki so na voljo na https://hub. docker.com/. Za pridobitev Docker slike iz registra Docker Hub uporabite ukaz docker pull. Osnovna sintaksa je: docker pull : Primer za pridobitev uradne Python slike verzije 3.9: docker pull python:3.9-slim Ce oznaka ni podana, se privzeto uporabi ˇ latest. Slika se prenese lokalno in jo lahko preverite z: docker images C.5 Uporaba Docker mape Za uporabo Docker mape ali sistemske mape pri zagonu kontejnerja uporabimo zastavico-v ali-mount. Glavne razlike: •-v je krajša sintaksa za osnovno uporabo •-mount ponuja veˇc možnosti za naprednejše scenarije Osnovna sintaksa je naslednja: docker run -v ime_volumna:/pot/v/kontejnerju slika Primer uporabe obstojeˇce docker mape: # Ustvari mapo (ˇ ce še ne obstaja) docker volume create moj_volume # Poženi kontejner z montirano mapo docker run -d -v moj_volume:/app/podatki nginx Primer uporabe sistemske mape: docker run -v /absolutna/pot/na/pc:/pot/v/kontejnerju slika Pri uporabi sistemskih map morajo biti nastavljene ustrezne pravice za branje/pisanje/izvajanje datotek in map tako na sistemu kot v Docker kontejnerju. Uporaba mape z omejitvami, na primer samo za branje (ang. read-only): docker run -v moj_volume:/podatki:ro alpine Preverjanje seznama Docker map v uporabi: # Pokaže podrobnosti o kontejnerju (vkljuˇ cno z volumni) docker inspect ime_kontejnerja # Pokaže vse volume docker volume ls 236 Poglavje C. Razvojna okolja C.5.1 Nastavitev Docker kontejnerja za daljinski dostop Daljinski dostop z “varno lupino” (SSH; ang. Secure Shell) je varen protokol za upravljanje in dostop do oddaljenih raˇcunalnikov ali strežnikov preko omrežja. Uporablja šifrirano povezavo za izmenjavo podatkov, kar zagotavlja varnost komunikacije, vkljuˇcno z gesli in ukazi. S pomoˇcjo ssh klienta lahko razvijalci dostopajo do terminala oddaljenega sistema, kot je Docker kontejner, izvajajo ukaze, upravljajo datoteke in izvajajo programe, kot so Python skripte za raˇcunalniški vid, ne da bi fiziˇcno sedeli pred tem sistemom. SSH je široko uporabljen zaradi svoje robustnosti in enostavnosti uporabe, zlasti v razvojnih in produkcijskih okoljih. SSH Veˇc informacij o SSH, njegovi namestitvi in uporabi, najdete na uradni strani OpenSSH projekta, dostopna na naslovu https://www.openssh.com/. Ta stran ponuja uradno dok-umentacijo, novice in vire o OpenSSH, ki je najbolj razširjena implementacija SSH pro-tokola. Na voljo so vodniki za namestitev na razliˇcnih operacijskih sistemih (Windows, macOS, Linux ), konfiguracijo strežnika in klienta, ter napredne teme, kot so uporaba SSH kljuˇcev, tuneliranje in varnostne nastavitve. Poleg tega vkljuˇcuje povezave do prenosov, FAQ in refer-enˇcno dokumentacijo (man pages). Za dodatne praktiˇcne primere priporoˇcamo tudi vadnice na DigitalOcean ter dokumentacijo na https://docs.github.com/en/authentication/ connecting-to-github-with-ssh spletni strani GitHub, zlasti ˇce uporabljate SSH v kon-tekstu Git ali Dockerja. Ti viri so zanesljivi, široko uporabljeni in redno posodobljeni. Za omogoˇcanje daljinskega dostopa do Docker kontejnerja preko SSH, morate prilagoditi Dockerfile in konfigurirati SSH strežnik. Poleg tega lahko kontejner zaženete v odcepljenem (ang. detached) naˇcinu, kar omogoˇca, da teˇce v ozadju, medtem ko ohranjate možnost dostopa preko SSH. Konfiguracija Dockerfile za SSH Ustvarite Dockerfile z naslednjo vsebino, ki temelji na Python sliki in doda SSH podporo: FROM python:3.9-slim # Namestitev OpenSSH strežnika in drugih orodij RUN apt-get update && apt-get install -y openssh-server RUN mkdir /var/run/sshd # Ustvarjanje uporabnika in nastavitev gesla RUN useradd -m -s /bin/bash developer && \ echo "developer:password123" | chpasswd # Konfiguracija SSH RUN echo "PermitRootLogin no" >> /etc/ssh/sshd_config && \ echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config && \ echo "AllowUsers developer" >> /etc/ssh/sshd_config # Omogoˇ canje dostopa do SSH porta EXPOSE 22 # Kopiranje requirements.txt in namestitev Python paketov WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt # Zagon SSH strežnika pri zagonu kontejnerja C.6 Uporaba spletnih razvojnih okolij: Google Colab 237 CMD ["/usr/sbin/sshd", "-D"] Nato zgradite sliko z: $ docker build -t python_ssh_image . Sending build context to Docker daemon 2.048kB Step 1/9 : FROM python:3.9-slim ---> 48e5f4513959 Step 2/9 : RUN apt-get update && apt-get install -y openssh-server ---> Running in 9a3b2c1d8e7f ... Successfully built python_ssh_image Za zagon kontejnerja v “odcepljenem” naˇcinu (ang. detached mode, omogoˇcimo s stikalom docker run -d ... ) uporabite naslednji ukaz, ki omogoˇca, da kontejner teˇce v ozadju in izpostavi port 22 za SSH: $ docker run -d -p 2222:22 python_ssh_image a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6 Tu ‘-d‘ zahteva zagon kontejnerja v odcepljenem naˇcinu, medtem ko ‘-p 2222:22‘ preusmeri lokalni port 2222 na port 22 v kontejnerju (lahko izberete drug port, npr. 2222, a razliˇcen od 22 da se izognemo konfliktom z lokalnim SSH). Za povezavo preko SSH iz drugega terminala ali raˇcunalnika uporabite: $ ssh developer@localhost -p 2222 The authenticity of host '[localhost]:2222 ([::1]:2222)' can't be established. ECDSA key fingerprint is SHA256:... Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '[localhost]:2222' (ECDSA) to the list of known hosts , → . developer@localhost's password: password123 Welcome to Python SSH Container! $ Ko ste povezani, lahko v kontejnerju izvajate ukaze, kot so zagon Python skript, namestitev dodatnih paketov ali obdelava podatkov za raˇcunalniški vid. Opomba : Uporaba gesel namesto SSH kljuˇcev ni najbolj varna za produkcijske okolje. Priporoˇcljivo je uporabiti SSH kljuˇce in onemogoˇciti gesla v produkciji. Poleg tega preverite, ali je vaš požarni zid (ang. firewall) nastavljen za dovoljenje izbranega porta (npr. 2222). Za dodatne informacije obišˇcite dokumentacijo Dockerja (https://docs.docker.com) in OpenSSH (https://www.openssh.com). C.6 Uporaba spletnih razvojnih okolij: Google Colab Google Colab (https://colab.research.google.com) je brezplaˇcno spletno razvojno okolje, ki omogoˇca uporabo Pythona brez potrebe po lokalni namestitvi, saj se izvaja neposredno v spletnem brskljalniku, kar ga naredi idealnega za zaˇcetnike in razvijalce, ki potrebujejo hitre teste ali dostop do naprednih raˇcunskih virov. Temelji na Jupyter delovnih zvezkih (za jezik Python imajo konˇcnico .pynb ) in omogoˇcajo kombiniranje besedila, grafik, tabel in enaˇcb z bloki Python kode (slika C.1). 238 Poglavje C. Razvojna okolja Slika C.1: Spletno razvojno okolje Google Colab. V tem podpoglavju bomo pregledali prednosti, navodila za uporabo, primer za raˇcunalniški vid ter omejitve tega orodja. Kljuˇcne prednosti Google Colab platforme so: • Brezplaˇcen dostop do GPU/TPU: Omogoˇca hitrejše izvajanje zahtevnejših raˇcunskih operacij, kot so globoko uˇcenje. • Enostavna delitev notebookov: Sodelovanje je preprosto prek povezave ali prek Google Drive. • Integracija z Google Drive: Shranjevanje in upravljanje projektov neposredno v oblaku. Nekatere omejitve platforme so odvisnost od internetne povezave, saj brez interneta ni mogoˇce dostopati do Colaba ali shranjevati dela. Viri za dolgotrajn raˇcunsko izvajanje so omejeni; nam-reˇc brezplaˇcni raˇcun ima omejeno koliˇcino RAM-a, CPU/GPU ˇcasa in shranjevalnega prostora. Dolgotrajno uˇcenje globokih modelov ali obdelava velikih podatkovnih naborov lahko zahtevajo nadgradnjo na plaˇcljive možnosti ali uporabo lokalnega okolja. C.6.1 Navodila za ustvarjanje in konfiguracijo projekta Za zaˇcetek uporabite Google Colab, obišˇcite https://colab.research.google.com in se pri-javite z Google raˇcunom. Ustvarite nov delovni zvezek z gumbom “New Notebook”. Osnovna konfiguracija vkljuˇcuje namestitev potrebnih knjižnic, kot je OpenCV, z uporabo ukaza !pip install v celici kode pa lahko namestite druge poljubne Python knjižnice, na primer: !pip install opencv-python matplotlib C.6 Uporaba spletnih razvojnih okolij: Google Colab 239 Collecting opencv-python Downloading opencv-python-4.5.5.64-cp39-cp39-manylinux1_x86_64.whl (49.4 MB) |••••••••••••••••••••••••••••••••| 49.4 MB 2.3 MB/s Collecting matplotlib Downloading matplotlib-3.5.2-cp39-cp39-manylinux1_x86_64.whl (11.3 MB) |••••••••••••••••••••••••••••••••| 11.3 MB 3.0 MB/s Installing collected packages: opencv-python, matplotlib Successfully installed matplotlib-3.5.2 opencv-python-4.5.5.64 Po namestitvi dodajte blok kode in preverite verzije: 1 import cv2 2 import matplotlib.pyplot as plt 3 4 print("OpenCV Version:", cv2.__version__) 5 print("Matplotlib Version:", plt.__version__) Izhod bo izgledal približno takole: OpenCV Version: 4.5.5.64 Matplotlib Version: 3.5.2 C.6.2 Primer uporabe Google Colab Google Colab je odliˇcen za naloge, kot so nalaganje slik, obdelava z OpenCV in vizualizacija z Matplotlib . Tukaj je primer kode za nalaganje slike iz spletnega mesta, njeno obdelavo in prikaz: 1 # Naloži sliko iz URL-ja 2 !wget https://fe.uni-lj.si/wp-content/uploads/2024/01/Inf_Laibach-206-scaled.jpg-O image.jpg 3 4 # Preberi in obdelaj sliko z OpenCV 5 import cv2 6 import matplotlib.pyplot as plt 7 8 img = cv2.imread(’image.jpg’) 9 img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Pretvori iz BGR v RGB za pravilno prikazovanje 10 11 # Prikaz slike 12 plt.imshow(img_rgb) 13 plt.axis(’off’) # Skrij osi 14 plt.title(’Obdelana slika’) 15 plt.show() Ta primer prikazuje, kako naložiti sliko, jo pretvoriti v pravilno barvno shemo (OpenCV privzeto uporablja BGR, medtem ko Matplotlib priˇcakuje RGB) in jo prikazati. Sliko lahko naložite tudi iz Google Drive z uporabo ustreznih Colab ukazov (npr. from google.colab import files), kot je prikazano v sliki C.1. C.6.3 Namigi za uporabo GPU/TPU: • V »Runtime« → »Change runtime type« → izberite »GPU«. 240 Poglavje C. Razvojna okolja • Uporabite !nvidia-smi za preverjanje GPU: !nvidia-smi Izhod bo izgledal (zavisi od vaše strojne opreme) približno tako: C.7 Lokalno razvojno okolje: Visual Studio Code Visual Studio Code (VS Code) je priljubljen, lahkoten urejevalnik kode s podporo za številne programske jezike, vkljuˇcno s Pythonom (vmesnik na sliki C.2). Ponuja zmogljive funkcije, kot so avtomatsko dokonˇcevanje kode, razhrošˇcevanje (ang. debugging) in integracija z orodji za upravljanje okolij. V tem podpoglavju bomo podrobno opisali namestitev in konfiguracijo VS Code, uporabo Python razširitve, upravljanje knjižnic z Conda ter uporabo Docker kontejnerjev za izolacijo in reproducibilnost projektov. C.7.1 Namestitev in osnovna konfiguracija Visual Studio Code VS Code je na voljo za Windows, macOS in Linux ter se prenese z uradne strani https://code. visualstudio.com/. Sledite tem korakom za namestitev: • Windows: – Prenesite namestitveni program z https://code.visualstudio.com/ – Zaženite namestitev z privzetimi nastavitvami – Med namestitvijo izberite možnost dodajanja v sistemsko pot (PATH), ˇce je na voljo • macOS: brew install --cask visual-studio-code • Linux (Ubuntu/Debian): sudo apt update sudo apt install code Preverite delovanje z odpiranjem terminala znotraj VS Code (“View > Terminal”) in vnesite: $ code --version 1.82.0 Ce ukaz deluje, je VS Code pravilno namešˇcen. Nadaljujte z namešˇcanjem Python razširitev: ˇ 1. Odprite VS Code in pojdite na “tržnico razširitev” (ang. Extensions Marketplace; bližnjica Ctrl+Shift+X ) 2. Poišˇcite in namestite “Python” razširitev od Microsoft s klikom na gumb “Install” 3. Priporoˇcene dodatne razširitve: • Pylance (za napredno dokonˇcevanje kode) • Python Indent (za pravilno oblikovanje) • Jupyter (za delo z notebooki) C.7 Lokalno razvojno okolje: Visual Studio Code 241 Slika C.2: Grafiˇcni vmesnik Visual Studio Code (VS Code). Po namestitvi razširitve odprite novo Python datoteko (.py) in zaˇcnite pisati kodo. Razširitev bo samodejno predlagala dokonˇcanje (npr. za funkcije iz numpy). Za linting namestite v terminalu v VS Code orodje, kot je flake8, z: $ pip install flake8 V nastavitvah VS Code omogoˇcite linting z dodajanjem v settings.json: { "python.linting.enabled": true, "python.linting.flake8Enabled": true } Za razhrošˇcevanje nastavite kontrolne toˇcke (ang. breakpoints) v kodi in uporabite gumb “Run and Debug” v levem meniju VS Code. Po zaustavitvi v kontrolni toˇcki lahko preverite vrednosti spremenljivk ali po potrebi tudi spremenite. C.7.2 Uporaba lokalne Python distribucije Predpostavimo, da ste že namestili Python preko uradne strani (https://www.python.org/ downloads/ ) ali Anaconda. VS Code mora prepoznati to distribucijo za zaganjanje Python programov: • Odprite VS Code in ustvarite novo mapo za projekt (“File > Open Folder”). 242 Poglavje C. Razvojna okolja • Odprite paleto ukazov (Ctrl+Shift+P ali Cmd+Shift+P na macOS) in vnesite “Python: Select Interpreter” (kot na sliki C.2). Izberite lokalno namešˇceno verzijo Pythona, npr.: Python 3.9.7 64-bit ('base': conda) Ce Python ni zaznana, preverite, ali je pot do ˇ python.exe (Windows) ali python3 (Linux/ma-cOS) dodana v sistemsko spremenljivko PATH. Na Windows lahko to nastavite roˇcno v Nastavitvah sistema, na Linux/macOS pa z urejanjem ~/.bashrc ali ~/.zshrc: $ echo 'export PATH="$HOME/.pyenv/bin:$PATH"' >> ~/.bashrc $ source ~/.bashrc Preverite delovanje v terminalu z: $ python --version Python 3.9.7 • Nastavite konfiguracijo za poganjanje kode in razhrošˇcevanje z ustvarjanjem launch.json (preko palete ukazov “Debug: Open launch.json”). Primer: { "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal" } ] } C.7.3 Uporaba lokalnega Docker kontejnerja VS Code omogoˇca delo z lokalnimi in oddaljenimi (takimi, ki teˇcejo na drugih raˇcunalnikih) Docker kontejnerji preko SSH. Predpogoj je, da imate namešˇcen in konfiguriran Docker, zagnan Docker kontejner s SSH strežnikom in omogoˇcen dostop do SSH porta v kontejnerju (kot je opisano v poglavju C.5.1) ter VS Code z ustreznimi razširitvami. Prepriˇcajte se, da imate: • Namešˇcen Docker Desktop ali Docker CE na vašem sistemu (https://www.docker.com). • Konfiguriran Dockerfile z SSH podporo, kot je prikazano prej (npr. z openssh-server in izpostavljenim portom 22). • Namešˇcen VS Code z razširitvijo “Remote - SSH” iz tržnice razširitev. Nato odprite paleto ukazov (Ctrl+Shift+P ali Cmd+Shift+P na macOS) in vnesite “Remote- SSH: Connect to Host”. To bo omogoˇcilo konfiguracijo SSH povezave. Preverite delovanje razširitve z odpiranjem terminala znotraj VS Code: $ code --install-extension ms-vscode-remote.remote-ssh Installing extension 'ms-vscode-remote.remote-ssh' v1.93.0... Extension 'ms-vscode-remote.remote-ssh' v1.93.0 was successfully installed. Predpostavimo, da imate že zagnan Docker kontejner z SSH v odcepljenem naˇcinu in izpostavlje port 2222. Nato konfigurirajte SSH v VS Code: 1. Ustvarite ali uredite datoteko konfiguracije SSH na vašem raˇcunalniku. Obiˇcajno se nahaja v ~/.ssh/config (Linux/macOS) ali C:\Users\YourUser\.ssh\config (Windows). Doda-jte naslednjo vsebino: C.7 Lokalno razvojno okolje: Visual Studio Code 243 Host docker-container HostName localhost User developer Port 2222 IdentityFile ~/.ssh/id_rsa Ce še nimate SSH kljuˇca, ga ustvarite v terminalu z: ˇ $ ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa Generating public/private rsa key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in ~/.ssh/id_rsa Your public key has been saved in ~/.ssh/id_rsa.pub Kopirajte javni kljuˇc (~/.ssh/id_rsa.pub) v kontejner, ˇce ga še niste (lahko uporabite docker cp ali roˇcno med zagonom). 2. V VS Code odprite paleto ukazov in izberite “Remote-SSH: Connect to Host”. Izberite “docker-container” iz konfiguracije. VS Code bo vzpostavil povezavo in odprl novo okno, povezano z vašim Docker kontejnerjem. Ko ste povezani preko SSH, lahko v VS Code urejate in izvajate Python kodo, kot bi bili na lokalnem sistemu. Lahko namestite dodatne knjižnice z pip ali conda znotraj kontejnerja. Lahko poganjate in razhrošˇcujete kodo z nastavitvijo kontrolnih toˇck in uporabo integriranega razhrošˇcevalnika v VS Code. Za dodatne informacije obišˇcite dokumentacijo VS Code (https: //code.visualstudio.com/docs/remote/ssh ) in Dockerja (https://docs.docker.com). D. Vodenje razliˇ cic kode z Git D.1 Kaj je Git in zakaj ga uporabljamo? Git je razširljiv, hiter in razpršen sistem za upravljanje in vodenje razliˇcic (angl. Version Control System, VCS), ki ga je leta 2005 ustvaril Linus Torvalds za razvoj jedra Linux. Omogoˇca sledenje spremembam v izvorni kodi, usklajeno sodelovanje veˇc razvijalcev in enostavno obnavljanje starejših razliˇcic projekta. Tu je nekaj kljuˇcnih namenov uporabe Gita: • Sledenje spremembam: Git beleži vse spremembe v datotekah, kar omogoˇca primerjavo ali povrnitev na prejšnje razliˇcice. • Sodelovanje: Veˇc razvijalcev lahko hkrati dela na istem projektu brez prepricov (branches, merge). • Odveˇcnost: Ker je razpršen, vsak razvijalec ima celotno zgodovino projekta – ni odvisen od centralnega strežnika. • Integracija s platformami: Git se pogosto uporablja s storitvami, kot so GitHub, GitLab in Bitbucket, za skupno razvijanje kode. • Hitrost in uˇcinkovitost: Git je optimiziran za hitro delo, tudi z velikimi projekti. Git se danes uporablja v skoraj vseh veˇcjih programerskih projektih, od odprtokodne programske opreme do komercialnih rešitev. Njegova fleksibilnost in zmogljivost sta kljuˇcna razloga za njegovo široko sprejetost. D.2 Namestitev Gita Git je razpoložljiv za vse glavne operacijske sisteme. Spodaj so navodila za namestitev na razliˇcne platforme. D.2.1 Namestitev na Windows 1. Obišˇcite uradno spletno stran https://git-scm.com/. 2. Prenesite namestitveni program za Windows (‘.exe‘ datoteko). 3. Zaženite namestitveni program in sledite navodilom (priporoˇcljivo je ohraniti privzete nastavitve). 246 Poglavje D. Vodenje razliˇ cic kode z Git D.2.2 Namestitev na macOS 1. ˇ Ce še nimate Homebrew, ga namestite z ukazom: /bin/bash -c "\$(curl -fsSL https://raw.githubusercontent.com/ , → Homebrew/install/HEAD/install.sh)" 2. Nato namestite Git z ukazom: brew install git D.2.3 Namestitev na Linux 1. Za distribucije, ki uporabljajo apt (npr. Ubuntu, Debian), uporabite: sudo apt update sudo apt install git 2. Za distribucije z yum (npr. Fedora, CentOS) uporabite: sudo yum install git D.2.4 Preverjanje namestitve Ne glede na operacijski sistem lahko po namestitvi preverite, ˇce Git deluje pravilno, z ukazom: git --version Ce je Git pravilno namešˇcen, se izpiše razliˇcica (npr. ˇ git version 2.40.1). D.3 Osnovne nastavitve Preden zaˇcnemo uporabljati orodje Git, je potrebno opraviti osnovno nastavitev, ki omogoˇca, da Git ve, kdo ste in kako naj deluje. V tem poglavju bomo pregledali osnovne nastavitve, kot so nastavitev uporabniškega imena in e-pošte, privzeto nastavitev urejevalnika ter prikaz trenutne nastavitve. D.3.1 Nastavitev uporabniškega imena in e-pošte Ena izmed prvih stvari, ki jih morate nastaviti pri uporabi Gita, je vaše uporabniško ime in e-poštni naslov. Ti podatki se uporabljajo za oznaˇcevanje sprememb, ki jih vnesete v repozitorij. Nastavitev je globalna in se izvede z ukazoma git config -global user.name za uporabniško ime in git config -global user.email za e-poštni naslov. Primer: \$ git config --global user.name "Vaše Ime" \$ git config --global user.email "vaša.email@primer.com" Ko enkrat nastavite te vrednosti, jih Git uporabi za vse vaše repozitorije, razen ˇce jih posebej prilagodite za posamezen repozitorij brez globalne nastavitve. D.3.2 Privzeto nastavljanje urejevalnika Git pogosto zahteva uporabo urejevalnika besedil, na primer pri pisanju sporoˇcil ob potrditvi sprememb (ang. commit messages) ali urejanju nastavitev. Privzeti urejevalnik lahko nastavite z ukazom git config -global core.editor. Pogosto uporabljeni urejevalniki so na primer Vim, Nano ali Visual Studio Code. Primer nastavitve za uporabo Vima: \$ git config --global core.editor "vim" Ce želite uporabiti drug urejevalnik, ga enostavno zamenjate z ustreznim imenom programa. ˇ To nastavitev je priporoˇcljivo opraviti takoj, saj olajša delo z Gitom. D.4 Ustvarjanje in kloniranje repozitorija 247 D.3.3 Prikaz konfiguracije Za preverjanje trenutnih nastavitev lahko uporabite ukaz git config -list, ki prikaže vse konfiguracijske nastavitve, ki jih Git uporablja. Primer izhoda: \$ git config --list user.name=Vaše Ime user.email=vaša.email@primer.com core.editor=vim ... Ta ukaz je koristen, ˇce želite preveriti, ali so vaše nastavitve pravilno shranjene ali ˇce potrebujete pregled nad trenutno konfiguracijo. ˇ Ce katera od nastavitev manjka, jo lahko kadar koli dodate ali spremenite z ustreznim ukazom git config. S temi osnovnimi nastavitvami ste pripravljeni na nadaljnjo uporabo Gita. Naslednji koraki obiˇcajno vkljuˇcujejo ustvarjanje repozitorijev in upravljanje verzij vaših projektov. D.4 Ustvarjanje in kloniranje repozitorija Ena izmed osnovnih operacij pri uporabi Gita je ustvarjanje novih repozitorijev ali kloniranje obstojeˇcih. V tem poglavju bomo pregledali, kako inicializirati nov repozitorij in kako klonirati že obstojeˇc repozitorij iz oddaljenega strežnika, kot je na primer GitHub. D.4.1 Inicializacija novega repozitorija Ce želite zaˇceti nov projekt pod verzijsko kontrolo z Gitom, morate inicializirati nov repozitorij. To ˇ naredite z ukazom git init, ki ustvari novo Git mapo (direktorij) v trenutni mapi. Primer: \$ git init Initialized empty Git repository in /path/to/your/project/.git/ Ko izvedete ta ukaz, Git ustvari skrito mapo .git, ki vsebuje vse potrebne datoteke za sledenje spremembam v vašem projektu. Po inicializaciji lahko zaˇcnete dodajati datoteke, jih zavezati (commit) in upravljati verzije. D.4.2 Kloniranje obstojeˇ cega repozitorija Ce želite delati z že obstojeˇcim repozitorijem, na primer tistim, ki je shranjen na platformi kot ˇ je GitHub, Bitbucket ali GitLab, lahko uporabite ukaz git clone . Ta ukaz naredi kopijo celotnega repozitorija, vkljuˇcno z zgodovino sprememb, na vaš lokalni raˇcunalnik. Primer: \$ git clone https://github.com/uporabnik/ime-repozitorija.git Cloning into 'ime-repozitorija'... remote: Enumerating objects: 100, done. remote: Counting objects: 100% (100/100), done. remote: Compressing objects: 100% (50/50), done. Receiving objects: 100% (100/100), 1.23 MiB | 1.23 MiB/s, done. Resolving deltas: 100% (60/60), done. V tem primeru nadomestite https://github.com/uporabnik/ime-repozitorija.git z dejanskim URL-jem repozitorija, ki ga želite klonirati. Po uspešnem kloniranju lahko zaˇcnete delati z datotekami in jih po potrebi spreminjati, zavezovati in pošiljati nazaj na oddaljeni strežnik. S temi koraki ste pripravljeni na upravljanje svojih projektov z Gitom, bodisi z ustvarjanjem novih repozitorijev bodisi z delom na obstojeˇcih. Naslednji koraki obiˇcajno vkljuˇcujejo dodajanje in posodabljanje datotek ter sodelovanje z drugimi razvijalci. 248 Poglavje D. Vodenje razliˇ cic kode z Git D.5 Osnovni Git ukazi Osnovni Git ukazi vam omogoˇcajo upravljanje sprememb v vašem projektu. Pregledali bomo ukaze za preverjanje statusa datotek, dodajanje sprememb v “staging” (priprava spremembe) obmoˇcje, ustvarjanje “commitov” (potrditev spremembe) in ogled zgodovine sprememb. D.5.1 Preverjanje statusa datotek Za preverjanje trenutnega stanja vašega repozitorija, vkljuˇcno z neizvedenimi spremembami, novimi datotekami in datotekami, pripravljenimi za potrditev spremembe (“commit”), uporabite ukaz git status. Ta ukaz prikaže podrobnosti o tem, kaj se je spremenilo od zadnje potrditve spremembe. Primer: \$ git commit On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add ..." to update what will be committed) (use "git restore ..." to discard changes in working directory) modified: dokumentacija.txt Untracked files: (use "git add ..." to include in what will be committed) nova_datoteka.txt no changes added to commit (use "git add" and/or "git commit -a") Ukaz git status je koristen za hitro oceno, kaj morate nadalje narediti. D.5.2 Dodajanje sprememb v pripravo Preden lahko spremembe potrdite (commit), jih morate dodati v stanje pripravljenosti (“staging”). To naredite z ukazom git add za posamezno datoteko ali git add . za dodajanje vseh sprememb v trenutni mapi. Primeri: \$ git add dokumentacija.txt # Dodajanje posamezne datoteke \$ git add . # Dodajanje vseh sprememb v trenutni mapi Ko so datoteke v stanju pripravljenosti, jih v naslednjem koraku lahko potrdite – ustvarite “commit”. D.5.3 Potrjevanje sprememb Za potrditev in shranjevanje sprememb v zgodovino repozitorija uporabite ukaz git commit -m "Sporoˇ cilo", kjer "Sporoˇcilo" opisuje, katere spremembe ste naredili. Primer: \$ git commit -m "Posodobljena dokumentacija in dodana nova datoteka" [main 7f3c5b6] Posodobljena dokumentacija in dodana nova datoteka 2 files changed, 15 insertions(+) create mode 100644 nova_datoteka.txt Sporoˇcilo potrditve spremembe mora biti jasno in opisno, da olajša razumevanje zgodovine projekta. D.5.4 Ogled zgodovine sprememb Za pregled zgodovine vseh sprememb v repozitoriju uporabite ukaz git log. Ta ukaz prikaže seznam commitov z njihovimi ID-ji, avtorji, datumi in sporoˇcili. Primer: D.6 Uporaba vejitev 249 \$ git log commit 7f3c5b6a9d8e7f6b5c4d3e2a1b0 (HEAD -> main, origin/main) Author: Vaše Ime Date: Wed Oct 16 14:30:22 2023 +0200 Posodobljena dokumentacija in dodana nova datoteka commit 5e4d3c2b9a8f7e6d5c4b3a2 (origin/develop, develop) Author: Druga Oseba Date: Tue Oct 15 10:15:00 2023 +0200 Osnovna struktura projekta Ukaz git log je koristen za sledenje spremembam in razumevanje razvoja projekta. S temi osnovnimi ukazi lahko uˇcinkovito upravljate svoje projekte z Gitom. Naslednji koraki lahko vkljuˇcujejo delo z vejami, združevanje sprememb in sodelovanje z drugimi razvijalci. D.6 Uporaba vejitev Vejitve (ang. branching) so eden izmed moˇcnejših funkcionalnosti Gita, ki omogoˇca vzporedno delo na razliˇcnih delih projekta brez vpliva na glavno vejo (npr. main ali master). V tem podpoglavju bomo pregledali, kako ustvarjati in preklapljati med vejami, kako združevati veje ter kako reševati morebitne konflikte. D.6.1 Ustvarjanje in preklapljanje vej Za upravljanje vej v Gitu uporabljamo ukaze, kot so git branch, git checkout in git switch. Z ukazom git branch lahko ustvarite novo vejo ali si ogledate obstojeˇce veje. Za preklapljanje med vejami pa lahko uporabite git checkout ali novejši git switch. Primeri: \$ git branch nova-vej # Ustvari novo vejo z imenom "nova-veja" \$ git branch # Prikaze vse obstojece veje (trenutna je , → oznaˇ cena z *) main * nova-veja \$ git checkout nova-veja # Preklopi na vejo "nova-veja" Switched to branch 'nova-veja' # Ali z novejšim ukazom: \$ git switch nova-veja # Preklopi na vejo "nova-veja" Switched to branch 'nova-veja' Ukaz git branch brez argumentov prikaže seznam vseh vej, medtem ko git checkout ali git switch omogoˇcata delo na izbrani veji. D.6.2 Združevanje vej Ko konˇcate delo na veji in želite njene spremembe vkljuˇciti v glavno vejo (npr. main), uporabite ukaz git merge. Najprej se preklopite na vejo, v katero želite združiti spremembe, nato pa izvedete ukaz merge. Primer: \$ git checkout main # Preklopi na glavno vejo Switched to branch 'main' 250 Poglavje D. Vodenje razliˇ cic kode z Git \$ git merge nova-veja # Združi vejo "nova-veja" v "main" Updating 7f3c5b6..a1b2c3d Fast-forward dokumentacija.txt | 2 ++ 1 file changed, 2 insertions(+) Ce ni konfliktov, Git avtomatsko združi veji. V nasprotnem primeru boste morali roˇcno reševati ˇ konflikte. D.6.3 Reševanje konfliktov Konflikti se pojavijo, ko dve veji spremenita isto vrstico v isti datoteki na razliˇcne naˇcine. Ko Git zazna konflikt med združevanjem (git merge), vas obvesti in datoteke, ki vsebujejo konflikte, morate roˇcno urediti. Primer konflikta in njegove rešitve: Predstavljajmo si, da imate datoteko dokumentacija.txt z naslednjim konfliktom po poskusu združitve: \$ git merge nova-veja Auto-merging dokumentacija.txt CONFLICT (content): Merge conflict in dokumentacija.txt Automatic merge failed; fix conflicts and then commit the result. Odprite datoteko dokumentacija.txt in poišˇcite konfliktne dele, ki izgledajo takole: <<<<<<< HEAD To je verzija iz glavne veje. ======= To je verzija iz veje nova-veja. >>>>>>> nova-veja Roˇcno uredite datoteko, da odpravite konflikt, na primer v dokumentacija.txt napišete: To je združena verzija obeh sprememb. Ko konflikt rešite, dodajte popravljeno datoteko v staging z git add in dokonˇcajte postopek združevanja z novo potrditvijo sprememb: \$ git add dokumentacija.txt \$ git commit [main 123abc4] Merge branch 'nova-veja' Git bo samodejno ustvaril sporoˇcilo commita, ki opisuje združevanje. Z uporabo vej lahko uˇcinkovito loˇcujete delo na razliˇcnih funkcijah ali popravkih, hkrati pa ohranjate stabilnost glavne veje projekta. Naslednji koraki lahko vkljuˇcujejo oddaljeno shranjevanje vej ali sodelovanje z drugimi razvijalci. D.7 Povezava z oddaljenim repozitorijem Za sodelovanje z drugimi razvijalci ali shranjevanje vašega projekta na oddaljenem strežniku, kot je GitHub, GitLab ali Bitbucket, morate vzpostaviti povezavo med vašim lokalnim repozitorijem in oddaljenim repozitorijem. V tem podpoglavju bomo pregledali, kako dodati oddaljeni repozitorij, poslati spremembe (git push) in pridobiti spremembe (git pull) iz njega. D.7.1 Dodajanje oddaljenega repozitorija Za povezavo vašega lokalnega repozitorija z oddaljenim repozitorijem uporabite ukaz git remote add origin , kjer je origin privzeto ime za oddaljeni repozitorij, pa naslov oddaljenega repozitorija (npr. HTTPS ali SSH URL). Primer: D.8 Pogoste napake in koristni namigi 251 \$ git remote add origin https://github.com/uporabnik/ime-repozitorija.git Po tem koraku lahko vaš lokalni repozitorij komunicira z oddaljenim. ˇ Ce želite preveriti, katere oddaljene repozitorije so konfigurirani, uporabite: \$ git remote -v origin https://github.com/uporabnik/ime-repozitorija.git (fetch) origin https://github.com/uporabnik/ime-repozitorija.git (push) D.7.2 Pošiljanje sprememb Ko ste pripravljeni deliti svoje lokalne spremembe z oddaljenim repozitorijem, uporabite ukaz git push. Ta ukaz pošlje vaše potrjene spremembe na oddaljeni repozitorij, obiˇcajno na vejo, kot je main ali master. Primer: \$ git push origin main Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 8 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (5/5), 623 bytes | 623.00 KiB/s, done. Total 5 (delta 1), reused 0 (delta 0), pack-reused 0 To https://github.com/uporabnik/ime-repozitorija.git 7f3c5b6..a1b2c3d main -> main Ce pošiljanje sprememb uspe, so vaše spremembe zdaj na voljo na oddaljenem strežniku. Prviˇc ˇ morda potrebujete poverila (uporabniško ime in geslo ali SSH kljuˇc), odvisno od konfiguracije. D.7.3 Pridobivanje sprememb Ce želite posodobiti svoj lokalni repozitorij z najnovejšimi spremembami iz oddaljenega repozitorija, ˇ uporabite ukaz git pull. Ta ukaz prenese in avtomatsko združi spremembe iz oddaljenega repozitorija v vašo trenutno vejo. Primer: \$ git pull origin main remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (2/2), done. remote: Total 2 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (2/2), 211 bytes | 211.00 KiB/s, done. From https://github.com/uporabnik/ime-repozitorija.git a1b2c3d..b2c3d4e main -> origin/main Updating a1b2c3d..b2c3d4e Fast-forward dokumentacija.txt | 1 + 1 file changed, 1 insertion(+) Ce pride do konfliktov med združevanjem, jih morate roˇcno rešiti, podobno kot pri združevanju ˇ vej (glej poglavje D.6.3). S temi koraki lahko uˇcinkovito upravljate sinhronizacijo med vašim lokalnim in oddaljenim repozitorijem, kar je kljuˇcno za timsko delo in varnostno kopiranje vašega projekta. D.8 Pogoste napake in koristni namigi Med delom z Gitom se lahko zgodi, da naredite napake ali potrebujete dodatne orodja za boljše upravljanje projekta. V tem podpoglavju bomo pregledali, kako razveljaviti spremembe, kako 252 Poglavje D. Vodenje razliˇ cic kode z Git pridobiti izgubljene potrjene spremembe in kako ignorirati nepotrebne datoteke z uporabo datoteke .gitignore . D.8.1 Razveljavljanje sprememb Pogosto se zgodi, da želite razveljaviti spremembe, ki ste jih naredili v delovni mapi ali pri pripravi datotek (staging). Za to lahko uporabite ukaze, kot sta git checkout - in git reset. Primeri: • ˇ Ce želite zavreˇci spremembe v doloˇceni datoteki, ki še niso bile dodane v stanje pripravljen- osti: \$ git checkout -- dokumentacija.txt To vrne datoteko dokumentacija.txt na stanje zadnjega commita. • ˇ Ce želite odstraniti datoteke iz stanja pripravljenosti, ne da bi spremenili njihovo vsebino v delovni mapi, uporabite: \$ git reset dokumentacija.txt Unstaged changes after reset: M dokumentacija.txt Ce ste že naredili potrditev spremembe ( ˇcommit), ki jo želite razveljaviti, lahko uporabite git reset z dodatnimi možnostmi, kot je-soft ali-hard, odvisno od tega, ali želite ohraniti spremembe ali ne. D.8.2 Pridobivanje izgubljenih potrjenih sprememb Vˇcasih lahko po pomoti izbrišete veje ali potrjene spremembe. Za obnovitev izgubljenih potrjenih sprememb lahko uporabite git reflog, ki beleži vse reference na prejšnje potrjene spremembe, tudi ˇce so bile izbrisane. Primer: \$ git reflog 7f3c5b6 HEAD@{0}: reset: moving to HEAD∧ a1b2c3d HEAD@{1}: commit: Posodobljena dokumentacija 5e4d3c2 HEAD@{2}: checkout: moving to main Ko najdete ID potrjene spremembe (npr. a1b2c3d), jo lahko obnovite z: \$ git checkout a1b2c3d Note: switching to 'a1b2c3d'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. Nato lahko ustvarite novo vejo iz te potrjene spremembe, ˇce želite ohraniti te spremembe. D.8.3 Ignoriranje datotek Za prepreˇcevanje, da bi Git sledil doloˇcenim datotekam (npr. zaˇcasnim datotekam, dnevnikom ali konfiguracijskim datotekam, za katere ne želiti shranjevati razliˇcic), uporabite datoteko .gitignore. V njo vnesete vzorce datotek ali map, ki jih želite ignorirati. Primer datoteke .gitignore: # Ignoriraj vse log datoteke *.log # Ignoriraj specifiˇ cno mapo temp/ D.9 Zakljuˇ cek 253 # Ignoriraj Python bytecode datoteke *.pyc Datoteko .gitignore ustvarite v korenu vašega repozitorija in jo dodate v vodenje razliˇcic z ukazom git add .gitignore in git commit. Od tega trenutka Git ne bo veˇc sledil navedenim datotekam ali mapam. S temi nasveti lahko hitreje rešujete pogoste težave in izboljšate svoje delo z Gitom. D.9 Zakljuˇ cek D.10 Dobra praksa priprave Git repozitorija Pri ustvarjanju in vzdrževanju Git repozitorija je pomembno upoštevati dobre prakse, ki olajšajo delo vam in drugim razvijalcem. Tukaj je pregled kljuˇcnih elementov, kot so README, oblikovanje, struktura kode in podatkov ter licenca. • README datoteka: Vsak repozitorij bi moral vsebovati datoteko README (najpogosteje v formatu README.md za Markdown), ki služi kot uvod v projekt. V njej navedite cilj projekta, kako ga namestiti, uporabljati in prispevati k njemu. Primer dobra praksa je, da vkljuˇcite naslove, slike, povezave in primere kode. • Oblikovanje: Kode in dokumentacije se držite konsistentnega stila. Uporabite orodja, kot so linterji ali formatirji (npr. Prettier za JavaScript ali Black za Python), da zagotovite, da je koda berljiva in skladna s standardi. Za jezik Python se držite standarda PEP 8 1 (ang. Python Enhancement Proposal 8). Dodajte tudi datoteke, kot so .editorconfig ali .prettierrc, za avtomatizacijo oblikovanja. • Struktura kode in podatkov: Organizirajte datoteke v logiˇcne mape, npr. src/ za izvorno kodo, tests/ za teste, docs/ za dokumentacijo in data/ za podatke. Izognite se kaotiˇcnemu dodajanju datotek v koren repozitorija. ˇ Ce projekt vsebuje velike datoteke ali binarne podatke, razmislite o uporabi orodij, kot je Git LFS (ang. Large File Storage). • Razvojno okolje: podajte navodila za pripravo razvojnega okolja (razliˇcica Python, tip razvojnega okolja) in vkljuˇcite requirements.txt v svoj Git repozitorij, omogoˇcite drugim, da hitro nastavijo razvojno okolje z enakimi odvisnostmi. Primer vsebine datoteke za izvedbo vaj v tem priroˇcniku je requirements.txt. • Licenca: Vsak repozitorij bi moral imeti jasno doloˇceno licenco, ki doloˇca, kako lahko drugi uporabljajo, spreminjajo ali distribuirajo vašo kodo. Pogoste izbire so MIT, GPL ali Apache licenca. Datoteko licence (npr. LICENSE) dodajte v koren repozitorija in jo omenite v README. Preverite katera licenca je primerna za vaš projekt na https://choosealicense.com/ ali se pozanimajte o posameznih licencah na https://tldrlegal.com/ S temi praksami zagotovite, da je vaš Git repozitorij profesionalen, enostaven za uporabo in privlaˇcen za potencialne sodelavce ali uporabnike. Naslednji koraki lahko vkljuˇcujejo redno posodabljanje dokumentacije in preverjanje skladnosti s temi standardi. D.11 Zakljuˇ cek V tem poglavju smo pregledali osnovne koncepte in ukaze orodja Git, ki so kljuˇcni za uˇcinkovito upravljanje verzij programske kode in drugih projektov. Git ni le orodje, temveˇc tudi temeljni del sodobnega razvojnega procesa, ki razvijalcem omogoˇca sodelovanje, sledenje spremembam in vzdrževanje stabilnosti projektov. V tem zakljuˇcku bomo povzeli, zakaj je Git kljuˇcen za razvijalce, ter predlagali nadaljnje vire za uˇcenje. 1 PEP 8: https://www.python.org/dev/peps/pep-0008/ 254 Poglavje D. Vodenje razliˇ cic kode z Git D.11.1 Zakaj je Git kljuˇ cen za razvijalce? Git je postal standardno orodje v svetu programske razvojne industrije zaradi svojih številnih prednosti: • Verzijska kontrola: Git omogoˇca sledenje vsaki spremembi v kodu, kar olajša razumevanje, kako se je projekt razvijal skozi ˇcas. S pomoˇcjo ukazov, kot so git log in git diff, lahko razvijalci hitro identificirajo spremembe in njihove avtorje. • Sodelovanje: Z uporabo vej (git branch, git merge) in oddaljenih repozitorijev (git push , git pull) lahko veˇc razvijalcev istoˇcasno dela na istem projektu brez konfliktov, kar je kljuˇcno za timsko delo. • Fleksibilnost in obnovitev: Napake so del razvoja, vendar Git omogoˇca njihovo hitro popravljanje z ukazi, kot so git reset, git checkout in git reflog, kar zmanjša tve-ganje izgube dela. • Široka uporaba: Platforme, kot so GitHub, GitLab in Bitbucket, temeljijo na Gitu, kar pomeni, da je znanje Gita nujno za sodelovanje v odprtokodnih projektih in profesionalnem okolju. Skratka, Git je nepogrešljiv, ker zagotavlja strukturo, varnost in uˇcinkovitost pri upravljanju kode, kar razvijalcem omogoˇca osredotoˇcanje na ustvarjanje, namesto na roˇcno upravljanje spre-memb. D.11.2 Nadaljnji viri za uˇ cenje Ce želite poglobiti svoje znanje o Gitu, je na voljo veliko virov, ki pokrivajo tako osnove kot ˇ napredne tehnike. Tukaj je nekaj priporoˇcil: • Pro Git knjiga: To je brezplaˇcna, poglobljena knjiga, ki jo je napisal Scott Chacon in Ben Straub. Na voljo je na spletu na naslovu https://git-scm.com/book. Pokriva vse od osnovnih ukazov, kot je git init, do naprednih tehnik, kot je ponastavitev korena repozitorija (git rebase). • Interaktivne vaje: Strani, kot so GitHub Learning Lab (https://lab.github.com/), ponujajo interaktivne vadnice, kjer lahko praktiˇcno vadite ukaze, kot so git add, git commit in git push, v realnem okolju. • Video vadnice in teˇcaji: Platforme, kot so Coursera, Udemy in YouTube, ponujajo številne teˇcaje o Gitu, ki vkljuˇcujejo praktiˇcne primere in nasvete od izkušenih razvijalcev. • Dokumentacija in forumi: Uradna stran Gita (https://git-scm.com/) in skupnosti, kot so Stack Overflow, nudijo podporo in odgovore na specifiˇcna vprašanja. S temi viri lahko nadaljujete uˇcenje in izpopolnite svoje vešˇcine, da bi še bolj izkoristili moˇc Gita v svojih projektih. Git ni le orodje, temveˇc tudi vešˇcina, ki se izpopolnjuje z prakso in razumevanjem njegovih zmogljivosti. Bibliografija Knjige [1] Wilhelm Burger in Mark J. Burge. Principles of Digital Image Processing: Fundamendal/ Core Techniques. Springer, 2009 (cit. na str. 20). [2] Miran Bürmen. Uvod v programski jezik Python. 1. izd. Ljubljana: Založba FE, 2016. ISBN: 978-961-243-318-5. URL: https://plus.cobiss.net/cobiss/si/sl/bib/287674112 (pridobljeno 7. 4. 2025) (cit. na str. 20, 225). [3] Gene H. Golub in Charles F. Van Loan. Matrix Computations. 3rd edition. Baltimore: Johns Hopkins University Press, okt. 1996. ISBN: 978-0-8018-5414-9 (cit. na str. 217). [4] Richard Hartley in Andrew Zisserman. Multiple View Geometry in Computer Vision. English. 2nd edition. Cambridge, UK ; New York: Cambridge University Press, apr. 2004. ISBN: 978-0-521-54051-3 (cit. na str. 178). [5] Reinhard Klette. Concise Computer Vision. Springer, 2014 (cit. na str. 20). [6] Boštjan Likar. Biomedicinska slikovna informatika in diagnostika. Založba FE in FRI, 2008 (cit. na str. 20). [7] Jan Erik Solem. Programming Computer Vision with Python: Techniques and Libraries for Imaging and Retrieving Information. Prva izdaja. Sebastopol, CA: O’Reilly Media, 2012. ISBN: 978-1449316549 (cit. na str. 226). [8] Milan Sonka, Vaclav Hlavac in Roger Boyle. Image Processing, Analysis, and Machine Vision . 4th edition. Stamford, CT, USA: Cengage Learning, jan. 2014. ISBN: 978-1-133- 59360-7 (cit. na str. 215). [9] Žiga Špiclin. Robotski vid: laboratorijske vaje v programskem jeziku Matlab. Opis vira z dne 5. 5. 2020. Univerza v Ljubljani, Fakulteta za elektrotehniko, 2016. URL: http : //lit.fe.uni-lj.si/gradivo/RV-LabVaje-2016-slo.pdf (cit. na str. 13). [10] Žiga Špiclin. Robotski vid: predavanja. Ljubljana: Univerza v Ljubljani, Fakulteta za ele- ktrotehniko, 2019. URL: https://plus.cobiss.net/cobiss/si/sl/bib/13747203 (pridobljeno 25. 3. 2025) (cit. na str. 20). 256 Bibliografija [11] Richard Szelizki. Computer Vision: Algorithms and Applications, 2nd ed. Predogled knjige za osebno rabo je dostopen tu: https://szeliski.org/Book/. Springer, 2022. URL: https : //link.springer.com/book/10.1007/978-3-030-34372-9 (cit. na str. 20). Clanki ˇ [12] Alexey Dosovitskiy in dr. »An image is worth 16x16 words: Transformers for image reco- gnition at scale«. V: arXiv preprint arXiv:2010.11929 (2020) (cit. na str. 78). [13] Berthold K. P. Horn, Hugh M. Hilden in Shahriar Negahdaripour. »Closed-form solution of absolute orientation using orthonormal matrices«. V: JOSA A 5.7 (jul. 1988). Publisher: Optica Publishing Group, str. 1127–1135. ISSN: 1520-8532. DOI: 10 . 1364 / JOSAA . 5 . 001127. URL: https://opg.optica.org/josaa/abstract.cfm?uri=josaa- 5- 7- 1127 (pridobljeno 1. 4. 2025) (cit. na str. 220). [14] Andriy Myronenko in Xubo Song. »Point Set Registration: Coherent Point Drift«. V: IEEE Transactions on Pattern Analysis and Machine Intelligence 32.12 (dec. 2010). Conference Name: IEEE Transactions on Pattern Analysis and Machine Intelligence, str. 2262–2275. ISSN: 1939-3539. DOI: 10.1109/TPAMI.2010.46. URL: https://ieeexplore.ieee. org/document/5432191 (pridobljeno 2. 4. 2025) (cit. na str. 109). [15] Shaoqing Ren in dr. »Faster R-CNN: Towards real-time object detection with region proposal networks«. V: Advances in neural information processing systems 28 (2015) (cit. na str. 78). Konferenˇ cni ˇ clanki [16] Nicolas Carion in dr. »End-to-end object detection with transformers«. V: European confe- rence on computer vision. Springer. 2020, str. 213–229 (cit. na str. 78). [17] Jia Deng in dr. »ImageNet: A large-scale hierarchical image database«. V: 2009 IEEE Conference on Computer Vision and Pattern Recognition. ISSN: 1063-6919. Jun. 2009, str. 248–255. DOI: 10.1109/CVPR.2009.5206848. URL: https://ieeexplore.ieee. org/document/5206848 (pridobljeno 27. 3. 2025) (cit. na str. 17, 20, 78). [18] Tsung-Yi Lin in dr. »Focal loss for dense object detection«. V: Proceedings of the IEEE international conference on computer vision. 2017, str. 2980–2988 (cit. na str. 78). [19] Wei Liu in dr. »SSD: Single shot multibox detector«. V: European conference on computer vision. Springer. 2016, str. 21–37 (cit. na str. 78). [20] Joseph Redmon in dr. »You only look once: Unified, real-time object detection«. V: Procee- dings of the IEEE conference on computer vision and pattern recognition . 2016, str. 779–788 (cit. na str. 78). [21] Mingxing Tan, Ruoming Pang in Quoc V Le. »Efficientdet: Scalable and efficient object detection«. V: Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2020, str. 10781–10790 (cit. na str. 78). Spletni viri [22] Kenneth Joy. Bresenham’s Algorithm. 1999. URL: https://www.ercankoclar.com/wp- content/uploads/2016/12/Bresenhams-Algorithm.pdf (pridobljeno 3. 3. 2025) (cit. na str. 33). [23] Mapa okolja in trajektorija kamere. URL: https://cvg.cit.tum.de/research/vslam (pridobljeno 25. 3. 2025) (cit. na str. 19). 257 [24] pointcloud.jpg (2000×850). URL: https : / / cvg . cit . tum . de / _media / research / lsdslam/pointcloud.jpg (pridobljeno 7. 4. 2025) (cit. na str. 175). [25] Robotski vid. sl-SI. URL: https://fe.uni- lj.si/predmeti/robotski- vid/ (prido- bljeno 25. 3. 2025) (cit. na str. 20). [26] Sledenje gibanja oseb na ulici. URL: https : / / learningspiral . ai / video - motion - tracking/ (pridobljeno 25. 3. 2025) (cit. na str. 19). [27] Strukturirana svetloba za 3D rekonstrukcijo kipa. URL: https://www.instructables. com/Structured-Light-3D-Scanning/ (pridobljeno 25. 3. 2025) (cit. na str. 18). [28] TIOBE Software. TIOBE Index for Programming Languages. Dostopano 20. oktobra 2023. 2023. URL: https://www.tiobe.com/tiobe-index/ (pridobljeno 20. 10. 2023) (cit. na str. 225). [29] Vizualno preverjanje kakovosti svedra. URL: https://sciotex.com/how- do- vision- inspection-systems-work/ (pridobljeno 25. 3. 2025) (cit. na str. 19). [30] Zaznavanje in pobiranje kosov na tekoˇcem traku. URL: https://koldpackindia.in/ robotic-quality-inspection-system/ (pridobljeno 25. 3. 2025) (cit. na str. 18). O avtorju Žiga Špiclin je leta 2006 diplomiral na podroˇcju elektrotehnike na Univerzi v Ljubljani, Fakulteta za elektrotehniko, in leta 2011 na isti ustanovi doktoriral iz elektrotehniških znanosti. Med doktorskim študijem je bil gostujoˇci raziskovalec na Harvardski Univerzi v Združenih državah Amerike, po doktorskem študiju pa gostujoˇci raziskovalec na Tehniški univerzi München v Nemˇciji. Od leta 2020 je izredni profesor na Univerzi v Ljubljani, Fakulteti za elektrotehniko. Njegovo raziskovalno delo v Laboratoriju za slikovne tehnologije obsega razvoj in vrednotenje algoritmov na podroˇcju raˇcunalniškega vida in analize medicinskih slik, ter razvoj aplikacij, predvsem na podroˇcju magnetnoresonanˇcnih preiskav glave za diagnostiko in prognostiko nevrodegenerativnih bolezni, srˇcno-žilnih obolenj kot so možganske anevrizme in prilagajanja naˇcrtov radioterapije. V znanstveno-raziskovalni skupnosti služi kot ˇclan programskega odbora konference MICCAI 2011-, MICCAI PRIME 2018-, bil je ˇclan organizacijskega odbora delavnic WBIR 2020 & 2024. Je prejemnik nagrade za najboljši raziskovalni dosežek Univerze v Ljubljani, v 2016 in 2024. Vizitka Žiga Špiclin izr. prof. dr. E-pošta: ziga.spiclin@fe.uni-lj.si Profilna stran na strani Laboratorija za slikovne tehnologije Povezava na SICRIS profil ¯ Povezava na LinkedIn profil  Povezava na Google Scholar profil Povezava na ORCiD profil Povezava na ResearchGate profil