Jelzőtábla érzékelése képfeldolgozási módszerekkel
Debreceni Egyetem Informatikai Kar, Mérnök Informatikus szak Vállalati információs rendszerek szakirány
Önálló laboratórium 2
Készítették: Kovács Róbert
N20RGZ
Palotai Ambrus JRHSXK
Debrecen 2013.
-1-
1. Bevezetés Az Önálló laboratórium 1 keretében elkezdett alakzatdetektálást, illetve forma felismerést folytatjuk és fejlesztjük tovább az Önálló laboratórium 2-n is. Az ötletünk az volt, hogy manapság egyre több autóvezető hagyja figyelmen kívül a közúti jelzőtáblákat, és ez legtöbbször figyelmetlenségből adódik. Az alakzat felismerést próbáltuk kombinálni a közúti jelzőtáblák felismerésével. Ebben a folyamatban ismét az OpenCV-t hívtuk segítségül, illetve a C programozási nyelvet. Ebben a dokumentumban szeretnénk leírni a fejlesztés menetét, az egyes megoldási módok leírását, illetve az általunk tett észrevételeket a képfeldolgozás témakörével kapcsolatban.
2. Feladatleírás A korábbiakban elkezdett munka további tervei között szerepelt a valós videó elemzés, ahol a környezet, mint zavaró tényező is megjelenik, továbbá a gyorsaságot is számításba kell venni. Projektünkben a célokat a Magyarországon megtalálható bizonyos KRESZ táblák felismerése felé specializáltuk. Ebbe a körbe tartozik az „Állj, elsőbbségadás kötelező”, az „Elsőbbségadás kötelező”, valamint háromszög alakú táblák, továbbá a kerek alakú táblák, és ezeken belül, hogy milyen információt is tartalmaz a kerek alakú tábla. Ezen feladatok még egy táblaérzékelés esetén megvalósítandó kommunikációval (jelküldéssel) is kiegészült. Különböző táblák detektálása estén különböző jelet küldünk a számítógép soros portjára, és a kiküldött értéket értelmezzük egy, a soros kábel másik végére csatlakoztatott mikrokontroller segítségével. Ez az eszköz a jelnek megfelelő utasítást hajt végre. A számítógép – mikrokontroller „párbeszéd” csatornájának kiválasztásánál azért esett az RS-232-re a választás, mert ez a technika az iparban a mai napig használatos, így további fejlesztés esetén az iparban való felhasználás lehetősége sem kizárt.
3. Előkészületek Eddigiek során az elemzéseket képeken, fotókon, illetve magunk által készített képekből összerakott videón (képsorozaton) végeztük. Önálló laboratórium 1 végén már tervbe vettük az élethű videó elemzést, hiszen a mindennapos használat során is ilyen környezettel fogunk
-2-
találkozni, akár vezetés, akár valamilyen későbbi ipari felhasználás esetén. Ennek a célnak megfelelően az elemezendő anyagunkat valós körülmények között vettük fel. Első ötletünk az autóban elhelyezett kamerával való útszakasz felvétel volt. Ez a sok zavaró tényező – szélvédő becsillanása, karcossága, kamera lassúsága, nagy sebesség esetén kép elmosódása ─ miatt kerékpározás közben rögzített útszakaszra módosult. További munkánk során ezt az inputot használjuk a programunk tesztelésére, fejlesztésére.
4. Kommunikáció Az elemzések folyamán elért eredményeket, felismert alakzatokat az elemezett videón jelöltük be real timeban. A tesztelések során felvetődött a gondolat, hogy alakzat detektálás esetén esetlegesen a számítógép adjon ki jeleket magán a gépen kívülre, melyet a későbbiekben lehet majd hasznosítani, feldolgozni. A számítógép a soros portjára adja ki ezeket a jeleket. Visual Studioban írt C programban [1] teszteltük ezt a küldést, és port monitor programmal
néztük,
hogy
megvalósult-e
a
soros
port
vezérlés.
1. ábra Miután a Device Monitoring Studio segítségével láttuk, hogy a C programból küldött értékünk megjelent a számítógép soros portján, a következő lépés az volt, hogy a kiküldött jelet fel is tudjuk dolgozni, illetve értelmezzük. Ehhez az Arduino 2560 MEGA
-3-
mikrokontrollert kötöttük a másik oldalra. A számítógépünk DB-9 es csatlakozójára kötött kábelről nekünk 2 vezetéket kell használni ahhoz, hogy az eszközre tudjunk küldeni értéket, ez pedig a 3-as pin (TX, kék színű), ami a KÜLD vezeték, a másik pedig az 5-ös pin (GND, narancssárga színű), ami a FÖLD kábel. Az RS-232 technológia +/- 12 V-os jelszintekkel dolgozik, míg az Arduino TTL jelszintekkel (0-5 V), így a megfelelő kommunikációhoz egy kis jelszint átalakító áramkör [2] megépítésére is szükségünk volt.
Arduino 2560 MEGA
2. ábra 4.1 Kommunikáció az Arduino-val Az alapötletünk az volt, hogy a táblakereső által érzékelt táblákat megfeleltetjük egy-egy jelnek. Például: Stop tábla = S, Elsőbbségadás kötelező = E, … stb. Ezeket a karaktereket küldtük volna tovább az Arduinonak, ami értelmezés után felkapcsolta volna a táblának megfelelő ledet. Az elküldött jelek értelmezésénél az Arduino programnyelvét kellett használnunk, maga a nyelv egyszerű és könnyen tanulható. A C program és az Arduinos program kommunikálása már komolyabb gondot jelentett, mivel a két nyelv karakter készlete eltérő. Így a C program által küldött jelet nem tudtuk értelmezni az Arduinon. A probléma oka szerintünk az eltérő karakterkészlet. Erre hosszas keresgélés és próbálkozás után sem leltünk megoldást.
-4-
Sikerült elérnünk, hogy ha a C programból küldtünk egy string-et akkor az Arduino érzékelte a string végén lévő záró karaktert. Ez a záró karakter a ’\0’. Az Arduino beépített mintaprogramjaiban a ’\n’ karaktert használják az ilyen jellegű vizsgálatoknál, de a C programmal összekombinálva már nem működött. Próbálkoztunk típuskényszerítéssel [3. ábra] is, hogy az átküldött jelet biztosan olyan formában kapjuk meg a kommunikációs csatornáról, ahogy azt a változókban deklaráltuk. De ez a módszer se vezetett eredményre.
3. ábra Próbáltuk magát az ASCII kódot is értelmezni, de ezzel a megoldással se értünk el sikert. Ez a módszer annyiban tér el a képen látható kódtól, hogy int típusú változót használunk és az összehasonlításnál a felsővonás jelet el kell hagyni, illetve az ’A’ karaktert a kódjával azonosítjuk.
4.2 Az áramkör A kommunikáció szemléltetésére létrehoztunk egy egyszerű áramkört, ami 3 db ledből áll. Ezek egy nyák lapon kaptak helyet, a 2. ábrán látható átalakító áramkörrel együtt. A cél az volt, hogy egy egyszerűen átlátható és jól működő áramkört építsünk fel, amivel meg tudjuk
-5-
jeleníteni a tábla keresés eredményeit. Sajnos a kipróbálása csak a jel érzékelésére korlátozódott, a fent említett karakterkódolási problémák miatt. Az áramkör megfelelően működött, mindig a felkapcsolni kívánt led lépett működésbe.
4. ábra
5. Alakzat függvények szerkezete A korábbi munkánk során alkalmazott alakzat szerinti függvényfelosztás megmaradt. Tehát
az
ezekhez
tartozó
függvénynevek
továbbra
is
TalalEsRajzolKort,
TalalEsRajzolNegyszoget, TalalEsRajzolHaromszoget. A függvények kapnak egy bemeneti képet (input), ezt feldolgozzák, és ha eredményesen végződött az alakzatkeresés, akkor visszaadnak egy-egy képet a függvények, ha eredménytelenül, akkor pedig 0-t. A visszaadott kép korábban annyiban tért el az inputként kapott képtől, hogy be voltak rajta rajzolva megfelelő színnel a megtalált formák. Mostani állapot szerint a függvények nem az egész bemeneti képet adják vissza, hanem a kép csak azon részletét, ahol az alakzat található. Pontosabban fogalmazva, az alakzat köré rajzolható téglalapnyi részt az eredeti képből.
5. ábra -6-
6. Mintaillesztő függvény Miután megtörtént az adott alakzatot kereső függvény lépéssorozata, és sikeres volt a keresés, tehát a függvény nem 0-t adott vissza, hanem - a fentebb említett - talált alakzat köré rajzolható téglalapnyi részt, akkor következik a mintaillesztés menete. Ennek a mintaillesztő függvénynek a meghívása a következőképpen néz ki: o körtábla esetén: keres2(ujabb1, imgTemplate30); o háromszög tábla esetén: keres2(ujabb2, imgTemplateF); o négyszögtábla esetén: keres2(ujabb3, imgTemplateKek). Az első függvény paraméter az alakzatkereső függvény által visszaadott kép. A második paraméterben pedig a formához tartozó előre definiált mintaképek vannak. A keres2 függvény először is létrehoz egy új képet, amiben majd az illesztés eredményét tárolja el. Az illesztéshez a cvMatchTemplate nevű openCV függvényt használjuk, melyben az eredménykép mérete szigorú szabályokhoz kötött. Az eredménykép méretei a keresendő kép, és a mintakép szélességinek, és magasságainak különbsége + 1. További kikötés, hogy egycsatornás 32 bites lebegőpontos kép legyen. Így az eredménykép – imgResultF – előállításához tartozó programsor: cvCreateImage( cvSize (
imgOriginalF -> width – imgTemplateF -> width + 1), imgOriginalF -> height – imgTemplateF -> height + 1), IPL_DEPTH_32F, 1);
Az imgOriginalF a kép, amelyen a keresés történik, míg az imgTemplateF a mintakép. Az előkészületek után van meghívva a mintakereső függvény. A keresés eredményének vizualizálásához a keresés eredményét lekérjük az eredményképről (lekérdezzük a minimumot és maximumot, valamint a lokális minimumot és maximumot). Az eredmény vizualizálásához a megtalált mintát körberajzoljuk egy téglalappal az eredeti ( imgOriginalF ) képen. A téglalaprajzoláshoz meg kell adni a bal felső sarkot - cvPoint(min_loc.x , min_loc.y), ami a mintatalálat bal felső sarka is, továbbá a jobb alsó sarkot, ami meg a találat bal felső koordinátáinak
és
a
mintakép
szélességének,
illetve
magasságának
összege:
cvPoint(min_loc.x + imgTemplateF -> width, min_loc.y + imgTemplateF -> height). A függvény visszatérési értéke ez a minta körülrajzolással kiegészített, eredeti, függvény első paraméterként megadott kép lesz. Ha nincs mintatalálat, akkor a függvény az eredeti paraméterben kapott képet adja vissza.
-7-
7. Főprogram A főprogramban történnek a deklarációk, a videó-, és képbetöltések, a felhasználó tájékoztatása, illetve a képkockák megjelenítése is. Itt történik továbbá a soros porthoz tartozó beállítások megadása. 7.1 Képkocka előméretezése A videó, pontosabban a videót alkotó képkockák feldolgozása, a számítógép számára megterhelő művelet. Fentebb említettük a gyorsaság kérdését, így hát kompromisszumokat kötöttünk az elemzés gördülékenyebbé tételéért. Az általunk, kerékpározás közben felvett videóban látható, hogy próbálkoztunk az autóval közlekedésben részt vevő szemszögéből látni a „világot”. Gépjárművezetés közben, ha az ember előre tekintve figyeli az utat, akkor többségében a közúti jelzőtáblák jobb kéz fele esnek. (Mi azt a lehetőséget nem vettük annyira meghatározónak, mikor például nem működnek a forgalomirányító lámpák, és akkor fontossá válnak a lámpa fölött elhelyezett táblák.) Megállapodásunk szerint, - a gyorsabb elemzés miatt - a képeknél nem vettük figyelembe azokat a részeket, ahol nem hordoznak a programunk szempontjából fontos információt. Kamránk 640x480-as felbontású videót vett fel (30 képkocka/másodpercben), így a programunkban mi is ilyen méretben dolgozzuk fel a frameket a torzítás elkerülése végett. A gördülékeny elemzés miatt csak minden 10. képkockát elemzünk, tapasztalataink szerint így még nem veszítünk sokat a videóból, úgymond „webkamera hatásúvá” tettük a videónkat. Egy változóval vizsgáljuk, hogy hányadik képkockánál járunk (frameszam), és ha ennek az értéknek a 10-zel való maradékos osztása 1, akkor feldolgozzuk, egyébként meg kérjük a következő frame-t a videóból. Videó feldolgozásnál kötelesek vagyunk nem az eredeti képkockát elemezni, hanem annak csupán egy klónozott másolatát, így mi is ezen végezzük el a számunkra érdekes képrészlet (Region Of
Interest ROI)
beállítását.
Ehhez
a
művelethez
a
cvSetImageROI(kicsi, cvRect(200,0,440,480)) paraméterezett függvényt használjuk, ahol a kicsi az aktuális képkocka klónja, a cvRect ben megadott értékek pedig a számunkra értékes tartomány koordinátái. A beállított területet át is kell tennünk egy másik képbe, hogy ott kizárólag csak az értékes terület szerepeljen. Létrehozunk egy ujabb2 nevű képet, melynek mérete pont az előbb beállított értékes téglalap méretű, mélysége, és csatorna száma pedig a kicsi vel megegyező [ kicsi->dept, kicsi->nChannels ]. cvCopy(kicsi, ujabb2) ezzel tehát megtörtént a megállapodás szerinti értékes terület különválasztása. Az aktuális képkocka
-8-
klónra beállított számunkra érdekes terület beállítást visszavonjuk [ cvResetImageROI(kicsi) ], memóriahiba elkerülése miatt.
6. ábra 7.2 Párhuzamos függvényhívások Önálló laboratórium 1 alatti munkánk során a kép-, és videó feldolgozást úgy végeztük, hogy a képre, illetve aktuális képkockára a detektáló függvényeinket szekvenciálisan hívtuk meg rájuk. Ezzel a megoldással az volt a baj, hogy míg az egyik függvény dolgozott a többi addig várta, hogy megkaphassa majd a képet, ám ez idő alatt a soron következő képkockák is várakoztak, mégpedig arra, hogy a 3as függvényszekvencián végighaladjon az éppen feldolgozás alatt lévő. A függvények visszatérési értéke is az egész aktuális kép volt, mert ha az egyik nem is talált a saját függvényében leírt formákat, a következő találhatott, így ez a „felesleges” küldözgetés is lassította a program futását. Ezen felvetődő problémák miatt a párhuzamosítás mellett döntöttünk. Az aktuális frame előméretezését csak egyszer hajtjuk végre, ám utána erre a képre (ami a számunkra értékes területet tartalmazza kizárólag) a 3 formakereső függvényt párhuzamosan hívjuk meg 3 külön szálon. Tapasztalataink alapján ez a megoldás körülbelül lefelezte a végrehajtási időt. Itt kell megemlíteni továbbá, hogy ezeken a párhuzamos szálakon belül történik a mintaillesztő függvények meghívása is, ha a függvény talált az ő általa keresendő alakzatot, tehát nem 0-t adott vissza, illetve a talált, és kivágott alakzat képének megjelenítése is. A szálak alapbeállításnak megfelelően bevárják egymást, és csak ezután halad tovább a program végrehajtása. Ha bekövetkezett ez az esemény, akkor
-9-
felszabadítjuk a memóriából az aktuális képkocka klónokat.[ cvReleaseImage(&frame_clone), cvReleaseImage(&kicsi) ] 7.3 Alternatív program szerkezet A fejlesztő munka során a képelemzések számának növekedésével arányosan csökkenni kezdett a programfutási sebessége. Ennek kiküszöbölésére próbáltuk leegyszerűsíteni a program szerkezetet, ezzel gyorsítva a mintaillesztő programot. Megvizsgáltuk, hogy mik azok a részek a programban, amik nagyon lassítják a futást. Az egyik ilyen tényező a túl sok adat küldözgetése függvényről függvényre. Megpróbáltuk csökkenteni a képek méretét amennyire csak lehetett, és ahol lehetséges volt egyszerűbb információhordozási módszert választottunk, például logikai változókat. Ezt a módszert csak körkeresésre készítettük el. A program meghívja a TalalEsRajzolKortVideora() nevű függvényt, ami egy képkockát vár és egy float típusú tömböt fog visszaadni eredményül, ami a megtalált tábla koordinátáit tartalmazza, vagy 0-t. A
TalalEsRajzolKortVideora()
függvény
kivágja a megtalált alakzatot, jelen esetben egy kört és erre a kép részletre meghívja KeresMinta() függvényt. A KeresMinta() függvény paraméterként a kivágott képrészletet és egy mintaképet kap meg a keresendő tábláról. Egy For ciklus és egy Case utasítás segítségével a TalalEsRajzolKortVideora() függvény az összes kör alakú táblára meghívható
7. ábra
egyszer erre a képrészletre. A KeresMinta() függvény először megvizsgálja a kivágott kép méreteit és utána hozzáigazítja a mintakép méreteihez. Így a képek méretei 70x70 és 100x100 között lesznek. Természetesen a két képnek azonos lesz a mérete. Erre azért van szükség, mert a fejlesztés során azt tapasztaltuk, hogy kisebb méretű képen sokkal gyorsabb a mintaillesztés.
- 10 -
Ha a keresés sikeres volt, és talált egyezést a kereső, akkor a TalalEsRajzolKortVideora() függvényben egy logikai változót igaz értékűre állítunk, és csak akkor küldi tovább a főprogramnak a körberajzolandó terület koordinátáit. Ez a megoldás még nagyon kezdetleges, de lényegesen felgyorsult vele a program futása. Így sokkal kevesebb adatot kell küldözgetni a függvények között, mint az eredeti módszerrel. A hátránya viszont az, hogy nehezen lehet megoldani az összes táblatípus vizsgálatát. Illetve a párhuzamosításnál is problémákat okozhat a program szerkezete.
8. Továbbfejlesztési lehetőségek A jelenlegi, Önálló laboratórium 2 tartárgy keretein belül elkészített programunk továbblépés volt az előző félévben elkészített programunkhoz képest. Láttuk az irányt, és a problémákat, amik idő közben előkerültek, gondolva itt a méretezés fontosságára, valamint a gyorsaságra, mint két nagyon fontos tényezőre. Projektünk során a gyorsaság kiküszöbölésére 2 fajta kísérletet is tettünk, így a munkánkhoz 2 darab csatolt program tartozik. A negyedKep.cpp programban a kép értékes területének beállítása alapján történő keresés, illesztés; míg a MintaKereses_tisztazasa.cpp programban a képrészlet küldözgetése helyett logikai érték küldözgetés szerepel, illetve itt a körkereső függvényre lett specializálva (7.3. fejezetpont). A programmal megvalósított, külvilág felé történő kommunikáció, a továbbiak során nagyon hasznos lehet, és univerzális felhasználásra is lehetőséget adhat. Ennek a kommunikációnak, illetve a kiküldött adat értelmezésének, és ennek pontosítása, gyorsítása, valamint akár a mikrokontrolleren megvalósított számítások elvégzésének alternatívája későbbi munka során előtérbe kerülhet. Az elkészített dokumentációnkban sok helyen ejtünk szót a gyorsaságról. Ebben a témakörben – képfeldolgozás, videó feldolgozás – mindig egy meghatározó tényező lesz, főképp, ha az eredeti tervben szereplő, autóvezetés közben felvett videó részlet elemzésére gondolunk. Az elemzendő videó részlet felvételt kerékpározás közben készítettük, és munkánk során ez a videó is gyorsnak bizonyult az algoritmusunknak. Tehát ezen az irányvonal mentén is van még fejlesztési lehetőség az elkészített programjainkon belül.
- 11 -
Egy másik továbbfejlesztési lehetőség az OpenCV kihagyása a későbbiekben a programból, és helyette a fuzzy logika alkalmazása. Ez a módszer nagyban leegyszerűsítheti a program szerkezetét, ezzel lehetővé téve, hogy kisebb teljesítményű eszközökön is használható legyen. Így hétköznapibb eszközökön és helyzetekben is alkalmazható lenne az algoritmus, mint például személyautókban.
Forrásjegyzék: [1] http://lubosweb.php5.sk/soft/05_serial_posix/serial_port_programming_win_linux.pdf [2] http://freecircuitdiagram.com/2009/03/27/simple-ttl-rs232-level-converter-using-transistor/
Felhasznált irodalom [3] Learning OpenCV by Gary Bradski and Adrian Kaehler, 2008 [4] http://opencv.jp/opencv-2.2_org/c/ [5] http://opencv.willowgarage.com/documentation/
- 12 -