Category Archives: Smart Systems 2014 – Fire Fighters

Bildegjenkjenning 2.0

Bildegjenkjenning for Firebot Vi bestemte oss tidlig for at bildegjenkjenning skulle være fremgangsmåten vi bruker for å finne branner (tente lys) i arbeidsområdet. Vi vurderte to alternative plasseringer av kamera: Enten fastmontert med utsyn over hele arbeidsområdet, eller montert på kjøretøyet. Begge alternativer ble utprøvd, og valget falt på kjøretøymontering. Vi kan dermed montere kameraet sammen med slukke/tenne-utstyret. Neste spørsmål ble da: Hvilken kameraløsning skal vi velge? Alternativene var mange, inkludert:

  • GoPro kamera: Lite og hendig, men vanskelig å hente video-strøm fra.
  • Web-kamera: Njaa …
  • Pixy CMUCam: Kameramodul med innebygget bildegjenkjenning. Hadde sikkert vært midt i blinken, men vi har ikke denne modulen tilgjengelig. Dessuten er det ikke mye læring i å bruke ferdigtygd vare …
  • Raspberry Pi kamera-modul: Denne løsningen viste seg å være et naturlig valg, siden vi uansett hadde beregnet å bruke Raspberry Pi (RasPi) til bildegjenkjenningsbiten. I tillegg viste det seg at skolen hadde Raspi-cam tilgjengelig.

Bildegjenkjenning er en komplisert prosess, og tilnærmet umulig å gjennomføre på den tiden vi har til rådighet dersom man ikke bruker et eksisterende bibliotek. Etter en del leting fant vi, som tidligere nevnt OpenCV. OpenCV (Open Source Computer Vision Library: http://opencv.org) er et anerkjent bildebehandlingsbibliotek med mye dokumentasjon og mange artikler og kodeeksempler tilgjengelig på Internett. En annen forutsetning for valg av bibliotek var at det måtte kunne kjøre på RasPi. Vi fant fort ut at dette skulle være greit. Det viste seg også at SD-kortet som fulgte med Pi’en ikke var stort nok for alt vi måtte installere. Et nytt SD-kort ble kjøpt inn, og Raspbian ble installert. Deretter lastet vi ned OpenCV, og satte i gang prosessen med å bygge dette for RasPi. Biblioteket er på totalt 3.8 GB, og Make-prosessen skulle vise seg å ta 13-14 timer, så dette var greit å gjøre hjemme … Vi bruker raspicam for å kontrollere kameraet fra C++. Dette biblioteket måtte også lastes ned og bygges, men det gikk kjappere. Dette biblioteket er basert på C, så ting må gjøres på en litt annen måte enn en die-hard OO-stakkar er vant til. Dette var imidlertid ikke så veldig store utfordringen, siden interaksjonen mot dette biblioteket er relativt liten. For å kommunisere med det Arduino-baserte styresystemet til roboten, bestemte vi oss for å bruke en I2C-buss. Denne bussen bruker et minimum av ledninger mellom nodene, samtidig som den har kapasitet til å håndtere mange noder samtidig. I vårt tilfelle trenger vi ikke mer enn én server og én klient, men I2C er likevel godt egnet. Vi bruker biblioteket WiringPi til å hjelpe oss med denne kommunikasjonen. En spesiell utfordring ved kommunikasjon mellom Arduino og RasPi er at RasPi ikke tåler mer enn 3.3V på inngangene, mens Arduino gjerne trykker ut 5V. Av denne grunn valgte vi å sette opp RasPien som master, og Arduinoen som slave. Dermed er det alltid RasPien som tilfører spenning til bussen, så den er trygg. Eneste betingelse er at Arduinoen aldri setter sine pinner «høye». Hensikten med bildegjenkjenningsfunksjonen er å finne branner i arbeidsområdet til roboten, og deretter sende koordinatene tilbake til Arduinoen. Dette gjøres ved å lete etter områder i kameraets synsfelt som er lysere enn et gitt nivå. Vi prøvde først å lete kun etter områder med flammens farge og intensitet, men dette viste seg å bli for snevert. Selv om denne fremgangsmåten ga lite støy i bildet, ble det rett og slett for få piksler i kameraets synsfelt til at vi kunne se lyset på litt avstand. Løsningen vi endte opp med var å lete etter lys i alle spekterets farger, med lav metning og høy intensitet. Dette matcher flammen og det nærmeste området av lyset bra, men medfører også en del støy i form av gjenskinn fra vinduer eller kunstig lys. RaspiCam kan levere videostrøm eller stillbilder etter behov. Vi har valgt å behandle stillbilder så fort som plattformen tillater. Dette vil si at et nytt bilde blir tatt så snart det forrige er prosessert. Denne fremgangsmåten gir oss en oppdateringsfrekvens på anslagsvis 4-6 bilder i sekundet, når programmet ikke bruker ressurser på å vise bilder til skjerm. Bildeprosesseringen foregår slik:

  • Et frame tas ved hjelp av en kommando som sendes til raspicam. Dette bildet lagres i et objekt av Mat-klassen fra OpenCV. I dette objektet lagres hver fargekanal i sin egen matrise.
  • Funksjonen cvtColor() brukes for å konvertere bildets farger fra RBG (rød-blå-grønn, kartesisk format) til HSV (hue-saturation-value, sylindrisk koordinatsystem). I HSV-området beskrives farger ved hjelp av fargehjulet med en ekstra akse, slik at det utvides til en sylinder. Hue (nyanse) angir farge som en verdi mellom 0 og 179, og angir «vinkelen» i forhold til startpunktet i hjulet. Saturation (metning) angir «hvor ren» fargen er, angitt ved hvor langt fra sentrum vi er. Lavere saturation (nærmere sentrum) betyr større grad av blanding av farger. Value angir intensiteten, og angis ved høyden på sylinderen.
  • En «threshold»-funksjon benyttes for å selektere piksler med intensitet over ønsket nivå. Resultatet av denne funksjonen lagres i en ny matrise i form av binære verdier, dvs. svarte eller hvite piksler.
  • Deretter går vi i gang med en filtreringsprosess for å fjerne mest mulig støy før vi prøver å finne konturer. Denne prosessen består av «eroding» og «dilation», hvor dilation forsøker å binde sammen nærstående enkeltpiksler til større samlinger (redusere ujevnheter i kantene av interessante områder), mens eroding har til hensikt å fjerne enkeltpiksler som står for seg selv (støy).
  • OpenCV forsøker deretter å finne konturer i bildet ved å se etter områder med sammenhengende piksler, og lagrer hvert av disse i en vector-liste. For hvert område som finnes beregnes arealet (antall piksler i området), samt tyngdepunktet (senteret av området).
  • Vi prøver å eliminere en del støy ved å bare vurdere områder med minimum 10 piksler. Dersom det finnes flere slike områder i bildet, velger vi det området med sentrum nærmest det punktet som ble valgt i forrige frame. Dermed unngår vi en del tilfeller av at forskjellige konturer velges fra frame til frame.
  • Nå sender vi dette punktet til Arduinoen via I2C-bussen som et punkt med verdier mellom (0, 0) og (255,255) hvor (0,0) er øvre venstre hjørne av synsfeltet og (255,255) er nedre høyre hjørne. Vi har valgt å lage en protokoll der vi først sender 3 kjente bytes for å indikere starten av en melding, før x og y-koordinatene sendes til slutt. Dette hindrer at vi får byttet om på rekkefølgen dersom en melding ikke kommer frem som den skal.

ImgProcess   Illustration 1: Debug-bilde til venstre viser treff på flammen. Høyre side viser området som matcher nivået vi ønsker.

Det har vært nok av utfordringer i prosessen med å få til bildeprosesseringen. Det har ikke vært bare enkelt å få alle biblioteker og annen software opp og gå på Raspberry Pi, men det gikk til slutt. Da vi endelig hadde fått det meste opp og gå, sa SD-kortet takk for seg. Masse «bad disk-block» ved boot… Dette er spesielt morsomt når det tar 13-14 timer å bygge bare det ene biblioteket. Jeg plugget kortet inn i en Windows-maskin for å se om det var mulig å kopiere det vha. et backup-program som leser blokker direkte fra disken. Dette gikk ikke; «disken er korrupt». Kortet ble imidlertid stående i maskinen en stund mens jeg bannet og svor. Etter ca. en halv time fant jeg ut at jeg skulle prøve igjen, og da fikk jeg kopiert det uten feilmeldinger. Kanskje det var oppvarmingen som hjalp? Kortet ble byttet på garanti, men før jeg tok det nye kortet i bruk, leste jeg en del på nett om andre som hadde hatt lignende opplevelser. RasPi er ganske nøye på hvilke SD-kort den liker eller ikke. http://elinux.org/RPi_SD_cards lister opp en mengde spesifikke SD-kort med informasjon om RasPi liker dem eller ikke. Det første kortet var imidlertid en av de godkjente typene, så jeg fortsatte å lete. En annen ting som mange nevnte var at det ofte oppstod problemer med SD-kort på RasPi dersom strømforsyningen ikke leverte nok spenning. Det er tydeligvis vitalt at spenningen (målt under drift) mellom TP1 og TP2 på RasPi ikke er under 4.75 eller over 5.25 volt. http://elinux.org/RPi_Hardware (under avsnittet «Power Supply Problems») forklarer hvordan spenningen skal måles under drift. File-RPI_Test_Points File-Voltmeter Jeg målte kun 4.6 volt når jeg brukte USB-porten på pc-en som strømkilde, så jeg regner med at det var en medvirkende årsak til at kortet ble ødelagt. Etter dette brukte vi kun en ekstern strømforsyning for å være sikker på at det var nok spenning. Etter å ha lagt tilbake sikkerhetskopien fra det første kortet, virket alt som det skulle. Jeg har imidlertid blitt mer nøye på å ta sikkerhetskopier med ujevne mellomrom … Når RasPi-en blir montert på kjøretøyet, vil vi ikke ha skjerm og tastatur tilgjengelig for å kunne starte bildeprosesseringsprosessen manuelt. Det må derfor startes automatisk når RasPi-en booter opp. Dette er tilsynelatende enkelt nok, og kan gjøres på mange forskjellige måter og ved forskjellige tidspunkter i boot-prosessen. Det som er viktig å tenke på er at prosessen ikke startes for tidlig, dvs. før alle nødvendige ressurser er tilgjengelig. Vi må også huske på å legge inn en mulighet for å avslutte programmet, f.eks. via en tastekombinasjon på tastaturet. Om man glemmer dette, vil man ikke kunne få tilgang til operativsystem-shellet i det hele tatt. Dooh… Vi endte opp med å legge inn en linje i .bashrc i hjemmekatalogen til brukeren «pi» for å starte opp programmet. På denne måten ble det enkelt å debugge oppstarten, siden dette skriptet kjøres hver gang man åpner et nytt terminalvindu (inkludert det første som operativsystemet åpner ved fullført boot). Vi måtte som tidligere nevnt forsøke en del forskjellige fremgangsmåter for å finne et tent lys på litt avstand, men mener nå at dette er i orden. Kommunikasjonen mellom RasPi og Arduino fungerer fint, og vi er nå klare til å implementere dette på kjøretøyet. 2014-10-24 10.08.152014-10-24 10.06.402014-10-24 10.08.28

Radiostyring

Hei bloggen<3

Forrgie styringsvideo brukte bluetooth koblet mot en smarttelefon. For å få kommunikasjon med en eventuell manuell styring, trengte vi et bluetooth-master kort, noe vi ikke hadde tilgjenglig. Vi valgte derfor å teste ut systemet med radiooverføring istedet. Vi laget også en improvisert fjernkontroll med en joystick. Dette ga mulighet for mer sensetiv styring. Roboten er også biltt bygget om.

Slukningssystem

For å slukke lysene har vi tenkt på flere muligheter. Bl.a. å putte en kappe over flammen for å kvele den, kjøre over lyset for å ødelegge det eller bruke butangass for å smelte det. Vi falt derimot på den litt mer nøkterne løsningen med en “vannpistol” hvor vi fyller et reservoar med vann og deretter etterfyller med luft for å skape et overtrykk. I og med at vi har falt på lego for å gi drivkraft valgte vi en lignende løsning for å gi lufttrykk. Photo on 14-10-14 at 09.26

 

Etter å ha testet en del forskjellige konstruksjoner fant vi ut at lego ikke fort nok ga oss det trykket vi ville ha. Ventetiden mellom hver gang vi fikk avfyrt “kanonen” var fra to til tre minutter. Vi valgte også å gå vekk fra vann da luften i seg selv var mye mer effektiv.

Valget falt derfor på en litt mer ordentlig luftpumpe som vi lånte fra en kompressor til en bil.

Photo on 14-10-14 at 09.51

 

Med denne pumpen oppnår vi ønsket trykk på under ett minutt.

Vi valgte også å gå tilbake til vann fordi det gir potensiale til å slukke større flammer. Og det er kulere.

 

Bildegjenkjenning

Et alternativ vi har sett på er å ha et stasjonært kamera som kjenner igjen både telys og roboten. “Stasjonen” vil da enten kunne gi koordinater som roboten navigerer seg frem til, eller kontinuelig oppdatering om hvilken retning den burde kjøre. Overføring kunne skjedd over bluetooth.

Det er mange problemstillinger ved denne fremgangsmåten. Først og fremst å få en datamaskin til å kjenne igjen et bord, et telys og roboten. Etter mye leting fant vi frem til opencv, it bibliotek for bildegjenkjenning. Fordelen med opencv er at det finnes moduler for å utvikle i Python, noe som øker progresonshastigheten.

Som en test tok vi bilde av et bord

table2

 

for å finne hjørnenene er det mange muligheter, men vi valge å bruke en blanding av mange algoritmer. Vår fremgangsmåte er:

  • finne alle kanter / piksler som er ved siden av hverandre med veldig forskjellig verdi.
  • skille ut disse og bruke en fill algoritme som fyller avgrensede områder i bildet.
  • velge det største avgrensede området (antar bordet er plassert i fokus).
  • fjerne ande kanter.
  • Bruke Hough transform til å kjenne igjen rette linjer, og finne deres skjæringspunkter. Disse skjæringspunktene er da hjørnene på bordet.

Etter kantdeteksjon:

canny

 

Ferdig prosessert bilde:

output

 

Neste steg er så å transformere dette perspektivet til et 2D plan slik at punkter kommer ut som koordinater. Dette er fullt mulig, da vi har bredde og lengde på bordet, men krever noen litt kinkige matrise transformasjoner.

Dette jobber vi med nå.