HoloLens (1ère génération) et Azure 310 : Détection d’objets

Notes

Les tutoriels Mixed Reality Academy ont été conçus pour les appareils HoloLens (1re génération) et les casques immersifs de réalité mixte. Nous estimons qu’il est important de laisser ces tutoriels à la disposition des développeurs qui recherchent encore des conseils pour développer des applications sur ces appareils. Notez que ces tutoriels ne sont pas mis à jour avec les derniers ensembles d’outils ou interactions utilisés pour HoloLens 2. Ils sont fournis dans le but de fonctionner sur les appareils pris en charge. Il y aura une nouvelle série de tutoriels qui seront publiés à l’avenir et qui montreront comment développer pour HoloLens 2. Cet avis sera mis à jour avec un lien vers ces tutoriels lorsqu’ils seront publiés.


Dans ce cours, vous allez apprendre à reconnaître le contenu visuel personnalisé et sa position spatiale dans une image fournie, à l’aide d’Azure Custom Vision fonctionnalités de « détection d’objets » dans une application de réalité mixte.

Ce service vous permet d’entraîner un modèle Machine Learning à l’aide d’images objet. Vous utiliserez ensuite le modèle entraîné pour reconnaître des objets similaires et se rapprocher de leur emplacement dans le monde réel, tel que fourni par la capture d’appareil photo de Microsoft HoloLens ou une caméra se connectant à un PC pour des casques immersifs (VR).

Résultat du cours

Azure Custom Vision, La détection d’objets est un service Microsoft qui permet aux développeurs de créer des classifieurs d’images personnalisés. Ces classifieurs peuvent ensuite être utilisés avec de nouvelles images pour détecter des objets dans cette nouvelle image, en fournissant des limites de zone dans l’image elle-même. Le service fournit un portail en ligne simple et facile à utiliser pour simplifier ce processus. Pour plus d’informations, consultez les liens suivants :

À l’issue de ce cours, vous disposerez d’une application de réalité mixte qui sera en mesure d’effectuer les opérations suivantes :

  1. L’utilisateur peut regarder un objet qu’il a entraîné à l’aide du service Azure Custom Vision, détection d’objets.
  2. L’utilisateur utilisera le mouvement d’appui pour capturer une image de ce qu’il regarde.
  3. L’application envoie l’image au service Azure Custom Vision.
  4. Il y aura une réponse du service qui affichera le résultat de la reconnaissance en tant que texte d’espace du monde. Pour ce faire, utilisez le suivi spatial de l’Microsoft HoloLens comme moyen de comprendre la position mondiale de l’objet reconnu, puis en utilisant l’étiquette associée à ce qui est détecté dans l’image pour fournir le texte de l’étiquette.

Le cours couvre également le chargement manuel d’images, la création de balises et l’apprentissage du service pour qu’il reconnaisse différents objets (dans l’exemple fourni, une tasse) en définissant la zone de limites dans l’image que vous envoyez.

Important

Après la création et l’utilisation de l’application, le développeur doit revenir au service Azure Custom Vision, identifier les prédictions effectuées par le service et déterminer si elles étaient correctes ou non (en marquant tout ce que le service a manqué et en ajustant les cadres englobants). Le service peut ensuite être réentraîné, ce qui augmente la probabilité qu’il reconnaisse des objets réels.

Ce cours vous apprend à obtenir les résultats du service Azure Custom Vision, Détection d’objets, dans un exemple d’application Unity. Il vous appartient d’appliquer ces concepts à une application personnalisée que vous pouvez créer.

Prise en charge des appareils

Cours HoloLens Casques immersifs
Réalité mixte - Azure - Cours 310 : Détection d’objets ✔️

Prérequis

Notes

Ce tutoriel est conçu pour les développeurs qui ont une expérience de base avec Unity et C#. Notez également que les prérequis et les instructions écrites dans ce document représentent ce qui a été testé et vérifié au moment de la rédaction (juillet 2018). Vous êtes libre d’utiliser les logiciels les plus récents, comme indiqué dans l’article installer les outils , bien qu’il ne soit pas supposé que les informations de ce cours correspondront parfaitement à ce que vous trouverez dans les logiciels plus récents que ce qui est listé ci-dessous.

Nous vous recommandons d’utiliser le matériel et les logiciels suivants pour ce cours :

Avant de commencer

  1. Pour éviter de rencontrer des problèmes lors de la création de ce projet, il est fortement recommandé de créer le projet mentionné dans ce didacticiel dans un dossier racine ou proche de la racine (les chemins de dossier longs peuvent entraîner des problèmes au moment de la génération).
  2. Configurez et testez votre HoloLens. Si vous avez besoin d’une prise en charge, consultez l’article sur la configuration de HoloLens.
  3. Il est judicieux d’effectuer l’étalonnage et le réglage des capteurs lorsque vous commencez à développer une nouvelle application HoloLens (il peut parfois être utile d’effectuer ces tâches pour chaque utilisateur).

Pour obtenir de l’aide sur l’étalonnage, suivez ce lien vers l’article Étalonnage HoloLens.

Pour obtenir de l’aide sur le réglage des capteurs, suivez ce lien vers l’article Paramétrage du capteur HoloLens.

Chapitre 1 - Le portail Custom Vision

Pour utiliser azure Custom Vision Service, vous devez configurer un instance de celui-ci afin qu’il soit mis à la disposition de votre application.

  1. Accédez à la page main service Custom Vision.

  2. Cliquez sur Prise en main.

    Capture d’écran mettant en évidence le bouton Prise en main.

  3. Connectez-vous au portail Custom Vision.

    Capture d’écran montrant le bouton Se connecter.

  4. Si vous n’avez pas encore de compte Azure, vous devez en créer un. Si vous suivez ce tutoriel dans une situation de classe ou de laboratoire, demandez à votre instructeur ou à l’un des surveillants de l’aide pour configurer votre nouveau compte.

  5. Une fois que vous êtes connecté pour la première fois, vous êtes invité à entrer le panneau Conditions d’utilisation. Cochez la case pour accepter les conditions. Cliquez ensuite sur J’accepte.

    Capture d’écran montrant le panneau Conditions d’utilisation.

  6. Après avoir accepté les termes, vous êtes maintenant dans la section Mes projets . Cliquez sur Nouveau projet.

    Capture d’écran montrant où sélectionner Nouveau projet.

  7. Un onglet s’affiche sur le côté droit, qui vous invite à spécifier des champs pour le projet.

    1. Insérer un nom pour votre projet

    2. Insérer une description pour votre projet (facultatif)

    3. Choisissez un groupe de ressources ou créez-en un. Un groupe de ressources permet de surveiller, de contrôler l’accès, de provisionner et de gérer la facturation d’une collection de ressources Azure. Il est recommandé de conserver tous les services Azure associés à un seul projet (par exemple, ces cours) sous un groupe de ressources commun).

      Capture d’écran montrant où ajouter des détails pour le nouveau projet.

    4. Définissez les types de projet comme Détection d’objets (préversion) .

  8. Une fois que vous avez terminé, cliquez sur Créer un projet. Vous êtes alors redirigé vers la page de projet Custom Vision Service.

Chapitre 2 - Formation de votre projet Custom Vision

Une fois dans le portail Custom Vision, votre objectif principal est d’entraîner votre projet à reconnaître des objets spécifiques dans des images.

Vous avez besoin d’au moins quinze (15) images pour chaque objet que vous souhaitez que votre application reconnaisse. Vous pouvez utiliser les images fournies avec ce cours (une série de tasses).

Pour entraîner votre projet Custom Vision :

  1. Cliquez sur le + bouton en regard de Balises.

    Capture d’écran montrant le bouton + en regard de Balises.

  2. Ajoutez un nom pour la balise qui sera utilisée pour associer vos images. Dans cet exemple, nous utilisons des images de tasses pour la reconnaissance. Nous avons donc nommé l’étiquette pour cela, Cup. Cliquez sur Enregistrer une fois que vous avez terminé.

    Capture d’écran montrant où ajouter un nom pour la balise.

  3. Vous remarquerez que votre balise a été ajoutée (vous devrez peut-être recharger votre page pour qu’elle apparaisse).

    Capture d’écran montrant où votre balise est ajoutée.

  4. Cliquez sur Ajouter des images au centre de la page.

    Capture d’écran montrant où ajouter des images.

  5. Cliquez sur Parcourir les fichiers locaux, puis accédez aux images que vous souhaitez charger pour un objet, le minimum étant quinze (15).

    Conseil

    Vous pouvez sélectionner plusieurs images à la fois, à charger.

    Capture d’écran montrant les images que vous pouvez charger.

  6. Appuyez sur Charger des fichiers une fois que vous avez sélectionné toutes les images avec lesquelles vous souhaitez effectuer l’apprentissage du projet. Les fichiers commencent à être téléchargés. Une fois que vous avez confirmation du chargement, cliquez sur Terminé.

    Capture d’écran montrant la progression des images chargées.

  7. À ce stade, vos images sont chargées, mais pas étiquetées.

    Capture d’écran montrant une image non marquée.

  8. Pour étiqueter vos images, utilisez votre souris. Lorsque vous pointez sur votre image, une sélection en surbrillance vous aidera en dessinant automatiquement une sélection autour de votre objet. S’il n’est pas exact, vous pouvez dessiner le vôtre. Pour ce faire, maintenez un clic gauche sur la souris et faites glisser la zone de sélection pour englober votre objet.

    Capture d’écran montrant comment baliser une image.

  9. Après la sélection de votre objet dans l’image, une petite invite vous demande d’ajouter une balise de région. Sélectionnez votre balise précédemment créée (« Cup », dans l’exemple ci-dessus), ou si vous ajoutez d’autres balises, tapez-la et cliquez sur le bouton + (plus).

    Capture d’écran montrant la balise que vous avez ajoutée à l’image.

  10. Pour baliser l’image suivante, vous pouvez cliquer sur la flèche à droite du panneau, ou fermer le panneau de balise (en cliquant sur le X dans le coin supérieur droit du panneau), puis cliquer sur l’image suivante. Une fois l’image suivante prête, répétez la même procédure. Effectuez cette opération pour toutes les images que vous avez chargées, jusqu’à ce qu’elles soient toutes étiquetées.

    Notes

    Vous pouvez sélectionner plusieurs objets dans la même image, comme l’image ci-dessous :

    Capture d’écran montrant plusieurs objets dans une image.

  11. Une fois que vous les avez tous étiquetés , cliquez sur le bouton étiqueté, à gauche de l’écran, pour afficher les images étiquetées.

    Capture d’écran mettant en évidence le bouton Étiqueté.

  12. Vous êtes maintenant prêt à entraîner votre service. Cliquez sur le bouton Entraîner pour que la première itération d’entraînement commence.

    Capture d’écran mettant en évidence le bouton Entraîner.

    Capture d’écran montrant la première itération d’entraînement.

  13. Une fois qu’il est généré, vous pouvez voir deux boutons appelés Effectuer la valeur par défaut et URL de prédiction. Cliquez d’abord sur Définir la valeur par défaut , puis cliquez sur URL de prédiction.

    Capture d’écran mettant en évidence le bouton Définir par défaut.

    Notes

    Le point de terminaison fourni à partir de celui-ci est défini sur l’itération qui a été marquée comme valeur par défaut. Par conséquent, si vous effectuez ultérieurement une nouvelle itération et la mettez à jour par défaut, vous n’aurez pas besoin de modifier votre code.

  14. Une fois que vous avez cliqué sur URL de prédiction, ouvrez le Bloc-notes, puis copiez et collez l’URL (également appelée point de terminaison de prédiction) et la clé de prédiction du service, afin de pouvoir la récupérer quand vous en avez besoin plus loin dans le code.

    Capture d’écran montrant le point de terminaison de prédiction et la clé de prédition.

Chapitre 3 - Configurer le projet Unity

Ce qui suit est une configuration classique pour le développement avec la réalité mixte, et en tant que tel, est un bon modèle pour d’autres projets.

  1. Ouvrez Unity , puis cliquez sur Nouveau.

    Capture d’écran mettant en évidence le bouton Nouveau.

  2. Vous devez maintenant fournir un nom de projet Unity. Insérez CustomVisionObjDetection. Assurez-vous que le type de projet est défini sur 3D et définissez l’emplacement sur un emplacement approprié pour vous (n’oubliez pas qu’il est préférable de se rapprocher des répertoires racines). Cliquez ensuite sur Créer un projet.

    Capture d’écran montrant les détails du projet et où sélectionner Créer un projet.

  3. Avec Unity ouvert, il est utile de vérifier que l’Éditeur de script par défaut est défini sur Visual Studio. Accédez à Modifier les>préférences , puis, dans la nouvelle fenêtre, accédez à Outils externes. Remplacez Éditeur de script externe par Visual Studio. Fermez la fenêtre Préférences.

    Capture d’écran montrant où remplacer l’éditeur de script externe par Visual Studio.

  4. Ensuite, accédez à Paramètres de > génération de fichiers, basculez la plateforme sur plateforme Windows universelle, puis cliquez sur le bouton Changer de plateforme.

    Capture d’écran mettant en évidence le bouton Changer de plateforme.

  5. Dans la même fenêtre Paramètres de build, vérifiez que les éléments suivants sont définis :

    1. L’appareil cible est défini sur HoloLens

    2. Type de build défini sur D3D

    3. Le Kit de développement logiciel (SDK) est défini sur Dernier installé

    4. La version de Visual Studio est définie sur Dernière installation

    5. Générer et exécuter est défini sur Ordinateur local

    6. Les autres paramètres, dans Paramètres de build, doivent être conservés par défaut pour l’instant.

      Capture d’écran montrant les options de configuration du paramètre de génération.

  6. Dans la même fenêtre Paramètres de build, cliquez sur le bouton Paramètres du lecteur. Le panneau associé s’ouvre dans l’espace où se trouve l’inspecteur.

  7. Dans ce panneau, quelques paramètres doivent être vérifiés :

    1. Sous l’onglet Autres paramètres :

      1. La version du runtime de script doit être expérimentale (équivalent.NET 4.6), ce qui déclenche la nécessité de redémarrer l’éditeur.

      2. Le serveur principal de script doit être .NET.

      3. Le niveau de compatibilité de l’API doit être .NET 4.6.

        Capture d’écran montrant l’option Niveau de compatibilité de l’API définie sur .NET 4.6.

    2. Sous l’onglet Paramètres de publication, sous Fonctionnalités, case activée :

      1. InternetClient

      2. Webcam

      3. SpatialPerception

        Capture d’écran montrant la moitié supérieure des options de configuration des fonctionnalités.Capture d’écran montrant la moitié inférieure des options de configuration des fonctionnalités.

    3. Plus loin dans le panneau, dans Paramètres XR (situé sous Paramètres de publication), cochez Réalité virtuelle prise en charge, puis vérifiez que le Windows Mixed Reality SDK est ajouté.

      Capture d’écran montrant que le sdk Windows Mixed Reality est ajouté.

  8. De retour dans les paramètres de build, Unity C# Projects n’est plus grisé : cochez la case en regard de ceci.

  9. Fermez la fenêtre Build Settings.

  10. Dans l’éditeur, cliquez sur Modifier les>graphiques des paramètres> du projet.

    Capture d’écran montrant l’option de menu Graphiques sélectionnée.

  11. Dans le panneau inspecteur , les paramètres graphiques sont ouverts. Faites défiler vers le bas jusqu’à voir un tableau appelé Always Include Shaders. Ajoutez un emplacement en augmentant la variable Size d’un (dans cet exemple, elle était 8, donc nous l’avons fait 9). Un nouvel emplacement s’affiche, à la dernière position du tableau, comme indiqué ci-dessous :

    Capture d’écran mettant en évidence le tableau de nuanceurs always inclus.

  12. Dans l’emplacement, cliquez sur le petit cercle cible en regard de l’emplacement pour ouvrir une liste de nuanceurs. Recherchez le nuanceur hérité/Transparent/Diffuse et double-cliquez dessus.

    Capture d’écran mettant en évidence le nuanceur hérité/transparent/diffus.

Chapitre 4 - Importation du package Unity CustomVisionObjDetection

Pour ce cours, vous disposez d’un package de ressources Unity appelé Azure-MR-310.unitypackage.

[CONSEIL] Tous les objets pris en charge par Unity, y compris les scènes entières, peuvent être empaquetés dans un fichier .unitypackage et exportés/importés dans d’autres projets. Il s’agit du moyen le plus sûr et le plus efficace de déplacer des ressources entre différents projets Unity.

Vous trouverez le package Azure-MR-310 que vous devez télécharger ici.

  1. Avec le tableau de bord Unity devant vous, cliquez sur Ressources dans le menu en haut de l’écran, puis cliquez sur Importer un package > personnalisé.

    Capture d’écran mettant en évidence l’option de menu Package personnalisé.

  2. Utilisez le sélecteur de fichiers pour sélectionner le package Azure-MR-310.unitypackage , puis cliquez sur Ouvrir. La liste des composants de cette ressource s’affiche. Confirmez l’importation en cliquant sur le bouton Importer .

    Capture d’écran montrant la liste des composants de ressources que vous souhaitez importer.

  3. Une fois l’importation terminée, vous remarquerez que les dossiers du package ont maintenant été ajoutés à votre dossier Assets . Ce type de structure de dossiers est typique pour un projet Unity.

    Capture d’écran montrant le contenu du dossier Assets.

    1. Le dossier Matériaux contient le matériau utilisé par le curseur du regard.

    2. Le dossier Plugins contient la DLL Newtonsoft utilisée par le code pour désérialiser la réponse web du service. Les deux (2) versions différentes contenues dans le dossier et le sous-dossier sont nécessaires pour permettre à la bibliothèque d’être utilisée et générée par l’éditeur Unity et la build UWP.

    3. Le dossier Prefabs contient les préfabriqués contenus dans la scène. Il s’agit des suivants :

      1. GazeCursor, le curseur utilisé dans l’application. Fonctionne avec le préfabriqué SpatialMapping pour pouvoir être placé dans la scène sur des objets physiques.
      2. Label, qui est l’objet d’interface utilisateur utilisé pour afficher la balise d’objet dans la scène si nécessaire.
      3. SpatialMapping, qui est l’objet qui permet à l’application d’utiliser la création d’une carte virtuelle, à l’aide du suivi spatial du Microsoft HoloLens.
    4. Dossier Scènes qui contient actuellement la scène prédéfinie pour ce cours.

  4. Ouvrez le dossier Scènes , dans le volet projet, puis double-cliquez sur l’objet ObjDetectionScene pour charger la scène que vous allez utiliser pour ce cours.

    Capture d’écran montrant l’objDetectionScene dans le dossier Scenes.

    Notes

    Aucun code n’est inclus. Vous allez écrire le code en suivant ce cours.

Chapitre 5 : Créer la classe CustomVisionAnalyser.

À ce stade, vous êtes prêt à écrire du code. Vous allez commencer par la classe CustomVisionAnalyser .

Notes

Les appels au service Custom Vision, effectués dans le code ci-dessous, sont effectués à l’aide de l’API REST Custom Vision. Grâce à cette méthode, vous verrez comment implémenter et utiliser cette API (utile pour comprendre comment implémenter quelque chose de similaire par vous-même). N’oubliez pas que Microsoft propose un kit de développement logiciel (SDK) Custom Vision qui peut également être utilisé pour passer des appels au service. Pour plus d’informations, consultez l’article sdk Custom Vision.

Cette classe est responsable des opérations suivantes :

  • Chargement de la dernière image capturée sous forme de tableau d’octets.

  • Envoi du tableau d’octets à votre instance Azure Custom Vision Service à des fins d’analyse.

  • Réception de la réponse sous forme de chaîne JSON.

  • Désérialisation de la réponse et passage de la prédiction obtenue à la classe SceneOrganiser , qui s’occupe de la façon dont la réponse doit être affichée.

Pour créer cette classe :

  1. Cliquez avec le bouton droit dans le dossier De ressources, situé dans le volet Projet, puis cliquez sur Créer un>dossier. Appelez le dossier Scripts.

    Capture d’écran montrant comment créer le dossier Scripts.

  2. Double-cliquez sur le dossier nouvellement créé pour l’ouvrir.

  3. Cliquez avec le bouton droit à l’intérieur du dossier, puis cliquez sur Créer un>script C#. Nommez le script CustomVisionAnalyser.

  4. Double-cliquez sur le nouveau script CustomVisionAnalyser pour l’ouvrir avec Visual Studio.

  5. Vérifiez que les espaces de noms suivants sont référencés en haut du fichier :

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  6. Dans la classe CustomVisionAnalyser , ajoutez les variables suivantes :

        /// <summary>
        /// Unique instance of this class
        /// </summary>
        public static CustomVisionAnalyser Instance;
    
        /// <summary>
        /// Insert your prediction key here
        /// </summary>
        private string predictionKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your prediction endpoint here
        /// </summary>
        private string predictionEndpoint = "Insert your prediction endpoint here";
    
        /// <summary>
        /// Bite array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Notes

    Veillez à insérer votre clé de prédiction de service dans la variable predictionKey et votre point de terminaison de prédiction dans la variable predictionEndpoint . Vous les avez copiés dans le Bloc-notes précédemment, au chapitre 2, étape 14.

  7. Le code pour Awake() doit maintenant être ajouté pour initialiser la variable d’instance :

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Ajoutez la coroutine (avec la méthode statique GetImageAsByteArray() en dessous), qui obtiendra les résultats de l’analyse de l’image, capturés par la classe ImageCapture .

    Notes

    Dans la coroutine AnalyseImageCapture , vous devez créer un appel à la classe SceneOrganiser . Par conséquent, laissez ces lignes commentées pour l’instant.

        /// <summary>
        /// Call the Computer Vision Service to submit the image.
        /// </summary>
        public IEnumerator AnalyseLastImageCaptured(string imagePath)
        {
            Debug.Log("Analyzing...");
    
            WWWForm webForm = new WWWForm();
    
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                Debug.Log("response: " + jsonResponse);
    
                // Create a texture. Texture size does not matter, since
                // LoadImage will replace with the incoming image size.
                //Texture2D tex = new Texture2D(1, 1);
                //tex.LoadImage(imageBytes);
                //SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
                // The response will be in JSON format, therefore it needs to be deserialized
                //AnalysisRootObject analysisRootObject = new AnalysisRootObject();
                //analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
                //SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  9. Supprimez les méthodes Start() et Update(), car elles ne seront pas utilisées.

  10. Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.

Important

Comme mentionné précédemment, ne vous inquiétez pas du code qui peut sembler avoir une erreur, car vous fournirez bientôt d’autres classes, ce qui corrigera ces problèmes.

Chapitre 6 - Créer la classe CustomVisionObjects

La classe que vous allez créer maintenant est la classe CustomVisionObjects .

Ce script contient un certain nombre d’objets utilisés par d’autres classes pour sérialiser et désérialiser les appels effectués au service Custom Vision.

Pour créer cette classe :

  1. Cliquez avec le bouton droit dans le dossier Scripts , puis cliquez sur Créer un>script C#. Appelez le script CustomVisionObjects.

  2. Double-cliquez sur le nouveau script CustomVisionObjects pour l’ouvrir avec Visual Studio.

  3. Vérifiez que les espaces de noms suivants sont référencés en haut du fichier :

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Supprimez les méthodes Start() et Update() à l’intérieur de la classe CustomVisionObjects . Cette classe doit maintenant être vide.

    Avertissement

    Il est important de suivre attentivement les instructions suivantes. Si vous placez les nouvelles déclarations de classe à l’intérieur de la classe CustomVisionObjects , vous obtiendrez des erreurs de compilation dans le chapitre 10, indiquant que AnalysisRootObject et BoundingBox sont introuvables.

  5. Ajoutez les classes suivantes en dehors de la classe CustomVisionObjects . Ces objets sont utilisés par la bibliothèque Newtonsoft pour sérialiser et désérialiser les données de réponse :

    // The objects contained in this script represent the deserialized version
    // of the objects used by this application 
    
    /// <summary>
    /// Web request object for image data
    /// </summary>
    class MultipartObject : IMultipartFormSection
    {
        public string sectionName { get; set; }
    
        public byte[] sectionData { get; set; }
    
        public string fileName { get; set; }
    
        public string contentType { get; set; }
    }
    
    /// <summary>
    /// JSON of all Tags existing within the project
    /// contains the list of Tags
    /// </summary> 
    public class Tags_RootObject
    {
        public List<TagOfProject> Tags { get; set; }
        public int TotalTaggedImages { get; set; }
        public int TotalUntaggedImages { get; set; }
    }
    
    public class TagOfProject
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int ImageCount { get; set; }
    }
    
    /// <summary>
    /// JSON of Tag to associate to an image
    /// Contains a list of hosting the tags,
    /// since multiple tags can be associated with one image
    /// </summary> 
    public class Tag_RootObject
    {
        public List<Tag> Tags { get; set; }
    }
    
    public class Tag
    {
        public string ImageId { get; set; }
        public string TagId { get; set; }
    }
    
    /// <summary>
    /// JSON of images submitted
    /// Contains objects that host detailed information about one or more images
    /// </summary> 
    public class ImageRootObject
    {
        public bool IsBatchSuccessful { get; set; }
        public List<SubmittedImage> Images { get; set; }
    }
    
    public class SubmittedImage
    {
        public string SourceUrl { get; set; }
        public string Status { get; set; }
        public ImageObject Image { get; set; }
    }
    
    public class ImageObject
    {
        public string Id { get; set; }
        public DateTime Created { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public string ImageUri { get; set; }
        public string ThumbnailUri { get; set; }
    }
    
    /// <summary>
    /// JSON of Service Iteration
    /// </summary> 
    public class Iteration
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public bool IsDefault { get; set; }
        public string Status { get; set; }
        public string Created { get; set; }
        public string LastModified { get; set; }
        public string TrainedAt { get; set; }
        public string ProjectId { get; set; }
        public bool Exportable { get; set; }
        public string DomainId { get; set; }
    }
    
    /// <summary>
    /// Predictions received by the Service
    /// after submitting an image for analysis
    /// Includes Bounding Box
    /// </summary>
    public class AnalysisRootObject
    {
        public string id { get; set; }
        public string project { get; set; }
        public string iteration { get; set; }
        public DateTime created { get; set; }
        public List<Prediction> predictions { get; set; }
    }
    
    public class BoundingBox
    {
        public double left { get; set; }
        public double top { get; set; }
        public double width { get; set; }
        public double height { get; set; }
    }
    
    public class Prediction
    {
        public double probability { get; set; }
        public string tagId { get; set; }
        public string tagName { get; set; }
        public BoundingBox boundingBox { get; set; }
    }
    
  6. Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.

Chapitre 7 - Créer la classe SpatialMapping

Cette classe définit le collisionneur de mappage spatial dans la scène afin de pouvoir détecter les collisions entre des objets virtuels et des objets réels.

Pour créer cette classe :

  1. Cliquez avec le bouton droit dans le dossier Scripts , puis cliquez sur Créer un>script C#. Appelez le script SpatialMapping.

  2. Double-cliquez sur le nouveau script SpatialMapping pour l’ouvrir avec Visual Studio.

  3. Vérifiez que les espaces de noms suivants sont référencés au-dessus de la classe SpatialMapping :

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. Ensuite, ajoutez les variables suivantes à l’intérieur de la classe SpatialMapping , au-dessus de la méthode Start() :

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SpatialMapping Instance;
    
        /// <summary>
        /// Used by the GazeCursor as a property with the Raycast call
        /// </summary>
        internal static int PhysicsRaycastMask;
    
        /// <summary>
        /// The layer to use for spatial mapping collisions
        /// </summary>
        internal int physicsLayer = 31;
    
        /// <summary>
        /// Creates environment colliders to work with physics
        /// </summary>
        private SpatialMappingCollider spatialMappingCollider;
    
  5. Ajoutez les éléments Awake() et Start() :

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Initialize and configure the collider
            spatialMappingCollider = gameObject.GetComponent<SpatialMappingCollider>();
            spatialMappingCollider.surfaceParent = this.gameObject;
            spatialMappingCollider.freezeUpdates = false;
            spatialMappingCollider.layer = physicsLayer;
    
            // define the mask
            PhysicsRaycastMask = 1 << physicsLayer;
    
            // set the object as active one
            gameObject.SetActive(true);
        }
    
  6. Supprimez la méthode Update().

  7. Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.

Chapitre 8 - Créer la classe GazeCursor

Cette classe est chargée de configurer le curseur à l’emplacement approprié dans l’espace réel, en utilisant le SpatialMappingCollider, créé dans le chapitre précédent.

Pour créer cette classe :

  1. Cliquez avec le bouton droit dans le dossier Scripts , puis cliquez sur Créer un>script C#. Appeler le script GazeCursor

  2. Double-cliquez sur le nouveau script GazeCursor pour l’ouvrir avec Visual Studio.

  3. Vérifiez que l’espace de noms suivant est référencé au-dessus de la classe GazeCursor :

    using UnityEngine;
    
  4. Ajoutez ensuite la variable suivante à l’intérieur de la classe GazeCursor, au-dessus de la méthode Start().

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Mettez à jour la méthode Start() avec le code suivant :

        /// <summary>
        /// Runs at initialization right after the Awake method
        /// </summary>
        void Start()
        {
            // Grab the mesh renderer that is on the same object as this script.
            meshRenderer = gameObject.GetComponent<MeshRenderer>();
    
            // Set the cursor reference
            SceneOrganiser.Instance.cursor = gameObject;
            gameObject.GetComponent<Renderer>().material.color = Color.green;
    
            // If you wish to change the size of the cursor you can do so here
            gameObject.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
        }
    
  6. Mettez à jour la méthode Update() avec le code suivant :

        /// <summary>
        /// Update is called once per frame
        /// </summary>
        void Update()
        {
            // Do a raycast into the world based on the user's head position and orientation.
            Vector3 headPosition = Camera.main.transform.position;
            Vector3 gazeDirection = Camera.main.transform.forward;
    
            RaycastHit gazeHitInfo;
            if (Physics.Raycast(headPosition, gazeDirection, out gazeHitInfo, 30.0f, SpatialMapping.PhysicsRaycastMask))
            {
                // If the raycast hit a hologram, display the cursor mesh.
                meshRenderer.enabled = true;
                // Move the cursor to the point where the raycast hit.
                transform.position = gazeHitInfo.point;
                // Rotate the cursor to hug the surface of the hologram.
                transform.rotation = Quaternion.FromToRotation(Vector3.up, gazeHitInfo.normal);
            }
            else
            {
                // If the raycast did not hit a hologram, hide the cursor mesh.
                meshRenderer.enabled = false;
            }
        }
    

    Notes

    Ne vous inquiétez pas de l’erreur pour la classe SceneOrganiser introuvable, vous allez la créer dans le chapitre suivant.

  7. Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.

Chapitre 9 : Créer la classe SceneOrganiser

Cette classe :

  • Configurez la caméra principale en y attachant les composants appropriés.

  • Lorsqu’un objet est détecté, il est responsable du calcul de sa position dans le monde réel et place une étiquette de balise à proximité avec le nom de balise approprié.

Pour créer cette classe :

  1. Cliquez avec le bouton droit dans le dossier Scripts , puis cliquez sur Créer un>script C#. Nommez le script SceneOrganiser.

  2. Double-cliquez sur le nouveau script SceneOrganiser pour l’ouvrir avec Visual Studio.

  3. Vérifiez que les espaces de noms suivants sont référencés au-dessus de la classe SceneOrganiser :

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
  4. Ajoutez ensuite les variables suivantes à l’intérieur de la classe SceneOrganiser , au-dessus de la méthode Start() :

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SceneOrganiser Instance;
    
        /// <summary>
        /// The cursor object attached to the Main Camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        public GameObject label;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.8f;
    
        /// <summary>
        /// The quad object hosting the imposed image captured
        /// </summary>
        private GameObject quad;
    
        /// <summary>
        /// Renderer of the quad object
        /// </summary>
        internal Renderer quadRenderer;
    
  5. Supprimez les méthodes Start() et Update().

  6. Sous les variables, ajoutez la méthode Awake(), qui initialisera la classe et configurera la scène.

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            // Use this class instance as singleton
            Instance = this;
    
            // Add the ImageCapture class to this Gameobject
            gameObject.AddComponent<ImageCapture>();
    
            // Add the CustomVisionAnalyser class to this Gameobject
            gameObject.AddComponent<CustomVisionAnalyser>();
    
            // Add the CustomVisionObjects class to this Gameobject
            gameObject.AddComponent<CustomVisionObjects>();
        }
    
  7. Ajoutez la méthode PlaceAnalysisLabel(), qui instancie l’étiquette dans la scène (qui à ce stade est invisible pour l’utilisateur). Il place également le quad (également invisible) où l’image est placée, et se chevauche avec le monde réel. Cela est important, car les coordonnées de zone récupérées à partir du service après l’analyse sont retracées dans ce quad pour déterminer l’emplacement approximatif de l’objet dans le monde réel.

        /// <summary>
        /// Instantiate a Label in the appropriate location relative to the Main Camera.
        /// </summary>
        public void PlaceAnalysisLabel()
        {
            lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
            lastLabelPlacedText.text = "";
            lastLabelPlaced.transform.localScale = new Vector3(0.005f,0.005f,0.005f);
    
            // Create a GameObject to which the texture can be applied
            quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
            quadRenderer = quad.GetComponent<Renderer>() as Renderer;
            Material m = new Material(Shader.Find("Legacy Shaders/Transparent/Diffuse"));
            quadRenderer.material = m;
    
            // Here you can set the transparency of the quad. Useful for debugging
            float transparency = 0f;
            quadRenderer.material.color = new Color(1, 1, 1, transparency);
    
            // Set the position and scale of the quad depending on user position
            quad.transform.parent = transform;
            quad.transform.rotation = transform.rotation;
    
            // The quad is positioned slightly forward in font of the user
            quad.transform.localPosition = new Vector3(0.0f, 0.0f, 3.0f);
    
            // The quad scale as been set with the following value following experimentation,  
            // to allow the image on the quad to be as precisely imposed to the real world as possible
            quad.transform.localScale = new Vector3(3f, 1.65f, 1f);
            quad.transform.parent = null;
        }
    
  8. Ajoutez la méthode FinaliseLabel(). Il est responsable de ce qui suit :

    • Définition du texte d’étiquette avec la balise de la prédiction avec la confiance la plus élevée.
    • Appel du calcul de la zone englobante sur l’objet quad, positionné précédemment, et placer l’étiquette dans la scène.
    • Ajuster la profondeur de l’étiquette à l’aide d’un Raycast vers le cadre englobant, qui doit entrer en collision avec l’objet dans le monde réel.
    • Réinitialisation du processus de capture pour permettre à l’utilisateur de capturer une autre image.
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void FinaliseLabel(AnalysisRootObject analysisObject)
        {
            if (analysisObject.predictions != null)
            {
                lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
                // Sort the predictions to locate the highest one
                List<Prediction> sortedPredictions = new List<Prediction>();
                sortedPredictions = analysisObject.predictions.OrderBy(p => p.probability).ToList();
                Prediction bestPrediction = new Prediction();
                bestPrediction = sortedPredictions[sortedPredictions.Count - 1];
    
                if (bestPrediction.probability > probabilityThreshold)
                {
                    quadRenderer = quad.GetComponent<Renderer>() as Renderer;
                    Bounds quadBounds = quadRenderer.bounds;
    
                    // Position the label as close as possible to the Bounding Box of the prediction 
                    // At this point it will not consider depth
                    lastLabelPlaced.transform.parent = quad.transform;
                    lastLabelPlaced.transform.localPosition = CalculateBoundingBoxPosition(quadBounds, bestPrediction.boundingBox);
    
                    // Set the tag text
                    lastLabelPlacedText.text = bestPrediction.tagName;
    
                    // Cast a ray from the user's head to the currently placed label, it should hit the object detected by the Service.
                    // At that point it will reposition the label where the ray HL sensor collides with the object,
                    // (using the HL spatial tracking)
                    Debug.Log("Repositioning Label");
                    Vector3 headPosition = Camera.main.transform.position;
                    RaycastHit objHitInfo;
                    Vector3 objDirection = lastLabelPlaced.position;
                    if (Physics.Raycast(headPosition, objDirection, out objHitInfo, 30.0f,   SpatialMapping.PhysicsRaycastMask))
                    {
                        lastLabelPlaced.position = objHitInfo.point;
                    }
                }
            }
            // Reset the color of the cursor
            cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the analysis process
            ImageCapture.Instance.ResetImageCapture();        
        }
    
  9. Ajoutez la méthode CalculateBoundingBoxPosition(), qui héberge un certain nombre de calculs nécessaires pour traduire les coordonnées englobantes récupérées à partir du service et les recréer proportionnellement sur le quad.

        /// <summary>
        /// This method hosts a series of calculations to determine the position 
        /// of the Bounding Box on the quad created in the real world
        /// by using the Bounding Box received back alongside the Best Prediction
        /// </summary>
        public Vector3 CalculateBoundingBoxPosition(Bounds b, BoundingBox boundingBox)
        {
            Debug.Log($"BB: left {boundingBox.left}, top {boundingBox.top}, width {boundingBox.width}, height {boundingBox.height}");
    
            double centerFromLeft = boundingBox.left + (boundingBox.width / 2);
            double centerFromTop = boundingBox.top + (boundingBox.height / 2);
            Debug.Log($"BB CenterFromLeft {centerFromLeft}, CenterFromTop {centerFromTop}");
    
            double quadWidth = b.size.normalized.x;
            double quadHeight = b.size.normalized.y;
            Debug.Log($"Quad Width {b.size.normalized.x}, Quad Height {b.size.normalized.y}");
    
            double normalisedPos_X = (quadWidth * centerFromLeft) - (quadWidth/2);
            double normalisedPos_Y = (quadHeight * centerFromTop) - (quadHeight/2);
    
            return new Vector3((float)normalisedPos_X, (float)normalisedPos_Y, 0);
        }
    
  10. Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.

    Important

    Avant de continuer, ouvrez la classe CustomVisionAnalyser et, dans la méthode AnalyseLastImageCaptured(),supprimez les marques de commentaire suivantes :

    // Create a texture. Texture size does not matter, since 
    // LoadImage will replace with the incoming image size.
    Texture2D tex = new Texture2D(1, 1);
    tex.LoadImage(imageBytes);
    SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
    // The response will be in JSON format, therefore it needs to be deserialized
    AnalysisRootObject analysisRootObject = new AnalysisRootObject();
    analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
    SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
    

Notes

Ne vous inquiétez pas du message « introuvable » de la classe ImageCapture , vous allez le créer dans le chapitre suivant.

Chapitre 10 - Créer la classe ImageCapture

La classe suivante que vous allez créer est la classe ImageCapture .

Cette classe est responsable des opérations suivantes :

  • Capture d’une image à l’aide de l’appareil photo HoloLens et stockage dans le dossier App .
  • Gestion des mouvements d’appui de l’utilisateur.

Pour créer cette classe :

  1. Accédez au dossier Scripts que vous avez créé précédemment.

  2. Cliquez avec le bouton droit à l’intérieur du dossier, puis cliquez sur Créer un>script C#. Nommez le script ImageCapture.

  3. Double-cliquez sur le nouveau script ImageCapture pour l’ouvrir avec Visual Studio.

  4. Remplacez les espaces de noms en haut du fichier par les éléments suivants :

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Ajoutez ensuite les variables suivantes à l’intérieur de la classe ImageCapture , au-dessus de la méthode Start() :

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static ImageCapture Instance;
    
        /// <summary>
        /// Keep counts of the taps for image renaming
        /// </summary>
        private int captureCount = 0;
    
        /// <summary>
        /// Photo Capture object
        /// </summary>
        private PhotoCapture photoCaptureObject = null;
    
        /// <summary>
        /// Allows gestures recognition in HoloLens
        /// </summary>
        private GestureRecognizer recognizer;
    
        /// <summary>
        /// Flagging if the capture loop is running
        /// </summary>
        internal bool captureIsActive;
    
        /// <summary>
        /// File path of current analysed photo
        /// </summary>
        internal string filePath = string.Empty;
    
  6. Le code pour les méthodes Awake() et Start() doit maintenant être ajouté :

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the Microsoft HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  7. Implémentez un gestionnaire qui sera appelé lorsqu’un mouvement d’appui se produit :

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            if (!captureIsActive)
            {
                captureIsActive = true;
    
                // Set the cursor color to red
                SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                // Begin the capture loop
                Invoke("ExecuteImageCaptureAndAnalysis", 0);
            }
        }
    

    Important

    Lorsque le curseur est vert, cela signifie que la caméra est disponible pour prendre l’image. Lorsque le curseur est rouge, cela signifie que l’appareil photo est occupé.

  8. Ajoutez la méthode utilisée par l’application pour démarrer le processus de capture d’image et stocker l’image :

        /// <summary>
        /// Begin process of image capturing and send to Azure Custom Vision Service.
        /// </summary>
        private void ExecuteImageCaptureAndAnalysis()
        {
            // Create a label in world space using the ResultsLabel class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending
                ((res) => res.width * res.height).First();
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(true, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 1.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });
        }
    
  9. Ajoutez les gestionnaires qui seront appelés lorsque la photo aura été capturée et quand elle sera prête à être analysée. Le résultat est ensuite passé à CustomVisionAnalyser pour analyse.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
            try
            {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
            }
            catch (Exception e)
            {
                Debug.LogFormat("Exception capturing photo to disk: {0}", e.Message);
            }
        }
    
        /// <summary>
        /// The camera photo mode has stopped after the capture.
        /// Begin the image analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            Debug.LogFormat("Stopped Photo Mode");
    
            // Dispose from the object in memory and request the image analysis 
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            // Call the image analysis
            StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath)); 
        }
    
        /// <summary>
        /// Stops all capture pending actions
        /// </summary>
        internal void ResetImageCapture()
        {
            captureIsActive = false;
    
            // Set the cursor color to green
            SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.

Chapitre 11 - Configuration des scripts dans la scène

Maintenant que vous avez écrit tout le code nécessaire pour ce projet, il est temps de configurer les scripts dans la scène, et sur les préfabriqués, pour qu’ils se comportent correctement.

  1. Dans l’éditeur Unity, dans le panneau Hiérarchie, sélectionnez l’appareil photo principal.

  2. Dans le panneau inspecteur, avec la caméra principale sélectionnée, cliquez sur Ajouter un composant, puis recherchez le script SceneOrganiser et double-cliquez pour l’ajouter.

    Capture d’écran montrant le script SceneOrganizer.

  3. Dans le panneau projet, ouvrez le dossier Prefabs, faites glisser le préfabriqué d’étiquette dans la zone d’entrée cible de référence vide Label, dans le script SceneOrganiser que vous venez d’ajouter à la caméra principale, comme illustré dans l’image ci-dessous :

    Capture d’écran montrant le script que vous avez ajouté à l’appareil photo principal.

  4. Dans le panneau Hiérarchie, sélectionnez l’enfant GazeCursor de la caméra principale.

  5. Dans le panneau inspecteur, avec le GazeCursor sélectionné, cliquez sur Ajouter un composant, recherchez le script GazeCursor et double-cliquez pour l’ajouter.

    Capture d’écran montrant où vous ajoutez le script GazeCursor.

  6. Là encore, dans le panneau hiérarchie, sélectionnez l’enfant SpatialMapping de la caméra principale.

  7. Dans le panneau Inspecteur, avec spatialMapping sélectionné, cliquez sur Ajouter un composant, puis recherchez le script SpatialMapping et double-cliquez pour l’ajouter.

    Capture d’écran montrant où vous ajoutez le script SpatialMapping.

Les scripts restants que vous n’avez pas définis seront ajoutés par le code dans le script SceneOrganiser , pendant l’exécution.

Chapitre 12 - Avant la génération

Pour effectuer un test approfondi de votre application, vous devez la charger sur votre Microsoft HoloLens.

Avant de le faire, assurez-vous que :

  • Tous les paramètres mentionnés dans le chapitre 3 sont définis correctement.

  • Le script SceneOrganiser est attaché à l’objet Main Camera .

  • Le script GazeCursor est attaché à l’objet GazeCursor .

  • Le script SpatialMapping est attaché à l’objet SpatialMapping .

  • Dans le chapitre 5, étape 6 :

    • Veillez à insérer votre clé de prédiction de service dans la variable predictionKey .
    • Vous avez inséré votre point de terminaison de prédiction dans la classe predictionEndpoint .

Chapitre 13 - Générer la solution UWP et charger votre application de manière indépendante

Vous êtes maintenant prêt à créer votre application en tant que solution UWP que vous pourrez déployer sur le Microsoft HoloLens. Pour commencer le processus de génération :

  1. Accédez à Paramètres de génération de fichiers>.

  2. Cochez Unity C# Projects.

  3. Cliquez sur Ajouter des scènes ouvertes. Cela ajoute la scène actuellement ouverte à la build.

    Capture d’écran mettant en évidence le bouton Ajouter des scènes ouvertes.

  4. Cliquez sur Générer. Unity lance une fenêtre Explorateur de fichiers, dans laquelle vous devez créer, puis sélectionner un dossier dans lequel générer l’application. Créez ce dossier maintenant et nommez-le Application. Ensuite, avec le dossier d’application sélectionné, cliquez sur Sélectionner un dossier.

  5. Unity commence à générer votre projet dans le dossier Application .

  6. Une fois la génération terminée (cela peut prendre un certain temps), Unity ouvre une fenêtre de Explorateur de fichiers à l’emplacement de votre build (case activée votre barre des tâches, car elle n’apparaît peut-être pas toujours au-dessus de vos fenêtres, mais vous avertit de l’ajout d’une nouvelle fenêtre).

  7. Pour effectuer un déploiement sur Microsoft HoloLens, vous aurez besoin de l’adresse IP de cet appareil (pour le déploiement à distance) et de vous assurer qu’il dispose également du mode développeur défini. Pour ce faire :

    1. Lorsque vous portez votre HoloLens, ouvrez les paramètres.

    2. Accédez à Network & Internet>Wi-Fi>Advanced Options

    3. Notez l’adresse IPv4 .

    4. Ensuite, revenez à Paramètres, puis à Mettre à jour & sécurité>pour les développeurs

    5. Définissez le mode développeuractivé.

  8. Accédez à votre nouvelle build Unity (le dossier App ) et ouvrez le fichier solution avec Visual Studio.

  9. Dans configuration de la solution, sélectionnez Déboguer.

  10. Dans Plateforme de solution, sélectionnez x86, Machine distante. Vous serez invité à insérer l’adresse IP d’un appareil distant (le Microsoft HoloLens, dans ce cas, que vous avez noté).

    Capture d’écran montrant où insérer l’adresse IP.

  11. Accédez au menu Générer et cliquez sur Déployer la solution pour charger l’application sur votre HoloLens.

  12. Votre application doit maintenant apparaître dans la liste des applications installées sur votre Microsoft HoloLens, prêtes à être lancées !

Pour utiliser l’application :

  • Examinez un objet que vous avez entraîné avec votre service Azure Custom Vision, détection d’objets, et utilisez le mouvement Appuyez.
  • Si l’objet est détecté, un texte d’étiquette d’espace mondial s’affiche avec le nom de la balise.

Important

Chaque fois que vous capturez une photo et que vous l’envoyez au service, vous pouvez revenir à la page Service et réentraîner le service avec les images nouvellement capturées. Au début, vous devrez probablement également corriger les zones englobantes pour être plus précis et réentraîner le service.

Notes

Le texte d’étiquette placé peut ne pas apparaître près de l’objet lorsque le Microsoft HoloLens capteurs et/ou le SpatialTrackingComponent dans Unity ne parvient pas à placer les collisionneurs appropriés, par rapport aux objets réels. Essayez d’utiliser l’application sur une autre surface si c’est le cas.

Votre application détection d’objets Custom Vision

Félicitations, vous avez créé une application de réalité mixte qui tire parti de l’API Détection d’objets Azure Custom Vision, qui peut reconnaître un objet à partir d’une image, puis fournir une position approximative pour cet objet dans l’espace 3D.

Capture d’écran montrant une application de réalité mixte qui tire parti de l’API Détection d’objets Azure Custom Vision.

Exercices bonus

Exercice 1

En ajoutant à l’étiquette de texte, utilisez un cube semi-transparent pour encapsuler l’objet réel dans un cadre englobant 3D.

Exercice 2

Entraînez votre service Custom Vision pour qu’il reconnaisse davantage d’objets.

Exercice 3

Lire un son lorsqu’un objet est reconnu.

Exercice 4

Utilisez l’API pour réententreîner votre service avec les mêmes images que celles que votre application analyse, afin de rendre le service plus précis (effectuez à la fois la prédiction et l’entraînement simultanément).