Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
Fakulteta za elektrotehniko, računalništvo in informatiko
Smetanova ulica 17 2000 Maribor, Slovenija
Pohitritev postopka za lokaliziranje
obrazov iz knjižnice OpenCV na
operacijskem sistemu Android
Maribor, januar 2015
Pohitritev postopka za lokaliziranje
obrazov iz knjižnice OpenCV na
operacijskem sistemu Android
Diplomsko delo
Študent: Boris Špringar
Študijski program: Visokošolski strokovni
Računalništvo in informacijske tehnologije
Smer: /
Mentor: izr. prof. dr. Božidar Potočnik
Somentor: doc. dr. Boris Cigale
Lektorica: Ksenija Pečnik, prof. slov. jezika
i
ii
ZAHVALA
Zahvaljujem se svojemu mentorju, dr.
Božidarju Potočniku, somentorju,
doc. dr. Borisu Cigaletu, posebna
zahvala pa gre tudi mojim staršem, ki
so me podpirali tekom študija.
iii
Pohitritev postopka za lokaliziranje
obrazov iz knjižnice OpenCV na
operacijskem sistemu Android
Ključne besede: Android, knjižnica OpenCV, razvojno okolje NDK, lokaliziranje obrazov,
pohitritev.
UDK: 004.932(043.2)
Povzetek
V diplomskem delu smo se ukvarjali s pohitritvijo postopkov za lokaliziranje obrazov.
Pregledno smo predstavili obstoječe algoritme za zaznavanje obrazov iz knjižnice
OpenCV. V praktičnem delu naloge smo spremenili osnovni algoritem za zaznavanje
obrazov iz knjižnice OpenCV, prirejen za mobilne naprave z operacijskim sistemom
Android, da algoritem za ceno manjše natančnosti deluje hitreje. Osnovni algoritem je
splošen in se lahko uporablja na vseh napravah, ki podpirajo izvajanje kode C++. Zaradi
želje po splošnosti se algoritem lahko izvaja na različnih napravah, a delovanje tega
algoritma na nobeni ni posebej hitro. V diplomskem delu smo ta algoritem spremenili iz
splošno namenskega v specifičnega tako, da smo mu odvzeli določene manj pomembne
funkcionalnosti, priredili kodo za naprave Android, ob tem pa smo optimizirali še hitrost
izvajanja, vse na račun malenkostnega zmanjšanja natančnosti lokaliziranja obrazov.
Eksperimentalni rezultati so pokazali, da s prilagajanjem algoritma ciljni napravi takšen
algoritem deluje bistveno hitreje (do 13 ms hitreje kot izvirni algoritem pri lokaliziranju
obrazov na enem posnetku) na račun zmanjšanja uspešnosti lokaliziranja obrazov za
18 %.
iv
Acceleration of face localization algorithm
from OpenCV library on operating system
Android
Keywords: Android, OpenCV library, NDK development kit, face localization,
acceleration.
UDK: 004.932(043.2)
Abstract
In the thesis we were working on accelerating algorithms for face localization. We
reviewed existing algorithms from the open source computer vision library OpenCV. We
changed the basic face detection algorithm from the OpenCV library, adapted for Android
mobile devices, in such a way that the algorithm runs faster, for the price of lower
accuracy. The basic algorithm is general and can be used on any device that supports
C++ code execution. Because of that requirement of generality, the algorithm is indeed
suitable for many devices, but its speed is lacking on all of them. In this thesis, we
changed this algorithm from a general algorithm to a more specific one, by removing
some functionalities, adapting the code for Android devices and optimizing its speed in
exchange for marginally smaller face detection rate. Experimental results pointed out that
we can significantly improve the speed of the algorithm by adapting it to the target device
(we gained by face localization up to 13 ms with respect to the original algorithm), for the
price of 18% reduction in face detection rate.
v
Kazalo
1. Uvod .......................................................................................................................... 1
2. Uporabljene tehnologije ............................................................................................. 3
2.1. Operacijski sistem Android ................................................................................. 3
2.2. Procesorska arhitektura ARM ............................................................................. 3
2.3. Android NDK ....................................................................................................... 3
2.4. CMake ................................................................................................................ 4
2.5. Knjižnica OpenCV............................................................................................... 5
2.6. Pregled algoritmov za zaznavanje obrazov ......................................................... 5
3. Algoritmi za zaznavanje obrazov v knjižnici OpenCV ................................................. 7
3.1. Haarove značilke ................................................................................................ 7
3.2. Histogrami HOG ................................................................................................11
3.3. Značilke LBP .....................................................................................................12
3.4. Kaskade klasifikatorjev ......................................................................................14
3.5. Razred DetectionBasedTracker .........................................................................16
4. Pohitritve algoritma za zaznavanje obrazov ..............................................................28
4.1. Odstranitev večnitenja v razredu DetectionBasedTracker ..................................28
4.2. Sprememba prikaza zaznanih objektov .............................................................30
4.3. Sprememba metode detectMultiScale() .............................................................32
4.4. Odstranitev klicev metode parallel_for_() ...........................................................33
4.5. Prilagajanje parametrov .....................................................................................34
4.6. Trivialne pohitritve..............................................................................................38
5. Rezultati ...................................................................................................................39
5.1. Opis eksperimentalnega okolja ..........................................................................39
5.2. Metrike učinkovitosti ..........................................................................................43
5.3. Rezultati testiranj na tablici ................................................................................44
6. Diskusija in zaključek ................................................................................................49
Viri ...................................................................................................................................50
vi
Kazalo slik
Slika 3.1: Haarove značilke [35]. ....................................................................................... 8
Slika 3.2: Značilke, postavljene na obraz [37]. .................................................................. 8
Slika 3.3: Računanje seštevka za piksel v integralni sliki [39]. ........................................... 9
Slika 3.4: Računanje regije pravokotnika v integralni sliki [41]. .........................................10
Slika 3.5: Delovanje kaskade klasifikatorjev. ....................................................................10
Slika 3.6: Zaznavanje kotov z značilkami HOG [47]. ........................................................12
Slika 3.7: Obraz z vrisanimi gradienti [47]. .......................................................................12
Slika 3.8: Različne okolice za vzorec LBP [49]. ................................................................13
Slika 3.9: Računanje LBP za en piksel [51]. .....................................................................13
Slika 3.10: Rezultat LBP transformacije [50]. ...................................................................14
Slika 3.11: Uniformni LBP vzorci [31]. ..............................................................................14
Slika 3.12: Diagram klicev metod za DetectionBasedTracker. .........................................17
Slika 3.13: Diagram klicev metod za CascadeClassifier. ..................................................18
Slika 3.14: Integralne slike v kaskadi LBP [53]. ................................................................26
Slika 3.15: Ustvarjanje LBP s pomočjo binarnega ALI......................................................27
Slika 5.1: Testni strežnik. .................................................................................................40
Slika 5.2: Spremembe različic. .........................................................................................41
Slika 5.3: Android aplikacija. ............................................................................................42
Slika 5.4: Graf hitrosti za video. .......................................................................................46
Slika 5.5: Zaznavanje več obrazov – izvirni algoritem. .....................................................47
Slika 5.6: Zaznavanje več obrazov – pospešeni algoritem. ..............................................47
vii
Kazalo kode
Koda 3.1: Kaskada klasifikatorjev v obliki XML. ...............................................................15
Koda 3.2: Metoda detectMultiScale(). ..............................................................................20
Koda 3.3: Metoda detectSingleScale(). ............................................................................21
Koda 3.4: Metoda operator() iz razreda CascadeClassifierInvoker. ..................................22
Koda 3.5: Metoda predictCategoricalStump(). .................................................................24
Koda 3.6: Metoda operator() iz razreda FeatureEvaluator. ..............................................24
Koda 3.7: Metoda calc(). ..................................................................................................25
Koda 3.8: Metoda CALC_SUM_. .....................................................................................27
Koda 4.1: Metoda runLargeDetector(). .............................................................................29
Koda 4.2: Dodana koda v metodi process(). ....................................................................30
Koda 4.3: Metoda calcTrackedObjectsPositionToShow(). ................................................31
Koda 4.4: Prilagojena metoda calcTrackedObjectsPositionToShow(). .............................32
Koda 4.5: Dodana koda v metodi detectMultiScale(). .......................................................32
Koda 4.6: Prilagojena koda v metodi detectMultiScale(). .................................................33
Koda 4.7: Izvirna metoda detectSingleScale()..................................................................34
Koda 4.8: Prilagojena metoda detectSingleScale(). .........................................................34
Koda 4.9: Parametri razreda DetectionBasedTracker. .....................................................38
Koda 5.1: Merjenje trajanja metode detect(). ...................................................................41
viii
Uporabljene kratice
NDK Native Development Kit
SDK Software Development Kit
JNI Java Native Interface
ART Android runtime
RISC Reduced Instruction Set Computing
EABI Embedded Application Binary Interface
BSD Berkley Software Distribution
QR Quick Response
FPS Frames Per Second
CSV Comma Separated Values
NFL No Free Lunch
SVM Support Vector Machine
1
1. Uvod
Že od začetkov računalniškega vida se ta ukvarja z zaznavanjem objektov. Naj si bo z
zaznavanjem robov in uporabo šablon ali z bolj prefinjenimi klasifikatorskimi metodami.
Zaznavanje objektov je procesorsko zahtevno, saj je treba posamično preiskati vsako
sliko s kamere, mobilne naprave pa niso znane po svoji procesorski moči, zato običajno
delamo s počasnimi enojedrnimi procesorji na arhitekturi ARM, ki imajo na voljo malo
pomnilnika. Prav tako Android aplikacije temeljijo na programskem jeziku Java, ki za
izvajanje uporablja Dalvik, to je virtualna naprava, ki deluje v jedru Linux. Imamo torej
dodano raven abstrakcije, kar sicer zniža hitrost, a izboljša splošnost in s tem
prenosljivost. Vsi Android programi delujejo na katerikoli Android napravi, podobno kot
Java aplikacije za namizne sisteme načeloma delujejo na kateremkoli računalniku, razen
če dodajamo sistemsko specifične ukaze. Knjižnica OpenCV je narejena v programskem
jeziku C++, ki se izvaja prek vmesnika NDK (angl. Native Development Kit), ki temelji na
JNI (angl. Java Native Interface). Uporaba NDK naredi aplikacijo kompleksnejšo, saj je za
vsak klic metod iz modulov C++ potrebna metoda v Java ovojnici. Če imamo veliko klicev
C++ metod, lahko to upočasni algoritem, a imamo v naši aplikaciji teh klicev malo, in sicer
le enega za vsako pridobljeno sliko, zato takšni klici ne predstavljajo velikega problema pri
časovni zahtevnosti. Na splošno pa je NDK priporočen samo za aplikacije, ki imajo
zahtevno procesorsko delo, na primer igre, simulacije, računalniški vid in za aplikacije,
kjer želimo uporabljati obstoječe knjižnice C++. Knjižnica OpenCV torej spada v dve od
teh kategorij; napisana je v programskem jeziku C++ in hkrati je detekcija obrazov
procesorsko zelo zahtevno opravilo.
Pri našem projektu se ukvarjamo z lokalizacijo obrazov z uporabo prenosnih naprav, ki
uporabljajo operacijski sistem Android. Za lokaliziranje obraza uporabljamo algoritme iz
odprtokodne knjižnice OpenCV. Namen diplomskega dela je bil spremeniti osnovne
algoritme iz knjižnice OpenCV, da bi delovali hitreje. Za to smo bili pripravljeni žrtvovati
splošnost in natančnost algoritma. V ta namen smo morali najprej natančno analizirati
delovanje teh algoritmov. Algoritem za zaznavanje, ki ga uporabljajo aplikacije Android, se
nahaja v razredu DetectionBasedTracker, ki za detekcijo uporablja razred
CascadeClassifier.
V naslednjih poglavjih smo najprej opisali obstoječe uporabljene tehnologije, kot so
operacijski sistem Android, procesorji ARM, knjižnica OpenCV, in obstoječe algoritme za
zaznavanje obrazov. Po tem smo pregledali implementacije algoritmov v knjižnici OpenCV
2
(različica 2.4.9) in natančno opisali njihovo delovanje, nato pa smo opisali še naše
modifikacije algoritmov in predstavili rezultate naših meritev ter diskutirali o smiselnosti
pohitritev.
3
2. Uporabljene tehnologije
2.1. Operacijski sistem Android
Android je operacijski sistem, ki ga za mobilne naprave razvija podjetje Google [1].
Temelji na jedru Linux, različica jedra pa je odvisna od naprave, najstarejša različica je
2.6.27 [2] [3]. V jedro so dodane funkcionalnosti za mobilne naprave, na primer razred
Binder, ki je sistem za medprocesno komunikacijo [4]. Aplikacije v operacijskem sistemu
Android temeljijo na kodi, napisani v programskem jeziku Java, ki jo izvaja virtualna
naprava Dalvik [5] [6], v novejših različicah pa eksperimentalna virtualna naprava ART
(angl. Android runtime).
2.2. Procesorska arhitektura ARM
Arhitektura ARM je procesorska arhitektura, ki temelji na RISC (angl. Reduced Instruction
Set Computing) [7] [8]. Ker uporabljajo RISC, je poraba takih procesorjev manjša, kar
prispeva k daljšemu trajanju baterije in manjšemu segrevanju, zaradi česar je ARM kot
nalašč za mobilne naprave, ki imajo omejeno zalogo energije in nimajo aktivnega hlajenja.
Naša Android naprava ima procesor Arm Cortex A8 [9], ki temelji na arhitekturi ARMv7a
[10], to je procesor z enim jedrom, 1000 Mhz taktom in 32-bitno arhitekturo.
2.3. Android NDK
Android NDK je zbirka orodij in algoritmov, ki uporablja JNI [11] [12], da omogoča
izvajanje C++ kode na operacijskem sistemu Android [13]. Vključuje vse od standardnih
knjižnic C++ do orodij, kot je ndk-build.cmd, to je program, ki ga uporabljamo za grajenje
C++ modulov za našo Android aplikacijo.
Po prevajanju lahko zgrajeni modul vključimo v aplikacijo z ukazom
System.loadLibrary(»libraryName«) namesto standardnega ukaza import package.library,
ki ga uporabljamo za vključevanje običajnih knjižnic Java. Ukaz loadLibrary poišče
knjižnico v mapi jni/libs/EABI. Vmesnik EABI (angl. Embedded Application Binary
Interface) je drugačen za vsak procesor, za katerega je prevedena koda C++. Za naš
procesor je to armeabi-v7a, kar pomeni, da ima naša naprava procesor z arhitekturo ARM
različice 7a.
EABI [14] [15] je vmesnik med dvema moduloma na ravni strojne kode, v našem primeru
knjižnica C++ in operacijski sistem Android.
4
Naložen modul uporablja Java ovojnica, ki ima definirane klice kode C++ na naslednji
način: private static native type methodName(arguments). Če je modul neuspešno
naložen ali nima metod, ki jih ovojnica kliče, to izvemo šele ob zagonu programa, ko se
sproži izjema.
V JNI delu aplikacije morajo biti metode pripravljene na klic iz Java ovojnice. To pomeni,
da mora imeti vsaka metoda posebno ime, da jo lahko Java ovojnica sploh definira.
Primer takega poimenovanja je JNIEXPORT jlong JNICALL Java_package_
name_JavaClassName_methodName(JNIEnv* jenv, jclass, jni parameters). Pri
poimenovanju je torej pomembno, da navedemo ime Java paketa in razreda, ki bo klical
metodo. Hkrati moramo dodati JNIEnv in jclass. Vse spremenljivke, ki jih uporabljamo v
klicu metod, morajo biti standardne za JNI, na primer jlong in jint. Prav tako ne moremo
podajati referenc na Java objekte, ampak moramo uporabiti njihovo reprezentacijo v
64-bitni celoštevilski (long) predstavitvi, ki jo dobimo s klicem metode
object.getNativeObjAddr() v Java ovojnici. Da lahko te objekte sploh uporabljamo, jim
moramo v JNI delu dodeliti tip (angl. casting), na primer, če delamo z matriko faces, v JNI
delu dobimo referenco na matriko v obliki jlong faces, da lahko dostopamo do lastnosti
matrike, ji dodelimo tip z ukazom ((Mat*)faces).
Za razhroščevanje kode C++ moramo zagnati orodje ndk-gdb ali pa si pomagamo z
beleženjem tako, da pošiljamo sporočila o delovanju aplikacije v beležko LogCat [16], ki je
del Android SDK (angl. Software Development Kit).
Ker NDK naredi našo kodo kompleksnejšo, uporaba tega ni priporočljiva zgolj zato, ker je
programiranje v C++ enostavneje, temveč je smiselna le takrat, ko imamo že obstoječe
C++ knjižnice ali ko izvajamo procesorsko intenzivne algoritme.
2.4. CMake
CMake je medplatformni odprtokodni sistem za izdelovanje opisnih datotek, ki se
uporabljajo pri grajenju strojne kode iz C++ kode (angl. makefile) [17]. V osnovi je orodje
narejeno za ukazno vrstico, v operacijskem sistemu Windows pa imamo na voljo še
grafični vmesnik CMake. Da lahko knjižnico OpenCV prevedemo v strojno kodo,
potrebujemo te opisne datoteke, ki jih izdelujemo s pomočjo CMake.
Cmake z uporabo nabora orodij za Android (angl. toolchain), ki jih priskrbi OpenCV v mapi
platforms/Android/android.toolchain.cmake, ustvari opisno datoteko za vse module, ki jih
5
uporabljamo v naši knjižnici. Te datoteke potem uporabi program NDK make.exe, da
zgradi knjižnice, ki jih uporablja naša Android aplikacija.
2.5. Knjižnica OpenCV
OpenCV je odprtokodna knjižnica, v kateri so implementirani algoritmi za računalniški vid
[18] [19], njegovi številni moduli pa so napisani v optimiziranem programskem jeziku C++.
Izdan je pod licenco BSD (angl. Berkley Software Distribution), kar pomeni, da ga lahko
uporabljajo, spreminjajo in kopirajo tako podjetja kot posamezniki [20] za komercialne ali
nekomercialne namene. Uporablja se lahko na vseh napravah in infrastrukturah, ki
podpirajo izvajanje kode, napisane v programskem jeziku C++. Tako jo lahko uporabljamo
za manj zahtevne stvari, kot so lokalizacija obrazov na mobilnem telefonu, branje kode
QR (angl. quick response) na mobilnem telefonu in podobno. Lahko pa ga uporabljamo
tudi za bolj zahtevne aplikacije, na primer za videonadzor in kontrolo rudarske opreme
[21].
Knjižnica OpenCV ima implementiranih prek 2500 algoritmov [21], od osnovnih algoritmov
za računalniški vid do naprednih algoritmov za računalniški vid in strojno učenje. Ti
algoritmi se lahko uporabljajo za naloge, kot sta detekcija in identifikacija objektov, za
klasifikacijo človeškega gibanja, sledenje objektom, filtriranje in urejanje slik in podobno.
Ima vmesnike v programskih jezikih C, C++, Java in Python, obstajajo pa tudi ovojnice za
višje programske jezike, na primer EmguCV za .NET C# [22] in JavaCV za Javo [23].
Knjižnica OpenCV podpira operacijske sisteme Windows, Linux, Android in Mac OS.
Uporabna je predvsem za aplikacije v realnem času in uporablja nabore ukazov MMX [24]
in SSE [25], ko so ti na voljo.
2.6. Pregled algoritmov za zaznavanje obrazov
Obraze lahko zaznavamo s pomočjo različnih algoritmov za procesiranje slik, od naivnih
pristopov do sofisticiranih algoritmov s strojnim učenjem [26]. Nekateri izmed njih so:
s slike lahko odstranimo ozadje in ga nadomestimo z enobarvnim ozadjem, obraz
pa lahko za tem preprosto zaznamo z iskanjem robov. Slabost tega pristopa je, da
ga ne moremo uporabiti v realnih pogojih; uporabimo ga lahko na primer le, ko
imamo na voljo zeleni zaslon;
uporabimo lahko barvne značilnosti obrazov, torej zaznavamo, kje na sliki imamo
področja, ki vsebujejo barvo obraza. Slabost tega pristopa je, da ne moremo
6
zaznati obrazov z različnimi kožnimi pigmenti, algoritem pa je občutljivejši na
nepravilno zaznane objekte;
na voljo imamo tudi zaznavanje gibanja, če predpostavljamo, da se v realnem
svetu obraz vedno vsaj malo premika. Slabost tega pristopa je, da zaznavamo še
vse druge premikajoče se objekte;
nevronske mreže so eden od pristopov s strojnim učenjem, ki se približujejo
naravnim sistemom, na primer človeškim možganom;
sledenje na osnovi modela uporablja zaznavanje robov in primerjanje šablon;
za nas najrelevantnejši so pristopi, ki uporabljajo šibke klasifikatorje, ki tvorijo
kaskade klasifikatorjev. Tudi ti pristopi uporabljajo strojno učenje, kaskade pa za
zaznavanje obrazov uporabljajo skupine značilk. Po obsežnem strojnem učenju z
velikimi množicami pozitivnih in negativnih primerkov lahko te kaskade dajejo zelo
točne rezultate. Algoritem, ki uporablja kaskado klasifikatorjev, smo uporabili v
našem projektu.
7
3. Algoritmi za zaznavanje obrazov v knjižnici OpenCV
Knjižnica OpenCV za zaznavanje objektov poleg drugih algoritmov uporablja tudi kaskade
klasifikatorjev [27]. Kaskada klasifikatorjev je ansambelska metoda, kar pomeni, da
uporabljamo več klasifikatorjev naenkrat. Imamo vrsto (kaskado) močnih klasifikatorjev
oziroma stopenj (angl. stages), ki jih tvorijo šibki klasifikatorji. Šibki klasifikatorji
klasificirajo posamezne značilke kot pozitivne ali negativne in glede na svoje uteži
dodajajo ali odvzemajo vrednost seštevku stopnje. Ko stopnja dokonča klasifikacijo, se
seštevek primerja s pragovno vrednostjo (angl. threshold). Če je seštevek stopnje manjši
kot pragovna vrednost, se slika zavrne in klasifikacija se konča, če pa je večji ali enak, pa
se klasifikacija nadaljuje. Slika je klasificirana pozitivno, ko jo vsaka posamezna stopnja
klasificira kot pozitivno. S tem zagotovimo hitrejše izločanje napačnih klasifikacij in s tem
hitrejše izvajanje. Začetnik takih klasifikatorjev je Viola-Jones algoritem za zaznavanje
obrazov, ki za klasifikacijo uporablja Haarove značilke [28].
V OpenCV so implementirane tri kaskade klasifikatorjev z značilkami tipov HOG [29],
Haar [30] in LBP [31].
3.1. Haarove značilke
Haarove značilke so dobile ime po Haarovih impulzih [32] [33], ki so nezvezni impulzi,
zato njihovih funkcij ne moremo odvajati, kar je pri običajni analizi signalov pomanjkljivost.
Po drugi strani je ta lastnost prednost, ko zaznavamo signale, ki imajo hitre prehode
amplitud, ko na primer iščemo okvare v strojih. Pri obdelavi slik so taki prehodi pogosti pri
robovih ali ko imamo jasne prelive iz svetlega področja (ozadja) v temno področje (objekt,
ki ga zaznavamo).
Haarove značilke [34] izrabljajo dejstvo, da so področja okoli oči, ust in nosa temnejša od
preostalega obraza. Kot je prikazano na sliki 3.1, je značilka pravokotne oblike z dvema
ali s tremi notranjimi pravokotniki.
8
Slika 3.1: Haarove značilke [35].
En klasifikator ima lahko n značilk. Pri uporabi značilke klasifikator odšteje seštevek
vrednosti pikslov v beli regiji od seštevka vrednosti pikslov v črni regiji [36]. Na sliki 3.2
vidimo značilke, postavljene na obraz, kjer primerjajo področje oči s področjem pod očmi,
ki mora biti svetlejše za pozitivno klasifikacijo.
Slika 3.2: Značilke, postavljene na obraz [37].
Kaskado klasifikatorjev lahko zgradimo s pomočjo strojnega učenja z algoritmom Gentle
AdaBoost [38], ki ga uporabimo tako za selekcijo značilk kot za učenje klasifikatorjev.
Značilk, ki jih ima na voljo učni algoritem, je običajno veliko več kot pikslov (Viola&Jones
[36] sta jih v izvirnem algoritmu uporabila 180.000), zato je pomembno, da učni algoritem
izbere najboljše značilke. Učni algoritem to doseže tako, da med učenjem kaskade za
vsako značilko ustvari svoj klasifikator, ki klasificira vse slike, nato učni algoritem izbere
klasifikatorje z najmanjšo napako.
Za primerjavo svetlosti regij uporabljajo kaskade s Haarovimi značilkami seštevke
svetlosti (vrednosti) pikslov. Seštevanje svetlosti vsakega piksla posebej za vsako regijo
bi bilo preveč procesorsko zahtevno, zato algoritmi uporabljajo še tehniko vmesne
integralne slike [39] [40]. Integralna slika oziroma tabela seštevkov je transformacija, kjer
je vsak piksel seštevek regije nad pikslom in levo od piksla, kot prikazuje slika 3.3.
9
Slika 3.3: Računanje seštevka za piksel v integralni sliki [39].
Ko računamo regijo za nek piksel, si lahko pomagamo s predhodno izračunanimi regijami.
Novemu pikslu prištejemo gornjo in levo vrednost iz integralne slike in odštejemo vrednost
levo zgoraj, kot prikazuje enačba 3.1. Tako se izognemo seštevanju svetlosti vsakega
piksla posebej v vsaki regiji in opravimo izračun integralne slike v enem prehodu slike.
𝑠(𝑥, 𝑦) = 𝑖(𝑥, 𝑦) + 𝑠(𝑥 − 1, 𝑦) + 𝑠(𝑥, 𝑦 − 1) − 𝑠(𝑥 − 1, 𝑦 − 1) (3.1)
kjer je s seštevek in i intenziteta piksla.
Ko imamo integralno predstavitev naše slike, je seštevek regije veliko lažji, saj je vsaka
točka v integralni sliki seštevek regije od izhodišča (točke 0,0) do trenutne točke. Pri
značilkah imamo štiri točke, postavljene na sliko. Da pridemo do seštevka regije v
integralni sliki, od spodnje desne točke odštejemo gornjo desno in spodnjo levo ter
prištejemo gornjo levo. Kot je prikazano na sliki 3.4, če iščemo seštevek regije ABCD,
moramo od regije D odšteti regiji zgoraj in desno (B in C) in prišteti regijo zgoraj desno
(A), ker smo jo z odštevanjem B in C odšteli dvakrat.
10
Slika 3.4: Računanje regije pravokotnika v integralni sliki [41].
Ta način je veliko manj procesorsko zahteven kot seštevanje vsakega piksla posebej,
hkrati pa lahko uporabimo isto integralno sliko za vse značilke, tako da izračunamo
integralno sliko samo enkrat za klasifikacijo ene slike.
Kot vidimo na sliki 3.5, kaskade klasifikatorjev delujejo tako, da stopnje ena za drugo
klasificirajo sliko. Da kaskada označi sliko kot pozitivno (torej da je v njej iskani objekt),
mora vsaka stopnja klasificirati sliko kot pozitivno. Takoj ko katera od stopenj zavrne sliko,
se klasifikacija konča in vrne se števec pozitivnih klasifikacij.
Slika 3.5: Delovanje kaskade klasifikatorjev.
Delovanje takih kaskad je precej hitro, vendar so Haarove kaskade zaradi svoje
obsežnosti (preko 100 stopenj) prezahtevne za našo Android napravo. Učenje kaskad pa
11
je precej zahtevno, ker potrebujemo več tisoč pozitivnih in negativnih primerkov (slik z ali
brez obraza) ter več tisoč značilk. V našem projektu nismo obravnavali učenja kaskad,
ampak smo uporabljali kaskade z značilkami tipa LBP (angl. Local Binary Patterns), ki jih
ponuja knjižnica OpenCV skupaj z algoritmom in s primeri uporabe.
3.2. Histogrami HOG
Histogrami HOG (angl. Histogram of Oriented Gradients) so histogrami orientiranih
gradientov. Pri našem projektu tega tipa značilk nismo uporabljali, zato je v nadaljevanju
opisan samo okvirno.
Gradienti predstavljajo usmerjenost funkcije. V primeru sivinskih slik to pomeni, da
gradienti kažejo v smeri preliva barve iz svetlejše v temnejšo [42] [43] [44]. Tako dobimo
značilke, ki opisujejo nek objekt, kot je na primer oko, ki je sestavljeno iz več prelivov iz
svetlejše slike v temnejšo oziroma je sestavljeno iz več gradientov [45].
Sliko razdelimo na manjše segmente (Dalal in Triggs [46] sta uporabila 16 x 16 bloke s
štirimi 8 x 8 celicami) in izračunamo gradient za vsak blok. Tudi tu si pomagamo z
integralnimi slikami.
Gradiente obravnavamo podobno kot Haarove značilke in iz njih ustvarimo kaskado
klasifikatorjev. Dalal in Triggs [46] sta namesto Gentle AdaBoost uporabila stroje SVM
(angl. Support Vector Machine).
Imamo torej skupine značilk, ki opisujejo prelive med svetlejšimi in temnejšimi področji in s
tem razlikujejo med različnimi področji (na primer med usti in očmi), ki jih uporablja
kaskada klasifikatorjev. Ta kaskada klasifikatorjev pa deluje na isti način kot pri Haarovih
značilkah.
Slika 3.6 prikazuje praktično uporabo zaznavanja kotov z detektorjem HOG, na sliki 3.7 pa
lahko vidimo vhodno sliko, razdeljeno na celice in gradiente v posameznih celicah.
12
Slika 3.6: Zaznavanje kotov z značilkami HOG [47].
Slika 3.7: Obraz z vrisanimi gradienti [47].
Hitrost kaskade klasifikatorjev z značilkami tipa HOG je podobna kaskadi klasifikatorjev z
značilkami tipa Haar, kaskada klasifikatorjev z značilkami tipa HOG pa je tudi bolj
natančna.
3.3. Značilke LBP
Značilke LBP (angl. Local Binary Patterns) oziroma lokalni binarni vzorci so lokalne
transformacije, kjer za vsak piksel v sliki ali njenem odseku izračunamo binarni vzorec s
pomočjo pikslov v njegovi okolici [48].
Za okolico vzamemo piksle okrog tistega, za katerega računamo vzorec LBP. Okolica je
krog, v katerem so enakomerno porazdeljene točke, s pomočjo katerih računamo vzorec
13
LBP. Srednji piksel je tisti, za katerega računamo binarni vzorec, lahko imamo okolice z
različnimi premeri in različnim številom okoliških točk, kot prikazuje slika 3.8. Če točka ni
na sredini piksla, se njena vrednost ekstrapolira iz okolice točke.
Slika 3.8: Različne okolice za vzorec LBP [49].
Binarni vzorec je bitni niz, ki ga izračunamo za sredinski piksel tako, da z njegovo
vrednostjo primerjamo vsak piksel v dani okolici. Ta primerjava je pragovna operacija, ki
vrača 1, če je vrednost piksla v okolici večja od sredinskega piksla ali 0, če je vrednost
manjša ali enaka, kot ponazarja slika 3.9. Bitni niz sestavimo tako, da vrednosti beremo v
smeri urinega kazalca ali proti urinem kazalcu. Bitni niz nato pretvorimo v decimalno
vrednost [50]. Vzorci LBP niso odporni na skaliranje.
Slika 3.9: Računanje LBP za en piksel [51].
14
Na sliki 3.10 je prikazana LBP pretvorba celotne slike. Algoritem je pri tej pretvorbi vsak
piksel v izvirni sliki zamenjal z njegovim vzorcem LBP. Vidimo, da so na pretvorjenih
slikah jasno vidni robovi in potemnitve, tudi ko je izvirna slika manj jasna zaradi slabe
osvetlitve.
Slika 3.10: Rezultat LBP transformacije [50].
Glede na karakteristike lokalnega vzorca lahko vzorec opišemo kot uniformen ali
neuniformen. Uniformni so tisti, ki imajo dva ali nič prehodov oziroma sprememb iz niza
enk v niz ničel, neuniformni pa so tisti, ki imajo enke in ničle razpršene, torej je med njimi
več prehodov. Iz uniformnih vzorcev lahko razločimo, ali je piksel na robu nekega objekta,
ali je na kotu, ali je na piki ali pa je na praznem področju. Uniformne vzorce LBP
ponazarja slika 3.11.
Slika 3.11: Uniformni LBP vzorci [31].
3.4. Kaskade klasifikatorjev
Algoritmi iz knjižnice OpenCV za klasifikacijo slik uporabljajo odločitvena drevesa. Teh
odločitvenih dreves ne učijo sproti, ampak uporabljajo odločitvena drevesa, ki so rezultat
predhodnega strojnega učenja. Za to učenje odločitvenih dreves potrebujemo veliko
število pozitivnih in negativnih vzorcev. Pozitivni vzorci so slike, ki vsebujejo iskani objekt
(v našem primeru obraz), negativni vzorci pa so slike, ki vsebujejo ozadja brez predmetov
15
ali predmete, ki jih odločitveno drevo pogosto napačno klasificira kot iskani objekt.
Knjižnica OpenCV vsebuje algoritme za učenje odločitvenih dreves. Za to učenje
potrebujemo več tisoč pozitivnih in negativnih vzorcev, traja pa lahko tudi več dni. Pri
našem projektu kaskad klasifikatorjev nismo posebej učili, ampak smo uporabili kaskade
klasifikatorjev, ki so bile vključene poleg primerov uporabe knjižnice OpenCV.
Odločitveno drevo je shranjeno v datoteki XML, v obliki, ki jo prikazuje koda 3.1.
<?xml version="1.0"?> <!-- number of positive samples 3000 number of negative samples 1500 --> <opencv_storage> <cascade type_id="opencv-cascade-classifier"> <stageType>BOOST</stageType> <featureType>LBP</featureType> <height>24</height> <width>24</width> <stageParams> <boostType>GAB</boostType> <minHitRate>0.9950000047683716</minHitRate> <maxFalseAlarm>0.5000000000000000</maxFalseAlarm> <weightTrimRate>0.9500000000000000</weightTrimRate> <maxDepth>1</maxDepth> <maxWeakCount>100</maxWeakCount></stageParams> <featureParams> <maxCatCount>256</maxCatCount></featureParams> <stageNum>20</stageNum> <stages> <!-- stage 0 --> <_> <maxWeakCount>3</maxWeakCount> <stageThreshold>-0.7520892024040222</stageThreshold> <weakClassifiers> <!-- tree 0 --> <_> <internalNodes> 0 -1 46 -67130709 -21569 -1426120013 -1275125205 -21585 -16385 587145899 -24005</internalNodes> <leafValues> -0.6543210148811340 0.8888888955116272</leafValues></_> <!-- tree 1 -->
Koda 3.1: Kaskada klasifikatorjev v obliki XML.
V datoteki XML imamo na vrhu osnovne informacije: tip stopenj, tip značilk, višino in širino
osnovne slike za zaznavanje, parametre stopenj, parametre značilk in število stopenj.
Kaskada je nato razdeljena na stopnje, ki so sestavljene iz šibkih klasifikatorjev. Vrednost
StageThreshold je prag, ki ga morajo doseči šibki klasifikatorji, da je stopnja klasificirana
kot pozitivna.
16
Znotraj šibkega klasifikatorja imamo vrednosti internalNodes, ki je vektor enajstih števil, ki
predstavljajo po vrsti:
levo in desno, ki sta vrednosti, ki jih za zaznavanje z značilkami tipa LBP ne
uporabljamo,
indeks značilke, ki jo uporablja šibki klasifikator, in
osem vrednosti, ki jih algoritem uporablja za ocenjevanje značilke [52].
Teh osem vrednosti imenujemo iskalna tabela (angl. Look Up Table ali LUT) in na osnovi
te iskalne tabele se algoritem odloča, ali bo k seštevku stopnje prištel levo ali desno
vrednost iz para vrednosti leafValues. Seštevek stopnje primerjamo s pragom stopnje, da
ugotovimo, ali je stopnja sliko klasificirala pozitivno.
Na dnu datoteke so opisane še značilke. Vsaka značilka ima svoje koordinate, širino in
višino.
3.5. Razred DetectionBasedTracker
Razred DetectionBasedTracker je razred na najvišji ravni v C++ delu naše aplikacije. Ima
vse lastnosti in metode, ki jih uporabljamo v JNI in ovojnicah Java. Razred
DetectionBasedTracker implementira splošne algoritme za predpripravo slike in
prikazovanje zaznanih objektov in uporablja nižje nivojske razrede za izvajanje detekcije.
Razred DetectionBasedTracker ima tudi nabor parametrov, s katerimi lahko uporabniki
razreda glede na omejitve svojih aplikacij prilagajajo delovanje algoritmov za zaznavanje
objektov.
Na sliki 3.12 lahko vidimo, kako z najvišje ravni (aplikacije Android) dostopamo do metode
process() v razredu DetectionBasedTracker prek vmesnika JNI.
17
Slika 3.12: Diagram klicev metod za DetectionBasedTracker.
Metoda process() vzame podano matriko sivinske slike, na njej izvede detekcijo in shrani
zaznane objekte. Te objekte (pravokotnike) lahko dobimo s pomočjo metode getObjects,
ki jih lahko v aplikaciji prikažemo na izvirni sliki.
Metoda process() komunicira z ločeno nitjo iz razreda SeparateDetectionWork, ki izvaja
detekcijo na vhodni sliki. Razred DetectionBasedTracker tako ne pregleduje celotne slike,
ampak zaznava samo v regijah objektov, ki jim sledi. Ko razred SeparateDetectionWork
konča s pregledovanjem slike, se nastavijo zastavice, ki glavni niti iz razreda
DetectionBasedTracker povedo, da je detekcija končana. Te zastavice se preverjajo ob
vsakem klicu metode process(&Mat). Ko razred DetectionBasedTracker ugotovi, da je
detekcija na celotni sliki končana, vzame novi seznam objektov in posreduje novo sliko
razredu SeparateDetectionWork.
Za zaznavanje uporabljata razreda DetectionBasedTracker in SeparateDetectionWork
metodo DetectMultiScale() iz razreda CascadeClassifier, ki ji posredujeta sliko. Po
zaznavanju se kliče še metoda updateTrackedObjects(), ki preverja, če se kateri od na
18
novo zaznanih objektov prekriva s katerim od shranjenih objektov, ki jim sledimo. Če se
na novo zaznani objekti prekrivajo s katerim od shranjenih objektov, se shranjenemu
objektu dodajo njihove pozicije, drugače se na novo zaznane objekte doda v seznam
objektov, ki jim sledimo.
Vsak objekt ima shranjenih več pozicij, ki jim sledimo, zato mora metoda getObjects()
pred vračanjem objektov najprej izračunati, kaj naj sploh pokaže. To stori s pomočjo
metode calcTrackedObjectsPositionToShow(), ki iz vseh pozicij objektov izračuna
povprečno vrednost, da izboljša estetiko prikaza. Algoritem uporablja to metodo, da se
pravokotnik okrog zaznanega objekta manj premika (pravokotnik okrog zaznanega
objekta se namreč zaradi šuma v sliki premika, tudi če se objekt ne).
Razred CascadeClassifier opravlja klasifikacijo s pomočjo kaskad klasifikatorjev in
njihovih značilk. Implementirane ima algoritme, ki uporabljajo značilke tipa Haar, HOG in
LBP.
Na sliki 3.13 lahko vidimo hierarhijo klicev metod od najvišje ravni v razredu
CascadeClassifier do najnižje ravni v razredu FeatureEvaluator.
Slika 3.13: Diagram klicev metod za CascadeClassifier.
Glavna metoda, ki jo uporabljamo v razredu DetectionBasedTracker, je metoda
detectMultiScale(), ki skalira sliko od večje proti manjši glede na parameter scaleFactor.
Istočasno metoda detectMultiScale() veča regijo za detekcijo, ki se imenuje
19
processingRectSize, znotraj katere algoritem izvaja klasifikacijo na osnovi značilk LBP. Po
skaliranju kličemo metodo detectSingleScale(), ki ji podamo skalirano sliko skupaj s
pripadajočimi parametri. Metoda detectSingleScale() zažene klasifikacijo za podano sliko
in v seznam kandidatov zapiše pravokotnike regij, ki jih je klasifikator pozitivno klasificiral.
Algoritem zaključi z izvajanjem, ko je slika manjša kot parameter originalWindowSize , ki
ga algoritem pridobi iz datoteke XML, ki opisuje kaskado in njene parametre. Parameter
originalWindowSize nam pove, kolikšna je izvirna velikost slik, iz katerih se je kaskada
klasifikatorjev učila, zato slike ne smemo skalirati na velikost, ki je manjša od originalne.
V kodi 3.2 vidimo pomembnejše dele metode detectMultiScale(). Sliko v obliki matrike
metodi podamo s pomočjo referenčne spremenljivke image. Referenca na vektor
pravokotnikov objects je namenjena pravokotnikom, ki predstavljajo zaznane objekte.
Prek reference na objects lahko zunaj metode detectMultiScale() dostopamo do zaznanih
objektov, metoda detectMultiScale() pa ne vrača ničesar. Vektorja rejectLevels in
rejectWeights sta namenjena testiranju zmogljivosti kaskade klasifikatorjev, česar v
našem projektu nismo uporabljali. Spremenljivka scaleFactor nam pove, kako hitro naj se
skalirata slika in velikost regije za zaznavanje processingRectSize. Spremenljivka
minNeighbors nam pove, koliko sosedov mora imeti kandidat, da ga vključimo med
zaznane objekte. Spremenljivko minNeighbors potrebujemo zato, da se znebimo
napačnih pozitivnih (angl. false positive) klasifikacij. Spremenljivki minObjectSize in
maxobjectSize pa povesta, kakšna je najmanjša in največja velikost objektov.
Po inicializaciji spremenljivk se začne zanka FOR, ki inicializira spremenljivko factor in jo
po vsakem zaključku zanke FOR pomnoži s spremenljivko scaleFactor. Zanko FOR
algoritem prekine, ko je zadoščeno kateremu od pogojev v stavkih IF znotraj zanke FOR.
Ti stavki IF se ovrednotijo po skaliranju in prekinejo zanko FOR ali ko je spremenljivka
windowSize večja kot maksimalna velikost objekta, ko je spremenljivka
processingRectSize manjša od nič ali ko metoda detectSingleScale() vrne FALSE. Na
začetku zanke FOR algoritem skalira izvirno sliko, nastavi velikost okna za zaznavanje,
nastavi velikost segmentov (angl. strips) za večnitenje in kliče metodo
detectSingleScale(). Po izhodu iz zanke FOR metoda kopira zaznane objekte iz vektorja
candidates v vektor objects in zažene metodo groupRectangles(), ki skupine objektov, ki
so si dovolj podobni glede na njihovo pozicijo in njihovo velikost, združi v en objekt.
void CascadeClassifier::detectMultiScale( const Mat& image, vector<Rect>& objects, vector<int>& rejectLevels, vector<double>& levelWeights, double scaleFactor, int minNeighbors,
20
int flags, Size minObjectSize, Size maxObjectSize, bool outputRejectLevels ) { {inicializacija} for( double factor = 1; ; factor *= scaleFactor ) { Size originalWindowSize = getOriginalWindowSize(); Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) ); Size scaledImageSize( cvRound( grayImage.cols/factor ), cvRound( grayImage.rows/factor ) ); Size processingRectSize( scaledImageSize.width - originalWindowSize.width, scaledImageSize.height - originalWindowSize.height ); if( processingRectSize.width <= 0 || processingRectSize.height <= 0 ) break; if( windowSize.width > maxObjectSize.width || windowSize.height > maxObjectSize.height ) break; if( windowSize.width < minObjectSize.width || windowSize.height < minObjectSize.height ) continue; {skaliranje slike} {nastavljanje yStep glede na tip evaluatorja} int stripCount, stripSize; const int PTS_PER_THREAD = 1000; stripCount = ((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep + PTS_PER_THREAD/2)/PTS_PER_THREAD; stripCount = std::min(std::max(stripCount, 1), 100); stripSize = (((processingRectSize.height + stripCount - 1)/stripCount + yStep-1)/yStep)*yStep; if( !detectSingleScale( scaledImage, stripCount, processingRectSize, stripSize, yStep, factor, candidates, rejectLevels, levelWeights, outputRejectLevels ) ) break; } objects.resize(candidates.size()); std::copy(candidates.begin(), candidates.end(), objects.begin()); if( outputRejectLevels ) { groupRectangles( objects, rejectLevels, levelWeights, minNeighbors, GROUP_EPS ); } else{ groupRectangles( objects, minNeighbors, GROUP_EPS ); } }
Koda 3.2: Metoda detectMultiScale().
Koda 3.3 prikazuje metodo detectSingleScale(), ki izvaja zaznavanje v skalirani sliki, ki jo
priskrbi metoda detectMultiScale(). Metoda detectSingleScale() najprej kliče metodo
setImage() iz razreda FeatureEvaluator, ki algoritmu nastavi skalirano sliko. Metoda
setImage() vrne FALSE, ko je slika premajhna za obdelavo (ko sta širina in višina manjša
kot širina in višina spremenljivke originalWindowSize). Če metoda setImage() vrne
FALSE, se izvajanje metode detectSingleScale() konča.
21
Po tem, ko metoda detectSingleScale() evaluatorju nastavi sliko, kliče še metodo
parallel_for_(), ki skrbi za večnitenje. Metodi parallel_for_() se posreduje sliko, razdeljeno
na segmente in objekt razreda CascadeClassifierInvoker. Metoda parallel_for_() je
namenjena temu, da razdeli izvajanje zaznavanja slike na več niti tako, da za vsak podan
segment slike ustvari svojo nit. V ta namen metoda parallel_for_() uporablja algoritme za
večnitenje, ki jih podpira operacijski sistem, na katerem se algoritem za zaznavanje izvaja.
Na operacijskem sistemu Android noben od ponujenih algoritmov ni podprt, zato se
zaznavanje izvaja v eni niti.
bool CascadeClassifier::detectSingleScale( const Mat& image, int stripCount, Size processingRectSize, int stripSize, int yStep, double factor, vector<Rect>& candidates, vector<int>& levels, vector<double>& weights, bool outputRejectLevels ) { if( !featureEvaluator->setImage( image, data.origWinSize ) ) return false; {…} parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor, candidatesVector, rejectLevels, levelWeights, false, currentMask, &mtx)); } candidates.insert( candidates.end(), candidatesVector.begin(), candidatesVector.end() ); #if defined (LOG_CASCADE_STATISTIC) logger.write(); #endif return true;
}
Koda 3.3: Metoda detectSingleScale().
Metoda parallel_for_() kliče metodo operator() iz razreda CascadeClassifierInvoker. Koda
3.4 prikazuje metodo operator(). Metodi operator() metoda parallel_for_() poda
spremenljivko range, ki je razpon segmentov slike, ki jih metoda operator() obdeluje. Slika
se obdeluje znotraj gnezdene zanke FOR s pomočjo spremenljivk y1 in y2, ki določata,
kje na osi Y se obdelava začne in konča. Notranja zanka FOR je namenjena obdelavi po
osi X in poteka od nič do širine spremenljivke processingRectSize. Spremenljivka x se po
vsakem izvajanju notranje zanke FOR poveča za vrednost yStep. To pomeni, da glede na
vrednost spremenljivke yStep preskakujemo stolpce, ki jih obdelujemo v sliki. Vrednost
spremenljivke yStep se s skaliranjem slike v metodi detectMultiScale() zmanjšuje, kar
pomeni, da pri večjih slikah preskočimo več vrstic, da prihranimo čas, pri manjših pa tega
ne delamo, da je klasifikacija bolj natančna. Notranja zanka FOR kliče metodo runAt() iz
razreda CascadeClassifier, ki poda tudi spremenljivki x in y v spremenljivki tipa Point, ki
določi, na kateri točki v sliki bo algoritem zagnal klasifikacijo. Metoda runAt() vrne
22
celoštevilski rezultat, ki je večji kot nič, kadar je klasifikacija pozitivna, in manjši ali enak
nič, kadar je negativna. Če je klasifikacija pozitivna, metoda operator() zaklene semafor
(angl. mutex) in na seznam pravokotnikov doda nov pravokotnik. Pozicijo pravokotnika
izračuna s pomočjo spremenljivk x, y in scalingFactor, širina in višina pravokotnika pa sta
širina in višina spremenljivke winSize.
void operator()(const Range& range) const { Ptr<FeatureEvaluator> evaluator = classifier->featureEvaluator->clone(); Size winSize(cvRound(classifier->data.origWinSize.width * scalingFactor), cvRound(classifier->data.origWinSize.height * scalingFactor)); int y1 = range.start * stripSize; int y2 = min(range.end * stripSize, processingRectSize.height); for( int y = y1; y < y2; y += yStep ) { for( int x = 0; x < processingRectSize.width; x += yStep ) { {…} int result = classifier->runAt(evaluator, Point(x, y), gypWeight); {…} else if( result > 0 ) { mtx->lock(); rectangles->push_back(Rect(cvRound(x*scalingFactor), cvRound(y*scalingFactor), winSize.width, winSize.height)); mtx->unlock(); } if( result == 0 ) x += yStep; }
Koda 3.4: Metoda operator() iz razreda CascadeClassifierInvoker.
Metoda runAt() najprej preveri, ali je kaskada enoravenska (angl. stump based). Kaskada
LBP, ki smo jo uporabljali pri našem projektu, je enoravenska, zato metoda runAt() kliče
metodo predictCategoricalStump().
Metoda predictCategoricalStump(), ki jo prikazuje koda 3.5, opravlja klasifikacijo slike s
pomočjo kaskade klasifikatorjev. Klasifikacija poteka znotraj gnezdenih zank FOR, kjer
notranja zanka FOR izvaja klasifikacijo šibkih klasifikatorjev in prišteva vrednosti listov
odločitvenega drevesa (levi list je negativen, desni pa pozitiven) k seštevku stopnje,
zunanja zanka FOR pa primerja seštevek stopnje s pragom stopnje. Če je seštevek
katere od stopenj manjši od praga stopnje, se klasifikacija konča in metoda
predictCategoricalStump() vrne negativno vrednost spremenljivke si. Spremenljivka si je
iterator stopnje, kar pomeni, da predictCategoricalStump() ob negativni klasifikaciji vrne
število vseh stopenj, ki so sliko klasificirale pozitivno, pomnoženo z -1. Negativen rezultat
23
nam tako pove hkrati, da je bila klasifikacija negativna in koliko stopenj je označilo
klasifikacijo za pozitivno.
Metoda predictCategoricalStump() je ena od najbolj procesorsko zahtevnih metod v
algoritmu. Napisana je v nizkoravenskem jeziku C++ in ima vse podatkovne strukture in
izračune optimizirane za hitrost. Stopnje, listi dreves in iskalne tabele za šibke
klasifikatorje so zapisani v obliki enodimenzionalnih tabel in do podatkov dostopamo s
pomočjo iteratorjev. Podobno je deljenje in množenje izvedeno s pomočjo bitnih premikov,
bitni nizi pa se primerjajo s pomočjo bitnega IN.
Spremenljivka c določa, kateri list bo metoda predictCategoricalStump() prištela k
seštevku stopnje. Spremenljivko c izračuna metoda operator() iz razreda
FeatureEvaluator. Ta metoda s pomočjo indeksa značilke izračuna vrednost LBP v regiji
te značilke in vrne vrednost med 0 in 255. Indeks lista metoda predictCategoricalStump()
izračuna s stavkom cascadeLeaves[subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1].
Razložimo zgoraj zapisan stavek:
subset[c>>5] c>>5 pomeni c/32. Algoritem uporablja bitni premik namesto
deljenja, ker je bitni premik hitrejša operacija. Ta operacija vrne vrednost med 0 in
7, algoritem to vrednost uporabi kot indeks, da si izbere eno od vrednosti iz iskalne
tabele (LUT), ki smo jo dobili iz kaskade;
(1 << (c & 31)) je bitni IN za spremenljivko c in 31, ki je v binarni predstavitvi
11111. To pomeni, da vzamemo prvih pet bitov iz spremenljivke c in na njih
izvedemo bitni premik enke v levo. Ekvivalent tej operaciji je 1 × 2𝑐&31.
Subset [c>>5] & (1 << (c & 31)) algoritem vzame prvih pet bitov iz c, na vrednosti
1 izvede bitni premik v levo za teh prvih pet bitov, izračunano vrednost nato z
bitnim IN primerja z vrednostjo iz iskalne tabele. Ta primerjava je ocenjena z 0,
kadar obe vrednosti nimata isto ležečih bitov, in z 1, kadar jih imata. Če je
vrednost primerjave 0, se izbere desni (pozitivni) list, drugače se izbere levi
(negativni) list.
template<class FEval> inline int predictCategoricalStump( CascadeClassifier& cascade, Ptr<FeatureEvaluator> &_featureEvaluator, double& sum ) { int nstages = (int)cascade.data.stages.size(); int nodeOfs = 0, leafOfs = 0; FEval& featureEvaluator = (FEval&)*_featureEvaluator;
24
size_t subsetSize = (cascade.data.ncategories + 31)/32; int* cascadeSubsets = &cascade.data.subsets[0]; float* cascadeLeaves = &cascade.data.leaves[0]; CascadeClassifier::Data::DTreeNode* cascadeNodes = &cascade.data.nodes[0]; CascadeClassifier::Data::Stage* cascadeStages = &cascade.data.stages[0]; for( int si = 0; si < nstages; si++ ) { CascadeClassifier::Data::Stage& stage = cascadeStages[si]; int wi, ntrees = stage.ntrees; sum = 0; for( wi = 0; wi < ntrees; wi++ ) { CascadeClassifier::Data::DTreeNode& node = cascadeNodes[nodeOfs]; int c = featureEvaluator(node.featureIdx); const int* subset = &cascadeSubsets[nodeOfs*subsetSize]; sum += cascadeLeaves[ subset[c>>5] & (1 << (c & 31)) ? leafOfs : leafOfs+1]; nodeOfs++; leafOfs += 2; } if( sum < stage.threshold ) return -si; } return 1;}
Koda 3.5: Metoda predictCategoricalStump().
Metoda operator() iz razreda FeatureEvaluator je prikazana v kodi 3.6. Ta metoda dobi
značilko iz vektorja značilk in zanjo kliče metodo calc(). Spremenljivka offset v tej metodi
je pretvorba koordinat x in y v odmik (angl. offset), ki se uporablja kot iterator v
enodimenzionalni predstavitvi slike. Spremenljivka offset je del razreda FeatureEvaluator
in se izračuna ob klicu metode setImage().
int operator()(int featureIdx) const { return featuresPtr[featureIdx].calc(offset); }
Koda 3.6: Metoda operator() iz razreda FeatureEvaluator.
Metoda calc(), ki je prikazana v kodi 3.7, uporablja metodo CALC_SUM_, da izračuna
vrednost LBP in jo vrne kot celo število. Namesto posameznih pikslov algoritem
obravnava devet regij v regiji značilke, kot kaže slika 3.14. Algoritem računa seštevke
vrednosti pikslov znotraj posameznih regij s pomočjo integralne slike. V sliki 3.14 vidimo
točke v integralni sliki in regije, ki jih te točke opisujejo.
Metoda calc() vzorec LBP računa s pomočjo bitnega ALI, kot prikazuje slika 3.15. Bitni ALI
nam vrne unijo dveh vrednosti (oziroma dveh bitnih nizov). Ta unija je bitni niz, kjer je na
ena nastavljen vsak bit, ki je nastavljen na ena v prvem ali v drugem nizu. Spremenljivka
cval predstavlja seštevek pikslov regije v sredini (angl. center value). Algoritem seštevek
regije v sredini primerja s seštevki regij okrog sredinske regije, če je seštevek okoliške
25
regije večji ali enak seštevku sredinske regije, v bitni niz vstavi 1, drugače vstavi 0. Če
pogledamo kodo 3.7, lahko vidimo, da metoda vrača bitni niz, v katerega vstavlja
posamezne bite, s pomočjo njihovih desetiških vrednosti. 128 je na primer 10000000. To
pomeni, da če dodajamo 128 v bitni niz z bitnim ALI, na ena nastavimo prvi bit z leve. 64
uporabimo za drugi bit, 32 za tretji bit in tako naprej.
V datoteki XML, ki opisuje kaskado klasifikatorjev, so značilke opisane s koordinatami x in
y in s širino in višino. Kot vidimo v sliki 3.14, širina in višina v datoteki XML ne opisujeta
širine in višine celotne značilke, ampak širino in višino ene regije v značilki.
inline int LBPEvaluator::Feature :: calc( int _offset ) const { int cval = CALC_SUM_( p[5], p[6], p[9], p[10], _offset ); return (CALC_SUM_( p[0], p[1], p[4], p[5], _offset ) >= cval ? 128 : 0) | // 0 (CALC_SUM_( p[1], p[2], p[5], p[6], _offset ) >= cval ? 64 : 0) | // 1 (CALC_SUM_( p[2], p[3], p[6], p[7], _offset ) >= cval ? 32 : 0) | // 2 (CALC_SUM_( p[6], p[7], p[10], p[11], _offset ) >= cval ? 16 : 0) | // 5 (CALC_SUM_( p[10], p[11], p[14], p[15], _offset ) >= cval ? 8 : 0)| // 8 (CALC_SUM_( p[9], p[10], p[13], p[14], _offset ) >= cval ? 4 : 0) | // 7 (CALC_SUM_( p[8], p[9], p[12], p[13], _offset ) >= cval ? 2 : 0) | // 6 (CALC_SUM_( p[4], p[5], p[8], p[9], _offset ) >= cval ? 1 : 0); }
Koda 3.7: Metoda calc().
26
Slika 3.14: Integralne slike v kaskadi LBP [53].
27
Slika 3.15: Ustvarjanje LBP s pomočjo binarnega ALI.
Metoda CALC_SUM_, ki jo lahko vidimo v kodi 3.8, za računanje seštevka regije
uporablja integralne slike. Spremenljivka p je kazalec na piksel v integralni sliki. Ta
kazalec se nastavi za vsako značilko posebej, v metodi setImage().
Metoda CALC_SUM_ uporablja točke v integralni sliki, da dobi seštevke vrednosti pikslov
v regijah, na isti način kot pri Haarovih značilkah.
#define CALC_SUM_(p0, p1, p2, p3, offset) ((p0)[offset] - (p1)[offset] - (p2)[offset] + (p3)[offset])
Koda 3.8: Metoda CALC_SUM_.
28
4. Pohitritve algoritma za zaznavanje obrazov
V tem diplomskem delu smo se ukvarjali s pohitritvijo postopkov za lokaliziranje obrazov,
ki so vključeni v knjižnico OpenCV. Detektorji obrazov, ki so implementirani v knjižnici
OpenCV, so hitri že sami po sebi. Na nižjih ravneh, v razredu CascadeClassifier, lahko
vidimo, da so se razvijalci posluževali nizkoravenskih, procesorsko manj zahtevnih metod
za obdelovanje slik. Algoritem sliko obravnava kot enodimenzionalni vektor, deljenje
izvaja s pomočjo bitnega IN, bite v LBP nastavlja s pomočjo bitnega ALI in vse podatke
prenaša po referenci. Na višjih ravneh, v razredu DetectionBasedTracker, pa že lahko
najdemo stvari, ki jih lahko spremenimo.
Vsaka pohitritev je v osnovi odvzem funkcionalnosti algoritmu. Za hitrost žrtvujemo
natančnost, estetiko ali katero od manj pomembnih funkcionalnosti. Za hitrost lahko
žrtvujemo tudi splošnost algoritma, ga priredimo specifično za operacijski sistem in
procesor, na katerih se izvaja.
V našem projektu smo za hitrost žrtvovali natančnost, estetiko in splošnost algoritma. V
nadaljevanju so opisane pohitritve, ki smo jih implementirali v algoritem ter jih tudi
preizkusili.
4.1. Odstranitev večnitenja v razredu DetectionBasedTracker
Razred DetectionBasedTracker je v izvirnem algoritmu zaznaval s pomočjo večnitenja. Za
večnitenje in sinhronizacijo niti je skrbel razred SeparateDetectionWork. Razred
SeparateDetectionWork je v ozadju pregledoval celotno sliko, medtem ko je metoda
process() v glavni niti pregledovala samo regije objektov, ki jim je algoritem že sledil. Ko je
ločena nit končala z detekcijo na celotni sliki, je algoritem nastavil zastavice, ki so metodi
process() povedale, naj vzame nove objekte in poda novo sliko za ločeno nit.
Naprava, na kateri smo testirali algoritem, je imela vgrajen procesor ARM Cortex v8, ki je
enojedrni procesor. To pomeni, da je večnitenje manj učinkovito zaradi dodatnega
procesiranja, ki ga potrebujemo za mednitno sinhronizacijo, kot sta zaklepanje in
odklepanje semaforjev (angl. mutex).
Algoritem smo pohitrili tako, da smo vzeli metodo workCycleObjectDetector() iz razreda
SepearateDetectionWork, jo spremenili v metodo runLargeDetector(), odstranili vse
mehanizme za mednitno sinhronizacijo in uporabili metodo runLargeDetector() v metodi
29
process() tako, da je metoda process() izvajala metodo runLargeDetector() v glavni niti.
Metodo runLargeDetector() prikazuje koda 4.1.
Rezultat take implementacije sta bila veliko počasnejše izvajanje algoritma in boljše
sledenje objektu – algoritem namreč ne preneha zaznavati obraza ob hitrem gibanju. Tak
rezultat smo dobili, ker je metoda process() po naši spremembi pregledovala celotno sliko
veliko bolj pogosto, kot jo je ločena nit v izvirnem algoritmu. Razred
SeparateDetectionWork je celotno sliko obdelal za približno vsakih devet klicev metode
process(). Da smo pohitrili izvajanje prilagojene metode process(), smo ji dodali
spremenljivko iterationCount, ki šteje klice metode process(). V razred
DetectionBasedTracker smo dodali še celoštevilski parameter
bigDetectorIterationWaitCount (privzeto nastavljen na 10), ki določa, kolikokrat naj
algoritem kliče metodo process(), preden z metodo runLargeDetector() obdela celotno
sliko. Dodano kodo v metodi process() prikazuje koda 4.2.
Končni rezultat pohitritve, po dodajanju parametra bigDetectorIterationWaitCount, je bila
povečana hitrost detekcije s 14 ms na 8 ms in znižana natančnost z 92 % na 85 %, na
našem vzorčnem videu. Natančnost se je znižala, ker je prilagojen algoritem obdelal
celotno sliko manj pogosto kot ločena nit. Stranski učinek pohitritve je bilo povečanje
hitrosti vzorčenja (angl. Frames Per Second ali FPS) za 2, kar je bila posledica tega, da
operacijskemu sistemu ni bilo več treba skrbeti za večnitenje. Končni FPS je bil 5 sličic na
sekundo.
//This method runs the detector on the whole image, in the same thread as the tracking thread is in //As opposed to the separateDetectionWork, it doesn't need any thread synchronization void DetectionBasedTracker::runLargeDetector(const Mat& imageGray, vector<Rect>& objects){ int minObjectSize=parameters.minObjectSize; //Size is width X height Size min_objectSize = Size(minObjectSize, minObjectSize); int maxObjectSize=parameters.maxObjectSize; Size max_objectSize(maxObjectSize, maxObjectSize); //CascadeInThread is of type CascadeClassifier, origin: objectdetect/cascadedetect //This is the call that does the actual detection work using a CascadeClassifier cascadeForTracking.detectMultiScale( imageGray, objects, parameters.scaleFactor, parameters.minNeighbors, 0 |CV_HAAR_SCALE_IMAGE, min_objectSize, max_objectSize );}
Koda 4.1: Metoda runLargeDetector().
30
//We only run the detector on the whole image every so often, so that we gain on performance //However: the tracker is more likely to lose a tracked object due to fast movement if(iterationCount>=parameters.bigDetectorIterationWaitCount){ runLargeDetector(imageGray,rectsWhereRegions); shouldHandleResult=true; iterationCount=0;}
Koda 4.2: Dodana koda v metodi process().
4.2. Sprememba prikaza zaznanih objektov
Za prikaz zaznanih objektov razred DetectionBasedTracker uporablja metodo
calcTrackedObjectPositionToShow(), prikazano v kodi 4.3, ki jo kliče metoda getObjects().
Za vsak zaznan objekt algoritem shranjuje več kot eno pozicijo, zato uporabimo metodo
calcTrackedObjectsPositionToShow(), ki vrne pravokotnik, ki predstavlja srednjo vrednost
vseh shranjenih pozicij za posamezen objekt.
Rect DetectionBasedTracker::calcTrackedObjectPositionToShow(int i) const { if ( (i < 0) || (i >= (int)trackedObjects.size()) ) { return Rect(); } if (trackedObjects[i].numDetectedFrames <= innerParameters.numStepsToWaitBeforeFirstShow){ return Rect(); } if (trackedObjects[i].numFramesNotDetected > innerParameters.numStepsToShowWithoutDetecting) { return Rect();} const TrackedObject::PositionsVector& lastPositions=trackedObjects[i].lastPositions; int N=lastPositions.size(); if (N<=0) { return Rect(); } int Nsize=std::min(N, (int)weightsSizesSmoothing.size()); int Ncenter= std::min(N, (int)weightsPositionsSmoothing.size()); Point2f center; double w=0, h=0; if (Nsize > 0) { double sum=0; for(int j=0; j < Nsize; j++) { int k=N-j-1; w+= lastPositions[k].width * weightsSizesSmoothing[j]; h+= lastPositions[k].height * weightsSizesSmoothing[j]; sum+=weightsSizesSmoothing[j]; } w /= sum; h /= sum; } else { w=lastPositions[N-1].width; h=lastPositions[N-1].height; } if (Ncenter > 0) { double sum=0;
31
for(int j=0; j < Ncenter; j++) { int k=N-j-1; Point tl(lastPositions[k].tl()); Point br(lastPositions[k].br()); Point2f c1; c1=tl; c1=c1* 0.5f; Point2f c2; c2=br; c2=c2*0.5f; c1=c1+c2; center=center+ (c1 * weightsPositionsSmoothing[j]); sum+=weightsPositionsSmoothing[j]; } center *= (float)(1 / sum); } else { int k=N-1; Point tl(lastPositions[k].tl()); Point br(lastPositions[k].br()); Point2f c1; c1=tl; c1=c1* 0.5f; Point2f c2; c2=br; c2=c2*0.5f; center=c1+c2; } Point2f tl=center-(Point2f(w,h)*0.5); Rect res(cvRound(tl.x), cvRound(tl.y), cvRound(w), cvRound(h)); return res; }
Koda 4.3: Metoda calcTrackedObjectsPositionToShow().
Naša pohitritev metode je bila odstranitev računanja srednje vrednosti pravokotnika in
prilagojena metoda je preprosto vračala zadnjo zaznano pozicijo. Prilagojena metoda
calcTrackedObjectsToShow() je prikazana v kodi 4.4.
Rect DetectionBasedTracker::calcTrackedObjectPositionToShow(int i) const { //A segmentation fault checker if ( (i < 0) || (i >= (int)trackedObjects.size()) ) { return Rect(); //We don't want to show rectangles, unless we're sure, that there's an object there, //meaning we have to detect an object more than once to show it, otherwise the screen would be full of rectangles if (trackedObjects[i].numDetectedFrames <= innerParameters.numStepsToWaitBeforeFirstShow){ return Rect(); } //Similarly, we don't show an object, once the non-detection threshold has been reached if (trackedObjects[i].numFramesNotDetected > innerParameters.numStepsToShowWithoutDetecting) { return Rect(); return trackedObjects[i].lastPositions.back();}
Koda 4.4: Prilagojena metoda calcTrackedObjectsPositionToShow().
32
Algoritem je bil po pohitritvi nezaznavno hitrejši – pridobili smo manj kot 1 ms. Pohitritev ni
imela učinka na natančnost, imela pa je učinek na estetiko prikaza, kvadrat okrog
zaznanega obraza se je namreč premikal bolj kot v izvirni različici algoritma.
4.3. Sprememba metode detectMultiScale()
Metoda detectMultiScale() poleg velikosti slike skalira tudi okno za zaznavanje od
manjšega proti večjemu. Ko je vhodna slika premajhna za zaznavanje, pa se metoda
zaključi.
S tem, da okno za zaznavanje s časom večamo, zagotovimo, da okrog zaznanega obraza
prikazujemo pravokotnik, ki zajame celoten obraz. Algoritem smo spremenili tako, da je
metoda detectMultiScale() vračala prvega zaznanega kandidata, namesto da bi naprej
skalirala sliko. S tem smo žrtvovali estetiko, ker zaznani pravokotnik ni več zajemal
celotnega obraza. Vrstico, s katero smo spremenili izvajanje metode detectMultiScale(),
prikazuje koda 4.5.
for( double factor = 1; ; factor *= scaleFactor ) { {…} if(candidates.size() > 0) break; {…}}
Koda 4.5: Dodana koda v metodi detectMultiScale().
Rezultat pohitritve sta bila zmanjšan čas izvajanja na 1 ms in zmanjšana stopnja
zaznavanja na 0 %. Razlog za tako slabo stopnjo zaznavanja je bil, da algoritem po
izhodu iz zanke FOR kliče metodo groupRectangles(), ki grupira pravokotnike glede na
podobnost [54]. Metoda groupRectangles() uporablja parameter minNeighbors, ki je
privzeto nastavljen na 2. Ker smo prekinili zanko FOR s samo enim kandidatom, je
metoda groupRectangles() zaznavanje obravnavala kot napačen pozitiven (angl. false
positive) zadetek in zavrgla objekt.
Da smo prišli nazaj na sprejemljivo raven zaznavanja, smo nastavili prag za število
zaznanih kandidatov s spremenljivko exitThreshold, ki smo jo nastavili na vrednost
minNeighbors*2. Prilagojeno vrstico prikazuje koda 4.6.
33
//exitThreshold is the threshold we'll be using to exit the for loop unsigned int exitThreshold=minNeighbors*2; if(candidates.size()>=exitThreshold) break;
Koda 4.6: Prilagojena koda v metodi detectMultiScale().
Poskusili smo še odstraniti klic metode groupRectangles(), ampak nam ta sprememba ni
prinesla nobene pohitritve, je pa znižala natančnost algoritma, ki je padla na 66 %.
Končni rezultat naše pohitritve je zmanjšan čas izvajanja za 2 ms (iz 8 ms na 6 ms),
natančnost pa je ostala pri 85 % za naš vzorčni video.
4.4. Odstranitev klicev metode parallel_for_()
Metoda detectSingleScale() kliče metodo parallel_for_(), ki je del ogrodja za paralelizacijo
iz knjižnice OpenCV. Ta najprej preveri, če je paralelizacijsko ogrodje knjižnice OpenCV
omogočeno. Po nekaj poskusih s pomočjo konzole LogCat za razhroščevanje smo
ugotovili, da v naši aplikaciji Android to ogrodje ni podprto, zato je klic metode
parallel_for_() nesmiseln.
Metoda parallel_for_() uporablja metodo operator() iz razreda CascadeClassifierInvoker.
Kodo iz metode operator() smo prestavili v metodo detectSingleScale() in se s tem izognili
uporabi metode parallel_for_(), ogrodja za paralelizacijo iz knjižnice OpenCV in njegove
logike za mednitno sinhronizacijo.
Koda 4.7 prikazuje izvirno metodo detectSingleScale(), ki vsebuje klic metode
parallel_for_(), skupaj z vsemi parametri in objektom razreda CascadeClassifierInvoker,
prek katerega paralelizacijsko ogrodje dobi vse potrebne podatke za večnitenje in
zaznavanje. Koda 4.8 pa prikazuje prilagojeno metodo detectSingleScale, ki vsebuje
logiko za zaznavanje, preneseno iz metode operator() iz razreda
CascadeClassifierInvoker.
Rezultat pohitritve je bil nezaznavno hitrejši algoritem (izvajanje je bilo hitrejše za manj kot
1 ms), kar pomeni, da uporaba ogrodja za paralelizacijo iz knjižnice OpenCV ne pomeni
velike obremenitve procesorja, še posebej, ko to ogrodje ni omogočeno.
bool CascadeClassifier::detectSingleScale( const Mat& image, int stripCount, Size processingRectSize, int stripSize, int yStep, double factor, vector<Rect>& candidates, vector<int>& levels, vector<double>& weights, bool outputRejectLevels ) { {…}
34
else { parallel_for_(Range(0, stripCount), CascadeClassifierInvoker( *this, processingRectSize, stripSize, yStep, factor, candidatesVector, rejectLevels, levelWeights, false, currentMask, &mtx)); } {…} }
Koda 4.7: Izvirna metoda detectSingleScale().
bool CascadeClassifier::detectSingleScale( const Mat& image, int stripCount, Size processingRectSize, int stripSize, int yStep, double factor, vector<Rect>& candidates, vector<int>& levels, vector<double>& weights, bool outputRejectLevels ) { {…} else { Size winSize(cvRound(data.origWinSize.width * factor), cvRound(data.origWinSize.height * factor)); Range range(0,stripCount); int y1 = range.start * stripSize; int y2 = min(range.end * stripSize, processingRectSize.height); for( int y = y1; y < y2; y += yStep ) { for( int x = 0; x < processingRectSize.width; x += yStep ) { double gypWeight; //runAT gives us the result of LBP classification at the current point int result = runAt(featureEvaluator, Point(x, y), gypWeight); if( result > 0 ) { candidatesVector.push_back(Rect(cvRound(x*factor), cvRound(y*factor), winSize.width, winSize.height)); } if( result == 0 ) x += yStep; } } } {…} }
Koda 4.8: Prilagojena metoda detectSingleScale().
4.5. Prilagajanje parametrov
Razred DetectionBasedTracker ima nabor parametrov, ki jih lahko spremenimo, da
prilagodimo delovanje algoritma naši aplikaciji.
35
Parametri, ki jih imamo na voljo (in njihove privzete vrednosti), so:
minObjectSize (96), ki določa minimalno velikost objekta, ki mu bomo še sledili,
maxObjectSize (INT_MAX),
scaleFactor (1,1) določa, kako hitro se bodo skalirali objekti v metodi
detectMultiScale(),
maxTrackLifetime (5) določa, koliko detekcij lahko preteče, brez da bi zaznali
objekt, preden ga označimo za izgubljenega,
minNeighbors (2) določa, koliko objektov mora biti zaznanih na eni poziciji, da jo
sprejmemo kot zaznan objekt. S tem parametrom se znebimo napačnih pozitivnih
(angl. false pozitive) klasifikacij,
minDetectionPeriod (20) določa, koliko časa naj počaka ločena nit iz razreda
SeparateDetectionWork, preden začne detekcijo z novo sliko.
Te glavne parametre lahko nastavljamo zunaj razreda DetectionBasedTracker, na primer
v ovojnici JNI. Poleg glavnih parametrov pa ima razred DetectionBasedTracker še
notranje parametre v razredu InnerParameters. Ti parametri (in njihove privzete vrednosti)
so:
numLastPositionsToTrack (4) določa, koliko pozicij shranjujemo za vsak objekt, ki
mu sledimo,
numStepsToWaitBeforeFirstShow (6) določa, koliko zaznav hočemo za vsak
objekt, preden ga sploh začnemo prikazovati,
numStepsToTrackWithoutDetectingIfObjectHasNotBeenShown (3) podobno kot
parameter maxTrackLifetime, s tem da ga uporabljamo, ko objekta še nismo
prikazali,
numStepsToShowWithoutDetecting (3) podobno kot parameter maxTrackLifetime,
s tem da algoritem parameter maxTrackLifetime uporablja, ko se odloča, ali bo
izbrisal objekt, parameter numStepsToShowWithoutDetecting pa uporablja samo
pri prikazovanju,
36
coeffTrackingWindowSize (2,0) je koeficient za nastavljanje velikosti sledilnega
okna,
coeffObjectSizeToTrack (0,85) je koeficient za nastavljanje velikost objekta za
sledenje,
coeffObjectSpeedUsingInPrediction (0,8) je koeficient, ki ga algoritem uporablja za
nastavljanje novega položaja objekta, ki mu sledi. Tukaj algoritem uporablja
predvidevanje glede na prejšnje pozicije objekta.
Da bi algoritem pohitrili, smo eksperimentirali s parametri. Vsakega posebej smo večali in
manjšali ter opazovali učinke na hitrost in natančnost algoritma ter prikazovanje zaznanih
objektov. Tiste spremembe parametrov, ki so zmanjšale čas izvajanja, smo obdržali v
končni različici pohitritve, parametre, ki niso vplivali na hitrost, pa smo nastavili nazaj na
privzeto vrednost. V nadaljevanju so opisani rezultati teh eksperimentiranj.
Ko smo povečali vrednost parametra minObjectSize, se je natančnost algoritma neznatno
povečala, hitrost pa se ni izboljševala. V končni različici pohitritve smo parameter
minObjectSize nastavili na privzeto vrednost.
Ko smo povečali vrednost parametra scaleFactor, se je hitrost sicer povečevala, ampak je
algoritem izgubljal natančnost (do 20 % manjša natančnost), hkrati je bil algoritem veliko
slabši pri zaznavanju obrazov, ki so bili bližje kameri. V končni različici pohitritve smo
parameter scaleFactor povečali z 1,1 na 1,4. Ta sprememba nam je dala hitrejši algoritem
z manjšo izgubo natančnosti.
Ko smo zmanjšali vrednost parametra maxtrackLifetime na 1, smo izgubili nekaj
natančnosti ob hitrem gibanju, na hitrost izvajanja algoritma pa ni bilo vpliva. V končni
različici pohitritve smo parameter maxTrackLifetime nastavili nazaj na privzeto vrednost.
Ko smo zmanjšali vrednost parametra minNeighbors na 1, je bilo izvajanje algoritma malo
hitrejše, brez posledic za natančnost. V končni različici pohitritve smo parameter
minNeighbors zmanjšali z 2 na 1.
Ko smo povečali vrednost parametra minDetectionPeriod, se je hitrost izvajanja algoritma
povečala, ampak smo izgubili natančnost pri hitrem gibanju. Razlog za to je, da je
algoritem dlje časa ignoriral vhodne slike in obdeloval samo objekte, ki jim je sledil,
medtem ko je ločena nit čakala (nastavljanje parametra minDetectionPeriod smo testirali,
37
preden smo odstranili večnitenje). V končni različici pohitritve smo parameter
minDetectionPeriod nastavili nazaj na privzeto vrednost.
Ko smo zmanjšali vrednost parametra numLastPositionsToTrack, algoritem ni deloval
znatno hitreje, bil pa je negativni učinek na prikazani pravokotnik okrog zaznanega
objekta, ki se je premikal bolj kot v izvirni različici algoritma. V končni različici pohitritve
smo vrednost parametra numLastPositionsToTrack nastavili nazaj na privzeto vrednost.
Ko smo zmanjšali vrednost parametra numStepsToWaitBeforeFirstShow, izvajanje ni bilo
hitrejše, povečala pa se je natančnost, ker je algoritem zaznani objekt prikazal takoj.
Negativna posledica te spremembe je, da tvegamo več napačnih pozitivnih (angl. false
positive) zadetkov. V končni različici pohitritve smo vrednost parametra
numStepsToWaitBeforeFirstShow zmanjšali s 6 na 3.
Ko smo manjšali vrednosti parametrov numStepsToTrackWithoutDetectingIfObjectHasNot
BeenShown in numStepstoShowWithoutDetecting, algoritem ni deloval hitreje, izgubili pa
smo natančnost, ker je bil algoritem bolj občutljiv na negativne klasifikacije in je hitreje
zavrgel objekte. V končni različici pohitritve smo vrednosti teh dveh parametrov nastavili
nazaj na privzeti vrednosti.
Ko smo zmanjšali vrednost parametra coeffTrackingWindowSize, se je algoritem izvajal
za 1 ms hitreje. V končni različici algoritma smo vrednost tega parametra zmanjšali z 2 na
1,5.
Ko smo povečali parameter coeffTrackingObjectSizeToTrack z 0,85 na 1,1, je bil
algoritem hitrejši, natančnost pa je bila prenizka. Ko smo ga zmanjšali na 0,5, smo
algoritem upočasnili. Ko smo ga povečali za manjšo vrednost (z 0,85 na 0,95), pa je bil
algoritem za eno milisekundo hitrejši in za 2 % manj natančen. Hkrati je postal manj
odziven ob približevanju objekta proti kameri in oddaljevanju objekta od kamere. V končni
različice pohitritve smo nastavili vrednost parametra coeffTrackingObjectSizeToTrack na
0,95.
Ko smo spreminjali vrednost parametra coeffObjectSpeedUsingInPrediction, pa nismo
vplivali na hitrost ali natančnost algoritma. V končni različici pohitritve smo vrednost tega
parametra nastavili nazaj na privzeto vrednost.
Končni nabor parametrov in njihovih vrednosti je prikazan v kodi 4.9.
38
minObjectSize = 96; maxObjectSize = INT_MAX; scaleFactor = 1.4; maxTrackLifetime = 5; minNeighbors = 1; minDetectionPeriod = 20; numLastPositionsToTrack = 4; numStepsToWaitBeforeFirstShow = 3; numStepsToTrackWithoutDetectingIfObjectHasNotBeenShown = 3; numStepsToShowWithoutDetecting = 3; coeffTrackingWindowSize = 1.5; coeffObjectSizeToTrack = 0.95; coeffObjectSpeedUsingInPrediction = 0.8;
Koda 4.9: Parametri razreda DetectionBasedTracker.
Končni rezultat prilagajanja parametrov so bili znatna pohitritev, izguba natančnosti in
opazne spremembe pri prikazovanju zaznanih objektov.
4.6. Trivialne pohitritve
Izvirni algoritem v razredih DetectionBasedTracker in CascadeClassifier ima veliko kode,
ki je naša implementacija ne uporablja. Primer take kode so vsi stavki IF, kjer se preverja
vrednost spremenljivke outputRejectLevels. Spremenljivka outputRejectLevels je v našem
primeru vedno FALSE, ker je namenjena testiranju učinkovitosti kaskade, kar pa nas ne
zanima, ker kaskade nismo trenirali sami, ampak smo uporabili že preizkušeno kaskado.
Drugi primer take kode so deklaracije skupaj z inicializacijami znotraj zank FOR. Ker se
deklaracija izvaja vsakič, ko se zažene zanka FOR, je hitreje, če je deklaracija zunaj
zanke in se znotraj zanke nastavlja le njena vrednost.
Za našo pohitritev smo odstranili neuporabljeno kodo in prestavili deklaracije izven zank
FOR. Rezultat te pohitritve je bil nezaznavno hitrejši algoritem (manj kot 1 ms hitrejše
izvajanje) brez vpliva na natančnost zaznave ali prikaz zaznanih objektov.
39
5. Rezultati
V tem poglavju so opisani rezultati naših pohitritev algoritma za zaznavanje obrazov iz
knjižnice OpenCV. Opisali smo eksperimentalno okolje, definirali, kako merimo
učinkovitost pohitritev, in prikazali rezultate naših testov.
5.1. Opis eksperimentalnega okolja
Pri testiranju hitrosti algoritma je pomembno, da ima odvzemanje testnih vzorcev čim
manj vpliva na izvajanje algoritma. Iz tega razloga naših vzorcev nismo prikazovali in
analizirali na tablici, ampak smo jih pošiljali prek internetne povezave na strežnik v obliki
niza. Strežnik je bila namizna aplikacija, napisana v programskem jeziku C#, ki je hranila
podatke v tekstovni obliki. Strežnik je imel naslednje funkcionalnosti:
funkcionalnost za prevajanje podatkov iz nizov v spremenljivke (angl. parsing),
funkcionalnost za prikaz podatkov,
funkcionalnost za izvoz podatkov v datoteko CSV (angl. Comma Separated
Values),
funkcionalnost za računanje povprečnega časa izvajanja za več izbranih
elementov v seznamu in
funkcionalnost za sledenje različici.
Zaslonsko sliko strežnika prikazuje slika 5.1.
40
Slika 5.1: Testni strežnik.
Različica je bila sestavljena iz spremenljivk release, major, minor in build, uporabljali pa
smo samo spremenljivki minor in build. Vrednost spremenljivke build se je povečevala s
pomočjo programa VersionIncrementer.exe, ki je bila konzolna aplikacija, napisana v
programskem jeziku C#. Ko smo zagnali aplikacijo VersionIncrementer.exe, je ta strežniku
poslala ukaz, da naj poveča vrednost spremenljivke build. To aplikacijo je zagnalo
razvojno okolje Eclipse vsakič, ko se je zgradila naša aplikacija za operacijski sistem
Android. Spremenljivka build je bila namenjena za grobo sledenje različici, da smo lahko
ločevali med zgodnejšimi in poznejšimi različicami naše aplikacije. Spremenljivko minor
pa smo spreminjali s pomočjo številčnice vsakič, ko smo implementirali ali odstranili
katero od pohitritev. Vrednost različice se je shranjevala in posodabljala v datoteki
version.txt.
Testni strežnik je imel tudi funkcionalnost, ki nam je omogočala, da smo shranjevali
številko različice in opis spremembe (angl. changelog) v datoteko HTML. Slika 5.2
prikazuje to datoteko, odprto v brskalniku Internet Explorer.
41
Slika 5.2: Spremembe različic.
Za pošiljanje podatkov smo v Android kodo implementirali razred Tester. Podatki o
posameznem testu so se shranjevali v seznamu, ki je vseboval objekte razreda Test.
Razred Test je imel spremenljivke za shranjevanje časa začetka in konca, seznam
detekcij in spremenljivko type, ki nam je povedala, ali gre za zaznavanje na sliki ali na
videu. Seznam detekcij je vseboval objekte razreda Detection, ki je vseboval spremenljivki
hit in duration. Spremenljivka hit nam je povedala, ali je bil pri tej detekciji zaznan obraz ali
ne, spremenljivka duration pa je bila celoštevilska in je predstavljala trajanje detekcije v
milisekundah.
Detekcija se je izvajala v ovojnici Java. Kot vidimo v kodi 5.1, smo za merjenje trajanja
detekcije izmerili trenutni čas (v milisekundah) pred in po klicu metode detect(). Metoda
detect() iz ovojnice JNI kliče metodo process() iz razreda DetectionBasedTracker v kodi
C++. Trajanje detekcije smo izračunali tako, da smo čas začetka detekcije odšteli od časa
konca detekcije. Razred Tester je zapisoval podatke zunaj ovojnice JNI znotraj aplikacije
Android.
long startTime=System.currentTimeMillis(); detectionBasedTracker.detect(grayscale, faces); long stopTime=System.currentTimeMillis();
Koda 5.1: Merjenje trajanja metode detect().
Pri videu smo test začeli takrat, ko je algoritem začel z obdelavo prve slike iz videa, in ga
končali takrat, ko je algoritem končal z obdelavo zadnje slike iz videa. Vmes so se
42
zapisovali rezultati obdelave posameznih slik v objekt razreda Test. Pri sliki s kamere
izbira začetka in konca testa ni tako preprosta. Problem nastane, ko se odločamo, kdaj
končati test. Ne moremo se namreč popolnoma zanašati na natančnost algoritma, ker če
ta ne zaznava obraza, še ne pomeni, da ni obraza, hkrati pa lahko ima napačne pozitivne
(angl. false positive) zadetke. Problem se pokaže, ko algoritem ob gibanju obraza tega
začasno izgubi. Če bi test prekinili takoj, ko obraza ni, bi bili rezultati testa nerealni. Za ta
namen smo dodali toleranco za detekcije brez zadetkov, ki test prekine šele, ko je 10
zaporednih detekcij praznih.
Po tem, ko se detekcija konča, razred Tester izračuna povprečno trajanje, povprečne
zadetke detekcije, prevede podatke v niz in pošlje niz testnemu strežniku.
Naša Android aplikacija je imela preprost uporabniški vmesnik z dvema zavihkoma. Eden
od zavihkov je namenjen sliki s kamere, drugi pa predvajanju videa. Ob zagonu aplikacije
se najprej naloži video, šele po tem se naložijo zavihki in preostanek uporabniškega
vmesnika. Zaslonsko sliko aplikacije lahko vidimo na sliki 5.3.
Slika 5.3: Android aplikacija.
Pri branju videa so nastajali problemi, ker smo morali brati vsako sliko posebej, da smo na
njej iskali obraze. Ker je video zakodiran, je nepraktično prebrati določen okvir, ga najprej
pretvoriti v format BMP in nato še v spremenljivko tipa Mat ter izvajati detekcijo na njem.
43
Poskusili smo pridobivanje posameznih okvirov z različnimi metodami, ki so nam bile na
voljo. Uporabili smo razred VideoCapture [55] iz knjižnice OpenCV, razred
MediaMetaDataRetriever [56] ki je del platforme Android, razred MediaExtractor [57], ki je
bil nedavno implementiran v platformo Android, in razred FFMPEGMediaMetadata
Retriever, ki ga je naredil William Seemann [58].
Razred MediaMetadataRetriever ni vračal ničesar, zato smo poskusili razred
VideoCapture, zanj naredili ovojnice v JNI in Java, ampak je vračal FALSE in v podano
matriko zapisoval prazno sliko. Ob uporabi razreda MediaExtractor pa je strojni kodek
javljal napako in zaradi pomanjkljive dokumentacije (implementacija razreda je bila takrat
še sveža) nismo našli rešitve za ta problem.
Edino delujoče orodje za naš namen je bil razred FFMPEGMediaMetadataRetriever, ki ga
je uporabljala tudi končna različica aplikacije. Ker je porabil veliko časa za pridobivanje
ene slike, smo zmanjšali ločljivost videa. Posledica manjše ločljivosti so bile hitrejše
detekcije na videu v primerjavi z detekcijami na slikah s kamere. Tudi z nižjo ločljivostjo pa
nismo predvajali videa z izvirno hitrostjo vzorčenja (12 slik na sekundo), ampak je bilo
predvajanje počasnejše, in sicer 4 slike na sekundo.
Razred FFMPEGMediaMetadataRetriever pa je imel poleg počasnega izvajanja še eno
napako. Po nekaj časa (video je bil običajno v svojem četrtem ciklu predvajanja) se je
naša aplikacija zrušila, brez da bi konzola LogCat zabeležila izjemo. Aplikacijo smo zato
spremenili tako, da se je video naložil pred začetkom izvajanja programa v seznam slik
formata BMP. To je bila pomnilniško zelo zahtevna implementacija, nalaganje pa je trajalo
skoraj dve minuti. S to implementacijo pa smo rešili problema počasnega predvajanja
(video ima spet izvornih 12 slik na sekundo) in nenadnega prenehanja delovanja
aplikacije.
5.2. Metrike učinkovitosti
Vsaka pohitritev je bila kombinacija pridobitve hitrosti in izgube natančnosti. Hitrost smo
merili v povprečnem času (milisekundah), ki je bil potreben za izvedbo ene detekcije,
natančnost pa v odstotku zaznanih obrazov. Učinkovitost pohitritev smo merili predvsem
na videu, tam smo imeli enak nabor slik za vsak test, na vseh slikah je bil obraz in v
vsakem testu je bilo enako število slik. Na kameri smo testirali predvsem zato, da smo
preverili, kako je algoritem deloval ob hitrem gibanju obraza, kako se je spreminjal prikaz
zaznanih obrazov in kako je algoritem deloval pri zaznavanju več obrazov.
44
5.3. Rezultati testiranj na tablici
Tablični računalnik, na katerem smo testirali našo Android aplikacijo, je bil Prestigio
MultiPad 7.0 Ultra + [9]. Imel je vgrajen procesor ARM Cortex A8, ki je enojedrni procesor,
ki je deloval s taktom 1 GHz. Tablični računalnik je imel na voljo še 512 MB delovnega
pomnilnika. Za zajem slike smo uporabljali sprednjo kamero, ki je imela 0,3 milijona
pikslov in je snemala video dimenzij 640 pikslov (širina) in 480 pikslov (višina).
V tabeli 5.1 lahko vidimo posamezne pohitritve, ki smo jih implementirali, in njihov vpliv na
hitrost in natančnost algoritma. Tabela vključuje podatke testov detekcije na videu. Pri
vsakem testu imamo 112 klasifikacij, ker imamo 112 slik v videu, rezultat testa pomeni
povprečno trajanje vseh 112 klasifikacij. V tabeli pa imamo povprečno trajanje vseh testov
za vsako pohitritev posebej. Pri pohitritvi, kjer smo odstranili večnitenje, lahko vidimo, da
večji kot je parameter bigDetectorWaitCount, hitreje se je izvajal algoritem. Pri pohitritvi,
kjer smo spremenili skaliranje, pa vidimo, da je prehiter izhod iz zanke FOR negativno
vplival na natančnost algoritma.
Graf hitrosti algoritma za video na sliki Slika 5.4 nam okvirno prikazuje, kako se je hitrost
algoritma spreminjala. Ta graf ni bil izdelan z uporabo podatkov iz tabele 5.1, ampak iz
filtriranih rezultatov vseh testov. Številke na vrhu predstavljajo zaporedne številke
pohitritev, kot so zapisane v tabeli 5.1, vrednosti na x osi predstavljajo zaporedno številko
testa (se ne sovpadajo s tabelo 5.1), vrednosti na y osi pa pomenijo povprečno trajanje
enega zaznavanja za vsak test (vsak test ima 112 ločenih klasifikacij, zato vzamemo za
trajanje njihovo povprečje). Ker smo imeli veliko število testov (preko 1800), so v podatkih
za graf vključeni samo tisti testi, ki so predstavljali končne različice pohitritev. Med njimi je
bilo namreč veliko rezultatov, ki smo jih dobili med popravljanjem napak v aplikaciji.
Tabela 5.1: Pohitritve algoritma.
Zaporedna številka
pohitritve
Povprečno trajanje
testa [ms]
Pohitritev izvajanja
[ms]
Povprečje zadetkov
[%]
Hitrost vzorčenja [slike na sekundo]
Opis spremembe
0 13,714 0 92,857 4,335 Izvirna različica algoritma.
1 56,125 -42,411 94,642 4,224 Odstranitev večnitenja, namesto tega smo uporabljali detektor na celotni sliki vsakič, ko kličemo metodo process().
2 12,571 43,554 90,178 5,270 Dodajanje parametra bigDetectorIterationWaitCoun
45
Zaporedna številka
pohitritve
Povprečno trajanje
testa [ms]
Pohitritev izvajanja
[ms]
Povprečje zadetkov
[%]
Hitrost vzorčenja [slike na sekundo]
Opis spremembe
t s privzeto vrednostjo 5, ki pove programu, koliko iteracij naj preteče, preden zažene detektor na celotni sliki.
3 8,285 4,286 85,714 5,308 Povečanje vrednosti parametra bigDetectorIterationWaitCount s 5 na 10.
4 7,826 0,459 86,157 5,564 Sprememba prikaza zaznanih pravokotnikov, namesto normaliziranega zdaj prikazujemo samo zadnjega.
5 1 6,826 0 5,781 Sprememba skaliranja v metodi detectMultiScale(), zdaj se vrne že prvi najdeni kandidat.
6 6,857 -5,857 86,734 5,527 Algoritem za skaliranje, spremenjen nazaj v izvirnega, ker detekcije niso delovale.
7 3 3,857 10,714 5,717 Dodana vrstica, ki konča zanko FOR za detekcijo v metodi detectMultiScale() takrat, ko najdemo vsaj enega kandidata.
8 4,5 -1,5 18,75 5,199 Sprememba prej dodane vrstice, da prekine zanko FOR takrat, ko sta najdena vsaj dva kandidata.
9 6 -1,5 85,714 5,412 Sprememba prej dodane vrstice, da prekine zanko FOR takrat, ko je najdenih vsaj dvakrat toliko kandidatov, kot je vrednost parametra minNeighbors.
10 6,066 -0,66 85,714 5,524 Odstranitev klicev metode parallel_for_(), učinkovite metode smo preselili v zanko FOR v metodi detectSingleScale().
11 1 5,066 76,785 12,735 Pohitritev s spremembo parametrov.
46
Slika 5.4: Graf hitrosti za video.
Iz naših podatkov pa ne vidimo sprememb pri prikazovanju zaznanih objektov. Po
implementaciji pohitritev je bil prikaz pravokotnika okrog zaznanega obraza opazno
drugačen od prikazovanja pri izvirni implementaciji algoritma. Pravokotnik se je veliko bolj
premikal, tudi ko se obraz ni premikal. Ena od posledic spremembe skaliranja je bila
razlika pri prilagajanju pravokotnika, ko se obraz približuje kameri ali oddaljuje od kamere.
Druga posledica spremembe skaliranja je bila ta, da pravokotnik ni več zajemal celotnega
obraza. Občutljivost na premikanje obraza pa je bila podobna izvirni implementaciji
algoritma, ker je ločena nit delala na podobnem intervalu kot je interval pri naši
implementaciji. Še ena funkcionalnost, za katero nimamo statističnih testov, je zaznavanje
več obrazov. Po nekaj preizkusih s pomočjo slike, ki vsebuje več obrazov [59], smo
ugotovili, da je algoritem sicer še sposoben zaznati več obrazov, ampak je pri tem manj
uspešen. Veliko obrazov namreč izpusti, kar je posledica spremenjenega skalirnega
faktorja. Na sliki 5.5 vidimo zaznavanje več obrazov z izvirnim algoritmom, na sliki 5.6 pa
zaznavanje več obrazov z našo implementacijo algoritma. Vidimo lahko, da je prilagojen
algoritem slabši pri zaznavanju več obrazov, vidimo pa lahko tudi, kako naše pohitritve
povzročijo manjši pravokotnik okrog zaznanega obraza.
47
Slika 5.5: Zaznavanje več obrazov – izvirni algoritem.
Slika 5.6: Zaznavanje več obrazov – pospešeni algoritem.
V končni pohitritvi smo implementirali odstranitev večnitenja (glej vrstico 3 v tabeli 5.1),
spremembo skaliranja v metodi detectMultiScale() (glej vrstico 9 v tabeli 5.1) in
48
prilagoditve parametrov (glej vrstico 11 v tabeli 5.1). Dosegli smo pohitritev za 13 ms, pri
čemer je natančnost padla za 18 %.
49
6. Diskusija in zaključek
V diplomskem delu smo proučili enega od algoritmov za zaznavanje obrazov iz knjižnice
OpenCV. Ugotovili smo, da je algoritem že sam po sebi precej hiter in zmogljiv. Algoritem
smo implementirali v Android aplikacijo, ki je delovala na tablici z enojedrnim procesorjem
in 512 MB delovnega pomnilnika. Algoritem smo še prilagodili, ga pohitrili in testirali
časovno zahtevnost na video vzorcu.
Rezultati so pokazali, da moramo za vsako pohitritev nekaj žrtvovati; natančnost,
funkcionalnost ali estetiko. Glede na namen aplikacije, ki ta algoritem uporablja, se
moramo odločiti, ali je smiselno uporabljati posamezne pohitritve.
Po testih lahko vidimo, da je algoritem možno znatno pohitriti s tem, da ga prilagajamo za
naše ciljno okolje, in s tem, da nižamo natančnost v zameno za hitrost. Hkrati pa se
moramo zavedati teorema NFL (angl. no free lunch) [60], ki v grobem pravi, da če
optimiziramo en vidik algoritma, drugi trpijo. Tako smo za naše pohitritve plačali z
natančnostjo, ki je se je zmanjšala za 18 % z 92 % na 74 %. Hkrati je trpela estetika
prikaza, kjer je bil zaznani pravokotnik vidno manjši in se je počasneje prilagajal
približevanju obraza h kameri in oddaljevanju obraza od kamere. Poslabšalo se je tudi
zaznavanje več obrazov hkrati; še vedno smo lahko zaznavali več kot enega, ampak je
imel algoritem težave pri zaznavanju velikega števila obrazov v eni sliki.
Vprašati se moramo, ali sploh še imamo vse funkcionalnosti, ki jih zahteva naša
aplikacija. Algoritma na primer ne moremo več uporabljati za fotografiranje, ker pri večjih
skupinah ljudi ne bi deloval. Za biometriko ali klasifikacijo izrazov bi potrebovali nadaljnje
prilagoditve algoritma, kot je večanje višine in širine okvirja za 10 %, da bi okvir zajel
celoten obraz in ga podal v nadaljnjo analizo. Za analiziranje množic, na primer pri
marketinških aplikacijah, ki merijo odzive ljudi na izdelek ali oglas, je algoritem
neprimeren.
Pri implementaciji algoritma bi morali torej pretehtati pozitivne in negativne lastnosti
posameznih pohitritev in dobro premisliti, ali je sploh smiselno uporabljati algoritem za
zaznavanje obrazov na prenosni opremi s tako slabo zmogljivostjo.
Za nadaljnje delo bi bilo treba osnovati način za testiranje na vzorcu z več obrazi,
pregledati možnosti prilagoditve algoritma LBP in se poglobiti v učenje kaskade
klasifikatorjev z uporabo evolucije ali metaevolucije, kjer dajemo večji poudarek na hitrost
in manjši poudarek na natančnost algoritma.
50
Viri
[1] „Android (operating system),“ Dostopno na:
http://en.wikipedia.org/wiki/Android_(operating_system) [12. 9. 2014].
[2] „Which android runs which linux kernel,“ Android Stack Exchange, Dostopno na:
http://android.stackexchange.com/questions/51651/which-android-runs-which-linux-
kernel [12. 9. 2014].
[3] „Android Kernel Versions,“ Elinux, Dostopno na:
http://elinux.org/Android_Kernel_Versions [12. 9. 2014].
[4] „Open Binder,“ Wikipedia, Dostopno na: http://en.wikipedia.org/wiki/OpenBinder [12.
9. 2014].
[5] „Dalvik technical information,“ Android, Dostopno na:
https://source.android.com/devices/tech/dalvik/ [12. 9. 2014].
[6] „Dalvik (software),“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Dalvik_(software) [12. 9. 2014].
[7] „Arm architecture,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/ARM_architecture [12. 9. 2014].
[8] „About ARM holdings,“ ARM holdings, Dostopno na:
http://ir.arm.com/phoenix.zhtml?c=197211&p=irol-homeprofile [12. 9. 2014].
[9] „Prestigio Multipad 7.0 ultra+ specs,“ Prestigio, Dostopno na:
http://www.prestigio.com/catalogue/MultiPads/NEW_MultiPad_7.0_ULTRA_Plus_#/pr
oduct-specs?article=PMP3670B_WH [12. 9. 2014].
[10] „Cortex a8,“ ARM, Dostopno na: http://www.arm.com/products/processors/cortex-
a/cortex-a8.php [12. 9. 2014].
[11] „Java Native Interface,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Java_Native_Interface [12. 9. 2014].
[12] „JDK 6 Java Native Interface,“ Oracle, Dostopno na:
http://docs.oracle.com/javase/6/docs/technotes/guides/jni/ [12. 9. 2014].
[13] „Android NDK,“ Android, Dostopno na:
https://developer.android.com/tools/sdk/ndk/index.html [12. 9. 2014].
[14] „Power PC Embedded ApplicationBinary Interface,“ Freescale, Dostopno na:
http://www.freescale.com/files/32bit/doc/app_note/PPCEABI.pdf [12. 9. 2014].
51
[15] „Application binary interface,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Application_binary_interface [12. 9. 2014].
[16] „LogCat,“ Android, Dostopno na: http://developer.android.com/tools/help/logcat.html
[12. 9. 2014].
[17] „Cmake,“ Kitware, Dostopno na: http://www.cmake.org/ [15. 9. 2014].
[18] „OpenCV,“ OpenCV, Dostopno na: http://opencv.org/ [13. 9. 2014].
[19] „OpenCV,“ Wikipedia, Dostopno na: http://en.wikipedia.org/wiki/OpenCV [13. 9.
2014].
[20] „BSD licences,“ Wikipedia, Dostopno na: http://en.wikipedia.org/wiki/BSD_licenses
[15. 9. 2014].
[21] „Open CV About,“ Itseez, Dostopno na: http://opencv.org/about.html [15. 9. 2014].
[22] „EmguCV,“ Emgu, Dostopno na: http://www.emgu.com/wiki/index.php/Emgu_CV [15.
9. 2014].
[23] „JavaCV,“ GitHub, Dostopno na: https://github.com/bytedeco/javacv [15. 9. 2014].
[24] „MMX instruction set,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/MMX_%28instruction_set%29 [15. 9. 2014].
[25] „Streaming SIMD Extensions (SSE),“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions [15. 9. 2014].
[26] „Facedetection Techniques,“ Dostopno na:
http://www.facedetection.com/facedetection/techniques.htm [15. 9. 2014].
[27] „Cascading classifiers,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Cascading_classifiers [15. 9. 2014].
[28] „Viole-Jones object detect framework,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Viola%E2%80%93Jones_object_detection_framework
[15. 9. 2014].
[29] Dalal, N., Triggs, B. „Histograms of Oriented Gradients for Human Detection,“ INRIA
Rhone-Alps, ˆ 655 avenue de l’Europe, Montbonnot 38334, France, 2005 Dostopno
na: http://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf [15. 9. 2014].
[30] „OpenCV documentation - Cascade Classification,“ Itseez, Dostopno na:
http://docs.opencv.org/modules/objdetect/doc/cascade_classification.htm [15. 9.
2014].
52
[31] „OpenCV face recognition with Local Binary Patterns Histogram,“ Itseez, Dostopno
na: http://docs.opencv.org/modules/contrib/doc/facerec/facerec_tutorial.html#local-
binary-patterns-histograms [15. 9. 2014].
[32] „Haar wavelets,“ UCF, Dostopno na: http://www.cs.ucf.edu/~mali/haar/ [16. 9. 2014].
[33] „Haar wavelets Wikipedia,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Haar_wavelet [16. 9. 2014].
[34] „Object Detection Using Haar-like Features,“ Intel, Dostopno na:
https://software.intel.com/sites/products/documentation/hpc/ipp/ippi/ippi_ch14/ch14_o
bject_detection_using_haar_like_features.html [15. 9. 2014].
[35] „Object Detection Using Haar-like features,“ Intel, Dostopno na:
https://software.intel.com/en-us/node/504530 [28. 11. 2014].
[36] Viola, P., Jones, M. „Rapid Object Detection using a Boosted Cascade of Simple
Features,“ Accepted Conference On Computer Vision And Pattern Recognition, 2001
Dostopno na: https://www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-
01.pdf [16. 9. 2014].
[37] „Face Detection Using Haar cascades OpenCV Documentation,“ Itseez, Dostopno
na:
http://docs.opencv.org/trunk/doc/py_tutorials/py_objdetect/py_face_detection/py_face
_detection.html [28. 11. 2014].
[38] „GentleAdaBoost wiki,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/AdaBoost#Gentle_AdaBoost [16. 9. 2014].
[39] „Integral image,“ Wordpress, Dostopno na:
http://computersciencesource.wordpress.com/2010/09/03/computer-vision-the-
integral-image/ [16. 9. 2014].
[40] „Integral images for block matching,“ Ipol, Dostopno na:
http://www.ipol.im/pub/pre/57/preprint.pdf [16. 9. 2014].
[41] „Summed Area Table,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Summed_area_table [9. 12. 2014].
[42] „Gradient wikipedija,“ Wikipedia, Dostopno na: http://en.wikipedia.org/wiki/Gradient
[18. 9. 2014].
[43] „Histogram of Oriented Gradients,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Histogram_of_oriented_gradients [15. 9. 2014].
[44] Yu-Jin Zhang, Hui-Xing Jia, „Fast Human Detection by Boosting Histograms of
Oriented Gradients,“ Fourth International Conference on Image and Graphics, (2007),
53
str. 1–6.
[45] Deniz, O., Bueno, G., Salido, J., De la Torre, F. „Face recognition using Histograms
of Oriented Gradients,“ Pattern Recognition Letters, 32, (2011), str. 1–6.
[46] Dalal, N., Triggs, B. „Histograms of Oriented Gradients for Human Detection,“ INRIA
Rhone-Alps, ˆ 655 avenue de l’Europe, Montbonnot 38334, France, 2005 Dostopno
na: http://lear.inrialpes.fr/people/triggs/pubs/Dalal-cvpr05.pdf [18. 9. 2014].
[47] „HOG with ofxCv,“ Flickr, Dostopno na:
https://www.flickr.com/photos/unavoidablegrain/8123343395/ [28. 11. 2014].
[48] „Local Binary Patterns,“ Scholarpedia, Dostopno na:
http://www.scholarpedia.org/article/Local_Binary_Patterns [24. 9. 2014].
[49] „Local Binary Patterns Wikipedia,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/Local_binary_patterns [28. 11. 2014].
[50] „Face Recognition with OpenCV,“ Itseez, Dostopno na:
http://docs.opencv.org/modules/contrib/doc/facerec/facerec_tutorial.html#local-
binary-patterns-histograms [24. 9. 2014].
[51] Bangyou, D., Nong, S. „SPIE Optical Engineering Local binary pattern based face
recognition by estimation of facial distinctive information distribution,“ SPIE 2009,
Dostopno na:
http://opticalengineering.spiedigitallibrary.org/article.aspx?articleid=1089401 [28. 11.
2014].
[52] „Understanding OpenCV LBP implementation,“ Stack Overflow, Dostopno na:
http://stackoverflow.com/questions/22565531/understanding-opencv-lbp-
implementation [30. 9. 2014].
[53] „ALgorithm - Understanding OpenCV LBP Implementation,“ StackOverflow, Dostopno
na: http://stackoverflow.com/questions/22565531/understanding-opencv-lbp-
implementation [6. 10. 2014].
[54] „CascadeClassifier documentation: GroupRectangles,“ Itseez, Dostopno na:
http://docs.opencv.org/modules/objdetect/doc/cascade_classification.html#grouprecta
ngles [9. 10. 2014].
[55] „OpenCV VideoCapture documentation,“ Itseez, Dostopno na:
http://docs.opencv.org/java/org/opencv/highgui/VideoCapture.html [15. 10. 2014].
[56] „Android Media Metadata Retriever documentation,“ Itseez, Dostopno na:
http://developer.android.com/reference/android/media/MediaMetadataRetriever.html
[15. 10. 2014].
54
[57] „Android Media Extractor documentation,“ Itseez, Dostopno na:
http://developer.android.com/reference/android/media/MediaExtractor.html [15. 10.
2014].
[58] „FFMPEG Media Metadata Retriever,“ GitHub, Dostopno na:
https://github.com/wseemann/FFmpegMediaMetadataRetriever [15. 10. 2014].
[59] „What the average human face looks like in every country,“ Dostopno na:
http://nedhardy.com/2014/05/30/average-human-face-looks-like-every-country/ [15.
10. 2014].
[60] „No Free Lunch Theorem,“ Wikipedia, Dostopno na:
http://en.wikipedia.org/wiki/No_free_lunch_theorem [17. 10. 2014].
[61] „Face Recognition with Local Binary Patterns, Spatial Pyramid Histograms and Naive
Bayes Nearest Neighbor classification,“ Asoto, Dostopno na:
http://asoto.ing.puc.cl/papers/Maturana-09.pdf [24. 10. 2014].
[62] Yann, R. „Face Detection and Verification using Local Binary Patterns,“ Á La Faculté
Des Sciences Et Techniques De L’ingénieur, 2006, Dostopno na:
http://infoscience.epfl.ch/record/146275/files/rodrig-idiap-rr-06-79.pdf [24. 9. 2014].
[63] „Videocapture From File Always Returns null,“ StackOverflow, Dostopno na:
http://answers.opencv.org/question/30146/android-videocapture-from-video-file-
always-return/ [15. 10. 2014].
[64] „Image Processing And Computer Vision Group,“ Dostopno na: http://www-
2.dc.uba.ar/grupinv/imagenes/objectdetection.php [28. 11. 2014].
55
56
57