Bine ați venit la cea mai recentă tranșă din seriile noastre premium de creștere a realității cu SDK-ul iOS! În tutorialul de astăzi, vă voi învăța cum să procesați și să analizați streaming video live de pe aparatul de fotografiat al aparatului, pentru a vă îmbunătăți viziunea asupra lumii cu informații de suprapunere utile.
În primul post din această serie, te-am prezentat la platformei AV Foundation
cadru și am făcut toate lucrările necesare pentru a începe să afișăm fluxul camerei în cadrul aplicației noastre. Dacă nu ați făcut-o deja, asigurați-vă că ați verificat partea 1!
Termenul "Realitatea augmentată" a devenit o frază în ultimii ani, deoarece smartphone-urile au devenit destul de puternice pentru a plasa această tehnologie în buzunarele noastre. Din păcate, toată publicitatea care înconjoară termenul a generat o mulțime de confuzie cu privire la ceea ce este realitatea augmentată și cum poate fi folosită pentru a spori interacțiunea noastră cu lumea. Pentru a clarifica, scopul unei aplicații Realitatea augmentată ar trebui să fie să permită vizualizarea sau percepția actuală a utilizatorului asupra lumii și să sporească această percepție prin furnizarea de informații sau perspective suplimentare care nu sunt evidente în mod natural. Una dintre cele mai vechi și mai practice implementări ale Realității Augmented este evidențiarea "First Down" frecvent observată atunci când vizionați jocuri de fotbal american la televizor. Natura subtilă a acestei suprapuneri este exact ceea ce o face o utilizare perfectă a AR. Utilizatorul nu este distras de tehnologie, dar perspectiva lor este în mod natural sporită.
Demo aplicația Augmented Reality, pe care o vom construi astăzi, este foarte tematic similară cu cea a primului și a 10-lea sistem de fotbal. Vom prelucra fiecare cadru video de la cameră și vom calcula culoarea medie RGB a cadrului respectiv. Apoi vom afișa rezultatul ca o suprapunere RGB, Hex și color swatch. Aceasta este o îmbunătățire simplă și ușoară a vizualizării, dar ar trebui să servească scopul nostru de a demonstra cum să accesăm fluxul video al camerei și să îl procesăm cu un algoritm personalizat. Deci sa începem!
Pentru a finaliza acest tutorial, vom avea nevoie de câteva cadre suplimentare.
Urmând aceleași pași descriși în pasul 1 din tutorialul anterior din această serie, importați aceste cadre:
După cum sugerează și numele, acest cadru este folosit pentru procesarea video și este în primul rând responsabil pentru furnizarea suportului pentru tamponarea video. Principalul tip de date de care suntem interesați din acest cadru este CVImageBufferRef
, care vor fi utilizate pentru a accesa datele de imagine tamponate din fluxul nostru video. De asemenea, vom folosi multe funcții CoreVideo, inclusiv CVPixelBufferGetBytesPerRow ()
și CVPixelBufferLockBaseAddress ()
. Ori de câte ori vedeți un "CV" prefixat unui nume de tip de date sau de funcție, veți ști că a venit din CoreVideo.
Core Media oferă suportul la nivel scăzut pe care este construit cadrul Fundației AV (adăugat în ultimul tutorial). Suntem foarte interesați doar de CMSampleBufferRef
tipul de date și CMSampleBufferGetImageBuffer ()
din acest cadru.
Quartz Core este responsabil pentru o mare parte din animația pe care o vedeți atunci când utilizați un dispozitiv iOS. Avem nevoie doar de acest lucru în proiectul nostru pentru un singur motiv, CADisplayLink
. Mai multe despre acest lucru mai târziu în tutorial.
În plus față de aceste cadre, vom avea de asemenea nevoie de proiectul UIColor Utilties pentru a adăuga o categorie la îndemână UIColor
obiect. Pentru a obține acest cod, puteți vizita pagina de proiect pe GitHub sau doar căutați fișierele UIColor-Expanded.h și UIColor-Expanded.m în codul sursă al acestui tutorial. În orice caz, va trebui să adăugați ambele fișiere în proiectul dvs. Xcode și apoi să importați categoria în ARDemoViewController.m:
#import "UIColor-Expanded.h"
Când am rămas în ultimul tutorial, am implementat un strat de previzualizare care arăta ce a fost capabil să vadă aparatul foto, dar nu am avut de fapt o modalitate de a accesa și procesa datele cadrului. Primul pas în acest sens este delegatul Fundației AV -captureOutput: didOutputSampleBuffer: fromConnection:
. Această metodă de delegat ne va permite accesul la un CMSampleBufferRef
din datele de imagine pentru cadrul video curent. Va trebui să masăm aceste date într-o formă utilă, dar acest lucru va fi un început bun.
În ARDemoViewController.h, trebuie să vă conformați delegatului potrivit:
@interface ARDemoViewController: UIViewController
Acum adăugați metoda delegatului în ARDemoViewController.m:
- (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) conexiune
Ultimul pas pe care trebuie să-l facem este să modificăm codul nostru de flux video din ultimul tutorial pentru a desemna controlorul curent de vizualizare drept delegat:
// Configurați ieșirea sesiunii de captare AVCaptureVideoDataOutput * videoOut = [[AVCaptureVideoDataOutput alloc] init]; [videoOut setAlwaysDiscardsLateVideoFrames: YES]; NSDictionary * videoSettings = [NSDictionary dicționarWithObject: [NSNumber numberWithInt: kCVPixelFormatType_32BGRA] pentruKey: (id) kCVPixelBufferPixelFormatTypeKey]; [videoOut setVideoSettings: videoSettings]; dispatch_queue_t color_queue = dispatch_queue_create ("com.mobiletuts.ardemo.processcolors", NULL); [videoOut setSampleBufferDelegate: coada de sine: color_queue]; [cameraCaptureSession addOutput: videoOut];
În linia 5 - 8 de mai sus, configurăm setări suplimentare pentru ieșirea video generată de site-ul nostru AVCaptureSession
. Mai specific, specificăm că dorim să primim fiecare tampon pixel formatat ca kCVPixelFormatType_32BGRA
, care în principiu înseamnă că acesta va fi returnat ca valoare de 32 biți comandată ca canale albastre, verde, roșii și alfabetice. Acesta este un pic diferit de RGBA de 32 de biți, care este cel mai frecvent utilizat, dar o ușoară modificare a ordonării canalelor nu ne va încetini. :)
Apoi, pe liniile 9-10 vom crea o coadă de expediere pe care tamponul de eșantionare va fi procesat în cadrul. O coadă este necesară pentru a ne oferi suficient timp pentru a procesa într-adevăr un cadru înainte de a primi următorul. În timp ce acest lucru teoretic înseamnă că vizionarea camerei noastre este capabilă doar să vadă trecutul, întârzierea nu ar trebui să fie deloc vizibilă dacă procesați fiecare cadru în mod eficient.
Cu codul de mai sus în loc, ARDemoViewController
ar trebui să înceapă să primească apeluri delegate cu un CMSampleBuffer al fiecărui cadru video!
Acum că avem a CMSampleBuffer
trebuie să îl convertim într-un format pe care îl putem prelucra mai ușor. Pentru că lucrăm cu Core Video, formatul ales de noi va fi CVImageBufferRef
. Adăugați următoarea linie la metoda delegat:
- (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) conexiune CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer (sampleBuffer);
Bine, acum este momentul să începeți de fapt procesarea acestui tampon de pixeli. Pentru acest tutorial, construim o aplicație Augmented Reality, care poate să privească orice scenă sau imagine și să ne spună ce înseamnă media de culoare Hex și RGB a cadrului. Să creați o nouă metodă numită findColorAverage:
doar pentru a îndeplini această sarcină.
Adăugați declarația de metodă în ARDemoViewController.h:
- (void) findColorAverage: (CVImageBufferRef) pixelBuffer;
Apoi adăugați un șir de implementare în ARDemoViewController.m:
- (void) findColorAverage: (CVImageBufferRef) pixelBuffer
În cele din urmă, apelați noua metodă la sfârșitul implementării delegatului:
- (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) conexiune CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer (sampleBuffer); [auto findColorAverage: pixelBuffer];
Pentru fiecare cadru video pe care îl primim, vrem să calculăm media tuturor valorilor pixelilor din imagine. Primul pas în acest sens este de a repeta pe fiecare pixel conținut în CVImageBufferRef
. În timp ce algoritmul pe care îl vom folosi în restul acestui tutorial este specific aplicației noastre, acest pas special, iterând peste pixelii de cadru, este o sarcină foarte obișnuită în multe aplicații de Augmented Reality diferite de acest tip.
Următorul cod se va repeta pe fiecare pixel din cadrul:
- (void) findColorAverage: (CVImageBufferRef) pixelBuffer CVPixelBufferLockBaseAddress (pixelBuffer, 0); int bufferHeight = CVPixelBufferGetHeight (pixelBuffer); int bufferWidth = CVPixelBufferGetWidth (pixelBuffer); int bytesPerRow = CVPixelBufferGetBytesPerRow (pixelBuffer); nesignificat char * pixel; caracterele nesemnate * rowBase = (caracterele nesemnate *) CVPixelBufferGetBaseAddress (pixelBuffer); pentru (int rând = 0; rând < bufferHeight; row += 8 ) for( int column = 0; column < bufferWidth; column += 8 ) pixel = rowBase + (row * bytesPerRow) + (column * 4); CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );
Pe linia 3 de mai sus, sunăm CVPixelBufferLockBaseAddress
pe tamponul nostru de pixeli pentru a preveni apariția unor modificări în timpul procesării datelor.
Pe liniile 5 - 7, primim câteva informații de bază despre memoria tampon pixel, și anume înălțimea și lățimea tamponului și numărul de octeți stocați în fiecare rând.
Pe linia 9, un pointer de caractere este folosit pentru a declara un pixel. În C, tipul de date despre caractere poate conține un singur octet de date. În mod implicit, un singur octet de date din C poate conține orice număr întreg în intervalul -128 până la 127. Dacă acel octet este "nesemnat", poate să dețină o valoare între 0 și 255. De ce este important acest lucru? Deoarece fiecare dintre valorile RGB pe care dorim să le accesăm se află în intervalul 0 - 255, ceea ce înseamnă că acestea necesită un singur octet pentru stocare. De asemenea, vă reamintim că am configurat ieșirea video pentru a returna o valoare de 32 biți în format BGRA. Deoarece fiecare octet este egal cu 8 biți, acest lucru ar trebui să aibă mai mult sens acum: 8 (B) + 8 (G) + 8 (R) + 8 (A) = 32. Pentru a rezuma: pentru a ne referi la datele pixelilor, deoarece fiecare valoare RGBA conține un octet de date.
Pe linia 11 folosim un pointer pentru a face referire la adresa de pornire a tamponului pixel în memorie. Un char este folosit aici din același motiv pentru care este folosit pentru variabila noastră de pixeli. Întregul reflexor tampon pixel este doar o serie de valori RGBA repetate, așadar prin setarea variabilei rowBase la prima adresă de memorie din buffer, vom putea să începem cu buclă peste toate valorile următoare.
Linile 13 - 14 formează o buclă imbricată care va itera peste fiecare valoare a pixelilor din tamponul pixel.
Pe linia 16, de fapt, atribuim tampon pixel la adresa de memorare de început a secvenței actuale RGBA. De aici vom putea să menționăm fiecare octet în ordine.
În cele din urmă, pe linia 21, deblocăm tamponul pixel după finalizarea procesării.
Repetarea tamponului de pixeli nu ne va face mult mai bine dacă nu vom folosi informațiile. Pentru proiectul nostru dorim să întoarcem o singură culoare care reprezintă media tuturor pixelilor din cadru. Începeți prin adăugarea unui a currentColor
variabilă la fișierul .h:
UICcolor * currentColor; @property (nonatomic, păstrează) UICoror * currentColor;
Asigurați-vă că ați sintetizat, de asemenea, această valoare:
@synthesize currentColor;
Apoi, modificați findColorAverage:
astfel:
- (void) findColorAverage: (CVImageBufferRef) pixelBuffer CVPixelBufferLockBaseAddress (pixelBuffer, 0); int bufferHeight = CVPixelBufferGetHeight (pixelBuffer); int bufferWidth = CVPixelBufferGetWidth (pixelBuffer); int bytesPerRow = CVPixelBufferGetBytesPerRow (pixelBuffer); nesemnate int red_sum = 0; nesemnate int green_sum = 0; nesemnate int blue_sum = 0; nesignificat int alpha_sum = 0; număr nesemnat int = 0; nesignificat char * pixel; caracterele nesemnate * rowBase = (caracterele nesemnate *) CVPixelBufferGetBaseAddress (pixelBuffer); pentru (int rând = 0; rând < 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];
Puteți vedea că am pornit modificările prin adăugarea variabilelor red_sum
, green_sum
, blue_sum
, alpha_sum
, și numara
. Calculul unei valori medii pentru valorile pixelilor se face în același mod în care ați calcula o medie pentru orice altceva. Deci, variabilele sumelor noastre RGBA vor păstra suma totală a fiecărei valori în timp ce vom interacționa, și numara
variabila va tine numarul total de pixeli, crescand de fiecare data prin bucla.
Încadrarea pixelilor apare de fapt pe liniile 24-26. Deoarece variabila noastră de pixeli este doar un indicator pentru un octet particular din memorie, putem accesa adresele de memorie ulterioare, la fel cum s-ar putea să vă așteptați la o matrice. Rețineți că ordonarea BGRA este ceea ce v-ați aștepta la valorile indexului: B = 0, G = 1, R = 2, A = 3. Nu vom folosi valoarea alfa pentru nimic util în acest tutorial, dar am l-am inclus aici de dragul exhaustivității.
După ce buclele noastre imbricate au terminat să iterăm prin matrice, este timpul să setăm rezultatul culorilor generat ca culoarea curentă. Aceasta este doar matematica elementara. Suma fiecărei valori RGB este împărțită la numărul total de pixeli din imagine pentru a genera media. Deoarece apelul metodei UICcolor se așteaptă la valori flotante și ne-am ocupat de numere întregi, noi împărțim din nou cu 255 pentru a obține valoarea echivalentă ca float.
În acest moment, fiecare cadru din cameră este procesat și currentColor
păstrează media tuturor culorilor din fiecare cadru! Destul de cool, dar până acum nu am majorat nimic. Utilizatorul nu are nici o modalitate de a beneficia de aceste informații până când nu furnizăm o suprapunere cu datele. O să facem asta în continuare.
Pentru a ajuta utilizatorii să înțeleagă informația pe care am calculat-o, vom crea o problemă cu trei obiecte: a UILabel
pentru a menține reprezentarea hexagonală a culorii, a UILabel
pentru a menține reprezentarea RGB a culorii și a UIView
pentru a afișa efectiv culoarea calculată.
Deschideți fișierul ARDemoViewController.xib și adăugați cele două etichete. Setați culoarea de fundal pentru fiecare în negru, iar culoarea fontului este albă. Acest lucru va asigura că se evidențiază pe orice fundal. Apoi, setați fontul la ceva asemănător cu Helvetica și măriți dimensiunea la aproximativ 28. Vrem ca textul să fie ușor vizibil. Conectați aceste etichete la IBOutlets
în ARDemoViewController.h, asigurându-vă că acestea sunt, de asemenea, sintetizate în ARDemoViewController.m (Interface Builder poate face acest lucru pentru tine cu drag-and-drop). Denumiți o etichetă hexLabel
și celălalt rgbLabel
.
În timp ce vă aflați încă în Interface Builder, trageți-și-drop a UIView
pe ecranul principal și reglați-o astfel încât să fie de dimensiunea și în poziția aleasă de dvs. Conectați vizualizarea ca a IBOutlet
și numește-o colorSwatch
.
După ce ați finalizat acest pas, fișierul dvs. XIB ar trebui să arate astfel:
Fiecare obiect pe care l-ați adăugat ar trebui să fie conectat prin intermediul IBOutlets la ARDemoViewController
.
Ultimul lucru pe care trebuie să-l faceți este să vă asigurați că aceste obiecte sunt vizibile după ce adăugăm stratul de previzualizare pe ecran. Pentru aceasta, adăugați următoarele linii -viewDidLoad
în ARDemoViewController
:
[self.view bringSubviewToFront: self.rgbLabel]; [self.view bringSubviewToFront: self.hexLabel]; [self.view bringSubviewToFront: self.colorSwatch];
Deoarece findColorAverage:
funcția trebuie să fie executată cât mai repede posibil pentru a preveni căderea cadrelor târzii în coadă, făcând o interfață în această funcție nu este recomandabilă. În schimb, findColorAverage:
calculează pur și simplu culoarea medie a cadrului și îl salvează pentru o utilizare ulterioară. Acum putem configura oa doua funcție care va face de fapt ceva cu currentColor
valoare, și am putea chiar să plasăm acea prelucrare pe un fir separat, dacă este necesar. Pentru acest proiect, dorim doar să actualizăm suprapunerea interfeței de aproximativ 4 ori pe secundă. Am putea folosi unul NSTimer
în acest scop, dar prefer să folosesc a CADisplayLink
ori de câte ori este posibil, deoarece este mai consistent și mai sigur decât NSTimer
.
În ARDemoViewController
fișier de implementare, adăugați următoarele la -viewDidLoad
metodă:
CADisplayLink * updateTimer = [afișare CADisplayLinkLinkWithTarget: selector auto: @selector (updateColorDisplay)]; [updateTimer setFrameInterval: 15]; [updateTimer addToRunLoop: [NSRunLoop currentRunLoop] pentruMode: NSDefaultRunLoopMode];
CADisplayLink
va declanșa o actualizare de 60 de ori pe secundă, prin setarea intervalului de cadre la 15, vom iniția un apel către selector updateColorDisplay
De 4 ori pe secundă.
Pentru a preveni aceasta din cauza unei erori de execuție, hai să mergem mai departe și să adăugăm un selector:
-(void) updateColorDisplay
Acum suntem pregătiți să îmbunătățim afișarea noastră cu informații cvasi-utile despre lumea din jurul nostru! Adăugați următoarele linii de cod la updateColorDisplay
:
-(void) updateColorDisplay auto.colorSwatch.backgroundColor = auto.currentColor; auto.rgbLabel.text = [NSString șirWithFormat: @ "R:% d G:% d B:% d", (int) ] * 255.0f), (int) ([auto.currentColor albastru] * 255.0f)]; self.hexLabel.text = [NSString șirWithFormat: @ "#% @", [self.currentColor hexStringFromColor]];
Ceea ce facem mai sus este foarte simplu. pentru că currentColor
este stocat ca un UIColor
obiect, putem seta doar culoare de fundal
proprietatea colorSwatch
să-l direct. Pentru ambele etichete, setăm doar un obicei NSString
format și de a utiliza UIColor-extins
pentru a accesa cu ușurință atât reprezentarea hexagonală a culorilor, cât și valorile RGB.
Pentru a vă testa munca, am inclus 3 fișiere HTML simple în dosarul "test" al descărcării proiectului. Fiecare fișier are un fundal solid (roșu, verde și albastru). Dacă completați monitorul computerului cu această pagină web deschisă și indicați aplicația iPhone pe ecran, ar trebui să vedeți afișajul de culoare corespunzător pe afișajul AR.
Felicitări! Tu ți-ai construit primul real Aplicație realitate augmentată!
Deși cu siguranță putem dezbate valoarea informațiilor pe care le îmbogățește opinia noastră asupra lumii, eu personal găsesc acest proiect mult mai interesant decât majoritatea aplicațiilor de realitate augmentate "în funcție de locație" care se află pe piață, inclusiv " Tuburi ". De ce? Pentru că atunci când mă aflu în oraș căutând un metrou, nu vreau săgeata "cercului-pământului" care să mă îndrepte spre clădire spre obiectivul meu. În schimb, este practic mai practic să utilizați pur și simplu indicații de orientare din Google Maps. Din acest motiv, fiecare aplicație de realitate Augmented Reality, pe care am întâlnit-o, este într-adevăr puțin mai mult decât un proiect de noutate. În timp ce proiectul pe care l-am construit astăzi este, de asemenea, oarecum un proiect de noutate, speranța mea este că a fost un exemplu distractiv de a începe să-ți faci propriile aplicații Augmented Reality care pot adăuga informații semnificative lumii din jurul nostru.
Dacă ați găsit acest tutorial util, permiteți-mi să știu pe Twitter: @markhammonds. Mi-ar plăcea să văd ce vă aduceți!
Așa cum sunt sigur că ați ghicit, până acum am atins doar suprafața a ceea ce pot face aplicațiile Augmented Reality. Următorul pas în educație va depinde în mare măsură de ceea ce doriți să obțineți cu o aplicație AR.
Dacă sunteți interesat de aplicațiile Augmented Reality, ar trebui să vă gândiți cu adevărat la utilizarea unui kit de instrumente AR cu sursă deschisă. Deși este cu siguranță posibil să codificați propriul set de instrumente AR, aceasta este o sarcină foarte complexă și avansată. V-aș încuraja să nu reinventați roata și să vă uitați la aceste proiecte:
Câteva soluții și proiecte de îmbunătățire a realității augmentate care sprijină implementările bazate pe markere includ:
Dacă sunteți interesat în mod special de procesarea imaginilor, așa cum am demonstrat în acest tutorial și am dori să obținem recunoașterea mai avansată a funcțiilor și a obiectelor, punctele de plecare bune includ:
După cum sper că puteți vedea din conținutul acestui tutorial, Realitatea Augmented este un subiect incredibil de larg, cu multe căi diferite de urmărit, de la procesarea imaginilor și recunoașterea obiectelor până la localizarea și chiar jocurile 3D.
Suntem cu siguranță interesați să acoperim toate cele de mai sus pe Mobiletuts +, dar dorim să ne asigurăm că conținutul nostru este relevant pentru ceea ce cititorii ar dori să vadă. Spuneți-ne ce aspect al realității augmentate ați dori să vedeți mai multe tutoriale, fie lăsând un comentariu pe Mobiletuts +, postând pe peretele grupului nostru Facebook, sau trimiteți-mi mesaje direct pe twitter: @markhammonds.
Pana data viitoare? mulțumesc pentru citire!