Áttörés (de nem a pofáján)

     Ezt a bejegyzést Brassai tanárúrnak dedikálom, aki ráébresztett arra, hogy egyedül is meg tudom csinálni és vissza tudom fejteni a megtanított neuronháló kódját egy olyan kóddá, amiből megírhatom a saját arcfelismerő algoritmusomat.

     Ez az egész bejegyzés ezt az arcfelismerő módszert fogja végigtárgyalni, egészen a tanítási folyamat kezdetétől a tanítást ellenőrző hisztogramig.

     Szükséges eszközök : MatlabR2010a meg egy képadatbázis. Az én képadatbázisom úgy néz ki, hogy 4533 arc, és 96781 nem arc. Pár bejegyzéssel korábban raktam fel képeket matlabból, ahol eloszlások (zöld-piros kis keresztek) illetve hisztogramok szerepelnek. Eddig összegyűlt 42 ilyen eloszlásom, meg hisztogramom. Ebből 10et használtam fel a mostani tanításhoz. Ezeknek elrendezését a "Neuronháló" bejegyzésben tárgyaltam részletesebben.

     Elindítjuk a Matlabot, s bepötyögjük, hogy nprtool, amire megjelenik a már jól ismert "Welcome to the Neural Network Pattern Recognition Tool." Itt nyomunk egy Next-et, majd az Inputs és Targets résznél beállítjuk a bemeneti és a kategóriaeldöntő változóinkat. Nálam ez a következőképpen nézett ki:


Nyomjuk a Next gombot, itt nagyon nincs amit állítani, a szokásos 70%, 15%, 15% tökéletesen megfelel bármilyen célnak. Itt egy újabb Next gombnyomásra megjelenik a legfontosabb beállítási lehetőség, vagyis, hogy hány rejtett réteg legyen a rendszerben. Az egyszerűség és könnyebb megértés kedvéért ezt 1-re állítjuk. (Nomeg azért is állítjuk egyre, mert egyelőre csak annak a működését értettem meg...)

      Itt határozhatjuk meg a neuronhálónk kinézetét. (Sajnos azt nem tudom, hogy ha valami bonyolultabb elrendezést akarunk, azt hogyan tudjuk elérni, viszont egyelőre elégedjünk meg azzal, hogy sok párhuzamosan kötött neuront be tudunk rakni, ha a program nem fut ki a memóriából...) Ugyancsak Next gomb megnyomása után a következő ablakot látjuk :

     Itt a pirossal bekeretezett Train gombra kattintva elindul a vonat, :P  vagyis elkezdődik a neuronháló súlyzóinak a behangolása a lehető legjobb osztályozási eredmény elérése érdekében. A tanítás a beadott paraméterek függvényében több-kevesebb ideig tarthat, nomeg a végeredmény is eltérhet az egyes tanítási ciklusok között. Ennek az az oka, hogy a súlyzókat a program véletlenszámokkal tölti fel, annak érdekében, hogy legyen olyan, aki kisebb eséllyel indul a tanulás során, illetve legyen olyan is, akinek nagyobbak az esélyei.

A tanulási folyamat során elért pontosságot is figyelemmel kisérhetjük, ha a Performance gombra kattintunk. Mivel nem voltam elég gyors, így csak a végét tudom megmutatni :

     Ezen a képen az látható, hogy hogyan változott a háló osztályozási pontossága a tanítási ciklusok alatt. Persze ez a 10^-7.en pontosság nekem sem elsőre jött össze... legtöbbször leragad a 10^-1 és 10^-2 között. Viszont az ideális bemeneti konfigurációval, illetve egy ideális kezdőértékkel nagyon szép eredményeket lehet elérni. A következő ábra, amit érdemes tárgyalni, az a találati pontossága ennek a hálónak:
A jobb alsó sarokban megjelenő egyik zöld négyzetben írja a 4533 arcot, a másikban az összes nem-arcot. Ez azt jelenti, hogy a találati arány 100 %, ami bizakodásra adhat okot. Most jön a nehezebb része, mégpedig a neuronháló megértése és visszafejtése.

     A legelső lépés a megértés felé a Simulink diagram kigenerálása, amit két Next gomb nyomás után meg is tehetünk a Generate Simulink Diagram gombra kattintva. Ha mindent jól csináltunk, akkor a következő képet látjuk a Simulink ablakban:
Itt a kék Neural Network négyzet rejti az igaz tudást, melynek bírtokában már Szarumán sem állíthat meg a vulkán felé vezető úton... :P
Ha az előző körben figyelmesek voltunk észrevehetjük, hogy egy hasonló ábrát már láttunk... Amikor beállítottuk, hogy hány rejtett réteg legyen a hálónkban. Ezt is tovább fogjuk boncolni. Legelső amit megnézünk, az az adatok előfeldolgozása. Ezt a legelső rózsaszín négyzetre kattintva tehetjük meg.
Itt a legelső kék négyzet azt mondja, hogy ő nem csinál semmit: "This processing function is not yet supported in Simulink. It currently returns its input without any change." vagyis ami a bemeneten, az a kimeneten. A középső kék négyzet "Removes rows from a vector in positions where the values are always constant." magyarul eltávolítja azokat a sorokat, ahol mindíg konstans értékek vannak, valószínűleg azért, mert az úgysem befolyásolná a tanítás eredményét. A harmadik pedig átlagolja a táblázatban szereplő értékeket -1 és 1 közé. Ezt pont nem tudom, hogy miért csinálja. Viszont azt tudom, hogy hogyan csinálja :

     megkeresi az első sorból a legnagyobb és legkisebb elemet, ezek lesznek az xmax és xmin. A -1 lesz az ymin, a +1 lesz az ymax. Tudjuk, hogy melyik értékünket akarjuk beállítani -1 és +1 közé, az lesz az X, a neki megfelelő kimeneti érték lesz az Y. A képlet pedig a következő:


y = (ymax-ymin)*(x-xmin)/(xmax-xmin) + ymin;

     Ezzel kiszámoljuk minden egyes bemeneti értékrea -1 és 1 közé eső átlagát. Ha azzal végeztünk, belépünk az első neuronháló rétegbe.
    Itt van egy Delay tagunk, amivel most nem foglalkozunk, majd egy weight, amiben vannak a behangolt súlyzóink.Ahhoz, hogy azokat a súlyzókat elérjük, duplaklikk a weight feliratra. Ez a belső felépítése:
Ez a bemenetére érkező tíz értéket skalárisan megszorozza a weights-ben lévő értékekkel, majd összeadja őket. A dotprod1 kimenetén a következő értéket fogjuk látni: sum(pd.*weights). Az én súlyzóim a következők:

[-1.9841331421273383;-1.3585126958692031;
-1.4351172518964324;-1.4932715678081847;
0.015225782582406353;7.1424511152721832;
6.8843449087072308;0.89608401038087471;
0.56418414319632859;0.24452191060179679]

Pontosan 10 darab majdnem tökéletesen behangolt súlyzó. Ha ezzel a szorzás-összeadás kombóval megvagyunk, akkor a következő lépés a Bias, vagy magyarul eltolás hozzáadása ehhez a sum(pd.*weights) értékhez. Az én Bias értékem = -0.12710590228495711.

Ezt az összeadást fogja elvégezni a netsum nevű kicsi kék négyzet, aminek a kimenete egy csúnya tangens hiperbolikusz szigmoid átviteli függvénynek a bemenetére van kötve. Ennek az a lényege, mint a komparátornak, vagyis egy bizonyos értéknél kapcsol, viszont itt a kapcsolás elég lágy. Egy kis programrészlettel szemléltetném a függvény kinézetét :

n = -5:0.1:5;
a = tansig(n);
plot(n,a) 
%a = tansig(n) = 2./(1+exp(-2.*n))-1 

A tangens-szigmoidnak az átviteli függvénye, fel van tüntetve a forráskódban, a kikommentált részben. A tangent-szigmoidnak a kimenetét nevezzük el az egyszerűség kedvéért "a"-nak. Ez az a lesz az első neuronháló rétegnek a kimenete, és ez szolgálja a bemenetet a második neuronháló rétegnek, a Layer2-nek.
A Layer2 láttán már meg sem ijedünk, mert majdnem ugyanúgy néz ki, mint a Layer1. Egyetlen lényeges különbség ott van, hogy a weight-re kattintva egy dupla súlyzós izét látunk. Franc essen bele... próbálok szépen fogalmazni, de még csak azt sem tudom, hogy hívják ezeket...
Itt a felső súlyzó értéke 5.9364589656690185, az alsó súlyzó értéke pedig -8.7710742682750453. Azért van itt két súlyzónk, mert két kimeneti kategóriánk van. A végigvitt példánál megtárgyaljuk a két kimeneti eredményt. Lényeg, hogy az "a" változónkat megszorozzuk mindkét számmal, ebből lesz az "a[1]" és az "a[2]". Ez lesz a weight kimenete, illetve a netsum egyik bemenete. A másik bemenete a már korábban megismert Bias, csak más értékekkel. A jelenlegi értékeink a következők : [-0.51040076648123345;1.4045288690576592] Itt is két értékünk van, mert a két kategóriába kell besorolni a bejövő adatot. A netsumnak a kimenete a tansig() bemenete lesz, vagyis ezt az értéket megint hasonlítjuk egy kapcsolási küszöbszinthez.

      Ennek a tansig()-nek a kimenete megy a "posztprocesszálló" blokkba, ahol visszalakítják az eredeti értékére (mert mint tudjuk, behangoltuk -1 és +1 közé).

Itt már megkapjuk, hogy melyik arc illetve melyik nem arc. A lényeg, hogy amelyik oldalon van az 1-es ahhoz a csoporthoz tartozik az illető kép. Nálam ez úgy nézett ki, hogy az arcokat az [1 0] transzponált vektor kategorizálta, a nem arcokat pedig a [0 1]. Ennek megfelelően, ha a tansig kimenetén azt az eredmenyt kapjuk, hogy

eredmeny =

    0.9990
   -0.9999

akkor azt mondhatjuk, hogy az egy arc. Ha azt az eredményt kapjuk, hogy

eredmeny =

   -0.9999
    1.0000

vagyis felül van a -1 és alul a +1, akkor az nem arc.

clear all
close all
clc

load neural_net.mat %itt vannak tarolva a kepek
for j=4500:4600 %4533-ig vannak az arcaim
            bemenet=zeros(10,1);
            bemenet = f_and_nf_proba(:,j); %beallitjuk a bemenetet
            %minimum es maximum ertekek meghatarozasa
            ymax=1;
            ymin=-1;
            xmin=zeros(10,1);
            for i=1:10
            xmin(i,1)=min(f_and_nf_proba(i,:));
            end
            xmax=zeros(10,1);
            for i=1:10
            xmax(i,1)=max(f_and_nf_proba(i,:));
            end
            y=zeros(10,1);%ide kerulnek a normalizalt bemeneti ertekek
            for i=1:10
            y(i,1) = (ymax-ymin)*(bemenet(i,1)-xmin(i,1))/(xmax(i,1)-xmin(i,1)) + ymin;
            end
            L1_w1=[-1.9841331421273383;-1.3585126958692031;-1.4351172518964324;-1.4932715678081847;0.015225782582406353;7.1424511152721832;6.8843449087072308;0.89608401038087471;0.56418414319632859;0.24452191060179679];
            %L1_w1 tartalmazza a Layer1-ben levo sulyzo ertekeit.
            L1_w1_ki=sum(L1_w1.*y);%ehhez hozzaadjuk a sulyzot
            eltolas_1=-0.12710590228495711;
            netsum_1_ki=eltolas_1+L1_w1_ki;%ez megy be a tangens hiperbolikuszba
            tansig_kimenet = tansig(netsum_1_ki);
            %ezt a tansig_kimenet-et neveztuk mi "a"-nak az egyszeruseg vegett
            %ez a tansig_kimenet megy be a Layer2-be. 
            sulyzo_2_1=5.9364589656690185;
            sulyzo_2_2=-8.7710742682750453;
            %ez a sulyzo_2_* a layer2-es ket sulyzoja
            arc_e=zeros(2,1);
            arc_e(1)=tansig_kimenet*sulyzo_2_1;
            arc_e(2)=tansig_kimenet*sulyzo_2_2;
            %ezt meg el kell toljuk a Layer2-Bias ertekevel
            eltolas_2=[-0.51040076648123345;1.4045288690576592];
            majdnemvege = arc_e+eltolas_2;
            eredmeny = tansig(majdnemvege);
            j
            eredmeny
            if(eredmeny(1)>0)display('Ez arc.');end
            if(eredmeny(2)>0)display('Ez nem arc.');end
end

     Remélem elég világos voltam, illetve azt is remélem, hogy a leírásban nincsenek szarvashibák. Ha mégis, akkor kell írni a szerkesztőség címére, s kijavítjuk.

     Ennyi mára.

Újra itt

     Egészségügy állapotomra hivatkozva volt egy pár nap kimaradás, sajnos. Viszont most azon vagyok, hogy bepótoljam. A mai téma a soros kommunikáció lesz. Persze nem az a Wikipediás , hanem egy kicsit hardverközelibb.

     Korábban irtam volt a wireless modulról, illetve a kameráról is. Hardveres témában az volt a következő a "to do" listámon, hogy az FPGA bordomat bővítsem mégegy RS232-es egységgel, hogy mindkettővel tudjak valós időben kommunikálni. (Persze a tesztelési fázis alatt az is elég, ha az egyik soros eszközön leolvasott adatokat el tudom küldeni a másik soros eszközönk keresztűl a számítógépnek...)

    A következő konfigurációból indultunk ki. Adott egy Nexys 2-s fejlesztőpanel:

Ezen felül rendelkezünk még egy RS232-es PMOD-al is,


valamint rendelkezésünkre áll a Xilinx SDK fejlesztőkörnyezet (újabb harminc napos licenszel). Kell még továbbá két darab soros átalakító kábel (mert a laptopon nincs soros bemenet). Egyiket egy rossz egérrel cseréltem, a másik jött a WiFly dobozban. Ha ezek mind megvannak, akkor már a nehezén túljutottunk.

     Most jön a kódolás. Létrehozunk egy új projektet, beállítjuk a fejlesztőpanelt, kiválasztjuk a perifériákat (itt az egyik soros eszközt, ami a fejlesztőpanelen rajta van, azt be is tudjuk rakni -automatice-). Ha elkészült a projekt, kigeneráljuk a download.bit-et, hogy lássuk, biztosan működik-e. (Nem egyszer szívtam meg, hogy már ez sem működött. Persze a saját kárán tanul az ember...) Ha működik, akkor hozzáadunk az IP Catalog-ból egy XPS Uart (Lite) IP-t,


hozzákötjük a Master PLB buszhoz, beállítjuk a lefoglalható memóriaterületet (64Kb), megadunk neki egy stílusos nevet (az én esetemben ez "RS232_SECOND" lett) és a portoknál beállítjuk, hogy az RX és TX külső elérésü portok lesznek. Ha ezt jól csináltuk, az External Ports alatt a következő látvány tárul a szemünk elé :

Persze a piros nyilacska nem lesz ott :P . Ezután a következő lépés a system.ucf-be beírni, hogy ezek a külsőnek deklarált portok a valóságban hová is fognak mutatni. Ez már elég trükkös, mert sok helyről kell összenézni az adatokat (nomeg kell ide a csillagok együttállása meg egy 38 jegyű prímszám is ahhoz, hogy működjön...). Nálam ez így nézett ki:

Itt az M14-M16 kombinációt eltalálni majdnem olyan nehéz, mint az ötöslottó. Ezután kiegészítjük a programunkat a következő sorokkal :


//(...)
#include "xuartlite.h"

//====================================================

#define BAUDRATE 9600
#define USE_PARITY 0
#define PARITY_ODD 1
#define DATABITS 8

//====================================================

int main (void) {
//(...)

unsigned int i;

char hello[12]="Masik soros.";

XUartLite Second_Rs;

XUartLite_Config UART_Configuration;
UART_Configuration.DeviceId = XPAR_RS232_SECOND_DEVICE_ID;
UART_Configuration.RegBaseAddr = XPAR_RS232_SECOND_BASEADDR;
UART_Configuration.BaudRate = BAUDRATE;
UART_Configuration.UseParity = USE_PARITY;
UART_Configuration.ParityOdd = PARITY_ODD;
UART_Configuration.DataBits = DATABITS; 

 //UART init:
XUartLite_CfgInitialize (&Second_Rs, &UART_Configuration, XPAR_RS232_SECOND_BASEADDR) ;


   xil_printf("-- Egyik soros port --\r\n");
    
    while(XUartLite_IsSending(&Second_Rs));
    
    i = XUartLite_Send(&Second_Rs, (Xuint8*)(hello), 12);

   return 0;
}

     Persze a kód nem teljes, csak a fontosabb kiegészítéseket másoltam ide be. S ha minden jól működött és szerencsénk volt (és a megfelelő 38 jegyű prímszámot választottuk), akkor az egyik soros porton megjelenik az
"-- Egyik soros port --"
másikon pedig a

"Masik soros."

szöveg. Ha valamit nem jól csináltunk és épp nem a mi munkánkkal vannak elfoglalva az Istenek, akkor még azt is meg kell nézni, hogy hogyan állnak a jumperek a PMod-on. A Pmod-ról annyit kell még tudni, hogy kétféleképpen lehet konfigurálni. Az egyik működési forma a háromszálas (RXD, TXD, GND), a másik az ötszálas (CTS, RTS, RXD, TXD, GND). Mi az előbbit részesítjük előnyben. Emiatt az egyes jumper nyitott állapotban, a kettes pedig zárt állapotban kell legyen, hogy a működés esélyeit növeljük.

(A képeket köszönjük a Digilentnek. Sajnos az én fényképezőgépemmel nem sikerült ilyen szépet készíteni...)

     Röviden ennyi volt a mai teljesítmény. Pénteken felmegyek az egyetemre, s ha a rektorúr ott lesz, utánakérdezek a neuronháló tanításnak.

Neuronháló

     Na, amíg a Matlab végzi a dolgát, leírom, hogy mire jutottam eddig... Hogyan fognánk neki, hogy megtanítsunk a számítógépnek valamit? Ez a nagy kérdés...
  1. Collect data.
  2. Create the network.
  3. Configure the network.
  4. Initialize the weights and biases.
  5. Train the network.
  6. Validate the network.
  7. Use the network.

     Nulladik lépés összegyüjteni az adatokat... Vegyünk egy fiktív példát. Azt szeretnénk, hogy a számítógép meg tudja különböztetni a gonosz embereket a jó emberektől... (ez egy igen hasznos tulajdonság... :P ) Meg kell határozni pár tulajdonságot, ami mérhető is, illetve amik alapján a számítógép majd dönteni tud.

     A tulajdonságok legyenek a következők : morcos nézés (mennyire néz morcosan? 1 - nagyonnagyon morcosan néz, 0 - aranyos kiskutya nézés), testtartás (1 - "pofánverlek" támadó pozíció, 0 - "állva elalszom" pozíció), fegyverzet (1 - boxer, nuncsaku, rakétavető, kevlár) (közben befejeződött az én tanítási ciklusom is... 10^-6.-on pontosság, vagyis hamis találati arány 1 az 1000000-ból...) (illetve 0 - ha csak egy fehér zászló van nála), szemöldöktávolság (köztudott, hogy a gonosz embereknek összenő a szemöldökük :P vagyis 1 - a teljesen összenőtt sövény, 0 - normális másfél ujjnyi távolság...

Van tíz emberünk. Az első öt jó ember, az utolsó öt pedig rossz ember... Mind a tíznek megvan a tulajdonságtáblázata, ami a következőképpen néz ki :


 A hovatartozás azért szükséges, hogy mi előre megmondjuk, hogy melyik tulajdonságkombináció milyen eredményhez vezet... Nagy vonalakban meg lehet figyelni bizonyos szabályosságot, viszont nem minden esetben egyértelmű... A tanítás arról szól, hogy a neuronháló sulyzóit behangolja a tanítóadatoknak megfelelően... Mert ebben a példában is látszik, hogy a szemöldöktávolság valószínűleg nem olyan mérvadó (nem annyira súlyos), mint például a fegyverzet... Mert ha valaki egy duplacsövűt nyom az arcodba, annak lehet akármilyen


nézése, és akármilyen szedett szemöldöke, attól még nem fogjuk szeretni...

      Komolyra fordítva a szót... A következő lépés az adatok bevitele matlabba... Ennek a részleteire most nem térnék ki... (közben kis szünet, összeszedem a házat, mert lehet, hogy este átjönnek Szabiék...) Miután ezzel megvagyunk, elmentjük mind a tulajdonságokat, mind a hovatartozást leíró táblázatot, egy mat fájlba. Az adatok elrendezésénél fontos arra vigyázni, hogy a bemeneti adatoknál annyi sor legyen, ahány tulajdonság van vizsgálva és annyi oszlop, ahány szereplője van a történetnek... A hovatartozási táblázat meg lehet ennél sokkal bonyolultabb is... (Ilyen jellegű információért lásd a kortárs szakirodalmat :P).


     Következő lépés bepötyögni a matlabba, hogy "nprtool" . Erre megjelenik egy grafikus felület, ahol kiválaszthatjuk, hogy melyik a bemeneti állományunk illetve melyik a hovatartozási táblázat... Kell párszor nyomkodni a next-et, majd figyelni, ahogy a matlab megtanítja a neuronhálónak, hogy melyik rossz ember és melyik nem... A végén meg lehet nézni, hogy mennyire volt hatékony a tanítás...

Ebből a rajzból egyedül a jobb alsó sarokban lévő eredményt emelném ki, miszerint a tanítás végső eredménye 97.9%os hatékonyság. Ennek a részleteire most nem térek ki... (S ahogy azt Bea mondaná ---> Szerintem ez elég korrekt... )

Ugyanitt lehetőségünk van kirajzolni pár (számomra még nem érthető) grafikont, illetve a későbbiek folyamán elmenteni a tanítás eredményét... Röviden ennyire jutottam... Mindenképp még foglalkozom a témával, mert ez a kezdeti siker nagyon felcsigázta az érdeklődésemet...

Szombat

     Nem valami termékeny a mai nap... Olvasgattam egy kicsit a neuronháló tanításról... Azt kell mondjam, nem is olyan egyszerű... Van pár dolog, amit még nem értek... Gondoltam felmegyek az egyetemre, hátha ott találom Márton László tanárurat, de aztán eszembejutott, hogy ma biztos nincs ott. Ma lett volna a második vizsgaidőpontunk, s mondta, hogy nem fog tudni eljönni... Így magamra maradtam...

    Várom, hogy lejöjjön a Xilinx ISE 12.4, mert az én verzióm (a 12.2) elég sok hibát tartalmaz... Remélem a nagyrészét legalább kijavították ebben a verzióban... Nomeg azért is, mert a licenszem lejárt... Remélem, hogy kapok egyet a Digilenttől... Egy életreszóló, minden verzióra érvényes licenszet... Hogy ne kelljen többet a 30 napos licensz után kuncsorogjak... Vagy igazán nyomhatnának egy Student Licens-et.

    Teszek új matlabot... 2010a. verziószámú... Mert az enyémből hiányzik az, amire leírást találtam interneten... Így plusz két óra az életemből fel és leinstallálni a programot... Remélem még a mai nap folyamán lejön a Xilinx ISE is, mert este meg akarom csinálni a dupla-soros kommunikációt...

     FPGA... hogy szeretem... Még régen (valamikor tavaly májusban) készítettem egy programot, amivel lehetett az FPGA egyes részeit irányítani... pl PWM kitöltés a ledekre, szám kiíratás a hétszegmenses kijelzőre, ilyesmik... Aztán nem fejeztem be... viszont emlékszem, nagyon jól szórakoztam volt vele... Elég bonyolult volt ahhoz, hogy ébrentartsa a figyelmemet...



     Time Left : 4 hours 30 minutes 10 seconds...

     Mit fogok én addig csinálni?
   

WiFly

      Na ez gyorsan ment... Kiderült, hogy a wireless modul is működik... (csak áram alá kell helyezni... az elején elfelejtettem rácsatlakoztatni a 9 voltos elemet, s semmiképp nem akart megindulni... ) Konfigurációja irtó egyszerű, és tényleg működik... Meg vagyok áldva ezekkel a könnyen működő soros kommunikációs cuccokkal...




     Kicsit részletesebben: Soros kábel, WiFly GSX, 9 voltos elem, null modem és az a szürke kábel... Nagyjából ennyi a hozzávaló... Amikor az elemet csatlakoztatjuk kigyúl a kék led, (de még milyen szép kék led ;;) ... ha ezt Zsombi látná :P ) jelezvén, hogy megkapja a szükséges áram-feszültség kombinációt. Jumpert a második pozícióba helyezni (még mielőtt ráadjuk az elemet... Azért második pozíció, mert az első az AD-HOC...) majd a konzol ablakba beírni:
$$$
Erre a modul átlép Command üzemmódba. Ebben a módban beállíthatunk pár működési paramétert... Többek között itt kérhetjük le a látható hálózatok listáját, itt állíthatjuk be a kapcsolódási jelszavat, illetve itt mondhatjuk meg, hogy melyik hálózathoz csatlakozzon. (Lásd "rn-131g-eval-um-v2.pdf")

     Miután a kis "divájsz" (:D úgy tetszik ez a szó... divájsz... Tisztára, mintha magyarul lenne...) csatlakozott a neki kijelölt hálózathoz, el lehet menteni a csatlakozási paramétereket, hogy legközelebb ne kelljen beállítani, hanem automatikusan csatlakozzon hozzá... Ezt egy egyszerű save parancs bepötyögésével tudjuk elérni...

WiFly Ver 2.20, 05-25-2010
MAC Addr=00:12:b8:00:86:b9
Auto-Assoc kemenynet chan=5 mode=MIXED SCAN OK
Joining kemenynet now..
*READY*
Associated!
DHCP: Start
Associated!
DHCP: Start
DHCP in 2982ms, lease=604800s
IF=UP
DHCP=ON
IP=192.168.0.172:2000
NM=255.255.255.0
GW=192.168.0.1
Listen on 2000

     Ha ezzel megvagyunk, akkor következhet a tényleges próba... Elindítottam a TelNet alkalmazást, ami a soros átalakító kábellel együtt érkezett, bepötyögtem a paramétereket, majd nyomtam neki egy OK-t.



      A választ már a WiFly modul küldte:

*HELLO*

Ezután bármi, amit beírtam a telnet oldalon, megjelent a soros port hallgató programban, illetve bármi, amit a soros oldalon pötyögtem be, a telnet oldalon láttam viszont... Innen a lehetséges felhasználási lehetőségeket mindenkinek a fantáziájára bízom... Lényeg, hogy kicsi és egyszerű... (és lassú... 9800 bit/sec sebességgel lehet küldeni neki az adatokat... Remélem ezen még lehet "hangolni", különben grrrr...)

     Már csak egy dolgom maradt hátra, mégpedig megírni a soros kommunikációs felületet mind a kamerának, mind a wireless modulnak, hogy tudjak velük játszani... Nomeg a kihagyhatatlan Felhasználói Kézikönyvek (avagy magyar nevén Júzer Manuál...) átböngészése, hátha tudnak még meglepetést okozni...

     Ennyi mára... Lehet később még Matlabozok kicsit, hogy haladjon az a része is...

LinkSprite

       Ma került sor rá, hogy kipróbáljam a kamerát... LinkSprite JPEG Color Camera - ez a teljes neve... Leírást róla elég sok helyen lehet találni. Viszont berakok egy képet, mert nagyon bejön a formája...


     Volt egy tesztprogram az interneten a leírás mellett, amivel ki lehetett próbálni, hogy az összeszerelt változat működik, vagy sem... Ezzel indítottam meg én is a kis kamerát a projekt fele vezető úton. Az összeszereléséről csak annyit, hogy négy szálat kellett hozzácinezni a kamerához : RXD, TXD, Föld és Táp... Az RXD-TXD kábelek másik végét pedig hozzácineztem az USB - soros átalakítókábelem végéhez. És már működik is... Beüzemelési idő megközelítőleg 15 perc... Előkészületek : 10 perc... Egész jó...



      Az egyetlen gond vele, hogy rettenetesen lassú... Szerintem, ha eléri az 1 (azaz EGY) frame/sec sebességet, akkor sokat mondtam... (ezt 320x240-es képméret mellett... 640x480-as képmérettel kell két-három másodperc, amíg sorosan felküldi az adatot... s ezen sokat a soros kommunikáció sebessége sem változtat...) Tartok tőle, hogy az arc-követés egy picit akadozni fog... Max a szervómozgatást kicsit lassabbra állítom, hogy kompenzáljam a kamera sebességét...

     S most fogok neki a wireless modulnak...

Szűrőfa

     Kicsi photoshop lefekvés előtt : sok sok új szűrő...


A harmadik kép csak egy kis puska a Matlabban a szűrőimplementáláshoz... Ennyi mára...

Hajónapló

      2011 Január 20. Csillagidő 18:33. Az igazság az, hogy semmi értelmesről nem tudok beszámolni. Ez a mai nap eléggé eltékozolt nap volt... Délelőtt lustálkodtam, délben összeírtam pár matlabos szűrőt, leteszteltem őket, (sajnos elszomorító eredményekkel), délután lustálkodtam...

      Ami még talán fontos lehet, hogy egyre inkább hajlok arrafele, hogy matlabban tanított neuronháló osztályozza a képrészleteket, mivel az én eredeti ötletem, miszerint az arcok "összetartóbbak" lesznek, mint a nem arcok, téves feltételezésnek bizonyult... Kicsit olvasgattam délután, többnyire már kész projektek dokumentációit és reménykedem, hogy megvilágosodok nemsokára...

     Tegnap volt egy levélváltásom Digilent nénivel (Monica Bot), ő az egyetlen, akivel azt mondhatom, hogy -tartom a kapcsolatot- ... Persze ez távol áll a valóságtól. Levelezés röviden arról szólt, hogy mire kell nekem a táp... Azzal indokoltam, hogy a szervók áramfelvétele meghaladja az USB port által nyújtható maximális értéket (vagyis az 500 mA-t)... S hogy az elbirálásommal mi a helyzet, azt még nem tudjuk... Én még bizakodom... (ösztönösen... mert okot nem igazán adnak a bizakodásra... semmi levél, semmi sms...)

     Berakok két képet, a két legutóbbi eredményemről... Rájöttem, hogy a képre kattintva megjelenik nagyban, s el lehet olvasni a rajta lévő szöveget is... (nem mintha bárkit is érdekelne...) Most, hogy egyre többet szemezek ezzel a képpel, kezdek rájönni valamire...


        hold on;
        plot(notface_second_feature,notface_first_feature,'g+');        
        plot(second_feature,first_feature,'r+');

Ezt a két sort szeretném kiemelni az egész programból. Ez a két sor rajzolja ki a kicsi piros és zöld + jeleket az ábrára.

     Hmmm... Az emberi hülyeség határtalan... Ott írja gyönyörű betűkkel, hogy a piros az arc, a zöld meg nem arc... Hogy lehet egy ilyet elnézni??? Waaaaaa... Komolyan... Ez már igazán nagy suttyóság volt a részemről... (Persze a fatális félrenézésemhez kiemelnék mégegy képrészletet, ami a kép jobb felső sarkában helyezkedik el és így néz ki :  )

Ez az egyetlen rész, ami el tudott bizonytalanítani minden egyes alkalommal, mikor megnéztem egy ilyen "statisztikai eloszlást"... Mindíg és mindíg az volt az érzésem, hogy a pirosak sokkal de sokkal strukturáltabbak, annak ellenére, hogy azok képviselik a háttérzajt... Erről a hibáról rántotta le a leplet az a két sor, amit fennebb bemásoltam... Ott van benne a megoldás kulcsa, mégpedig a notface előtag illetve a '+g' utótag. Vagyis az, ami zöld színnel van kirajzolva, az épp a háttérzaj...

       Mesterséges intelligencia törölve. Marad minden az eredeti terv szerint... Jöjjenek a képek:


     Ennyi mára. Legyetek jók.

U.I.: A kis cimkét a jobb felső sarokban még mindíg nem cseréltem ki... :-S

Matlab 2

      Mai statisztika : 4533 arc, 96781 nem arc... Az összesből integrálkép számolva, illetve lefuttatva egy statisztika, ami kissé el is szomorított... A "zaj" eloszlása ugyanaz, mint az arcok eloszlása... Remélem a többi szűrőnél más lesz, különben valami új öltet után kell nézzek vagy a csillagok együttállásában kell bízzak...

     Erről a csillagok együttállásáról úgy emlékszem, hogy írtam már pár sort, de ha mégsem, akkor itt felelevenítem, miről is van szó... Van nekem kb 110 darab "szűrőm", amivel megvizsgálom, milyen arányban állnak a képen belül az egyes pixelek bizonyos tartományokon belül... Ezzel akarok én egy statisztikát felállítani, hogy nagy általánosságban hol helyezkednek el az arcok a nem arcokhoz képest... S miért csillagok együttállása? Mert nem csak egy feltételnek kell eleget tegyen a kép, hanem az összes feltételt kell teljesítse ahhoz, hogy arcnak legyen nyilvánítva...

     És íme a ma délutáni eredmény :

Ezen a képen a vízszintes és függőleges szimmetria tulajdonságokat kisérhetjük figyelemmel. Látható (elméletileg), hogy a piros pöttyök (vagyis az arcok) szimmetrikusabbak, mint a zöld pöttyök... Persze ez csak a látszat... Ha megfigyeljük az eloszlásgörbét (hisztogram - szakszóval élve), akkor láthatjuk, hogy a véletlen képek sokkal szimmetrikusabbak, mint az arcok...
 
Eloszlásgörbe. Felül bal oldalon az arcok függőleges szimmetriájának az eloszlása látható (vagyis, hogy az arcnak a két oldalán lévő pixelek értéke mennyire egyforma). Felül jobbra a vízszintes szimmetria kísérhető figyelemmel... Ez azt jelenti, hogy a sötét és világos pixelek milyen arányban állnak egymással.
Alul bal oldalon a véletlenszerűen kivágott képek függőleges szimmetriatulajdonásgait figyelhetjük meg. Ha kicsit jobban megnézzük a képet, (amit a nagyon gyenge minősége miatt szerintem a neten majd nem lehet látni) észrevehetjük, hogy kb 98 százaléka a véletlenszerű képeknek -0.1 és 0.1 közé esik... Ez nem olyan jó hír, mert az arcok is ezen a határon belül vannak, ergó ezzel a feltétellel szinte semmit nem fogunk kizárni...

Matlab forráskód integrálkép számítására (amit én használtam) :

clear all
close all
clc

notface4=zeros(24,18,24853);
for i=100001:124853
    i
    img = imread(int2str(i));
    intImage = cumsum(cumsum(double(img)),2);
    notface4(:,:,i-100000)=intImage;
end

savefile = 'notface4.mat';
save(savefile, 'notface4')
     Ez a rész épp a nem-arc adatbázisomat töltötte fel vagy 24ezer képpel... Ugyanígy számítottam az arcokból is az integrálképet... Egész gyors... Talán gyorsabb, mint a C... mint az OpenCV... Legalábbis megírni gyorsabb volt... Statisztikai számításokra meg szinte tökéletes... (csak az a változódeklaráció idegesít időnként... meg a mátrixméret kompatibilitási problémák)

Matlab forráskód statisztikai számításokra:

clear all;
close all;
clc;

load 'all.mat';
load 'nf.mat';
        first_feature = zeros(1,4533);
            first_feature(1,:)=(fi(24,9,:)-(fi(24,18,:)-fi(24,9,:)))/110160;
            min(first_feature)
            max(first_feature)
        second_feature = zeros(1,4533);
            second_feature(1,:)=(fi(12,18,:)-(fi(24,18,:)-fi(12,18,:)))/110160;
            min(second_feature)
            max(second_feature)
        notface_first_feature = zeros(1,96781);
            notface_first_feature(1,:)=(nfi(24,9,:)-(nfi(24,18,:)-nfi(24,9,:)))/110160;
        notface_second_feature = zeros(1,96781);
            notface_second_feature(1,:)=(nfi(12,18,:)-(nfi(24,18,:)-nfi(12,18,:)))/110160;
        %scatter(first_feature,second_feature);   %szoras kirajzolasa
        x=sum(second_feature(1,1:4533))/4533;    %atlagertek 
        hold on;
        plot(notface_second_feature,notface_first_feature,'g+');        
        plot(second_feature,first_feature,'r+');
        
        legend('4533 Face','96781 NonFace');
        
        xlabel('Fuggoleges szimmetria (-0.5<f<0.5)');
        ylabel('Vizszintes szimmetria (-0.5<f<0.5)');
        figure(2);
        subplot(221);
        hist(first_feature,100);
        title('Arcok hisztogramja. Elso kriterium');
        subplot(222);
        hist(second_feature,100);
        title('Arcok hisztogramja. Masodik kriterium');
        subplot(223);
        hist(notface_first_feature,100);
        title('NemArcok hisztogramja. Elso kriterium');
        subplot(224);
        hist(notface_second_feature,100);
        title('NemArcok hisztogramja. Masodik kriterium');  
     Részletekért ajánlom Viola-Jones cikkét...


     Holnap fogok neki, s bepötyögöm mind a 115 szűrőt, amit Chesnokov Yuriy volt olyan kedves és megosztott velem... Ez is elég négermunka, viszont ennek már szemmellátható eredménye van... Mindenik szűrő után nézhetek egy hisztogramot és egy eloszlási képet... Remélem vezet valahová...

     Digilent-től még semmi válasz... Kezdek türelmetlen lenni... Még ezen a héten eljátszadozom ezzel a képfelismeréssel, aztán jövőhéttől fogok neki hardware-t programozni... Kipróbálom a kamerát és a wireless modult... Na az lesz az igazi meccs... Mert azt debugolni nem lehet...

Non-Face Image

     Ahhoz, hogy az arcfelismerés helyesen működjön, szükség van sok sok nem arcot tartalmazó képre is. Gondoltam nem teszem tönkre magam ezzel, megírok egy kicsi programot, ami paraméternek megkap egy nagyon nagy képet, amiből kivág magának a lehető legtöbb 18*24-es képet... Egyszerű program...

#include <cv.h>
#include <highgui.h>
#include <iostream>
using namespace std;
// ch3_ex3_12 image_name x y width height add# 
int main()
{

    IplImage* src;
    src=cvLoadImage("D:\\img.jpg",0);
    IplImage *img2 = cvCreateImage(cvSize(18,24),8,1);
        int x = 0;
        int y = 0;
        int width = 18;
        int height = 24;
        int nev=100000;
        char buffer[33];
        for(x=0;x<(src->width-20);x++)
            for(y=0;y<(src->height-30);y++)
            {
                cvSetImageROI(src, cvRect(x,y,width,height));        
                cvCopyImage(src,img2);
                cvResetImageROI(src);
                itoa (nev,buffer,10);
                buffer[6]='.';
                buffer[7]='j';
                buffer[8]='p';
                buffer[9]='g';
                buffer[10]='\0';
                cvSaveImage((const char *)buffer,img2);
                nev++;
                printf("X = %i\tY = %i\n",x,y);
            }
      cvWaitKey();
  cvReleaseImage( &src );
  return 0;
} 


      Kerestem neten egy 35ooX24oo-as képet, (amiből durván 8 millió kép kijön ezzel a módszerrel) s ráugrasztottam a programot... Persze előzetes számítások nélkül... Gondoltam lesz 5-6 ezer kép belőle, s milyen boldog leszek... És kimentem ebédelni... Mikor végeztem, bementem, megnézzem hogy halad a progi... Látom, hogy hoppá, már 35O ezer képet ki is vágott... Most meg azon dolgozom, hogy azt a 35o ezer képet le tudjam törölni a gépről... Windows explorer mindíg lefagy, mikor belemegyek a mappába... Total Commander szintén... Most azon vagyok, hogy törlöm a gyökérkönyvtárat TC-ben... Elindítottam a törlést, s nekifogtam megírni ezt a bejegyzést... S azóta, amióta írom, megvan a törlésnek 3o%-a... Halad...

    Valami jobb módszert kell kitaláljak képszerzésre... Csak már végezne a törléssel...

Matlab

      Nem bírtam nekifogni a programnak, annyira izgatta a fantáziámat ez a sok sok arc... Kiváncsi voltam, hogy mi jön ki belőle... Milyen átlagértékeket kapok? Hasonlítani fog egyáltalán arra, ahogy én azt elképzeltem?

      Találtam egy nagyon szép leírást interneten erre a Viola Jones féle arcfelismerésre...
http://www.codeproject.com/KB/audio-video/haar_detection.aspx
Itt már ki voltak számolva ezek az átlagértékek... Azt nem értem, hogy jöttek ki neki negatív értékek... Mit osztott el mivel? Hmmm... Vagy mit vont ki miből... Ha kivonom én azokat az értékeket egymásból, akkor nekem 1ooooX nagyobb szám jön ki, mint neki... S ha elosztom, akkor meg nem jön ki negatív...
     
       Fura... De nem hagyom annyiban... Fogok neki megint dokumentálódni, hátha onnan kiderül...

Eredmény

      Röviden beszámolok az elmult két nap eredményéről: 4533 arc gyűlt össze eddig. Ezek több szögből (2o fokos maximális eltéréssel) és több megvilágításból ábrázolják az arcokat. Egy részük koreai vagy vietnámi (bár remélem ez nem okoz különösebb problémát). Az aggasztóbb szerintem az, hogy kb 1/1o-e csak a nőnemű... Ha visszamegyek az egyetemre, még rágyúrok a témára, lekapok pár lányt, hogy növeljem az arányukat...

     Most fogok neki, s írok egy programot, ami a nem-arcokat fogja nekem összegyűjteni... remélem ez már gyorsabban fog menni...

Victory - Avagy munka után pihenés

      Túléltük... S nem csak túléltük, hanem felemelt fővel vonultunk ki az egyetem ajtaján szombat délben... Az volt az utolsó vizsgám. Tegnap még átjött Szabi délután, s kicsi optimum-sör-hálózat keverék mellett osztottuk egyet az élet értelmét... S most következik a pihenés... Végre azzal foglalkozom, amivel szeretnék. Nagy erőkkel belevetettem magam az arc-adatbázisom kiépítésébe... Gyülnek is rendesen... (Szóval ha ott voltál a 2o1o-es ballagáson, akkor valószínűleg te is benne vagy az adatbázisban...) Az internetet sem hagytam nyugodni, egy pillanatra sem. Már vagy 6ooo képet letöltöttem, mindenféle arcfelismeréssel foglalkozó oldalról... Valószínüleg mikorra kiválogatom, örülhetek, ha fele megmarad...

     Hű segítőtársam a Photoshop is meg tudott még lepni újdonságokkal... Példának okáért, ha megnyomjuk az Alt+F9-et, akkor oda fl lehet vetetni bizonyos szekvenciákat, amiket egy előre beállított gombkomibáció megnyomásakor végrehajt... Vagyis kivágás-új lap-beillesztés-átméretezés-elmentés-bezárás egyetlen gombnyomással megvan... Legalább ezzel nem kell töltsem az időt...

     Fogok is neki...

     A képekről még csak annyit, hogy 18*24 pixel nagyságúra vannak átméretezve, mert nekem csak statisztikai célból kell. Kiszámolok pár (jópár) átlagértéket belőlük, felrajzolom a grafikonra, megnézem a legkisebb és legnagyobb értéket, majd azt beírom az FPGA-nak, mint alsó és felső határ...

     Ha ezzel végeztem, majd össze kell gyüjtsek pár nem-arcot tartalmazó képet is... (Kb 3o-4o ezret...) De ez már piskóta... nem arcot akármiből lehet kivágni... Egyetlen Sapientias csoportképből lazán lehet párezret kivágni...

     Mára ennyi... Ha lesz valami áttörés, akkor még jelentkezem.

Zsákutca

     Na jó reggelt (12:55 :P)... Nem most keltem fel, csak kellett valami megszólítás az elejére. Tegnap este egész jól elszórakoztam ezzel a keretes képkereséssel, bár konkrét kézzelfogható eredményem nem igazán volt. Ideoda rakott kicsi karikákat és annyi. Mindjárt dobok be pár képet, csak ez a grrrrrr ... Integral image... Rosszabb, mint a matlab... Pedig azt hittem, annál rosszabb nem is létezik... Egy egyszerű összeadási képleten dolgozom reggel 9 óta szinte semmi eredménnyel... (Az egyetlen eredmény az, hogy megértettem, hogyan is kellene működjön, ha jól működne...)

      Mindjárt begorombulok... (vajon van egyáltalán ilyen szó???)...

      13:30 - Megvan a hiba. Az első ciklusban nem kezeltem le a színcsatornákat. :D Ez nem jó hír, de legalább megvan a hiba.
      13:49 - A hiba ki van javítva. Ennek ellenére az integral image-t nem rajzolja ki. Valami nem szimpatikus a programnak az én integral imagemben...
      13:54 - Na ki a király? (Üvegtigris - Sanyi a király :P ) ... Rakok be ebből is pár képet... Egész ügyes. És működik... „Kicsit savanyú, kicsit sárga… de a mienk” (A Tanú című filmből idézve...)

     Megyek ebédelni, utána jöhet a képkivágás.



IntegralImage

















Forráskód

#include <highgui.h>


int main()

{

    IplImage* img;

    IplImage* intimage;

    IplImage* int_show;

    uchar *data;

    int *int_image_data;

    uchar *int_show_data;

    int i,j;

    int height,width,step,channels;

    int num, num_min_egy;

    int r1 = 0, r2 = 0, r3 = 0;


    img = cvLoadImage("d:\\Soundwave.jpg",1); //CV_LOAD_IMAGE_GRAYSCALE

    cvNamedWindow("PIC",CV_WINDOW_AUTOSIZE);

    cvNamedWindow("Integral",CV_WINDOW_AUTOSIZE);

    cvShowImage("PIC",img);


    data = (uchar *)img->imageData;


    height = img->height;

    width = img->width;

    step = img->widthStep;

    channels = img->nChannels;

    intimage = cvCreateImage( cvSize(width, height), IPL_DEPTH_32F, channels );

    int_show = cvCreateImage( cvSize(width, height), IPL_DEPTH_8U, channels );

    int_image_data = (int *) intimage->imageData;

    int_show_data = (uchar*)int_show->imageData;


    //--------------------------------------------------------------------


    //Integral Image szamolas


    int_image_data[0] = data[0];


    // Az elso sor

    for (i = 1; i < step; i++)

    {

      

        int_image_data[i] = int_image_data[i-1] + data[i];

        int_show_data[i]=int_image_data[i]/(width*height);

        //printf("%i -> %i\n",int_image_data[i],int_show_data[i]);

    }


    // A tobbi sor

    for(i=1; i<(int)(height) ; i++)

    {

        r1 = 0, r2 = 0, r3 = 0;


        for(j=0;j <(int)(width);j++)

            {

                //itt figyelni kell arra, hogy tobb szincsatorna van.

                num=i*step+j*channels;

                num_min_egy=(i-1)*step+j*channels;

                r1+=data[num];

              

                int_image_data[num]=int_image_data[num_min_egy]+r1;

                int_show_data[num]=int_image_data[num]/(width*height);


                num=i*step+j*channels+1;

                num_min_egy=(i-1)*step+j*channels+1;

                r2+=data[num];


                int_image_data[num]=int_image_data[num_min_egy]+r2;

                int_show_data[num]=int_image_data[num]/(width*height);


                num=i*step+j*channels+2;

                num_min_egy=(i-1)*step+j*channels+2;

                r3+=data[num];


                int_image_data[num]=int_image_data[num_min_egy]+r3;

                int_show_data[num]=int_image_data[num]/(width*height);

            }

    }

    // Integral Image szamolas vege.

    // az int_image_data tartalmazza a konkret integral ertekeket

    // az int_show_data pedig egy megjelenitheto valtozatat tartalmazza az

    // int_image_datanak (0 es 255 koze beallitva az ertekeket.)

    //----------------------------------------------------


    // mivel az int_image_data kirajzolasa sehogy nem sikerult, igy egy kis

    // "huncutsaghoz" folyamodtam, vagyis az adatokat belemasoltam az eredeti

    // kep informacioit tartalmazo vektorba, es azt jelenitettem meg. Ez magan a

    // szerkezeten semmit nem valtoztatott. Egyszeruen nekem csak igy mukodik...

    for(i=0; i<(int)(step*height) ; i++)

    {

        data[i] = int_show_data[i];

    }


    cvShowImage("Integral",img);

    //------------------------------------


    // takaritas

    cvWaitKey();

     cvDestroyWindow("PIC");

    cvDestroyWindow("Integral");

    cvReleaseImage(&intimage);

    cvReleaseImage(&img);

    cvReleaseImage(&int_show);


    return 0;

}

      Ha valakit komolyabban érdekel (hmmm... ebben erősen kételkedem), akkor szivesen kommentelem, hogy melyik rész mit csinál. A .cpp fájlt sajnos nem tudom ide feltölteni (a blog szerver csak képet enged feltölteni...) viszont tudok linkelni egy másik inspirációs anyagot, amit én is felhasználtam a program megírásában.

      Ez 32 bites lebegőpontos ábrázolású képnek számolja ki az integrálját szintén 32 bites képet térítve vissza.  http://www.etalonsoft.com/code/integral.cpp



Keretezés 

     A keretezésnek az a lényege, hogy egy előre jól meghatározott keret-rendszerrel végigpásztázom a képemet, bizonyos szabályszerűségek után kutatva. Amennyire én tudom, a "nagyok" is így csinálták meg az arcfelismerést az OpenCV-s könyvtári függvényekhez. Lényege, hogy egyszerű szabályrendszert állítunk fel, és ha mindenik szabálynak megfelel a képnek egy része, akkor elmondhatjuk, hogy az egy arc. 

     A könyvtári függvény esetében lehetőségünk van ezeknek a paramétereknek a hangolására (más szóval tanítani tudjuk a rendszert), hogy azt ismerjen fel, amit mi elvárunk tőle. Én konkrétan a szemöldök közötti részt céloztam meg. Ehhez egy T alakú keretet használtam. Ennek a keretnek az a lényege, hogy átlagot
számolunk minden egyes kis négyzet alatti pixelekből (mivel fekete-fehér képekkel dolgoztam, így ez csak egy színcsatornát jelent), és összehasonlítjuk az egyes kis négyzetek alatti átlagértékeket. A kis négyzetek 3oX3o pixel méretűek, persze könnyedén lehet skálázni őket. Az összehaonlítási kritériumok egyszerűek kell legyenek, hogy ne igényeljen nagy számolási időt. Nálam két összehasonlítási kritérium van: először keresek a felső három négyzet szerint, vagyis keresek olyan helyet, ahol egy világos területet két sötét terület vesz közre. Azt is megmondtam, hogy a sötét terület átlaga legalább 15 egységgel kell sötétebb legyen, mint a világos terület átlaga. Ha ez a feltétel teljesül, akkor megyünk a második szintre. Itt az alsó három négyzetnek az értékét figyelem és hasonlítom össze szintén a jobb és bal felső négyzetek átlagértékével. Itt küszöbérték nincs meghatározva. Csak legyen világosabb és boldog leszek.

      Felmerül az emberben a kérdés, hogy hogy lesz ebből arcfelismerés (elvégre édesanyámat sem ilyen módszerrel ismerem fel, meg a szomszéd Juliskát sem ;) ). Ez csak az első lépés az arcfelismerés fele. A "nagyoknál" (OpenCV haarcascade_frontface_alt.xml) 21 szint és átlagban 15o-2oo közötti szűrő van, majdhogynem tökéletesen behangolva. Ők aztán tényleg tudják, hogy hogyan kell arcot felismerni.

      Itt jön a témába az IntegralImage fogalom. Ahhoz, hogy én ki tudjam számítani egy régiónak az átlag pixelszámát, ahhoz végig kell járni a régiót és össze kell adni az összes pixelnek az értékét, ami abban a régióban található, majd el kell osztani a pixelek számával. Egyszerű matematika. Viszont, ha ezt egy 64oX48o pixel méretű képnél akarja az ember megcsinálni az elég időigényes feladat. Figyelembe véve azt, hogy az arc mérete nem mindig ugyanakkora egy képen belül, vagyis szem előtt kell tartani a szűrő skálázhatóságát, akkor már ki is futottunk a valósidejű arcfelismerés feladatköréből. Ekkor jött a Viola-Jones páros (akik nem ma kezdték) és elmagyarázták, hogyan működik az IntegralImage.

     Részletesebb leírást a http://www.cse.yorku.ca/~kosta/CompVis_Notes/integral_representations.pdf cikkben találhatunk.

Ennek lényege, hogy ha kiszámoljuk a leírásban megadott képlettel mindenik pixelhez tartozó integrált értéket, (vagyis a tőle balra és fölötte lévő értékeket összeadom), akkor az eredeti képen A-val jelölt területen lévő pixelek átlagértékét egy nagyon egyszerű képlettel ki tudom számítani, mégpedig : L4+L1-(L2+L3) eredményt elosztom a pixelszámmal. Ezzel minden egyes esetben, amikor dupla for ciklussal végig kellene pásztázzam a képet spóroltam egy nagy rakás processzoridőt.

      Ami a szép az egészben, hogy én ezt nem így oldottam meg. Persze az enyém még csak 1.0a verzió. Én a RegionOfInterest és cvMean függvényeket használtam. Az első kijelöli a képnek azt a részét, amivel éppen dolgozni akarok, a másik pedig kiszámolja az átlagát (mindenképp sokkal lassabban, mint a Viola-Jones páros képlete).



Forráskód

#include <highgui.h>

#include <cv.h>

#include <stdio.h>

#include "math.h"


int main()

{

    double avg1=0,avg2=0,avg3=0,avg4=0,avg5=0,avg6=0;

    int scan_rect_size=30;

    int x=0,y=0,width=scan_rect_size,height=scan_rect_size;

    int tsh1=15;

    IplImage* img;

    IplImage* temp;


    img=cvLoadImage("D:\\paris.jpg",CV_LOAD_IMAGE_GRAYSCALE);

            cvSmooth( img, img, CV_BLUR, 5,5 );

            temp = cvCloneImage( img );

            CvSize size = cvGetSize(img);

            //printf("Mikrofonpróba 1 2 3.\n");

            cvNamedWindow("Processed",CV_WINDOW_AUTOSIZE);

            cvShowImage("Processed",temp);

            for (y=0;y<(size.height-height);y=y+4)  

            {

                for (x=0;x<(size.width-(width*3));x=x+4)

                {

                    cvSetImageROI(img,cvRect(x,y,width,height));

                    avg1 = cvMean(img);

                    cvResetImageROI(img);

                    cvSetImageROI(img,cvRect(x+width,y,width,height));

                    avg2 = cvMean(img);

                    cvResetImageROI(img);

                    cvSetImageROI(img,cvRect(x+(2*width),y,width,height));

                    avg3 = cvMean(img);

                    cvResetImageROI(img);


                    if ((avg1<avg2)&&(avg3<avg2)&&(avg2-avg1>tsh1)&&(avg2-avg3>tsh1))

                    { //ez azt jelenti, hogy az elso feltetelen tuljutottunk

                         //cvCircle(temp,cvPoint(x+(width*1.5),y+(height/2)),5,cvScalar(250,0,0));

                         //cvWaitKey();

                        if ((x+width<size.width)&&(y+(3*height)<size.height))

                        {

                                    cvSetImageROI(img,cvRect(x+width,y+height,width,height));

                                    avg4 = cvMean(img);

                                    cvResetImageROI(img);

                                    cvSetImageROI(img,cvRect(x+width,y+(2*height),width,height));

                                    avg5 = cvMean(img);

                                    cvResetImageROI(img);

                                    cvSetImageROI(img,cvRect(x+width,y+(3*height),width,height));

                                    avg6 = cvMean(img);

                                    cvResetImageROI(img);

                                    if ((avg1<avg4)&&(avg1<avg5)&&(avg1<avg6)&&(avg3<avg4)&&(avg3<avg5)&&(avg3<avg6))

                                    {

                                        printf("Masodik szint:\t Avg1 : %f\tAvg2 : %f\tAvg3 : %f\n",avg1,avg2,avg3);

                                        cvCircle(temp,cvPoint(x+(width*1.5),y+(height/2)),5,cvScalar(250,0,0));

                                        cvShowImage("Processed",temp);

                                    }

                        }

                    }           

                }

            }

    cvNamedWindow("Processed",CV_WINDOW_AUTOSIZE);

    cvShowImage("Processed",temp);


    cvWaitKey();

    cvReleaseImage(&img);

    cvReleaseImage(&temp);

    cvDestroyWindow("Original");


    return 0;

}


Eredmények






       Ezek csak a jó eredmények. Rosszat nem rakok fel, mert minek? :P



      A képek egy előzetes áltagoló-simító szűrőn mentek keresztül, azért ilyen homályosak. Jól látható, hogy a program még nagyon messze van a tökéletestől. Keretméret és küszöbérték változtatásával ennél sokkal másabb eredményeket is el lehet érni.
A program további fejlesztési lehetősége egy harmadik feltétel lenne, mégpedig, hogy a T fölött keressen még két oldalt egy-egy fekete csíkot (a szemöldököt). Valami ilyenre gondoltam. Vagy mittudomén. Most fogok neki, s megpróbálom kideriteni, hogy konkrétan mit is használnak a nagyok... Utána meg lazulás meg spagetti :D
Holnap fogok neki rendszerelméletnek meg a PLC projektet debugolom, emiatt nem fogok pár napig ide semmit írni. Viszont lélekben mindíg itt leszek... Már alig várom, hogy lejárjon a szesszió.
Return top