SDK iOS Réalité augmentée traitement vidéo

Bienvenue dans le dernier volet de notre série premium sur la réalité augmentée avec le SDK iOS! Dans le tutoriel d'aujourd'hui, je vais vous apprendre à traiter et à analyser le streaming vidéo en direct à partir de la caméra de l'appareil afin d'améliorer notre vision du monde avec des informations de superposition utiles..


Où nous nous sommes laissés?

Dans le premier article de cette série, je vous ai présenté le AVFoundation cadre et nous avons effectué tous les préparatifs nécessaires pour afficher le flux de la caméra dans notre application. Si vous ne l'avez pas déjà fait, assurez-vous de consulter la partie 1!


L'application de démonstration d'aujourd'hui

Le terme "réalité augmentée" est devenu une expression à la mode ces dernières années, les smartphones étant devenus suffisamment puissants pour placer cette technologie dans nos poches. Malheureusement, toute la publicité entourant ce terme a généré une grande confusion quant à ce qu'est la réalité augmentée et à la manière dont il peut être utilisé pour améliorer notre interaction avec le monde. Pour clarifier, l'objectif d'une application de réalité augmentée devrait être de prendre en compte la vue ou la perception du monde existante d'un utilisateur et d'améliorer cette perception en fournissant des informations supplémentaires ou des perspectives qui ne sont pas naturellement apparentes. L'une des applications les plus anciennes et les plus pratiques de la réalité augmentée est le point culminant du "Premier essai" généralement observé lorsque vous regardez des matchs de football américain à la télévision. La nature subtile de cette superposition est exactement ce qui en fait une utilisation parfaite de la RA. L'utilisateur n'est pas distrait par la technologie, mais sa perspective est naturellement améliorée.

La démo de l’application de réalité augmentée que nous allons construire aujourd’hui est très similaire au système Football first and 10. Nous allons traiter chaque image vidéo de la caméra et calculer la couleur RVB moyenne de cette image. Nous afficherons ensuite le résultat sous forme de superposition de nuance RVB, Hex et couleur. Il s’agit d’une simple amélioration de la vue, mais elle devrait également nous permettre de montrer comment accéder au flux vidéo de la caméra et le traiter à l’aide d’un algorithme personnalisé. Alors, commençons!


Étape 1: Ajouter des ressources de projet

Pour compléter ce tutoriel, nous aurons besoin de quelques frameworks supplémentaires.

En suivant les étapes décrites à l'étape 1 du didacticiel précédent de cette série, importez ces cadres:

  • Cadre vidéo de base

    Comme son nom l'indique, ce cadre est utilisé pour le traitement de la vidéo et est principalement responsable de la prise en charge de la mise en mémoire tampon de la vidéo. Le type de données principal de ce cadre qui nous intéresse est: CVImageBufferRef, qui sera utilisé pour accéder aux données d’image tamponnées à partir de notre flux vidéo. Nous utiliserons également de nombreuses fonctions CoreVideo, notamment: CVPixelBufferGetBytesPerRow () et CVPixelBufferLockBaseAddress (). Chaque fois que vous verrez un "CV" ajouté à un type de données ou à un nom de fonction, vous saurez qu'il provient de CoreVideo..

  • Cadre multimédia de base

    Core Media fournit le support de bas niveau sur lequel repose la structure AV Foundation (ajoutée dans le dernier tutoriel). Nous sommes vraiment juste intéressés par le CMSampleBufferRef type de données et le CMSampleBufferGetImageBuffer () fonctionner à partir de ce cadre.

  • Cadre de base de quartz

    Quartz Core est responsable de la plupart des animations que vous voyez lorsque vous utilisez un appareil iOS. Nous n’avons besoin de cela dans notre projet que pour une raison, la CADisplayLink. Plus d'informations à ce sujet plus tard dans le tutoriel.

Outre ces frameworks, nous aurons également besoin du projet UIColor Utilties pour ajouter une catégorie pratique au UIColor objet. Pour obtenir ce code, vous pouvez soit visiter la page du projet sur GitHub, soit simplement rechercher les fichiers. UIColor-Expanded.h et UIColor-Expanded.m dans le code source de ce tutoriel. De toute façon, vous devrez ajouter ces deux fichiers à votre projet Xcode, puis importer la catégorie dans ARDemoViewController.m:

 #import "UIColor-Expanded.h"

Étape 2: Implémentation du délégué AVCapture

Lorsque nous avions arrêté le dernier tutoriel, nous avions implémenté une couche de prévisualisation affichant ce que la caméra de l'appareil pouvait voir, mais nous n'avions en réalité aucun moyen d'accéder aux données de la trame et de les traiter. La première étape est le délégué de la Fondation AV. -captureOutput: didOutputSampleBuffer: fromConnection:. Cette méthode de délégué nous donnera accès à un CMSampleBufferRef des données d'image pour l'image vidéo en cours. Nous devrons transformer ces données en une forme utile, mais ce sera un bon début.

Dans ARDemoViewController.h, vous devez vous conformer au bon délégué:

 @interface ARDemoViewController: UIViewController 

Maintenant, ajoutez la méthode delegate dans ARDemoViewController.m:

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

La dernière étape consiste à modifier le code de notre flux vidéo à partir du dernier tutoriel pour désigner le contrôleur de vue actuel en tant que délégué:

 // Configuration de la sortie de la session de capture AVCaptureVideoDataOutput * videoOut = [[AVCaptureVideoDataOutput alloc] init]; [videoOut setAlwaysDiscardsLateVideoFrames: YES]; NSDictionary * videoSettings = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: kCVPixelFormatType_32BGRA] pourKey: (id) kCVPixelBufferPixelFormatTypeKey]; [videoOut setVideoSettings: videoSettings]; dispatch_queue_t color_queue = dispatch_queue_create ("com.mobiletuts.ardemo.processcolors", NULL); [videoOut setSampleBufferDelegate: file d'attente automatique: file_couleur]; [cameraCaptureSession addOutput: videoOut];

Aux lignes 5 à 8 ci-dessus, nous configurons des paramètres supplémentaires pour la sortie vidéo générée par notre AVCaptureSession. Plus précisément, nous spécifions que nous voulons recevoir chaque tampon de pixels au format kCVPixelFormatType_32BGRA, ce qui signifie fondamentalement qu'il sera renvoyé sous forme de valeur 32 bits ordonnée sous forme de canaux bleu, vert, rouge et alpha. Ceci est un peu différent du RGBA 32 bits, qui est le plus couramment utilisé, mais un léger changement dans l’ordre des canaux ne nous ralentira pas. :)

Ensuite, aux lignes 9 à 10, nous créons une file d’attribution dans laquelle le tampon échantillon sera traité. Une file d’attente est nécessaire pour nous laisser suffisamment de temps pour traiter réellement une image avant de recevoir la suivante. Cela signifie théoriquement que notre vision de la caméra n'est jamais capable que de voir le passé, mais le retard ne devrait pas être perceptible du tout si vous traitez chaque image efficacement.

Avec le code ci-dessus en place, le ARDemoViewController devrait commencer à recevoir des appels de délégués avec un CMSampleBuffer de chaque image vidéo!


Étape 3: Convertir le tampon d'échantillon

Maintenant que nous avons un CMSampleBuffer nous devons le convertir en un format que nous pouvons traiter plus facilement. Parce que nous travaillons avec Core Video, le format de notre choix sera CVImageBufferRef. Ajoutez la ligne suivante à la méthode delegate:

 - (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) connexion CVImageBufferRef pixelBuffer = CMSampleBufferGeterBeterGetImage (tamponBuffer); 

Bon, maintenant il est temps de commencer à traiter réellement ce tampon de pixels. Pour ce tutoriel, nous construisons une application de réalité augmentée qui peut regarder n'importe quelle scène ou image et nous dire quelle est la moyenne des couleurs Hex et RVB du cadre. Créons une nouvelle méthode appelée findColorAverage: juste pour accomplir cette tâche.

Ajouter la déclaration de méthode dans ARDemoViewController.h:

 - (void) findColorAverage: (CVImageBufferRef) pixelBuffer;

Et puis ajoutez un talon de mise en œuvre dans ARDemoViewController.m:

 - (void) findColorAverage: (CVImageBufferRef) pixelBuffer 

Enfin, appelez la nouvelle méthode à la fin de l'implémentation du délégué:

 - (void) captureOutput: (AVCaptureOutput *) captureOutput didOutputSampleBuffer: (CMSampleBufferRef) sampleBuffer fromConnection: (AVCaptureConnection *) connexion CVImageBufferRef pixelBuffer = CMSampleBufferGeterBeterGetImage (tamponBuffer); [auto findColorAverage: pixelBuffer]; 

Étape 4: Itérer sur la mémoire tampon de pixels

Pour chaque image vidéo que nous recevons, nous souhaitons calculer la moyenne de toutes les valeurs de pixels contenues dans l'image. Pour ce faire, la première étape consiste à parcourir chaque pixel contenu dans la CVImageBufferRef. Bien que l'algorithme que nous allons utiliser dans la suite de ce didacticiel soit spécifique à notre application, cette étape particulière, qui consiste à effectuer une itération sur les pixels de la trame, est une tâche très courante dans de nombreuses applications de ce type en réalité augmentée..

Le code suivant va parcourir chaque pixel du cadre:

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

Sur la ligne 3 ci-dessus, nous appelons CVPixelBufferLockBaseAddress sur notre mémoire tampon de pixels pour empêcher toute modification des données pendant son traitement.

Sur les lignes 5 à 7, nous obtenons des méta-informations de base sur le tampon de pixels, à savoir la hauteur et la largeur du tampon et le nombre d'octets stockés dans chaque ligne..

Sur la ligne 9, un pointeur de caractère est utilisé pour déclarer un pixel. En C, le type de données char peut contenir un seul octet de données. Par défaut, un seul octet de données en C peut contenir n'importe quel entier compris entre -128 et 127. Si cet octet est "non signé", il peut contenir un entier compris entre 0 et 255. Pourquoi est-ce important? Parce que chacune des valeurs RVB auxquelles nous voulons accéder se situe dans la plage de 0 à 255, ce qui signifie qu'elles ne nécessitent qu'un seul octet à stocker. Vous vous souviendrez également que nous avons configuré notre sortie vidéo pour renvoyer une valeur 32 bits au format BGRA. Parce que chaque octet est égal à 8 bits, cela devrait avoir plus de sens maintenant: 8 (B) + 8 (G) + 8 (R) + 8 (A) = 32. Pour résumer: nous utilisons un pointeur de caractère faire référence à nos données de pixels, car chaque valeur RGBA contient un octet de données.

Sur la ligne 11, nous utilisons un pointeur pour référencer l'adresse de départ du tampon de pixels en mémoire. Un caractère est utilisé ici pour la même raison qu'il est utilisé pour notre variable de pixel. La totalité de la mémoire tampon de pixels ref est juste une série de valeurs RGBA répétitives. Par conséquent, en définissant la variable rowBase à la première adresse mémoire du tampon, nous pourrons commencer à boucler sur toutes les valeurs ensuite..

Les lignes 13 à 14 forment une boucle imbriquée qui itérera sur chaque valeur de pixel dans le tampon de pixels..

Sur la ligne 16, nous assignons effectivement le tampon de pixels à l'adresse mémoire de début de la séquence RGBA en cours. De là, nous pourrons référencer chaque octet dans la séquence.

Enfin, à la ligne 21, nous déverrouillons le tampon de pixels après avoir terminé notre traitement..


Étape 5: Trouver la moyenne des couleurs de cadre

Itérer sur le tampon de pixels ne nous fera pas beaucoup de bien à moins d'utiliser les informations. Pour notre projet, nous cherchons à renvoyer une seule couleur qui représente la moyenne de tous les pixels du cadre. Commencez par ajouter un couleur courante variable dans le fichier .h:

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

Veillez également à synthétiser cette valeur:

 @synthèse currentColor;

Ensuite, modifiez le findColorAverage: méthode comme suit:

 - (void) findColorAverage: (CVImageBufferRef) pixelBuffer CVPixelBufferLockBaseAddress (pixelBuffer, 0); int bufferHeight = CVPixelBufferGetHeight (pixelBuffer); int bufferWidth = CVPixelBufferGetWidth (pixelBuffer); int bytesPerRow = CVPixelBufferGetBytesPerRow (pixelBuffer); unsigned int red_sum = 0; unsigned int green_sum = 0; unsigned int blue_sum = 0; unsigned int alpha_sum = 0; unsigned int count = 0; unsigned char * pixel; unsigned char * rowBase = (unsigned char *) CVPixelBufferGetBaseAddress (pixelBuffer); pour (int row = 0; row < 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]; 

Vous pouvez voir que nous avons commencé nos modifications en ajoutant les variables red_sum, green_sum, blue_sum, alpha_sum, et compter. Le calcul d'une moyenne pour les valeurs de pixels s'effectue de la même manière que vous calculeriez une moyenne pour toute autre chose. Donc, nos variables de somme RGBA tiendra la somme totale de chaque valeur que nous interagissons, et compter variable tiendra le nombre total de pixels, incrémentant à chaque fois à travers la boucle.

L'attribution de pixels se produit en fait sur les lignes 24 à 26. Etant donné que notre variable de pixel est simplement un pointeur sur un octet particulier de la mémoire, nous sommes en mesure d'accéder aux adresses de mémoire suivantes, exactement comme vous le souhaiteriez avec un tableau. Notez que l'ordre BGRA correspond à ce que vous attendez pour les valeurs d'index: B = 0, G = 1, R = 2, A = 3. Nous n'utiliserons réellement la valeur alpha pour rien qui soit utile dans ce tutoriel, mais j'ai inclus ici par souci d'exhaustivité.

Une fois que notre boucle imbriquée a terminé son itération dans le tableau, il est temps de définir le résultat de couleur généré comme couleur actuelle. Ceci est juste des mathématiques élémentaires. La somme de chaque valeur RVB est divisée par le nombre total de pixels de l'image pour générer la moyenne. Comme l'appel de la méthode UIColor attend des valeurs float et que nous avons affaire à des entiers, nous divisons à nouveau par 255 pour obtenir la valeur équivalente sous forme de float.

À ce stade, chaque image de la caméra est en cours de traitement et couleur courante détient la moyenne de toutes les couleurs de chaque image! Plutôt cool, mais jusqu'ici nous n'avons pas vraiment augmenté quoi que ce soit. L'utilisateur n'a aucun moyen de bénéficier de ces informations jusqu'à ce que nous fournissions une superposition des données. Nous ferons ça ensuite.


Étape 6: Ajouter des superpositions d'interface

Pour aider les utilisateurs à comprendre les informations que nous avons calculées, nous allons créer une analyse à trois objets: a UILabel tenir la représentation hexadécimale de la couleur, un UILabel pour tenir la représentation RVB de la couleur, et un UIView pour afficher la couleur calculée.

Ouvrez le fichier ARDemoViewController.xib et ajoutez les deux étiquettes. Définissez la couleur d'arrière-plan pour chacun en noir et la couleur de police en blanc. Cela garantira qu'il se démarque sur n'importe quel fond. Définissez ensuite la police sur Helvetica et augmentez la taille à environ 28. Nous souhaitons que le texte soit facilement visible. Connectez ces étiquettes à IBOutlets dans ARDemoViewController.h, en s'assurant qu'ils sont également synthétisés dans ARDemoViewController.m (Interface Builder peut maintenant le faire pour vous avec un glisser-déposer). Nommez une étiquette hexLabel et l'autre rgbLabel.

Toujours dans Interface Builder, faites glisser un élément UIView sur la vue principale et ajustez-la pour qu'elle soit de la taille et de la position de votre choix. Connectez la vue en tant que IBOutlet et nommez-le nuancier.

Une fois cette étape terminée, votre fichier XIB doit ressembler à ceci:


Chaque objet que vous avez ajouté doit également être connecté via IBOutlets pour ARDemoViewController.

La dernière chose à faire est de s’assurer que ces objets sont visibles après l’ajout du calque de prévisualisation à l’écran. Pour ce faire, ajoutez les lignes suivantes à -viewDidLoad dans ARDemoViewController:

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

Étape 7: Créer un CADisplayLink

Parce que le findColorAverage: La fonction doit être exécutée aussi rapidement que possible pour éviter que des images en retard ne soient déposées dans la file d'attente. Il est déconseillé de faire fonctionner une interface dans cette fonction. Au lieu de cela, le findColorAverage: calcule simplement la couleur moyenne du cadre et l’enregistre pour une utilisation ultérieure. Nous pouvons maintenant configurer une deuxième fonction qui fera réellement quelque chose avec le couleur courante valeur, et nous pourrions même placer ce traitement sur un thread séparé si nécessaire. Pour ce projet, nous souhaitons simplement mettre à jour l'interface superposée environ 4 fois par seconde. Nous pourrions utiliser un NSTimer à cette fin, mais je préfère utiliser un CADisplayLink chaque fois que possible, car il est plus cohérent et fiable que NSTimer.

dans le ARDemoViewController fichier d’implémentation, ajoutez ce qui suit au -viewDidLoad méthode:

 CADisplayLink * updateTimer = [CADisplayLink displayLinkWithTarget: sélecteur automatique: @selector (updateColorDisplay)]; [updateTimer setFrameInterval: 15]; [updateTimer addToRunLoop: [NSRunLoop currentRunLoop] pour le mode: NSDefaultRunLoopMode];

CADisplayLink lancera une mise à jour 60 fois par seconde, donc en réglant l'intervalle de trame sur 15, nous initierons un appel au sélecteur updateColorDisplay 4 fois par seconde.

Pour éviter que cela ne provoque une erreur d'exécution, ajoutons un stub de sélecteur:

 -(void) updateColorDisplay 

Étape 8: Mettre à jour les valeurs d'interface

Nous sommes maintenant prêts à enrichir notre affichage d'informations quasi utiles sur le monde qui nous entoure! Ajoutez les lignes de code suivantes à updateColorDisplay:

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

Ce que nous faisons ci-dessus est très simple. Parce que couleur courante est stocké en tant que UIColor objet, nous pouvons simplement définir la Couleur de fond propriété de nuancier à elle directement. Pour les deux étiquettes, nous venons de configurer un NSString formater et utiliser le UIColor-Expanded catégorie pour accéder facilement à la fois à la représentation hexadécimale de la couleur et aux valeurs RVB.


Étape 9: Test de l'application

Afin de tester votre travail, j'ai inclus 3 fichiers HTML simples dans le dossier "test" du téléchargement du projet. Chaque fichier a un arrière-plan solide (rouge, vert et bleu). Si vous remplissez l'écran de votre ordinateur avec cette page Web ouverte et pointez notre application iPhone vers l'écran, vous devriez pouvoir voir la couleur contextuelle appropriée sur l'écran de l'AR..


Emballer

Toutes nos félicitations! Vous avez construit votre premier réal Application de réalité augmentée!

Bien que nous puissions certainement débattre de la valeur des informations avec lesquelles nous augmentons notre vision du monde, je trouve personnellement ce projet beaucoup plus intéressant que la plupart des applications de réalité augmentée "sensibles à la localisation" actuellement sur le marché, y compris le séminal " "Tubes". Pourquoi? Parce que quand je suis dans la ville à la recherche d'un métro, je ne veux pas d'une flèche "cercle de terre" qui me dirige vers mon but à travers les bâtiments. Au lieu de cela, il est infiniment plus pratique d’utiliser simplement les instructions de Google Maps. Pour cette raison, chaque application de réalité augmentée et géolocalisée que j'ai rencontrée n'est vraiment qu'un projet de nouveauté. Bien que le projet que nous avons construit aujourd'hui soit également un projet de nouveauté, j'espère qu'il constitue un exemple divertissant de la création de vos propres applications de réalité Augmetned pouvant réellement enrichir le monde qui nous entoure..

Si vous avez trouvé ce tutoriel utile, faites-le moi savoir sur Twitter: @markhammonds. J'aimerais voir ce que vous venez avec!


Où aller en partant d'ici?

Comme vous l'aurez deviné, je suis sûr que jusqu'à présent, nous n'avons vraiment abordé que ce que les applications de réalité augmentée peuvent faire. La prochaine étape de votre formation dépendra en grande partie de ce que vous souhaitez réaliser avec une application AR..

Si vous êtes intéressé par les applications de réalité augmentée sensibles à la localisation, vous devriez vraiment penser à utiliser une boîte à outils RA open source. Bien qu'il soit certainement possible pour vous de coder votre propre boîte à outils AR, il s'agit d'une tâche très complexe et avancée. Je vous encourage à ne pas réinventer la roue et à regarder plutôt ces projets:

  • Boîte à outils iOS AR (Open-Source)
  • Boîte à outils iOS AR (Commercial)
  • 3Dar
  • Mixare
  • Wikitude

Quelques solutions et projets supplémentaires de réalité augmentée prenant en charge les implémentations basées sur des marqueurs incluent:

  • Kit de développement logiciel de réalité augmentée Qualcomm
  • NyARToolkit

Si vous êtes particulièrement intéressé par le traitement des images, comme nous l'avons montré dans ce didacticiel, et que vous souhaitez approfondir la reconnaissance des fonctionnalités et des objets, voici un bon point de départ:

  • OpenCV
  • ArUco
  • iOS 5 Core Image Framework

La prochaine fois?

Comme j'espère que vous pouvez voir dans le contenu de ce didacticiel, la réalité augmentée est un sujet extrêmement vaste qui propose de nombreuses possibilités, allant du traitement des images à la reconnaissance d'objets, en passant par la recherche de localisation et même les jeux en 3D..

Nous sommes certainement intéressés à couvrir tout ce qui précède sur Mobiletuts +, mais nous voulons nous assurer que notre contenu correspond à ce que les lecteurs aimeraient voir. Dites-nous quel aspect de la réalité augmentée vous aimeriez voir plus de tutoriels en laissant un commentaire sur Mobiletuts +, en le postant sur le mur de notre groupe Facebook ou en m'envoyant un message directement sur Twitter: @markhammonds.

Jusqu'à la prochaine fois? Merci d'avoir lu!