5.11. Képkocka-különbségképzés¶
A képkocka-különbségképzés minden új képkockát egy eltárolt referencia-képkockához hasonlít, hogy megtalálja a jelenet megváltozott részeit. Ez a gerince azoknak a kamerás alkalmazásoknak, amelyek valamilyen esemény bekövetkezését figyelik – mozgásra indított felvétel, behatolásriasztás, „ments el egy videót, ha valami megmozdul” –, és teljes egészében a korábban tárgyalt képpontonkénti műveletekből épül fel: egy abszolút különbségből, egy küszöbölésből és egy területkeresésből, amelyeket minden képkockán lefuttatunk.
5.11.1. Az alapvető folyamat¶
Az első lépés egy referencia beszerzése. Az indulás közelében valamikor – ideális esetben akkor, amikor a jelenet abban az állapotban van, amit a „nincs változás” jelent – az alkalmazás rögzít egy képkockát, és megőrzi azt. Ez a képkocka lesz az az alapérték, amelyhez minden ezt követő felvételt hasonlítunk.
reference = csi0.snapshot().copy()
A .copy() számít. A csi0.snapshot() önmagában egy olyan Image objektumot ad vissza, amelynek puffere a képkocka-pufferben él, ahol a snapshot következő hívása felül fogja írni. A .copy() külön puffert foglal a referenciának, és lehetővé teszi, hogy ennek a képkockának a képpontjai túléljék a következő felvételt.
A második lépés minden képkockán lefut: rögzít egy friss képet, majd kiszámítja az abszolút különbséget közte és a referencia között. Pontosan ezt teszi a difference():
current = csi0.snapshot()
current.difference(reference)
E hívás után a current egy olyan képet tartalmaz, amelynek nem nulla képpontjai minden olyan pozíciót megjelölnek, ahol a jelenet a referencia rögzítése óta megváltozott, az egyes képpontok nagysága pedig arányos azzal, hogy az adott pozíción mennyit változott.
A harmadik lépés küszöböli a különbségképet. A nyers különbség mindig tartalmaz némi zajt: az érzékelő shot-zajából eredő apró fényerő-ingadozásokat, a megvilágítás elcsúszásából eredő gradiensváltozásokat, az enyhe kameramozgásból eredő képponton belüli remegést. Egy küszöbölési lépés – a binary() a zajszint fölé állított küszöbértékkel – csak azokat a változásokat tartja meg, amelyek elég nagyok ahhoz, hogy valódi mozgásnak számítsanak, a többit pedig eldobja, így egy bináris képet hoz létre, amelynek nem nulla képpontjai a ténylegesen megváltozott pozíciók.
A negyedik lépés kinyeri ennek a bináris maszknak az összefüggő régióit – a szomszédos nem nulla képpontok csoportjait, amelyek folytonos foltokat alkotnak. A find_blobs() ezt egyetlen hívással elvégzi, és visszaad egy listát a mozgásrégiókról, mindegyiket egy határoló dobozzal és egy képpontszámmal, amelyekre az alkalmazás többi része reagálhat.
A képkocka-különbségképzési folyamat: egy referencia-képkockából és egy aktuális képkockából különbségkép lesz; a küszöbölés a különbséget a megváltozott pozíciók bináris maszkjává alakítja; egy összefüggő-régió lépés a maszkot a mozgásrégiók listájává alakítja.¶
5.11.2. Memóriabeli és lemezen tárolt referenciák¶
Az alapvető folyamat a referencia-képkockát a RAM-ban tartja. Ez a helyes megoldás akkor, amikor a referenciát a szkript ezen futása során rögzítjük, és csak addig kell fennmaradnia, ameddig a szkript fut.
Egy hosszan futó alkalmazás esetén – egy kamera, amelynek áramkimaradás után folytatnia kell a változásészlelést, egy időszakos szkript, amelynek bármilyen változást észlelnie kell egy korábbi pillanat óta – a referencia-képkockának túl kell élnie a futó szkriptet. A minta az, hogy a referenciát elmentjük a lemezre:
csi0.snapshot().save("/sdcard/reference.bmp")
és minden futás elején visszatöltjük:
reference = image.Image("/sdcard/reference.bmp")
A különbségképzési logika nem változik; csak az, hogy hol él a referencia a felvételek között. Néhány finomítás természetes módon kiegészíti ezt a lemezen tárolt változatot – a referencia automatikus újrarögzítése időzítővel, opcionális mozgóátlagok a lassú megvilágítás-elcsúszás követésére –, de a középponti helyettesítés ugyanaz.
5.11.3. Fényforrás-izolálás¶
Ugyanez a kivonási minta egy kissé eltérő helyzetben is felbukkan: egy fényforrás elkülönítésekor a jelenet többi részétől. A trükk az, hogy rögzítünk egy „lekapcsolt fény” referenciát – egy olyan képkockát, amelyet akkor készítünk, amikor az, amit éppen észlelni akarunk (egy IR-jeladó, egy képernyőképpont, egy állapotjelző), nem világít –, és ezt a referenciát kivonjuk minden ezt követő képkockából. Az eredmény mindenhol nulla fényerejű, ahol a jelenet mindkét felvételen azonos volt, és csak ott nem nulla fényerejű, ahol a fényforrás ténylegesen felvillant.
5.11.4. A difference vagy a sub választása¶
Egy gyakorlati megjegyzés arról, hogy melyik aritmetikai műveletet válasszuk. A difference() a változás abszolút értékét adja vissza – előjel nélkül –, ami érzékennyé teszi bármelyik irányú változásra (világosodás vagy sötétedés), annak árán, hogy nem árulja el az alkalmazásnak, melyik irányba ment a változás. A tiszta mozgásészleléshez ez a helyes megoldás: bármi, ami megmozdult, érdekes, függetlenül attól, hogy melyik irányba tolódott el a fényerő.
A fényforrás-észleléshez a megvilágított képpont mindig világosabb, mint a lekapcsolt fény referencia, ezért a sub() (a nullánál történő levágásával) a becsületesebb választás. Mindenütt, ahol az aktuális képkocka sötétebb a referenciánál (ami a nem megvilágított érték körüli érzékelőzaj lenne), nullára vág, ahelyett hogy hamis „a fény be volt kapcsolva” jelet jelentene.