5.11. Kehysten erotus

Kehysten erotus vertaa jokaista uutta kehystä tallennettuun viitekehykseen löytääkseen ne näkymän osat, jotka ovat muuttuneet. Se on perustyökalu kamerasovelluksissa, jotka tarkkailevat jonkin tapahtumista – liikkeen laukaisema kuvaus, tunkeutumishälytykset, ”tallenna video, kun jokin liikkuu” – ja se rakentuu kokonaan aiemmin käsitellyistä pikselikohtaisista operaatioista: itseisarvoerotuksesta, kynnystyksestä ja aluehausta, joita ajetaan jokaiselle kehykselle.

5.11.1. Perusputki

Ensimmäinen vaihe on hankkia viite. Jossakin vaiheessa pian käynnistyksen jälkeen – mieluiten kun näkymä on siinä tilassa, jota ”ei muutosta” tarkoittaa – sovellus ottaa kehyksen ja säilyttää sen. Kehyksestä tulee perustaso, johon jokaista myöhempää kuvausta verrataan.

reference = csi0.snapshot().copy()

.copy() on tärkeä. Pelkkä csi0.snapshot() palauttaa Image-olion, jonka puskuri sijaitsee kehyspuskurissa, jossa seuraava snapshot-kutsu ylikirjoittaa sen. .copy() varaa viitteelle erillisen puskurin ja antaa tämän kehyksen pikselien säilyä seuraavan kuvauksen yli.

Toinen vaihe ajetaan jokaiselle kehykselle: ota tuore kuva ja laske sitten itseisarvoerotus sen ja viitteen välillä. Juuri tämän difference() tekee:

current = csi0.snapshot()
current.difference(reference)

Tämän kutsun jälkeen current sisältää kuvan, jonka nollasta poikkeavat pikselit merkitsevät jokaisen kohdan, jossa näkymä on muuttunut viitteen ottamisen jälkeen, ja jokaisen pikselin suuruus on verrannollinen siihen, kuinka paljon se muuttui kyseisessä kohdassa.

Kolmas vaihe kynnystää erotuskuvan. Raaka erotus sisältää aina jonkin verran kohinaa: pieniä kirkkausvaihteluita sensorin valokohinasta, gradienttimuutoksia valaistuksen ajautumisesta, alipikselin värinää kameran lievästä liikkeestä. Kynnystysvaihe – binary(), jonka kynnysarvo on asetettu tämän kohinatason yläpuolelle – säilyttää vain ne muutokset, jotka ovat riittävän suuria laskettaviksi todelliseksi liikkeeksi, ja hylkää loput, tuottaen binäärikuvan, jonka nollasta poikkeavat pikselit ovat todellisuudessa muuttuneet kohdat.

Neljäs vaihe poimii kyseisen binäärimaskin yhtenäiset alueet – vierekkäisten nollasta poikkeavien pikselien ryhmät, jotka muodostavat yhtenäisiä laikkuja. find_blobs() tekee sen yhdellä kutsulla palauttaen listan liikealueista, joista jokaisella on rajauslaatikko ja pikselimäärä, joiden pohjalta loppuosa sovelluksesta voi toimia.

Vaakasuuntainen putkikaavio. Kaksi vasemmanpuoleisinta paneelia ovat viitekehys ja nykyinen kehys vierekkäin, niiden välissä plusmerkki. Nuoli johtaa parista kolmanteen paneeliin, jonka nimi on erotus ja jossa muutamat laikut loistavat kirkkaina tummaa taustaa vasten. Nuoli johtaa sieltä neljänteen paneeliin, jossa näkyy erotuksen binaarisesti kynnystetty versio, samat laikut nyt täysin valkoisina. Viimeinen nuoli johtaa viidenteen paneeliin, jossa näkyy binäärimaski, johon on merkitty suorakulmaiset rajauslaatikot kunkin laikun ympärille.

Kehysten erotuksen putki: viitekehyksestä ja nykyisestä kehyksestä tulee erotuskuva; kynnystys muuttaa erotuksen muuttuneiden kohtien binäärimaskiksi; yhtenäisten alueiden vaihe muuttaa maskin liikealueiden listaksi.

5.11.2. Muistissa olevat ja levyllä olevat viitteet

Perusputki pitää viitekehyksen RAM-muistissa. Se on oikea ratkaisu, kun viite otetaan tällä skriptin ajokerralla ja sen täytyy säilyä vain niin kauan kuin skripti pysyy käynnissä.

Pitkään käynnissä olevassa sovelluksessa – kamera, jonka pitäisi jatkaa muutoksen tunnistusta virtakatkoksen jälkeen, ajoittainen skripti, jonka pitää tunnistaa mikä tahansa muutos jonkin aiemman hetken jälkeen – viitekehyksen täytyy elää käynnissä olevaa skriptiä pidempään. Tapana on tallentaa viite levylle:

csi0.snapshot().save("/sdcard/reference.bmp")

ja ladata se takaisin jokaisen ajon alussa:

reference = image.Image("/sdcard/reference.bmp")

Erotuslogiikka ei muutu; vain se, missä viite sijaitsee kuvausten välillä, muuttuu. Muutamat hienosäädöt laajentavat luonnostaan tätä levyllä olevaa muunnelmaa – viitteen automaattinen uudelleenkuvaus ajastimella, valinnaiset liukuvat keskiarvot hitaan valaistuksen ajautumisen seuraamiseen – mutta ydinkohdassa oleva korvaus on sama.

5.11.3. Valonlähteen eristäminen

Sama vähennyskaava esiintyy hieman erilaisessa yhteydessä: valonlähteen eristämisessä muusta näkymästä. Niksi on ottaa ”valot pois” -viite – kehys, joka on otettu, kun tunnistettava kohde (IR-majakka, näytön pikseli, tilailmaisin) ei ole valaistuna – ja vähentää tämä viite jokaisesta myöhemmästä kehyksestä. Tuloksessa on nollakirkkaus kaikkialla, missä näkymä oli sama molemmissa kuvauksissa, ja nollasta poikkeava kirkkaus vain siellä, missä valonlähde todella syttyi.

5.11.4. Eron tai vähennyksen valitseminen

Käytännön huomautus siitä, mikä aritmeettinen operaatio kannattaa valita. difference() palauttaa muutoksen itseisarvon – etumerkittömänä – mikä tekee siitä herkän muutokselle kumpaan tahansa suuntaan (kirkastuminen tai tummeneminen) sillä hinnalla, ettei se kerro sovellukselle, mihin suuntaan muutos tapahtui. Pelkkään liikkeentunnistukseen tämä on oikea ratkaisu: kaikki, mikä liikkui, on kiinnostavaa riippumatta siitä, kumpaan suuntaan kirkkaus muuttui.

Valonlähteen tunnistuksessa valaistu pikseli on aina kirkkaampi kuin valot pois -viite, joten sub() (nollaan leikkaavana) on rehellisempi valinta. Kaikkialla, missä nykyinen kehys on tummempi kuin viite (mikä olisi sensorikohinaa valaisemattoman arvon ympärillä), leikkautuu nollaan sen sijaan, että raportoitaisiin virheellinen ”valo oli päällä” -signaali.