HoloLens (1ère génération) et Azure 302b : Vision personnalisée
Remarque
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 qui montreront comment développer pour HoloLens 2. Cet avis sera mis à jour avec un lien vers ces didacticiels lorsqu’ils sont publiés.
Dans ce cours, vous allez apprendre à reconnaître du contenu visuel personnalisé dans une image fournie, à l’aide des fonctionnalités Azure Custom Vision 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 allez ensuite utiliser le modèle entraîné pour reconnaître des objets similaires, comme fourni par la capture de caméra de Microsoft HoloLens ou une caméra connectée à votre PC pour les casques immersifs (VR).
Azure Custom Vision est un service cognitif 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 reconnaître ou classifier des objets au sein de cette nouvelle image. Le service fournit un portail en ligne simple et facile à utiliser pour simplifier le processus. Pour plus d’informations, consultez la page Du service Azure Custom Vision.
Une fois ce cours terminé, vous aurez une application de réalité mixte qui sera en mesure de fonctionner en deux modes :
Mode d’analyse : configuration manuelle du service Custom Vision en chargeant des images, en créant des balises et en formant le service pour reconnaître différents objets (dans ce cas, souris et clavier). Vous allez ensuite créer une application HoloLens qui capturera des images à l’aide de l’appareil photo et essayez de reconnaître ces objets dans le monde réel.
Mode d’entraînement : vous allez implémenter du code qui active un « mode d’entraînement » dans votre application. Le mode d’entraînement vous permet de capturer des images à l’aide de l’appareil photo de HoloLens, de charger les images capturées dans le service et d’entraîner le modèle de vision personnalisée.
Ce cours vous apprend à obtenir les résultats du service Custom Vision dans un exemple d’application basé sur Unity. Il vous sera possible d’appliquer ces concepts à une application personnalisée que vous créez peut-être.
Prise en charge des appareils
Cours | HoloLens | Casques immersifs |
---|---|---|
MR et Azure 302b : Vision personnalisée | ✔️ | ✔️ |
Remarque
Bien que ce cours se concentre principalement sur HoloLens, vous pouvez également appliquer ce que vous apprenez dans ce cours aux casques immersifs Windows Mixed Reality (VR). Étant donné que les casques immersifs (VR) n’ont pas de caméras accessibles, vous aurez besoin d’une caméra externe connectée à votre PC. À mesure que vous suivez le cours, vous verrez des notes sur les modifications que vous devrez peut-être utiliser pour prendre en charge les casques immersifs (VR).
Prérequis
Remarque
Ce tutoriel est conçu pour les développeurs qui ont une expérience de base avec Unity et C#. Sachez également que les conditions préalables et les instructions écrites contenues dans ce document représentent ce qui a été testé et vérifié au moment de l’écriture (juillet 2018). Vous êtes libre d’utiliser le logiciel le plus récent, comme indiqué dans l’article d’installation des outils , bien qu’il ne soit pas supposé que les informations de ce cours correspondent parfaitement à ce que vous trouverez dans les logiciels plus récents que ceux répertoriés ci-dessous.
Nous vous recommandons le matériel et les logiciels suivants pour ce cours :
- Un PC de développement, compatible avec Windows Mixed Reality pour le développement de casque immersif (VR)
- Windows 10 Fall Creators Update (ou version ultérieure) avec le mode développeur activé
- Le sdk Windows 10 le plus récent
- Unity 2017.4
- Visual Studio 2017
- Un casque immersif Windows Mixed Reality (VR) ou Microsoft HoloLens avec le mode développeur activé
- Caméra connectée à votre PC (pour le développement de casque immersif)
- Accès Internet pour la configuration d’Azure et la récupération de l’API Custom Vision
- Une série d’au moins cinq (5) images (10) recommandées pour chaque objet que vous souhaitez que le service Custom Vision reconnaisse. Si vous le souhaitez, vous pouvez utiliser les images déjà fournies avec ce cours (une souris d’ordinateur et un clavier).
Avant de commencer
- 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 quasi-racine (des chemins de dossier longs peuvent provoquer des problèmes au moment de la génération).
- Configurez et testez votre HoloLens. Si vous avez besoin de support pour configurer votre HoloLens, veillez à consulter l’article de configuration de HoloLens.
- Il est judicieux d’effectuer l’étalonnage et le réglage des capteurs lors du développement d’une nouvelle application HoloLens (parfois, il peut aider à effectuer ces tâches pour chaque utilisateur).
Pour obtenir de l’aide sur l’étalonnage, suivez ce lien vers l’article d’é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 - Portail du service Custom Vision
Pour utiliser le service Custom Vision dans Azure, vous devez configurer une instance du service à rendre disponible pour votre application.
Tout d’abord, accédez à la page principale du service Custom Vision.
Cliquez sur le bouton Prise en main .
Connectez-vous au portail du service Custom Vision.
Remarque
Si vous n’avez pas encore de compte Azure, vous devez en créer un. Si vous suivez ce tutoriel dans une situation de salle de classe ou de laboratoire, demandez à votre instructeur ou à l’un des proctoreurs de vous aider à configurer votre nouveau compte.
Une fois que vous êtes connecté pour la première fois, vous serez invité à utiliser le panneau Conditions d’utilisation . Cliquez sur la case à cocher pour accepter les conditions. Cliquez ensuite sur je suis d’accord.
Après avoir accepté les termes, vous êtes redirigé vers la section Projets du portail. Cliquez sur Nouveau projet.
Un onglet s’affiche sur le côté droit, ce qui vous invite à spécifier certains champs pour le projet.
Insérez un nom pour votre projet.
Insérez une description pour votre projet (facultatif).
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.
Définir les types de projet sur classification
Définissez les domaines en tant que général.
Si vous souhaitez en savoir plus sur les groupes de ressources Azure, consultez l’article du groupe de ressources.
Une fois que vous avez terminé, cliquez sur Créer un projet, vous êtes redirigé vers le service Custom Vision, page du projet.
Chapitre 2 - Formation de votre projet Custom Vision
Une fois dans le portail Custom Vision, votre objectif principal est d’entraîner votre projet pour reconnaître des objets spécifiques dans des images. Vous avez besoin d’au moins cinq images (5), bien que dix (10) soient préférées, pour chaque objet que vous souhaitez que votre application reconnaisse. Vous pouvez utiliser les images fournies avec ce cours (une souris d’ordinateur et un clavier).
Pour effectuer l’apprentissage de votre projet Custom Vision Service :
Cliquez sur le + bouton en regard des balises.
Ajoutez le nom de l’objet que vous souhaitez reconnaître. Cliquez sur Save(Enregistrer).
Vous remarquerez que votre balise a été ajoutée (vous devrez peut-être recharger votre page pour qu’elle apparaisse). Cochez la case en regard de votre nouvelle balise, si elle n’est pas déjà cochée.
Cliquez sur Ajouter des images dans le centre de la page.
Cliquez sur Parcourir les fichiers locaux et recherchez, puis sélectionnez les images que vous souhaitez charger, avec au minimum cinq (5). N’oubliez pas que toutes ces images doivent contenir l’objet que vous entraînez.
Remarque
Vous pouvez sélectionner plusieurs images à la fois pour charger.
Une fois que vous pouvez voir les images dans l’onglet, sélectionnez la balise appropriée dans la zone Mes balises .
Cliquez sur Charger des fichiers. Les fichiers commencent à être chargés. Une fois que vous avez confirmé le chargement, cliquez sur Terminé.
Répétez le même processus pour créer une balise nommée Clavier et charger les photos appropriées. Veillez à décocher la souris une fois que vous avez créé les nouvelles balises, afin d’afficher la fenêtre Ajouter des images .
Une fois que vous avez configuré les deux balises, cliquez sur Entraîner et la première itération d’entraînement commence à générer.
Une fois qu’il est généré, vous serez en mesure de voir deux boutons appelés Make default and Prediction URL. Cliquez d’abord sur Définir la valeur par défaut , puis sur l’URL de prédiction.
Remarque
L’URL du point de terminaison fournie à partir de ce point de terminaison est définie sur la valeur par défaut de l’itération. Par conséquent, si vous effectuez ultérieurement une nouvelle itération et que vous la mettez à jour comme valeur par défaut, vous n’aurez pas besoin de modifier votre code.
Une fois que vous avez cliqué sur l’URL de prédiction, ouvrez le Bloc-notes, puis copiez et collez l’URL et la clé de prédiction, afin de pouvoir la récupérer lorsque vous en avez besoin plus tard dans le code.
Cliquez sur le Cog en haut à droite de l’écran.
Copiez la clé d’entraînement et collez-la dans un Bloc-notes pour une utilisation ultérieure.
Copiez également votre ID de projet et collez-le dans votre fichier bloc-notes pour une utilisation ultérieure.
Chapitre 3 - Configurer le projet Unity
Voici 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.
Ouvrez Unity , puis cliquez sur Nouveau.
Vous devez maintenant fournir un nom de projet Unity. Insérez AzureCustomVision. Vérifiez que le modèle de projet est défini sur 3D. Définissez l’emplacement sur un emplacement approprié pour vous (n’oubliez pas que les répertoires racines sont plus proches). Cliquez ensuite sur Créer un projet.
Avec Unity ouvert, il vaut la peine 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 à partir de la nouvelle fenêtre, accédez à Outils externes. Remplacez l’éditeur de script externe par Visual Studio 2017. Fermez la fenêtre Préférences.
Ensuite, accédez à Paramètres de build de fichier > et sélectionnez plateforme Windows universelle, puis cliquez sur le bouton Basculer la plateforme pour appliquer votre sélection.
Tout en restant dans les paramètres de build de fichier > et assurez-vous que :
L’appareil cible est défini sur HoloLens
Pour les casques immersifs, définissez l’appareil cible sur n’importe quel appareil.
Le type de build est défini sur D3D
Le KIT SDK est défini sur La dernière version installée
La version de Visual Studio est définie sur La dernière version installée
La génération et l’exécution sont définies sur Ordinateur local
Enregistrez la scène et ajoutez-la à la build.
Pour ce faire, sélectionnez Ajouter des scènes ouvertes. Une fenêtre d’enregistrement s’affiche.
Créez un dossier pour cela et toute scène future, puis sélectionnez le bouton Nouveau dossier pour créer un dossier, nommez-le Scènes.
Ouvrez votre dossier Scènes nouvellement créé, puis dans le champ Fichier : champ de texte, tapez CustomVisionScene, puis cliquez sur Enregistrer.
Sachez que vous devez enregistrer vos scènes Unity dans le dossier Assets , car elles doivent être associées au projet Unity. La création du dossier scènes (et d’autres dossiers similaires) est un moyen typique de structurer un projet Unity.
Les paramètres restants, dans Paramètres de build, doivent être laissés comme valeurs par défaut pour l’instant.
Dans la fenêtre Paramètres de build, cliquez sur le bouton Paramètres du lecteur, ce qui ouvre le panneau associé dans l’espace où se trouve l’inspecteur.
Dans ce panneau, quelques paramètres doivent être vérifiés :
Sous l’onglet Autres paramètres :
La version du runtime de script doit être expérimentale (équivalent .NET 4.6), ce qui déclenche un redémarrage de l’éditeur.
Le serveur principal de script doit être .NET
Le niveau de compatibilité des API doit être .NET 4.6
Sous l’onglet Paramètres de publication, sous Fonctionnalités, vérifiez :
InternetClient
Webcam
Microphone
Plus loin dans le panneau, dans les paramètres XR (trouvés ci-dessous paramètres de publication), cochez Virtual Reality Pris en charge, vérifiez que le SDK Windows Mixed Reality est ajouté.
De retour dans les projets Unity C# des paramètres de build n’est plus grisé ; cochez la case en regard de cela.
Fermez la fenêtre Build Settings.
Enregistrez votre scène et votre projet (FILE > SAVE SCENE / FILE > SAVE PROJECT).
Chapitre 4 - Importation de la DLL Newtonsoft dans Unity
Important
Si vous souhaitez ignorer le composant De configuration Unity de ce cours et continuer directement dans le code, n’hésitez pas à télécharger ce package Azure-MR-302b.unity, à l’importer dans votre projet en tant que package personnalisé, puis à partir du chapitre 6.
Ce cours nécessite l’utilisation de la bibliothèque Newtonsoft , que vous pouvez ajouter en tant que DLL à vos ressources. Le package contenant cette bibliothèque peut être téléchargé à partir de ce lien. Pour importer la bibliothèque Newtonsoft dans votre projet, utilisez le package Unity fourni avec ce cours.
Ajoutez le .unitypackage à Unity à l’aide de l’option de menu Package personnalisé d’importationde package> deressources.>
Dans la zone Importer un package Unity qui s’affiche, vérifiez que tous les plug-ins sous (et y compris) sont sélectionnés.
Cliquez sur le bouton Importer pour ajouter les éléments à votre projet.
Accédez au dossier Newtonsoft sous Plug-ins dans l’affichage du projet et sélectionnez le plug-in Newtonsoft.Json.
Une fois le plug-in Newtonsoft.Json sélectionné, vérifiez que n’importe quelle plateforme est désactivée, puis vérifiez que WSAPlayer est également désactivé, puis cliquez sur Appliquer. Il s’agit simplement de confirmer que les fichiers sont correctement configurés.
Remarque
Le marquage de ces plug-ins les configure uniquement pour être utilisés dans l’éditeur Unity. Il existe un ensemble différent d’entre eux dans le dossier WSA qui sera utilisé une fois le projet exporté à partir d’Unity.
Ensuite, vous devez ouvrir le dossier WSA dans le dossier Newtonsoft . Vous verrez une copie du même fichier que celui que vous venez de configurer. Sélectionnez le fichier, puis dans l’inspecteur, vérifiez que
- Toute plateforme n’est pas cochée
- seul WSAPlayer est activé
- Le processus n’est pas vérifié
Chapitre 5 - Configuration de la caméra
Dans le panneau Hiérarchie, sélectionnez l’appareil photo principal.
Une fois sélectionné, vous pourrez voir tous les composants de la caméra principale dans le panneau Inspecteur.
L’objet caméra doit être nommé Main Camera (notez l’orthographe !)
La balise Main Camera doit être définie sur MainCamera (notez l’orthographe !)
Vérifiez que la position de transformation est définie sur 0, 0, 0
Définissez Clear Flags sur Solid Color (ignorez-le pour le casque immersif).
Définissez la couleur d’arrière-plan du composant caméra sur Black, Alpha 0 (Code hexadécimal : #00000000000) (ignorez ceci pour le casque immersif).
Chapitre 6 : créez la classe CustomVisionAnalyser.
À ce stade, vous êtes prêt à écrire du code.
Vous commencerez par la classe CustomVisionAnalyser .
Remarque
Les appels au service Custom Vision effectués dans le code ci-dessous sont effectués à l’aide de l’API REST Custom Vision. À l’aide de cela, vous verrez comment implémenter et utiliser cette API (utile pour comprendre comment implémenter quelque chose de similaire à votre propre). N’oubliez pas que Microsoft propose un KIT de développement logiciel (SDK) du service Custom Vision qui peut également être utilisé pour effectuer des appels au service. Pour plus d’informations, consultez l’article du Kit de développement logiciel (SDK) du service Custom Vision.
Cette classe est responsable des points suivants :
Chargement de la dernière image capturée sous la forme d’un 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 transmission de la prédiction résultante à la classe SceneOrganiser, qui s’occupera de la façon dont la réponse doit être affichée.
Pour créer cette classe :
Cliquez avec le bouton droit dans le dossier de ressources situé dans le volet projet, puis cliquez sur Créer > un dossier. Appelez les scripts du dossier.
Double-cliquez sur le dossier que vous venez de créer pour l’ouvrir.
Cliquez avec le bouton droit dans le dossier, puis cliquez sur Créer>un script C#. Nommez le script CustomVisionAnalyser.
Double-cliquez sur le nouveau script CustomVisionAnalyser pour l’ouvrir avec Visual Studio.
Mettez à jour les espaces de noms en haut de votre fichier pour qu’ils correspondent aux éléments suivants :
using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking; using Newtonsoft.Json;
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> /// Byte array of the image to submit for analysis /// </summary> [HideInInspector] public byte[] imageBytes;
Remarque
Veillez à insérer votre clé de prédiction dans la variable predictionKey et votre point de terminaison de prédiction dans la variable predictionEndpoint . Vous les avez copiées dans le Bloc-notes précédemment dans le cours.
Le code pour Awake() doit maintenant être ajouté pour initialiser la variable d’instance :
/// <summary> /// Initialises this class /// </summary> private void Awake() { // Allows this instance to behave like a singleton Instance = this; }
Supprimez les méthodes Start() et Update().
Ensuite, ajoutez la coroutine (avec la méthode GetImageAsByteArray() statique ci-dessous, qui obtient les résultats de l’analyse de l’image capturée par la classe ImageCapture.
Remarque
Dans la coroutine AnalyseImageCapture , il existe un appel à la classe SceneOrganiser que vous n’êtes pas encore à créer. 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) { 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; // The response will be in JSON format, therefore it needs to be deserialized // The following lines refers to a class that you will build in later Chapters // Wait until then to uncomment these lines //AnalysisObject analysisObject = new AnalysisObject(); //analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse); //SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject); } } /// <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); }
Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.
Chapitre 7 - Créer la classe CustomVisionObjects
La classe que vous allez créer est maintenant 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.
Avertissement
Il est important de noter le point de terminaison fourni par le service Custom Vision, car la structure JSON ci-dessous a été configurée pour fonctionner avec Custom Vision Prediction v2.0. Si vous avez une autre version, vous devrez peut-être mettre à jour la structure ci-dessous.
Pour créer cette classe :
Cliquez avec le bouton droit dans le dossier Scripts, puis cliquez sur Créer un>script C#. Appelez le script CustomVisionObjects.
Double-cliquez sur le nouveau script CustomVisionObjects pour l’ouvrir avec Visual Studio.
Ajoutez les espaces de noms suivants en haut du fichier :
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking;
Supprimez les méthodes Start() et Update() à l’intérieur de la classe CustomVisionObjects ; cette classe doit maintenant être vide.
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 /// </summary> [Serializable] public class AnalysisObject { public List<Prediction> Predictions { get; set; } } [Serializable] public class Prediction { public string TagName { get; set; } public double Probability { get; set; } }
Chapitre 8 - Créer la classe VoiceRecognizer
Cette classe reconnaît l’entrée vocale de l’utilisateur.
Pour créer cette classe :
Cliquez avec le bouton droit dans le dossier Scripts, puis cliquez sur Créer un>script C#. Appelez le script VoiceRecognizer.
Double-cliquez sur le nouveau script VoiceRecognizer pour l’ouvrir avec Visual Studio.
Ajoutez les espaces de noms suivants au-dessus de la classe VoiceRecognizer :
using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Windows.Speech;
Ajoutez ensuite les variables suivantes à l’intérieur de la classe VoiceRecognizer , au-dessus de la méthode Start() :
/// <summary> /// Allows this class to behave like a singleton /// </summary> public static VoiceRecognizer Instance; /// <summary> /// Recognizer class for voice recognition /// </summary> internal KeywordRecognizer keywordRecognizer; /// <summary> /// List of Keywords registered /// </summary> private Dictionary<string, Action> _keywords = new Dictionary<string, Action>();
Ajoutez les méthodes Awake() et Start(), dont la dernière configure les mots clés utilisateur à reconnaître lors de l’association d’une balise à une image :
/// <summary> /// Called on initialization /// </summary> private void Awake() { Instance = this; } /// <summary> /// Runs at initialization right after Awake method /// </summary> void Start () { Array tagsArray = Enum.GetValues(typeof(CustomVisionTrainer.Tags)); foreach (object tagWord in tagsArray) { _keywords.Add(tagWord.ToString(), () => { // When a word is recognized, the following line will be called CustomVisionTrainer.Instance.VerifyTag(tagWord.ToString()); }); } _keywords.Add("Discard", () => { // When a word is recognized, the following line will be called // The user does not want to submit the image // therefore ignore and discard the process ImageCapture.Instance.ResetImageCapture(); keywordRecognizer.Stop(); }); //Create the keyword recognizer keywordRecognizer = new KeywordRecognizer(_keywords.Keys.ToArray()); // Register for the OnPhraseRecognized event keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized; }
Supprimez la méthode Update().
Ajoutez le gestionnaire suivant, qui est appelé chaque fois que l’entrée vocale est reconnue :
/// <summary> /// Handler called when a word is recognized /// </summary> private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args) { Action keywordAction; // if the keyword recognized is in our dictionary, call that Action. if (_keywords.TryGetValue(args.text, out keywordAction)) { keywordAction.Invoke(); } }
Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.
Remarque
Ne vous inquiétez pas du code qui peut sembler avoir une erreur, car vous fournirez bientôt d’autres classes, ce qui les corrigera.
Chapitre 9 - Créer la classe CustomVisionTrainer
Cette classe va chaîner une série d’appels web pour entraîner le service Custom Vision. Chaque appel sera expliqué en détail juste au-dessus du code.
Pour créer cette classe :
Cliquez avec le bouton droit dans le dossier Scripts, puis cliquez sur Créer un>script C#. Appelez le script CustomVisionTrainer.
Double-cliquez sur le nouveau script CustomVisionTrainer pour l’ouvrir avec Visual Studio.
Ajoutez les espaces de noms suivants au-dessus de la classe CustomVisionTrainer :
using Newtonsoft.Json; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; using UnityEngine.Networking;
Ajoutez ensuite les variables suivantes à l’intérieur de la classe CustomVisionTrainer, au-dessus de la méthode Start().
Remarque
L’URL de formation utilisée ici est fournie dans la documentation Custom Vision Training 1.2 et a une structure de : https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
Pour plus d’informations, consultez l’API de référence Custom Vision Training v1.2.Avertissement
Il est important que vous notiez le point de terminaison que le service Custom Vision vous fournit pour le mode d’entraînement, car la structure JSON utilisée (dans la classe CustomVisionObjects) a été configurée pour fonctionner avec Custom Vision Training v1.2. Si vous avez une autre version, vous devrez peut-être mettre à jour la structure Objects .
/// <summary> /// Allows this class to behave like a singleton /// </summary> public static CustomVisionTrainer Instance; /// <summary> /// Custom Vision Service URL root /// </summary> private string url = "https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/"; /// <summary> /// Insert your prediction key here /// </summary> private string trainingKey = "- Insert your key here -"; /// <summary> /// Insert your Project Id here /// </summary> private string projectId = "- Insert your Project Id here -"; /// <summary> /// Byte array of the image to submit for analysis /// </summary> internal byte[] imageBytes; /// <summary> /// The Tags accepted /// </summary> internal enum Tags {Mouse, Keyboard} /// <summary> /// The UI displaying the training Chapters /// </summary> private TextMesh trainingUI_TextMesh;
Important
Assurez-vous d’ajouter votre valeur de clé de service (clé d’entraînement) et la valeur ID de projet, que vous avez notée précédemment. Il s’agit des valeurs que vous avez collectées à partir du portail plus haut dans le cours (chapitre 2, étape 10 et versions ultérieures).
Ajoutez les méthodes Start() et Awake() suivantes. Ces méthodes sont appelées lors de l’initialisation et contiennent l’appel pour configurer l’interface utilisateur :
/// <summary> /// Called on initialization /// </summary> private void Awake() { Instance = this; } /// <summary> /// Runs at initialization right after Awake method /// </summary> private void Start() { trainingUI_TextMesh = SceneOrganiser.Instance.CreateTrainingUI("TrainingUI", 0.04f, 0, 4, false); }
Supprimez la méthode Update(). Cette classe n’en aura pas besoin.
Ajoutez la méthode RequestTagSelection(). Cette méthode est la première à être appelée lorsqu’une image a été capturée et stockée dans l’appareil et est maintenant prête à être envoyée au service Custom Vision pour l’entraîner. Cette méthode affiche, dans l’interface utilisateur d’apprentissage, un ensemble de mots clés que l’utilisateur peut utiliser pour baliser l’image capturée. Il avertit également la classe VoiceRecognizer de commencer à écouter l’utilisateur pour l’entrée vocale.
internal void RequestTagSelection() { trainingUI_TextMesh.gameObject.SetActive(true); trainingUI_TextMesh.text = $" \nUse voice command \nto choose between the following tags: \nMouse\nKeyboard \nor say Discard"; VoiceRecognizer.Instance.keywordRecognizer.Start(); }
Ajoutez la méthode VerifyTag(). Cette méthode reçoit l’entrée vocale reconnue par la classe VoiceRecognizer et vérifie sa validité, puis démarre le processus d’entraînement.
/// <summary> /// Verify voice input against stored tags. /// If positive, it will begin the Service training process. /// </summary> internal void VerifyTag(string spokenTag) { if (spokenTag == Tags.Mouse.ToString() || spokenTag == Tags.Keyboard.ToString()) { trainingUI_TextMesh.text = $"Tag chosen: {spokenTag}"; VoiceRecognizer.Instance.keywordRecognizer.Stop(); StartCoroutine(SubmitImageForTraining(ImageCapture.Instance.filePath, spokenTag)); } }
Ajoutez la méthode SubmitImageForTraining(). Cette méthode démarre le processus d’entraînement du service Custom Vision. La première étape consiste à récupérer l’ID d’étiquette du service associé à l’entrée vocale validée de l’utilisateur. L’ID de balise sera ensuite chargé avec l’image.
/// <summary> /// Call the Custom Vision Service to submit the image. /// </summary> public IEnumerator SubmitImageForTraining(string imagePath, string tag) { yield return new WaitForSeconds(2); trainingUI_TextMesh.text = $"Submitting Image \nwith tag: {tag} \nto Custom Vision Service"; string imageId = string.Empty; string tagId = string.Empty; // Retrieving the Tag Id relative to the voice input string getTagIdEndpoint = string.Format("{0}{1}/tags", url, projectId); using (UnityWebRequest www = UnityWebRequest.Get(getTagIdEndpoint)) { www.SetRequestHeader("Training-Key", trainingKey); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; Tags_RootObject tagRootObject = JsonConvert.DeserializeObject<Tags_RootObject>(jsonResponse); foreach (TagOfProject tOP in tagRootObject.Tags) { if (tOP.Name == tag) { tagId = tOP.Id; } } } // Creating the image object to send for training List<IMultipartFormSection> multipartList = new List<IMultipartFormSection>(); MultipartObject multipartObject = new MultipartObject(); multipartObject.contentType = "application/octet-stream"; multipartObject.fileName = ""; multipartObject.sectionData = GetImageAsByteArray(imagePath); multipartList.Add(multipartObject); string createImageFromDataEndpoint = string.Format("{0}{1}/images?tagIds={2}", url, projectId, tagId); using (UnityWebRequest www = UnityWebRequest.Post(createImageFromDataEndpoint, multipartList)) { // Gets a byte array out of the saved image imageBytes = GetImageAsByteArray(imagePath); //unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream"); www.SetRequestHeader("Training-Key", trainingKey); // The upload handler will help uploading the byte array with the request www.uploadHandler = new UploadHandlerRaw(imageBytes); // The download handler will help receiving the analysis from Azure www.downloadHandler = new DownloadHandlerBuffer(); // Send the request yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; ImageRootObject m = JsonConvert.DeserializeObject<ImageRootObject>(jsonResponse); imageId = m.Images[0].Image.Id; } trainingUI_TextMesh.text = "Image uploaded"; StartCoroutine(TrainCustomVisionProject()); }
Ajoutez la méthode TrainCustomVisionProject(). Une fois l’image envoyée et étiquetée, cette méthode est appelée. Il crée une itération qui sera entraînée avec toutes les images précédentes envoyées au service, ainsi que l’image juste chargée. Une fois l’entraînement terminé, cette méthode appelle une méthode pour définir l’itération nouvellement créée comme valeur par défaut, afin que le point de terminaison que vous utilisez pour l’analyse soit l’itération entraînée la plus récente.
/// <summary> /// Call the Custom Vision Service to train the Service. /// It will generate a new Iteration in the Service /// </summary> public IEnumerator TrainCustomVisionProject() { yield return new WaitForSeconds(2); trainingUI_TextMesh.text = "Training Custom Vision Service"; WWWForm webForm = new WWWForm(); string trainProjectEndpoint = string.Format("{0}{1}/train", url, projectId); using (UnityWebRequest www = UnityWebRequest.Post(trainProjectEndpoint, webForm)) { www.SetRequestHeader("Training-Key", trainingKey); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; Debug.Log($"Training - JSON Response: {jsonResponse}"); // A new iteration that has just been created and trained Iteration iteration = new Iteration(); iteration = JsonConvert.DeserializeObject<Iteration>(jsonResponse); if (www.isDone) { trainingUI_TextMesh.text = "Custom Vision Trained"; // Since the Service has a limited number of iterations available, // we need to set the last trained iteration as default // and delete all the iterations you dont need anymore StartCoroutine(SetDefaultIteration(iteration)); } } }
Ajoutez la méthode SetDefaultIteration(). Cette méthode définit l’itération créée et entraînée précédemment comme valeur par défaut. Une fois terminée, cette méthode devra supprimer l’itération précédente existante dans le service. Au moment de l’écriture de ce cours, il existe une limite de dix (10) itérations maximales autorisées à exister en même temps dans le service.
/// <summary> /// Set the newly created iteration as Default /// </summary> private IEnumerator SetDefaultIteration(Iteration iteration) { yield return new WaitForSeconds(5); trainingUI_TextMesh.text = "Setting default iteration"; // Set the last trained iteration to default iteration.IsDefault = true; // Convert the iteration object as JSON string iterationAsJson = JsonConvert.SerializeObject(iteration); byte[] bytes = Encoding.UTF8.GetBytes(iterationAsJson); string setDefaultIterationEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iteration.Id); using (UnityWebRequest www = UnityWebRequest.Put(setDefaultIterationEndpoint, bytes)) { www.method = "PATCH"; www.SetRequestHeader("Training-Key", trainingKey); www.SetRequestHeader("Content-Type", "application/json"); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; if (www.isDone) { trainingUI_TextMesh.text = "Default iteration is set \nDeleting Unused Iteration"; StartCoroutine(DeletePreviousIteration(iteration)); } } }
Ajoutez la méthode DeletePreviousIteration(). Cette méthode recherche et supprime l’itération précédente non par défaut :
/// <summary> /// Delete the previous non-default iteration. /// </summary> public IEnumerator DeletePreviousIteration(Iteration iteration) { yield return new WaitForSeconds(5); trainingUI_TextMesh.text = "Deleting Unused \nIteration"; string iterationToDeleteId = string.Empty; string findAllIterationsEndpoint = string.Format("{0}{1}/iterations", url, projectId); using (UnityWebRequest www = UnityWebRequest.Get(findAllIterationsEndpoint)) { www.SetRequestHeader("Training-Key", trainingKey); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; // The iteration that has just been trained List<Iteration> iterationsList = new List<Iteration>(); iterationsList = JsonConvert.DeserializeObject<List<Iteration>>(jsonResponse); foreach (Iteration i in iterationsList) { if (i.IsDefault != true) { Debug.Log($"Cleaning - Deleting iteration: {i.Name}, {i.Id}"); iterationToDeleteId = i.Id; break; } } } string deleteEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iterationToDeleteId); using (UnityWebRequest www2 = UnityWebRequest.Delete(deleteEndpoint)) { www2.SetRequestHeader("Training-Key", trainingKey); www2.downloadHandler = new DownloadHandlerBuffer(); yield return www2.SendWebRequest(); string jsonResponse = www2.downloadHandler.text; trainingUI_TextMesh.text = "Iteration Deleted"; yield return new WaitForSeconds(2); trainingUI_TextMesh.text = "Ready for next \ncapture"; yield return new WaitForSeconds(2); trainingUI_TextMesh.text = ""; ImageCapture.Instance.ResetImageCapture(); } }
La dernière méthode à ajouter dans cette classe est la méthode GetImageAsByteArray(), utilisée sur les appels web pour convertir l’image capturée en tableau d’octets.
/// <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); }
Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.
Chapitre 10 - Créer la classe SceneOrganiser
Cette classe :
Créez un objet Cursor à attacher à la caméra principale.
Créez un objet Label qui s’affiche lorsque le service reconnaît les objets réels.
Configurez l’appareil photo principal en lui attachant les composants appropriés.
En mode Analyse, générez les étiquettes au moment de l’exécution, dans l’espace mondial approprié par rapport à la position de la caméra principale et affichez les données reçues du service Custom Vision.
Quand vous êtes en mode d’entraînement, générez l’interface utilisateur qui affiche les différentes étapes du processus d’entraînement.
Pour créer cette classe :
Cliquez avec le bouton droit dans le dossier Scripts, puis cliquez sur Créer un>script C#. Nommez le script SceneOrganiser.
Double-cliquez sur le nouveau script SceneOrganiser pour l’ouvrir avec Visual Studio.
Vous n’aurez besoin que d’un espace de noms, supprimez les autres de la classe SceneOrganiser ci-dessus :
using UnityEngine;
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 camera /// </summary> internal GameObject cursor; /// <summary> /// The label used to display the analysis on the objects in the real world /// </summary> internal GameObject label; /// <summary> /// Object providing the current status of the camera. /// </summary> internal TextMesh cameraStatusIndicator; /// <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.5f;
Supprimez les méthodes Start() et Update().
Juste en dessous des variables, ajoutez la méthode Awake(), qui initialise la classe et configure 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 CustomVisionTrainer class to this GameObject gameObject.AddComponent<CustomVisionTrainer>(); // Add the VoiceRecogniser class to this GameObject gameObject.AddComponent<VoiceRecognizer>(); // Add the CustomVisionObjects class to this GameObject gameObject.AddComponent<CustomVisionObjects>(); // Create the camera Cursor cursor = CreateCameraCursor(); // Load the label prefab as reference label = CreateLabel(); // Create the camera status indicator label, and place it above where predictions // and training UI will appear. cameraStatusIndicator = CreateTrainingUI("Status Indicator", 0.02f, 0.2f, 3, true); // Set camera status indicator to loading. SetCameraStatus("Loading"); }
Ajoutez maintenant la méthode CreateCameraCursor() qui crée et positionne le curseur de la caméra principale et la méthode CreateLabel(), qui crée l’objet Analysis Label .
/// <summary> /// Spawns cursor for the Main Camera /// </summary> private GameObject CreateCameraCursor() { // Create a sphere as new cursor GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere); // Attach it to the camera newCursor.transform.parent = gameObject.transform; // Resize the new cursor newCursor.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f); // Move it to the correct position newCursor.transform.localPosition = new Vector3(0, 0, 4); // Set the cursor color to red newCursor.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse")); newCursor.GetComponent<Renderer>().material.color = Color.green; return newCursor; } /// <summary> /// Create the analysis label object /// </summary> private GameObject CreateLabel() { // Create a sphere as new cursor GameObject newLabel = new GameObject(); // Resize the new cursor newLabel.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f); // Creating the text of the label TextMesh t = newLabel.AddComponent<TextMesh>(); t.anchor = TextAnchor.MiddleCenter; t.alignment = TextAlignment.Center; t.fontSize = 50; t.text = ""; return newLabel; }
Ajoutez la méthode SetCameraStatus(), qui gère les messages destinés au maillage de texte fournissant l’état de l’appareil photo.
/// <summary> /// Set the camera status to a provided string. Will be coloured if it matches a keyword. /// </summary> /// <param name="statusText">Input string</param> public void SetCameraStatus(string statusText) { if (string.IsNullOrEmpty(statusText) == false) { string message = "white"; switch (statusText.ToLower()) { case "loading": message = "yellow"; break; case "ready": message = "green"; break; case "uploading image": message = "red"; break; case "looping capture": message = "yellow"; break; case "analysis": message = "red"; break; } cameraStatusIndicator.GetComponent<TextMesh>().text = $"Camera Status:\n<color={message}>{statusText}..</color>"; } }
Ajoutez les méthodes PlaceAnalysisLabel() et SetTagsToLastLabel(), qui génère et affiche les données du service Custom Vision dans la scène.
/// <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>(); } /// <summary> /// Set the Tags as Text of the last label created. /// </summary> public void SetTagsToLastLabel(AnalysisObject analysisObject) { lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>(); if (analysisObject.Predictions != null) { foreach (Prediction p in analysisObject.Predictions) { if (p.Probability > 0.02) { lastLabelPlacedText.text += $"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}"; Debug.Log($"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}"); } } } }
Enfin, ajoutez la méthode CreateTrainingUI(), qui génère l’interface utilisateur affichant les plusieurs étapes du processus d’entraînement lorsque l’application est en mode entraînement. Cette méthode sera également exploitée pour créer l’objet d’état de la caméra.
/// <summary> /// Create a 3D Text Mesh in scene, with various parameters. /// </summary> /// <param name="name">name of object</param> /// <param name="scale">scale of object (i.e. 0.04f)</param> /// <param name="yPos">height above the cursor (i.e. 0.3f</param> /// <param name="zPos">distance from the camera</param> /// <param name="setActive">whether the text mesh should be visible when it has been created</param> /// <returns>Returns a 3D text mesh within the scene</returns> internal TextMesh CreateTrainingUI(string name, float scale, float yPos, float zPos, bool setActive) { GameObject display = new GameObject(name, typeof(TextMesh)); display.transform.parent = Camera.main.transform; display.transform.localPosition = new Vector3(0, yPos, zPos); display.SetActive(setActive); display.transform.localScale = new Vector3(scale, scale, scale); display.transform.rotation = new Quaternion(); TextMesh textMesh = display.GetComponent<TextMesh>(); textMesh.anchor = TextAnchor.MiddleCenter; textMesh.alignment = TextAlignment.Center; return textMesh; }
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 des lignes suivantes :
AnalysisObject analysisObject = new AnalysisObject();
analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);
Chapitre 11 - Créer la classe ImageCapture
La classe suivante que vous allez créer est la classe ImageCapture .
Cette classe est responsable des points suivants :
Capture d’une image à l’aide de l’appareil photo HoloLens et le stockage dans le dossier de l’application.
Gestion des mouvements d’appui de l’utilisateur.
Maintien de la valeur Enum qui détermine si l’application s’exécute en mode Analyse ou Mode d’entraînement .
Pour créer cette classe :
Accédez au dossier Scripts que vous avez créé précédemment.
Cliquez avec le bouton droit dans le dossier, puis cliquez sur Créer > un script C#. Nommez le script ImageCapture.
Double-cliquez sur le nouveau script ImageCapture pour l’ouvrir avec Visual Studio.
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;
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> /// Loop timer /// </summary> private float secondsBetweenCaptures = 10f; /// <summary> /// Application main functionalities switch /// </summary> internal enum AppModes {Analysis, Training } /// <summary> /// Local variable for current AppMode /// </summary> internal AppModes AppMode { get; private set; } /// <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;
Les méthodes Code for Awake() et Start() doivent maintenant être ajoutées :
/// <summary> /// Called on initialization /// </summary> private void Awake() { Instance = this; // Change this flag to switch between Analysis Mode and Training Mode AppMode = AppModes.Training; } /// <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 HoloLens API gesture recognizer to track user gestures recognizer = new GestureRecognizer(); recognizer.SetRecognizableGestures(GestureSettings.Tap); recognizer.Tapped += TapHandler; recognizer.StartCapturingGestures(); SceneOrganiser.Instance.SetCameraStatus("Ready"); }
Implémentez un gestionnaire qui sera appelé lorsqu’un mouvement Tap se produit.
/// <summary> /// Respond to Tap Input. /// </summary> private void TapHandler(TappedEventArgs obj) { switch (AppMode) { case AppModes.Analysis: if (!captureIsActive) { captureIsActive = true; // Set the cursor color to red SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red; // Update camera status to looping capture. SceneOrganiser.Instance.SetCameraStatus("Looping Capture"); // Begin the capture loop InvokeRepeating("ExecuteImageCaptureAndAnalysis", 0, secondsBetweenCaptures); } else { // The user tapped while the app was analyzing // therefore stop the analysis process ResetImageCapture(); } break; case AppModes.Training: if (!captureIsActive) { captureIsActive = true; // Call the image capture ExecuteImageCaptureAndAnalysis(); // Set the cursor color to red SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red; // Update camera status to uploading image. SceneOrganiser.Instance.SetCameraStatus("Uploading Image"); } break; } }
Remarque
En mode Analyse , la méthode TapHandler agit comme un commutateur pour démarrer ou arrêter la boucle de capture de photos.
En mode Entraînement , il capture une image à partir de l’appareil photo.
Lorsque le curseur est vert, cela signifie que l’appareil photo est disponible pour prendre l’image.
Lorsque le curseur est rouge, cela signifie que l’appareil photo est occupé.
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() { // Update camera status to analysis. SceneOrganiser.Instance.SetCameraStatus("Analysis"); // Create a label in world space using the SceneOrganiser 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(false, delegate (PhotoCapture captureObject) { photoCaptureObject = captureObject; CameraParameters camParameters = new CameraParameters { hologramOpacity = 0.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); }); }); }
Ajoutez les gestionnaires qui seront appelés lorsque la photo a été capturée et pour le moment où elle est prête à être analysée. Le résultat est ensuite passé à CustomVisionAnalyser ou CustomVisionTrainer en fonction du mode sur lequel le code est défini.
/// <summary> /// Register the full execution of the Photo Capture. /// </summary> void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result) { // Call StopPhotoMode once the image has successfully captured photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode); } /// <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; switch (AppMode) { case AppModes.Analysis: // Call the image analysis StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath)); break; case AppModes.Training: // Call training using captured image CustomVisionTrainer.Instance.RequestTagSelection(); break; } } /// <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; // Update camera status to ready. SceneOrganiser.Instance.SetCameraStatus("Ready"); // Stop the capture loop if active CancelInvoke(); }
Veillez à enregistrer vos modifications dans Visual Studio avant de revenir à Unity.
Maintenant que tous les scripts ont été terminés, revenez dans l’Éditeur Unity, puis cliquez et faites glisser la classe SceneOrganiser du dossier Scripts vers l’objet Caméra principale dans le panneau Hierarchy.
Chapitre 12 - Avant la construction
Pour effectuer un test approfondi de votre application, vous devez la charger de manière indépendante sur votre HoloLens.
Avant de procéder, assurez-vous que :
Tous les paramètres mentionnés dans le chapitre 2 sont correctement définis.
Tous les champs de la caméra principale, panneau inspecteur, sont attribués correctement.
Le script SceneOrganiser est attaché à l’objet Main Camera .
Veillez à insérer votre clé de prédiction dans la variable predictionKey .
Vous avez inséré votre point de terminaison de prédiction dans la variable predictionEndpoint .
Vous avez inséré votre clé d’entraînement dans la variable trainingKey , de la classe CustomVisionTrainer .
Vous avez inséré votre ID de projet dans la variable projectId , de la classe CustomVisionTrainer .
Chapitre 13 - Générer et charger une version test de votre application
Pour commencer le processus de génération :
Accédez aux paramètres de génération de fichier>.
Cochez les projets C# Unity.
Cliquez sur Générer. Unity lance une fenêtre Explorateur de fichiers, où vous devez créer, puis sélectionner un dossier dans lequel générer l’application. Créez maintenant ce dossier et nommez-le App. Ensuite, avec le dossier d’application sélectionné, cliquez sur Sélectionner un dossier.
Unity commence à générer votre projet dans le dossier Application .
Une fois que Unity a terminé la génération (cela peut prendre un certain temps), il ouvre une fenêtre de Explorateur de fichiers à l’emplacement de votre build (vérifiez votre barre des tâches, car elle peut ne pas toujours apparaître au-dessus de vos fenêtres, mais vous informera de l’ajout d’une nouvelle fenêtre).
Pour déployer sur HoloLens :
Vous aurez besoin de l’adresse IP de votre HoloLens (pour le déploiement à distance) et de vous assurer que votre HoloLens est en mode développeur. Pour ce faire :
Tout en portant votre HoloLens, ouvrez les paramètres.
Accéder aux options avancées réseau et Wi-FiInternet>>
Notez l’adresse IPv4 .
Ensuite, revenez aux paramètres, puis à Update &Security>for Developers
Définissez le mode développeur activé.
Accédez à votre nouvelle build Unity (dossier d’application ) et ouvrez le fichier solution avec Visual Studio.
Dans la configuration de la solution, sélectionnez Déboguer.
Dans la plateforme de solutions, sélectionnez x86, Ordinateur distant. Vous serez invité à insérer l’adresse IP d’un appareil distant (HoloLens, dans ce cas, que vous avez noté).
Accédez au menu Générer , puis cliquez sur Déployer la solution pour charger l’application sur votre HoloLens.
Votre application doit maintenant apparaître dans la liste des applications installées sur votre HoloLens, prête à être lancée !
Remarque
Pour déployer sur un casque immersif, définissez la plateforme de solution sur ordinateur local et définissez la configuration sur Débogage, avec x86 comme plateforme. Ensuite, déployez sur l’ordinateur local, à l’aide de l’élément de menu Générer , en sélectionnant Déployer la solution.
Pour utiliser l’application :
Pour basculer la fonctionnalité d’application entre le mode d’entraînement et le mode prédiction , vous devez mettre à jour la variable AppMode , située dans la méthode Awake() située dans la classe ImageCapture .
// Change this flag to switch between Analysis mode and Training mode
AppMode = AppModes.Training;
or
// Change this flag to switch between Analysis mode and Training mode
AppMode = AppModes.Analysis;
En mode Entraînement :
Examinez la souris ou le clavier et utilisez le mouvement Tap.
Ensuite, le texte s’affiche pour vous demander de fournir une balise.
Dites souris ou clavier.
En mode prédiction :
Examinez un objet et utilisez le mouvement Tap.
Le texte s’affiche en fournissant l’objet détecté, avec la probabilité la plus élevée (ce qui est normalisé).
Chapitre 14 - Évaluer et améliorer votre modèle Custom Vision
Pour rendre votre service plus précis, vous devez continuer à entraîner le modèle utilisé pour la prédiction. Cela s’effectue à l’aide de votre nouvelle application, avec les modes d’apprentissage et de prédiction , ce qui vous oblige à visiter le portail, qui est ce qui est abordé dans ce chapitre. Préparez-vous à revisiter votre portail plusieurs fois, afin d’améliorer continuellement votre modèle.
Accédez à nouveau à votre portail Azure Custom Vision, puis une fois que vous êtes dans votre projet, sélectionnez l’onglet Prédictions (dans le centre supérieur de la page) :
Vous verrez toutes les images qui ont été envoyées à votre service pendant l’exécution de votre application. Si vous pointez sur les images, elles vous fournissent les prédictions effectuées pour cette image :
Sélectionnez l’une de vos images pour l’ouvrir. Une fois ouvert, vous verrez les prédictions effectuées pour cette image à droite. Si les prédictions étaient correctes et que vous souhaitez ajouter cette image au modèle d’entraînement de votre service, cliquez sur la zone d’entrée Mes balises , puis sélectionnez la balise que vous souhaitez associer. Lorsque vous avez terminé, cliquez sur le bouton Enregistrer et fermer en bas à droite, puis passez à l’image suivante.
Une fois que vous revenez à la grille des images, vous remarquerez que les images auxquelles vous avez ajouté des balises (et enregistrées) seront supprimées. Si vous trouvez des images que vous pensez ne pas avoir votre élément étiqueté dans ces éléments, vous pouvez les supprimer, en cliquant sur la coche de cette image (peut le faire pour plusieurs images), puis en cliquant sur Supprimer dans le coin supérieur droit de la page de grille. Dans la fenêtre contextuelle qui suit, vous pouvez cliquer sur Oui, Supprimer ou Non, pour confirmer la suppression ou l’annuler, respectivement.
Lorsque vous êtes prêt à continuer, cliquez sur le bouton Train vert en haut à droite. Votre modèle de service sera entraîné avec toutes les images que vous avez maintenant fournies (ce qui le rendra plus précis). Une fois l’entraînement terminé, veillez à cliquer une fois de plus sur le bouton Créer la valeur par défaut afin que votre URL de prédiction continue d’utiliser l’itération la plus récente de votre service.
Votre application API Custom Vision terminée
Félicitations, vous avez créé une application de réalité mixte qui tire parti de l’API Azure Custom Vision pour reconnaître les objets réels, entraîner le modèle de service et afficher la confiance de ce qui a été vu.
Exercices bonus
Exercice 1
Entraîner votre service Custom Vision pour reconnaître d’autres objets.
Exercice 2
Pour développer ce que vous avez appris, effectuez les exercices suivants :
Lire un son lorsqu’un objet est reconnu.
Exercice 3
Utilisez l’API pour réentreîner votre service avec les mêmes images que celles que votre application analyse, afin de rendre le service plus précis (effectuez simultanément la prédiction et l’entraînement).