kedd, október 25, 2011

Na, mozgás

Nézzük, mennyit fejlődött az előző verzióhoz képest a mozgásérzékelős móka: 


Az elmúlt két hétben (mondjuk inkább netto 8 nap), habár csak éjszaka tudtam vele foglalkozni napi max 3 órát, mégis rengeteg tapasztalatot szereztem. Bár már a kezdeti állapotában szinte kész volt a program azért némi csiszolás és némi plusz "igény", néhány ötlet amit itt kipróbálhatok után elég szép kis alkalmazás lett.
Ahogy fejlődött a program a nagy egybegyúrt kódot szépen különszedegettem. Jelenleg három nagyobb rész van a programban, ezekről néhány szó:

A WebCam kliens
Sikerült egy egész általános klienst írni aminek egy tetszőleges webes címet meg lehet adni és ha az jpeg képet (igaziból bármilyen képet) vagy mjpeg stream-et ad vissza akkor kezeli. A sima jpg abszolút kihívásmentes, viszont az mjpg már kalandosabb.
Első guglizásom eredményeként egy már kész modult találtam aforge néven, meg azt az infót, hogy eléggé körülményes a stream-et direktben kezelni így inkább az aforge-ot javasolják. Nos, én ritkán használok ilyen külsős modult, főleg ha látom a reményt arra, hogy én is lefejleszthessem.
Az mjpg (szerintem) borzalmasan egyszerű. Van egy jól beazonosítható fejléce és utána jön maga a kép.
Első körben a bejövő byte áradatot egy nagy tömbbe nyomtam ezt szöveggé alakítottam majd regexp-el megkerestem benne a fejlécet, majd az eredeti byte tömbből kihalásztam a képet. Ez iszonyat lassú természetesen.
Ezután jött egy elegánsabb megoldás ahol már BinaryReader-el dolgoztam. Ez addig vizsgálgatta a bejövő byte-okat amíg azok nem egyeztek meg a fejléc első sorával, utána szépen soronként feldolgozta majd a kép adatot kinyerte. Ez már jóval gyorsabb (és áttekinthetőbb) volt és elég jól tudta követni a kamera adatfolyamát, de a processzor 100%-on pörgött és tudva, hogy ez később egy gyenge notin fog futni 24 órában, tudtam, hogy tovább kell mennem. Ezután ugrottam fejest az igazi byte vadászatba.
Régebben kicsit féltem ettől, de rá kellett jönnöm, hogy semmi para nincs benne. Van egy adott hosszúságú byte tömb amit feltöltök a bejövő adatfolyamból, byte-ról-byte-ra olvasom, ha a végére értem újra feltöltöm és kezdem elölről. Közben van még két byte tömb, egy stringbuffer meg egy filebuffer ezeket is (majdnem) byte-onként töltögetem attól függően, hogy a headereket dolgozom fel vagy a file adatokat nyerem ki.
Belekerült az is, hogy mindaddig amíg a programnak nincs igazából szüksége a képre (erről majd később) addig nem is csinálok belőle képet, csak egy byte tömbben várja (a tulajdonképpen egy jpg file a memóriában), hogy vagy feldolgozom vagy az enyészeté lesz.

A mozgás-elemző
A mozgás-elemzés igaziból két kép különbségének a feldolgozása. Első lépésben két képet egymásra teszek és az összes képponton megnézem, hogy az mennyit változott az előzőhöz képest. Ennek az eredménye látszik a régi képen jobb oldalt (minél világosabb a kép annál nagyobb volt a változás).
Két kép között nem csak a mozgás miatt hanem különféle zajok, kódolási hibák és egyebek miatt is vannak különbségek, ezeket egy úgynevezett limiterrel távolítom el. Ez annyit tesz, hogy mindegyik különbséget megvizsgálom, hogy az egy érték alatt van-e, ha igen akkor mégsem számít különbségnek.
Sajnos így is maradnak "tüskék" a képen amiket nem lehet ezzel a módszerrel eltávolítani, viszont "erózióval" igen. Ez még implementálás alatt van, de nagyjából úgy működik, hogy csak azok az érzékelt képpontok maradnak meg amiket 1-2-3..n pixeles körben körülvesznek további ugyanilyen pontok. Így a tényleges bemozdulás területe is csökken, de nem baj ha ezzel a zajt is sikeresen eltávolítom.
Ha mindez megvan akkor már csak egy viszonyszám kell, hogy hány pixelnek kell változnia (és hány mp-en keresztül kell ennek az értéknek minimum megmaradnia) ahhoz, hogy a rendszer "riasszon".
A fenti képen egyébként azért van ekkora vörös folt mert a program mutatja az pár képkockával előző mozgásokat is (ghost trail), így látványosabb egy kicsit :)

A probléma ott kezdődik, hogy amikor kimondom, hogy "két képet összehasonlítok pixelenként) az összesen 921600 pontot jelent (640x480 pont, 3 színnel) amit egyenként kell vizsgálni, így bármiféle kevésbé hatékony megoldásból származó hiba szó szerint a milliószorosára nő. Így ha azt akarom, hogy ez az algoritmus másodpercenként 10-16 (vagy mégtöbb) képkockát dolgozzon fel akkor már nem milliszekundumban kell mérni az időt hanem órajelben és mindent amit csak lehet egyszerűsíteni kell.
Tudom, hogy ha igazán minden szaftot ki akarnék csavarni a gépből akkor nem C#-ban kellene ezt csinálnom, hanem mondjuk C-ben, de nem akarok emiatt nyelvet váltani.
Az alábbi lépések segítettek sokat:
- unsafe kód és pointerek: Ezzel lehet talán a legnagyobbat lépni. Veszélyes kicsit mert direktben érjük el a memóriát és bizonyos esetekben összekuszálhatjuk azt aminek eredménye random fagyás vagy kékhalál is lehet.
A pixel-összehasonlító részben ...
- Nincs deklarálás : bármilyen változót használjak is, azt még a cikluson kívül deklarálom.
- Nincs property elérés: amire szükségem van a cikluson belül azokat előtte egy lokális változóba átteszem.
- Nincs managed tömb: az eredményeket ugyanúgy egy pointeren keresztül egy lefoglalt memóriaterületre másolom.
- Nincs fölösleges matematikai művelet, amit csak lehet előre ki kell számolni.
- Csak egész számok vannak. Abból is csak Int és Byte. Int azért kell mert negatív és 255-nél nagyobb számokat is kezelnem kell.
- Nincs szorzás és osztás a kalkulációban mert ezek elég lassú műveletek.
- Nincs switch helyette van if. Kezdetben (a kétféle megjelenítéshez) switch-et alkalmaztam, de jóval gyorsabb egy if/elseif ág. Sőt lehet, hogy az egészet a cikluson kívülre teszem és inkább megjelnítésenként ugyanaz a kód fog szerepelni. Ez kód-kinézet szempontjából rusnya de a sebesség mindenek felett áll.

Küzdelmes és nagyon érdekes része ez a fejlesztésnek.

A FOSCAM Server
A Projekt azon része aminek igazából semmi köze nincs az eredeti elképzeléshez.
Jött egyszer az ötlet, hogy mi lenne, ha a fenti képet/videót (a "vörös folttal") ugyanúgy megnézhetném az Androidos mobilon, ráadásul ugyanazzal a programmal (TinyCam) amivel amúgy is nézem a kamerát.
Belevágtam és egy viszonylag primitív webserver-t összedobtam ami pont azokat a kéréseket tudja kezelni amik feltétlenül szükségesek. Ez összesen három dolog: Statikus kép kérése, MJPEG stream kérése és a kamera vezérlés.
Igen, a kamerát vezérlő gombok (a fenti képen) nem a fizikai kamerát vezérlik, hanem magát a programot. Egyelőre csak a limitert, agc-t lehet ki/be kapcsolni, a megjelnítési módot váltani és a limiter értékét növelni/csökkenteni. De vicces, hogy ugyanezt az androidos progiból is megtehetem.

Szálkezelés:
Mindhárom rész külön-külön szálon, párhuzamosan fut.
Érdekes és tanulságos, hogy hogyan lehet ezeket a szálakat szinkronban tartani lehetőleg úgy, hogy ez a legkevésbé akassza meg a különböző szálakat. A webkamera kliens folyamatosan kéri a képeket, a mozgásérzékelő pedig a saját tempójában dolgozza fel az épp aktuálisat. Ha nem végez elég gyorsan van hogy egy-két beérkező képet el kell dobni és csak az azutánival fog foglalkozni.
A szerver is folyamatosan fut, minden becsatlakozó kliensnek megmutatja a legutolsó képet és addig nem is ad újat amíg a mozgásérzékelő nem dolgozta fel az előzőt (és amíg a szerver a kliensek felé továbbítja a képet addig a mozgásérzékelő már egy újabb képen dolgozik).
Ezzel kapcsolatban itt: egy nagyon hasznos olvasnivaló.

Ami még hátravan:
Még van mit csiszolni a rendszeren. Jelenleg még mindig a mozgáselemzés optimalizálásán dolgozok plusz az eróziós algoritmust is most fejlesztem bele de a terveimben szerepel még:

- Detektálási régió, hogy a kép melyik részére legyen érzékeny igazán a program
- Egy "MOZOG" jelzés ami végül is a végcél, de még nincs implementálva
- Analitika: A különböző értékek (bejövő képek száma, feldolgozott képek száma, "mozgó" pixelek száma, a különböző program-részek működési ideje/hatékonysága) rögzítése és elemzése (ez szintén egy külön szálon futó rész lesz) plusz ezeket az értékeket meg akarom jeleníteni a kamera-képen (kikapcsolhatóan persze)
- OSD (egy ilyen checkbox van is a képen de még semmit nem csinál) ami arra lesz jó, hogy ha nem a windows-os progit nézem, hanem a telefonon keresztül vezérlem akkor jelenleg semmilyen visszacsatolás nincs, hogy mi változott. Első körben egy sima szöveges kiírás lesz, későbbiekben egy menürendszer amiben a (kamerát forgató) nyilakkal lehet majd navigálni.
- A foscam vezérlés "pass-throught" módban is menjen vagyis ezen a programon keresztül lehessen az igazi kamerát vezérelni.

Ezekután még lekapcsolhatóvá teszem a különféle extra funkciókat és a végén előáll reményeim szerint az a kód amit később a KépkeretPC-n futó progiban is használni fogok.

1 megjegyzés: