iOS SDK Augmented Reality Videobehandling

Velkommen til den siste avbetalingen i vår premiumserie om å øke virkeligheten med iOS SDK! I dagens veiledning vil jeg lære deg hvordan du behandler og analyserer live video-streaming fra enhetskameraet for å forbedre vårt syn på verden med nyttig overleggsinformasjon.


Hvor vi forlot?

I det første innlegget i denne serien, introduserte jeg deg til Foundation- rammeverk og vi gjorde alt grunnlaget for å begynne å vise kameraet feed i vår app. Hvis du ikke allerede har det, må du sjekke ut del 1!


Dagens Demo App

Begrepet "Augmented Reality" har blitt en buzz setning i de senere årene som smartphones har blitt kraftige nok til å plassere denne teknologien i lommene. Dessverre har all publisitet rundt begrepet generert mye forvirring om hva Augmented Reality faktisk er, og hvordan det kan brukes til å forbedre samspillet med verden. For å klargjøre, må målet med en Augmented Reality-applikasjon være å ta en brukeres eksisterende syn eller oppfattelse av verden og forbedre denne oppfatningen ved å gi ytterligere informasjon eller perspektiver som ikke er naturlig synlige. En av de tidligste og mest praktiske implementeringene av Augmented Reality er "First Down" høydepunktet som ofte settes når man ser på amerikanske fotballspill på fjernsyn. Den subtile karakteren til dette overlegget er akkurat det som gjør det til en perfekt bruk av AR. Brukeren er ikke distrahert av teknologien, men deres perspektiv er naturlig forbedret.

Demo Augmented Reality-applikasjonen som vi skal bygge i dag, er veldig tematisk lik fotballens første og 10-system. Vi behandler hver videoramme fra kameraet og beregner gjennomsnittlig RGB-farge på rammen. Vi viser deretter resultatet som et RGB-, Hex- og fargestøtteroverlegg. Dette er en liten, enkel forbedring av visningen, men det bør tjene vår hensikt å demonstrere hvordan du får tilgang til kamerastrømmen og behandler den med en tilpasset algoritme. Så, la oss komme i gang!


Trinn 1: Legg til prosjektressurser

For å fullføre denne opplæringen trenger vi noen få flere rammer.

Følg de samme trinnene som er beskrevet i trinn 1 i den forrige veiledningen i denne serien, og importer disse rammene:

  • Core Video Framework

    Som navnet antyder, er dette rammen brukt til videobehandling og er primært ansvarlig for å gi videobufferstøtte. Hoveddatatypen som vi er interessert i fra denne rammen er CVImageBufferRef, som vil bli brukt til å få tilgang til de buffede bildedataene fra vår videostrøm. Vi bruker også mange forskjellige CoreVideo-funksjoner, inkludert CVPixelBufferGetBytesPerRow () og CVPixelBufferLockBaseAddress (). Når du ser en "CV" prepended til en datatype eller et funksjonsnavn, vet du at det kom fra CoreVideo.

  • Core Media Framework

    Core Media gir støtte på lavt nivå som AV Foundation-rammen (lagt til i den siste opplæringen) er bygget på. Vi er egentlig bare interessert i CMSampleBufferRef datatype og CMSampleBufferGetImageBuffer () fungere fra dette rammeverket.

  • Quartz Core Framework

    Quartz Core er ansvarlig for mye av animasjonen som du ser når du bruker en iOS-enhet. Vi trenger bare dette i vårt prosjekt av en grunn, den CADisplayLink. Mer om dette senere i opplæringen.

I tillegg til disse rammene skal vi også ha UIColor Utilties-prosjektet for å legge til en praktisk kategori til UIColor gjenstand. For å få denne koden, kan du enten besøke prosjektsiden på GitHub eller bare se etter filene UIColor-Expanded.h og UIColor-Expanded.m i kildekoden for denne opplæringen. Uansett må du legge til begge disse filene i Xcode-prosjektet, og deretter importere kategorien i ARDemoViewController.m:

 #import "UIColor-Expanded.h"

Trinn 2: Implementer AVCapture Delegate

Da vi sluttet i den siste opplæringen, hadde vi implementert et forhåndsvisningslag som viste hva enhetskameraet kunne se, men vi hadde faktisk ikke mulighet til å få tilgang til og behandle rammedataene. Det første trinnet i dette er AV-stiftelsesdelegatet -captureOutput: didOutputSampleBuffer: fromConnection:. Denne delegerte metoden gir oss tilgang til a CMSampleBufferRef av bildedataene for gjeldende videoramme. Vi må massere disse dataene til en nyttig form, men dette vil være en god start.

I ARDemoViewController.h, du må samsvare med den rette delegaten:

 @interface ARDemoViewController: UIViewController 

Legg nå delegatemetoden i ARDemoViewController.m:

 - (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) forbindelse 

Det siste skrittet vi må ta, er å modifisere vår videostrømskode fra den siste opplæringen for å utpeke den nåværende visningskontrollen som delegat:

 // Konfigurer innspillingsøksutgang AVCaptureVideoDataOutput * videoOut = [[AVCaptureVideoDataOutput alloc] init]; [videoOut setAlwaysDiscardsLateVideoFrames: YES]; NSDictionary * videoSettings = [NSDictionary DictionaryWithObject: [NSNummer nummerWithInt: kCVPixelFormatType_32BGRA] forKey: (id) kCVPixelBufferPixelFormatTypeKey]; [videoOut setVideoSettings: videoSettings]; dispatch_queue_t color_queue = dispatch_queue_create ("com.mobiletuts.ardemo.processcolors", NULL); [videoOut settSampleBufferDelegate: selvkø: color_queue]; [cameraCaptureSession addOutput: videoOut];

På linje 5 - 8 ovenfor konfigurerer vi ytterligere innstillinger for videoutgangen generert av vår AVCaptureSession. Spesifikt spesifiserer vi at vi vil motta hver pikselbuffer formatert som kCVPixelFormatType_32BGRA, som i utgangspunktet betyr at den vil bli returnert som en 32-biters verdi bestilt som Blue, Green, Red og Alpha kanaler. Dette er litt forskjellig fra 32 bit RGBA, som er mest brukt, men en liten endring i kanalbestilling vil ikke bremse oss ned. :)

Deretter lager vi på linjer 9 - 10 en ekspedisjonskø som prøvebufferen skal behandles innenfor. En kø er nødvendig for å gi oss nok tid til å faktisk behandle en ramme før du mottar den neste. Selv om dette teoretisk betyr at vår kameravisning bare er i stand til å se fortiden, bør forsinkelsen ikke være merkbar i det hele tatt hvis du behandler hver ramme effektivt.

Med ovennevnte kode på plass, vil ARDemoViewController bør begynne å motta delegerte samtaler med en CMSampleBuffer av hver videoramme!


Trinn 3: Konverter prøvebufferen

Nå som vi har a CMSampleBuffer Vi må konvertere det til et format som vi lettere kan behandle. Fordi vi jobber med Core Video, vil formatet til vårt valg være CVImageBufferRef. Legg til følgende linje i delegatemetoden:

 - (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) tilkobling CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer (sampleBuffer); 

Ok, nå er det på tide å begynne å faktisk behandle denne pikselbufferen. For denne opplæringen bygger vi en Augmented Reality-app som kan se på hvilken som helst scene eller et bilde, og fortelle oss hva Hex og RGB-fargegjenomsnittet av rammen er. La oss lage en ny metode kalt findColorAverage: bare for å oppnå den oppgaven.

Legg til metoden deklarasjonen i ARDemoViewController.h:

 - (void) findColorAverage: (CVImageBufferRef) pixelBuffer;

Og legg deretter til en implementeringsstub i ARDemoViewController.m:

 - (void) findColorAverage: (CVImageBufferRef) pixelBuffer 

Til slutt, ring den nye metoden på slutten av delegatets gjennomføring:

 - (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) tilkobling CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer (sampleBuffer); [self findColorAverage: pixelBuffer]; 

Trinn 4: Iterate Over Pixel Buffer

For hver videoramme vi mottar, vil vi beregne gjennomsnittet av alle pikselverdier som finnes i bildet. Det første trinnet i å gjøre dette er å iterere over hver piksel som finnes i CVImageBufferRef. Mens algoritmen vi vil bruke i resten av denne opplæringen, er spesifikk for søknaden vår, er dette spesielle trinnet, detererer over rammepikselene, en svært vanlig oppgave i mange forskjellige Augmented Reality-applikasjoner av denne type.

Følgende kode vil iterere over hver piksel i rammen:

 - (void) findColorAverage: (CVImageBufferRef) pixelBuffer CVPixelBufferLockBaseAddress (pixelBuffer, 0); int bufferHeight = CVPixelBufferGetHeight (pixelBuffer); int bufferWidth = CVPixelBufferGetWidth (pixelBuffer); int bytesPerRow = CVPixelBufferGetBytesPerRow (pixelBuffer); usignert char * pixel; usignert char * rowBase = (usignert char *) CVPixelBufferGetBaseAddress (pixelBuffer); for (int rad = 0; rad < bufferHeight; row += 8 )  for( int column = 0; column < bufferWidth; column += 8 )  pixel = rowBase + (row * bytesPerRow) + (column * 4);   CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 ); 

På linje 3 ovenfor kaller vi CVPixelBufferLockBaseAddress på vår pikselbuffer for å hindre at endringer forekommer i dataene mens de blir behandlet.

På linjene 5-7 får vi noen grunnleggende meta-informasjon om pikselbufferen, nemlig buffertens høyde og bredde og antall byte lagret i hver rad.

På linje 9 brukes en char pointer til å deklarere en piksel. I C kan char data typen holde en enkelt byte av data. Som standard kan en enkelt byte av data inneholde et helt tall i området -128 til 127. Hvis den byte er "usignert", kan den inneholde en heltallverdi mellom 0 og 255. Hvorfor er dette viktig? Fordi alle RGB-verdiene vi ønsker å få tilgang til, ligger innenfor området 0 - 255, noe som betyr at de krever en enkelt byte som skal lagres. Du vil også huske at vi har konfigurert vår videoutgang for å returnere en 32-biters verdi i BGRA-format. Fordi hver byte er lik 8 biter, bør dette gjøre mye mer fornuftig nå: 8 (B) + 8 (G) + 8 (R) + 8 (A) = 32. For å oppsummere: vi bruker en karbon å referere til våre pikseldata fordi hver RGBA-verdi inneholder en byte av data.

På linje 11 bruker vi en peker til å referere til startadressen til pikselbufferen i minnet. En karbon brukes her av samme grunn som den brukes til vår pikselvariabel. Hele pixelbufferrefleksen er bare en serie av gjentatte RGBA-verdier, så ved å sette rowBase-variabelen til den første minneadressen i bufferen, kan vi begynne å løkke over alle verdiene neste.

Linjene 13 - 14 danner en nestet sløyfe som vil iterere over hver pikselverdi i pikselbufferen.

På linje 16 tildeler vi faktisk pikselbufferen til startminneadressen til den nåværende RGBA-sekvensen. Herfra kan vi referere hver byte i sekvensen.

Til slutt, på linje 21, låser vi opp pikselbufferen etter å ha fullført behandlingen.


Trinn 5: Finn rammefarge Gjennomsnittlig

Iterering over pikselbufferen vil ikke gjøre oss veldig bra med mindre vi bruker informasjonen. For vårt prosjekt ønsker vi å returnere en enkelt farge som representerer gjennomsnittet av alle piksler i rammen. Begynn med å legge til en current variabel til .h-filen:

 UIColor * currentColor;  @property (nonatomic, behold) UIColor * currentColor;

Pass på at du også syntetiserer denne verdien:

 @synthesize currentColor;

Deretter endrer du findColorAverage: metode slik:

 - (void) findColorAverage: (CVImageBufferRef) pixelBuffer CVPixelBufferLockBaseAddress (pixelBuffer, 0); int bufferHeight = CVPixelBufferGetHeight (pixelBuffer); int bufferWidth = CVPixelBufferGetWidth (pixelBuffer); int bytesPerRow = CVPixelBufferGetBytesPerRow (pixelBuffer); usignert int red_sum = 0; usignert int green_sum = 0; usignert int blue_sum = 0; usignert int alpha_sum = 0; unsigned int count = 0; usignert char * pixel; usignert char * rowBase = (usignert char *) CVPixelBufferGetBaseAddress (pixelBuffer); for (int rad = 0; rad < bufferHeight; row += 8 )  for( int column = 0; column < bufferWidth; column += 8 )  pixel = rowBase + (row * bytesPerRow) + (column * 4); red_sum += pixel[2]; green_sum += pixel[1]; blue_sum += pixel[0]; alpha_sum += pixel[3]; count++;   CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 ); self.currentColor = [UIColor colorWithRed:red_sum / count / 255.0f green:green_sum / count / 255.0f blue:blue_sum / count / 255.0f alpha:1.0f]; 

Du kan se at vi har startet endringene våre ved å legge til variablene red_sum, green_sum, blue_sum, alpha_sum, og telle. Beregning av et gjennomsnitt for pixelverdier gjøres på samme måte som du vil beregne et gjennomsnitt for noe annet. Så våre RGBA sum variabler vil holde summen av hver verdi som vi interate, og telle variabel vil holde totalt antall pixel, øke hver gang gjennom løkken.

Pikseloppgave forekommer faktisk på linjene 24 - 26. Fordi vår pikselvariabel bare er en peker til en bestemt byte i minnet, kan vi få tilgang til etterfølgende minneadresser akkurat som du kanskje regner med å kunne med en matrise. Legg merke til at BGRA-bestillingen er hva du kan forvente for indeksverdier: B = 0, G = 1, R = 2, A = 3. Vi vil faktisk ikke bruke alfaverdien for noe som er nyttig i denne opplæringen, men jeg har inkludert det her for fullstendighetens skyld.

Etter at vår nestede sløyfe har fullført iterering gjennom matrisen, er det på tide å angi fargenesultatet som er generert som gjeldende farge. Dette er bare elementær matte. Summen av hver RGB-verdi er delt med totalt antall piksler i bildet for å generere gjennomsnittet. Fordi UIColor-metallsamtalen forventer flytende verdier og vi har håndtert heltall deler vi igjen med 255 for å få den likeverdige verdien som en flyte.

På dette punktet blir hver ramme fra kameraet behandlet og current holder gjennomsnittet av alle farger fra hver ramme! Ganske kul, men så langt har vi egentlig ikke forsterket noe. Brukeren har ingen måte å beneift fra denne informasjonen til vi gir et overlegg med dataene. Vi gjør det neste.


Trinn 6: Legg til grensesnittoverlegg

For å hjelpe brukerne til å forstå for informasjonen vi har beregnet, skal vi lage en overalje med tre objekter: a UILabel å holde hex-representasjonen av fargen, a UILabel for å holde RGB-representasjonen av fargen, og a UIView å faktisk vise fargen beregnet.

Åpne ARDemoViewController.xib-filen og legg til de to etikettene. Sett bakgrunnsfargen for hver til svart og skriftfargen til hvit. Dette vil sikre at det skiller seg ut på hvilken som helst bakgrunn. Deretter setter du skrift til noe som Helvetica og øker størrelsen til rundt 28. Vi vil at teksten skal være lett synlig. Koble disse etikettene til IBOutlets i ARDemoViewController.h, sørge for at de også syntetiseres i ARDemoViewController.m (Interface Builder kan nå gjøre dette for deg med dra og slipp). Gi navn på en etikett hexLabel og den andre rgbLabel.

Mens du fortsatt er i Interface Builder, dra og slipp a UIView på hovedvisningen og juster den for å være av størrelse og i den posisjonen du velger. Koble visningen som en IBOutlet og nev det colorSwatch.

Når du har fullført dette trinnet, bør XIB-filen din se slik ut:


Hvert objekt du har lagt til, skal også kobles via IBOutlets til ARDemoViewController.

Den siste tingen å gjøre er å sørge for at disse objektene er synlige etter at vi har lagt til forhåndsvisningslaget på skjermen. For å gjøre dette, legg til følgende linjer til -viewDidLoad i ARDemoViewController:

 [self.view bringeSubviewToFront: self.rgbLabel]; [self.view bringeSubviewToFront: self.hexLabel]; [self.view bringeSubviewToFront: self.colorSwatch];

Trinn 7: Lag en CADisplayLink

Fordi det findColorAverage: funksjonen må utføres så raskt som mulig for å hindre at senkerammer blir tapt i køen, og å gjøre et grensesnittarbeid i den funksjonen er ikke tilrådelig. I stedet er det findColorAverage: beregner bare den gjennomsnittlige fargen på rammen og sparer den for senere bruk. Vi kan nå sette opp en annen funksjon som faktisk vil gjøre noe med current verdi, og vi kan til og med plassere behandlingen på en separat tråd om nødvendig. For dette prosjektet, vil vi bare oppdatere grensesnittets overlegg ca 4 ganger per sekund. Vi kunne bruke en NSTimer for dette formålet, men jeg foretrekker å bruke en CADisplayLink når det er mulig fordi det er mer konsekvent og pålitelig enn NSTimer.

I ARDemoViewController implementeringsfil, legg til følgende i -viewDidLoad metode:

 CADisplayLink * updateTimer = [CADisplayLink displayLinkWithTarget: selvvalg: @selector (updateColorDisplay)]; [updateTimer setFrameInterval: 15]; [updateTimer addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];

CADisplayLink vil brann en oppdatering 60 ganger per sekund, så ved å sette rammeintervallet til 15, starter vi et anrop til velgeren updateColorDisplay 4 ganger hvert sekund.

For å forhindre at dette forårsaker en kjøretidsfeil, la oss gå videre og legge til en velgerstub:

 -(ugyldig) updateColorDisplay 

Trinn 8: Oppdater grensesnittverdier

Vi er nå klare til å faktisk utvide vår skjerm med praktisk nyttig informasjon om verden rundt oss! Legg til følgende linjer med kode til updateColorDisplay:

 -(void) updateColorDisplay self.colorSwatch.backgroundColor = self.currentColor; self.rgbLabel.text = [NSString stringWithFormat: @ "R:% d G:% d B:% d", (int) ([self.currentColor red] * 255.0f), (int) ([self.currentColor green ] * 255.0f), (int) ([self.currentColor blue] * 255.0f)]; self.hexLabel.text = [NSString stringWithFormat: @ "#% @", [self.currentColor hexStringFromColor]]; 

Det vi gjør over er egentlig ganske grei. Fordi current er lagret som en UIColor objekt, vi kan bare sette bakgrunnsfarge tilhører colorSwatch til det direkte. For begge etikettene konfigurerer vi bare en egendefinert NSString format og bruk av UIColor-Utvidet kategori for å enkelt få tilgang til både hex-representasjonen av fargen og RGB-verdiene.


Trinn 9: Teste applikasjonen

For å teste arbeidet ditt har jeg tatt med 3 enkle HTML-filer i "test" -mappen for prosjektnedlastingen. Hver fil har en solid bakgrunn (rød, grønn og blå). Hvis du fyller skjermbildet med denne nettsiden, åpner og peker på vår iPhone-app på skjermen, bør du kunne se riktig fargepopp på AR-skjermen.


Wrap Up

Gratulerer! Du har bygget din første ekte Forbedret virkelighet søknad!

Selv om vi sikkert kan diskutere verdien av informasjonen, forstår vi vårt syn på verden med, og jeg finner dette prosjektet langt mer interessant enn de fleste av de "stedbevisste" utvidede virkelighetsappene som er på markedet, inkludert den seminal " Rør "-applikasjon. Hvorfor? Fordi når jeg er i byen på jakt etter en t-bane, vil jeg ikke ha en "sirkel-av-jord" -pilen som peker meg gjennom bygninger mot målet mitt. I stedet er det uendelig mer praktisk å bare bruke veibeskrivelse fra Google Maps. Av denne grunn er alle lokasjonsbevisste Augmented Reality-applikasjoner jeg har kommet over, veldig lite mer enn et nyhetsprosjekt. Mens prosjektet vi bygde i dag, er også noe av et nyhetsprosjekt, håper jeg at det har vært et underholdende eksempel på hvordan du begynner å lage dine egne Augmetned Reality-applikasjoner som faktisk kan legge til meningsfull informasjon til verden rundt oss.

Hvis du fant denne veiledningen nyttig, gi meg beskjed på Twitter: @markhammonds. Jeg vil gjerne se hva du kommer med!


Hvor å gå fra her?

Som jeg er sikker på at du har gjettet, så langt har vi egentlig bare rørt overflaten av hva Augmented Reality-applikasjoner kan gjøre. Det neste trinnet i utdanningen din vil i stor grad avhenge av hva du vil oppnå med en AR-søknad.

Hvis du er interessert i stedbevisste Augmented Reality-applikasjoner, bør du virkelig tenke på å bruke en åpen kildekode AR-toolkit. Mens det sikkert er mulig for deg å kode din egen AR toolkit, er det en svært komplisert og avansert oppgave. Jeg vil oppfordre deg til ikke å oppfinne hjulet, og å se på disse prosjektene i stedet:

  • iOS AR Toolkit (åpen kildekode)
  • iOS AR Toolkit (Commercial)
  • 3Dar
  • Mixare
  • Wikitude

Noen få ekstra Augmented Reality-løsninger og prosjekter som støtter markørbaserte implementeringer inkluderer:

  • Qualcomm Augmented Reality SDK
  • NyARToolkit

Hvis du er spesielt interessert i bildebehandling som vi demonstrerte i denne opplæringen og ønsker å komme inn i mer avansert funksjon og objektgjenkjenning, inkluderer gode utgangspunkter:

  • OpenCV
  • ArUco
  • iOS 5 Core Image Framework

Neste gang?

Som jeg håper du kan se fra innholdet i denne opplæringen, er Augmented Reality et utrolig bredt emne med mange forskjellige veier å forfølge, fra bildebehandling og objektgjenkjenning til plassering og enda 3D-spilling.

Vi er absolutt interessert i å dekke alle de ovennevnte på Mobiletuts +, men vi vil sørge for at innholdet vårt er relevant for hva leserne ønsker å se. Gi oss beskjed om hvilket aspekt av Augmented Reality du vil se flere opplæringsprogrammer på, enten ved å legge igjen en kommentar på Mobiletuts +, legge ut på vår Facebook-gruppevegg eller meld meg direkte på twitter: @markhammonds.

Til neste gang? takk for at du leste!