Delen via


HoloLens (1e generatie) en Azure 302b: Custom Vision


Notitie

De Mixed Reality Academy-zelfstudies zijn ontworpen met het oog op HoloLens (1e generatie) en Mixed Reality Immersive Headsets. Daarom vinden we het belangrijk om deze zelfstudies te laten staan voor ontwikkelaars die nog steeds op zoek zijn naar hulp bij het ontwikkelen van deze apparaten. Deze zelfstudies worden niet bijgewerkt met de nieuwste hulpprogrammasets of interacties die worden gebruikt voor HoloLens 2. Ze worden onderhouden om te blijven werken op de ondersteunde apparaten. Er komt een nieuwe reeks zelfstudies die in de toekomst worden gepost, waarin wordt gedemonstreerd hoe u kunt ontwikkelen voor HoloLens 2. Deze kennisgeving wordt bijgewerkt met een koppeling naar deze zelfstudies wanneer deze worden gepost.


In deze cursus leert u hoe u aangepaste visuele inhoud binnen een opgegeven afbeelding kunt herkennen met behulp van Azure Custom Vision-mogelijkheden in een mixed reality-toepassing.

Met deze service kunt u een machine learning-model trainen met behulp van objectafbeeldingen. Vervolgens gebruikt u het getrainde model om vergelijkbare objecten te herkennen, zoals opgegeven door de camera-opname van Microsoft HoloLens of een camera die is aangesloten op uw pc voor immersive (VR)-headsets.

cursusresultaat

Azure Custom Vision is een Microsoft Cognitive Service waarmee ontwikkelaars aangepaste afbeeldingsclassificaties kunnen bouwen. Deze classificaties kunnen vervolgens worden gebruikt met nieuwe afbeeldingen om objecten in die nieuwe afbeelding te herkennen of te classificeren. De Service biedt een eenvoudig, eenvoudig te gebruiken online portal om het proces te stroomlijnen. Ga naar de pagina Azure Custom Vision Service voor meer informatie.

Na voltooiing van deze cursus hebt u een mixed reality-toepassing die in twee modi kan werken:

  • Analysemodus: de Custom Vision Service handmatig instellen door afbeeldingen te uploaden, tags te maken en de Service te trainen om verschillende objecten te herkennen (in dit geval muis en toetsenbord). Vervolgens maakt u een HoloLens-app die afbeeldingen vastlegt met behulp van de camera en probeert u deze objecten in de echte wereld te herkennen.

  • Trainingsmodus: u implementeert code waarmee een 'trainingsmodus' in uw app wordt ingeschakeld. Met de trainingsmodus kunt u afbeeldingen vastleggen met behulp van de camera van de HoloLens, de vastgelegde afbeeldingen uploaden naar de Service en het Custom Vision-model trainen.

In deze cursus leert u hoe u de resultaten van de Custom Vision Service kunt ophalen in een voorbeeldtoepassing op basis van Unity. Het is aan u om deze concepten toe te passen op een aangepaste toepassing die u mogelijk bouwt.

Ondersteuning voor apparaten

Cursus HoloLens Immersive headsets
MR en Azure 302b: Custom Vision ✔️ ✔️

Notitie

Hoewel deze cursus zich voornamelijk richt op HoloLens, kunt u wat u in deze cursus leert ook toepassen op Windows Mixed Reality immersive (VR) headsets. Omdat insluitende (VR)-headsets geen toegankelijke camera's hebben, hebt u een externe camera nodig die is aangesloten op uw pc. Terwijl u de cursus volgt, ziet u notities over eventuele wijzigingen die u mogelijk moet toepassen om insluitende (VR)-headsets te ondersteunen.

Vereisten

Notitie

Deze zelfstudie is bedoeld voor ontwikkelaars die basiservaring hebben met Unity en C#. Houd er ook rekening mee dat de vereisten en schriftelijke instructies in dit document overeenkomen met wat is getest en geverifieerd op het moment van schrijven (juli 2018). U bent vrij om de nieuwste software te gebruiken, zoals vermeld in het artikel over het installeren van de hulpprogramma's , hoewel er niet van mag worden uitgegaan dat de informatie in deze cursus perfect overeenkomt met wat u vindt in nieuwere software dan wat hieronder wordt vermeld.

Voor deze cursus raden we de volgende hardware en software aan:

Voordat u begint

  1. Om problemen met het bouwen van dit project te voorkomen, wordt u sterk aangeraden het project te maken dat in deze zelfstudie wordt vermeld in een hoofdmap of een bijna-hoofdmap (lange mappaden kunnen problemen veroorzaken tijdens het bouwen).
  2. Stel uw HoloLens in en test deze. Als u ondersteuning nodig hebt bij het instellen van uw HoloLens, raadpleegt u het artikel Over het instellen van HoloLens.
  3. Het is een goed idee om kalibratie en sensorafstemming uit te voeren wanneer u begint met het ontwikkelen van een nieuwe HoloLens-app (soms kan het helpen om deze taken voor elke gebruiker uit te voeren).

Volg deze koppeling naar het artikel HoloLens-kalibratie voor hulp bij kalibratie.

Volg deze koppeling naar het artikel HoloLens Sensor Tuning voor hulp bij sensorafstemming.

Hoofdstuk 1: De Custom Vision Service-portal

Als u de Custom Vision Service in Azure wilt gebruiken, moet u een exemplaar van de Service configureren dat beschikbaar moet worden gesteld aan uw toepassing.

  1. Navigeer eerst naar de hoofdpagina van Custom Vision Service.

  2. Klik op de knop Aan de slag .

    Aan de slag met Custom Vision Service

  3. Meld u aan bij de Custom Vision Service Portal.

    Aanmelden bij de portal

    Notitie

    Als u nog geen Azure-account hebt, moet u er een maken. Als u deze zelfstudie volgt in een leslokaal- of labsituatie, vraagt u uw docent of een van de docenten om hulp bij het instellen van uw nieuwe account.

  4. Zodra u voor het eerst bent aangemeld, wordt u gevraagd om het deelvenster Servicevoorwaarden . Klik op het selectievakje om akkoord te gaan met de voorwaarden. Klik vervolgens op Ik ga akkoord.

    Servicevoorwaarden

  5. Als u akkoord bent gegaan met de Voorwaarden, wordt u naar de sectie Projecten van de portal genavigeerd. Klik op Nieuw project.

    Nieuw project maken

  6. Aan de rechterkant wordt een tabblad weergegeven, waarin u wordt gevraagd om enkele velden voor het project op te geven.

    1. Voeg een naam in voor uw project.

    2. Voeg een beschrijving voor uw project in (optioneel).

    3. Kies een resourcegroep of maak een nieuwe. Een resourcegroep biedt een manier om een verzameling Azure-assets te bewaken, toegang te beheren, in te richten en facturering te beheren. Het wordt aanbevolen om alle Azure-services die zijn gekoppeld aan één project (zoals deze cursussen) onder een gemeenschappelijke resourcegroep te houden.

    4. Stel de projecttypen in op Classificatie

    5. Stel de domeinen in op Algemeen.

      De domeinen instellen

      Als u meer wilt lezen over Azure-resourcegroepen, gaat u naar het artikel over resourcegroepen.

  7. Wanneer u klaar bent, klikt u op Project maken. U wordt omgeleid naar de projectpagina Custom Vision Service.

Hoofdstuk 2 - Uw Custom Vision-project trainen

Eenmaal in de Custom Vision-portal is uw primaire doel om uw project te trainen om specifieke objecten in afbeeldingen te herkennen. U hebt ten minste vijf (5) afbeeldingen nodig, hoewel tien (10) de voorkeur heeft, voor elk object dat u door uw toepassing wilt laten herkennen. U kunt de afbeeldingen van deze cursus (een computermuis en een toetsenbord) gebruiken.

Uw Custom Vision Service-project trainen:

  1. Klik op de + knop naast Tags.

    Nieuwe tag toevoegen

  2. Voeg de naam toe van het object dat u wilt herkennen. Klik op Opslaan.

    Objectnaam toevoegen en opslaan

  3. U ziet dat de tag is toegevoegd (mogelijk moet u de pagina opnieuw laden om deze weer te geven). Klik op het selectievakje naast de nieuwe tag, als dit nog niet is ingeschakeld.

    Nieuwe tag inschakelen

  4. Klik op Afbeeldingen toevoegen in het midden van de pagina.

    Afbeeldingen toevoegen

  5. Klik op Bladeren in lokale bestanden en zoek en selecteer vervolgens de afbeeldingen die u wilt uploaden, met een minimum van vijf (5). Onthoud dat al deze afbeeldingen het object moeten bevatten dat u traint.

    Notitie

    U kunt meerdere afbeeldingen tegelijk selecteren om te uploaden.

  6. Zodra u de afbeeldingen op het tabblad kunt zien, selecteert u de juiste tag in het vak Mijn tags .

    Tags selecteren

  7. Klik op Bestanden uploaden. De bestanden worden geüpload. Zodra u de bevestiging van het uploaden hebt, klikt u op Gereed.

    Bestanden uploaden

  8. Herhaal hetzelfde proces om een nieuwe tag met de naam Toetsenbord te maken en de juiste foto's hiervoor te uploaden. Zorg ervoor dat u Muisuit schakelt nadat u de nieuwe tags hebt gemaakt, om het venster Afbeeldingen toevoegen weer te geven.

  9. Zodra u beide tags hebt ingesteld, klikt u op Trainen, waarna de eerste trainingsiteratie wordt gestart.

    Trainingsiteratie inschakelen

  10. Zodra het is gebouwd, ziet u twee knoppen met de naam Standaard maken en Voorspellings-URL. Klik op Eerst standaard maken en klik vervolgens op Voorspellings-URL.

    Standaard- en voorspellings-URL instellen

    Notitie

    De eindpunt-URL die hieruit wordt verstrekt, wordt ingesteld op de iteratie die als standaard is gemarkeerd. Als u later een nieuwe iteratie maakt en deze als standaard bijwerkt, hoeft u de code dus niet te wijzigen.

  11. Zodra u op Voorspellings-URL hebt geklikt, opent u Kladblok en kopieert en plakt u de URL en de voorspellingssleutel, zodat u deze later in de code kunt ophalen wanneer u deze nodig hebt.

    URL en voorspellingssleutel kopiëren en plakken

  12. Klik op het tandwiel rechtsboven in het scherm.

    Klik op het tandwielpictogram om instellingen te openen

  13. Kopieer de trainingssleutel en plak deze in een Kladblok voor later gebruik.

    Trainingssleutel kopiëren

  14. Kopieer ook uw project-id en plak deze in uw Kladblok-bestand voor later gebruik.

    Project-id kopiëren

Hoofdstuk 3- Het Unity-project instellen

Het volgende is een typische set-up voor het ontwikkelen met mixed reality en is daarom een goede sjabloon voor andere projecten.

  1. Open Unity en klik op Nieuw.

    Nieuw Unity-project maken

  2. U moet nu een Unity-projectnaam opgeven. Voeg AzureCustomVision in. Zorg ervoor dat de projectsjabloon is ingesteld op 3D. Stel de locatie in op een locatie die geschikt is voor u (houd er rekening mee dat dichter bij hoofdmappen beter is). Klik vervolgens op Project maken.

    Projectinstellingen configureren

  3. Als Unity is geopend, is het de moeite waard om te controleren of de standaardscripteditor is ingesteld op Visual Studio. Ga naar Voorkeuren bewerken> en navigeer vervolgens vanuit het nieuwe venster naar Externe hulpprogramma's. Wijzig De externe scripteditor in Visual Studio 2017. Sluit het venster Voorkeuren .

    Externe hulpprogramma's configureren

  4. Ga vervolgens naar File > Build Settings en selecteer Universeel Windows-platform en klik vervolgens op de knop Switch Platform om uw selectie toe te passen.

    Build-instellingen configureren

  5. Terwijl u zich nog steeds in de instellingen voor het > maken van bestanden hebt en controleert u of:

    1. Doelapparaat is ingesteld op HoloLens

      Voor de immersive headsets stelt u Doelapparaat in op Elk apparaat.

    2. Buildtype is ingesteld op D3D

    3. SDK is ingesteld op Laatst geïnstalleerd

    4. Visual Studio-versie is ingesteld op Laatst geïnstalleerd

    5. Bouwen en uitvoeren is ingesteld op Lokale computer

    6. Sla de scène op en voeg deze toe aan de build.

      1. U doet dit door Open scènes toevoegen te selecteren. Er wordt een venster voor opslaan weergegeven.

        Open scène toevoegen aan buildlijst

      2. Maak hiervoor een nieuwe map en een toekomstige scène en selecteer vervolgens de knop Nieuwe map om een nieuwe map te maken, geef deze de naam Scènes.

        Nieuwe scènemap maken

      3. Open de zojuist gemaakte map Scènes en typ in het tekstveld Bestandsnaam:CustomVisionScene en klik vervolgens op Opslaan.

        Nieuw scènebestand een naam opgeven

        Houd er rekening mee dat u uw Unity-scènes moet opslaan in de map Assets , omdat deze moeten worden gekoppeld aan het Unity-project. Het maken van de scènemap (en andere vergelijkbare mappen) is een typische manier om een Unity-project te structureren.

    7. De overige instellingen in Build-instellingen moeten voorlopig als standaard worden ingesteld.

      Standaard build-instellingen

  6. Klik in het venster Build-instellingen op de knop Spelerinstellingen . Hiermee opent u het gerelateerde deelvenster in de ruimte waar de Inspector zich bevindt.

  7. In dit deelvenster moeten enkele instellingen worden geverifieerd:

    1. Op het tabblad Overige instellingen :

      1. De runtimeversie van script moet experimenteel zijn (equivalent van.NET 4.6), waardoor de editor opnieuw moet worden gestart.

      2. De back-end van scripts moet .NET zijn

      3. API-compatibiliteitsniveau moet .NET 4.6 zijn

      API-compatibiliteit instellen

    2. Schakel op het tabblad Publicatie-instellingen onder Mogelijkheden het volgende in:

      1. InternetClient

      2. Webcam

      3. Microfoon

      Publicatie-instellingen configureren

    3. Verderop in het deelvenster, in XR-instellingen (onder Publicatie-instellingen), vinkt u Virtual Reality Ondersteund aan en controleert u of de Windows Mixed Reality SDK is toegevoegd.

    XR-instellingen configureren

  8. Terug in build-instellingenwordt Unity C#-projecten niet langer grijs weergegeven; schakel het selectievakje naast dit selectievakje in.

  9. Sluit het venster Build Settings.

  10. Sla uw scène en project op (SCÈNE OPSLAAN BESTAND>/ PROJECT OPSLAAN >BESTAND).

Hoofdstuk 4- Het Newtonsoft-DLL-bestand importeren in Unity

Belangrijk

Als u het onderdeel Unity Set up van deze cursus wilt overslaan en direct verder wilt gaan met code, kunt u dit Azure-MR-302b.unitypackage downloaden, het in uw project importeren als een aangepast pakket en vervolgens doorgaan vanuit Hoofdstuk 6.

Voor deze cursus is het gebruik van de Newtonsoft-bibliotheek vereist, die u als dll-bestand aan uw assets kunt toevoegen. Het pakket met deze bibliotheek kan worden gedownload via deze koppeling. Als u de Newtonsoft-bibliotheek in uw project wilt importeren, gebruikt u het Unity-pakket dat bij deze cursus is geleverd.

  1. Voeg het .unitypackage toe aan Unity met behulp van de menuoptie Aangepastpakket voor hetimporteren> van assets>.

  2. Zorg ervoor dat in het venster Unity Package importeren alles onder (en inclusief) Invoegtoepassingen is geselecteerd.

    Alle pakketitems importeren

  3. Klik op de knop Importeren om de items toe te voegen aan uw project.

  4. Ga naar de map Newtonsoft onder Plugins in de projectweergave en selecteer de invoegtoepassing Newtonsoft.Json.

    Newtonsoft-invoegtoepassing selecteren

  5. Terwijl de Newtonsoft.Json-invoegtoepassing is geselecteerd, controleert u of Any Platform is uitgeschakeld en zorgt u ervoor dat WSAPlayer ook is uitgeschakeld en klikt u vervolgens op Toepassen. Dit is alleen om te bevestigen dat de bestanden correct zijn geconfigureerd.

    Newtonsoft-invoegtoepassing configureren

    Notitie

    Als u deze invoegtoepassingen markeert, worden ze alleen geconfigureerd voor gebruik in de Unity-editor. De WSA-map bevat een andere set die wordt gebruikt nadat het project uit Unity is geëxporteerd.

  6. Vervolgens moet u de map WSA openen in de map Newtonsoft . U ziet een kopie van hetzelfde bestand dat u zojuist hebt geconfigureerd. Selecteer het bestand en controleer vervolgens in de inspector of

    • Elk platform is uitgeschakeld
    • alleenWSAPlayer is ingeschakeld
    • Niet-proces is ingeschakeld

    Platforminstellingen voor Newtonsoft-invoegtoepassing configureren

Hoofdstuk 5 - Camera-instelling

  1. Selecteer in het deelvenster Hiërarchie de hoofdcamera.

  2. Eenmaal geselecteerd, kunt u alle onderdelen van de hoofdcamera in het deelvenster Inspector zien.

    1. Het cameraobject moet de naam Hoofdcamera hebben (let op de spelling!)

    2. De tag Hoofdcamera moet worden ingesteld op MainCamera (let op de spelling!)

    3. Zorg ervoor dat de transformatiepositie is ingesteld op 0, 0, 0

    4. Stel Markeringen wissen in op Effen kleur (negeer dit voor immersive headset).

    5. Stel de achtergrondkleur van het cameraonderdeel in op Zwart, Alpha 0 (Hex Code: #000000000) (negeer dit voor immersive headset).

    Eigenschappen van cameraonderdelen configureren

Hoofdstuk 6: Maak de klasse CustomVisionAnalyser.

Op dit moment bent u klaar om code te schrijven.

U begint met de klasse CustomVisionAnalyser .

Notitie

De aanroepen naar de Custom Vision Service in de onderstaande code worden uitgevoerd met behulp van de Custom Vision REST API. Door dit te gebruiken, ziet u hoe u deze API kunt implementeren en gebruiken (handig om te begrijpen hoe u zelf iets dergelijks kunt implementeren). Houd er rekening mee dat Microsoft een Custom Vision Service SDK biedt die ook kan worden gebruikt om de Service aan te roepen. Zie het artikel Custom Vision Service SDK voor meer informatie.

Deze klasse is verantwoordelijk voor:

  • De meest recente installatiekopieën laden die zijn vastgelegd als een matrix van bytes.

  • De bytematrix verzenden naar uw Azure Custom Vision Service-exemplaar voor analyse.

  • Het antwoord ontvangen als een JSON-tekenreeks.

  • Het antwoord deserialiseren en de resulterende voorspelling doorgeven aan de klasse SceneOrganiser , die ervoor zorgt hoe het antwoord moet worden weergegeven.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de assetmap in het projectvenster en klik vervolgens op Map maken>. Roep de map Scripts aan.

    Map scripts maken

  2. Dubbelklik op de map die u zojuist hebt gemaakt om deze te openen.

  3. Klik met de rechtermuisknop in de map en klik vervolgens opC#-scriptmaken>. Geef het script de naam CustomVisionAnalyser.

  4. Dubbelklik op het nieuwe CustomVisionAnalyser-script om het te openen met Visual Studio.

  5. Werk de naamruimten boven aan het bestand bij zodat deze overeenkomen met het volgende:

    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    using Newtonsoft.Json;
    
  6. Voeg in de klasse CustomVisionAnalyser de volgende variabelen toe:

        /// <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;
    

    Notitie

    Zorg ervoor dat u uw voorspellingssleutel invoegt in de variabele predictionKey en uw voorspellingseindpunt in de variabele predictionEndpoint . U hebt deze eerder in de cursus naar Kladblok gekopieerd.

  7. Code voor Awake() moet nu worden toegevoegd om de exemplaarvariabele te initialiseren:

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Verwijder de methoden Start() en Update().

  9. Voeg vervolgens de coroutine toe (met de statische methode GetImageAsByteArray() eronder, waarmee de resultaten worden verkregen van de analyse van de afbeelding die is vastgelegd door de klasse ImageCapture .

    Notitie

    In de coroutine AnalyseImageCapture wordt de klasse SceneOrganiser aangeroepen die u nog moet maken. Laat deze regels daarom voorlopig van commentaar voorzien.

        /// <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);
        }
    
  10. Zorg ervoor dat u uw wijzigingen opslaat in Visual Studio voordat u terugkeert naar Unity.

Hoofdstuk 7- De klasse CustomVisionObjects maken

De klasse die u nu gaat maken, is de klasse CustomVisionObjects .

Dit script bevat een aantal objecten die door andere klassen worden gebruikt om de aanroepen naar de Custom Vision Service te serialiseren en te deserialiseren.

Waarschuwing

Het is belangrijk dat u het eindpunt noteert dat de Custom Vision Service u biedt, omdat de onderstaande JSON-structuur is ingesteld voor gebruik met Custom Vision Prediction v2.0. Als u een andere versie hebt, moet u mogelijk de onderstaande structuur bijwerken.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de map Scripts en klik vervolgens opC#-scriptmaken>. Roep het script CustomVisionObjects aan.

  2. Dubbelklik op het nieuwe CustomVisionObjects-script om het te openen met Visual Studio.

  3. Voeg de volgende naamruimten toe aan het begin van het bestand:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Verwijder de methoden Start() en Update() in de klasse CustomVisionObjects ; deze klasse moet nu leeg zijn.

  5. Voeg de volgende klassen toe buiten de klasse CustomVisionObjects . Deze objecten worden gebruikt door de Newtonsoft-bibliotheek om de antwoordgegevens te serialiseren en te deserialiseren:

    // 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; }
    }
    

Hoofdstuk 8: De klasse VoiceRecognizer maken

Deze klasse herkent de spraakinvoer van de gebruiker.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de map Scripts en klik vervolgens opC#-scriptmaken>. Roep het script VoiceRecognizer aan.

  2. Dubbelklik op het nieuwe VoiceRecognizer-script om het te openen met Visual Studio.

  3. Voeg de volgende naamruimten toe boven de klasse VoiceRecognizer :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Windows.Speech;
    
  4. Voeg vervolgens de volgende variabelen toe in de klasse VoiceRecognizer, boven de methode 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>();
    
  5. Voeg de methoden Awake() en Start() toe, waarbij de laatste de trefwoorden van de gebruiker instelt die moeten worden herkend bij het koppelen van een tag aan een afbeelding:

        /// <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;
        }
    
  6. Verwijder de methode Update().

  7. Voeg de volgende handler toe, die wordt aangeroepen wanneer spraakinvoer wordt herkend:

        /// <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();
            }
        }
    
  8. Zorg ervoor dat u uw wijzigingen opslaat in Visual Studio voordat u terugkeert naar Unity.

Notitie

U hoeft zich geen zorgen te maken over code die een fout lijkt te hebben, omdat u binnenkort meer klassen zult verstrekken, waarmee deze worden opgelost.

Hoofdstuk 9- De klasse CustomVisionTrainer maken

Deze klasse koppelt een reeks weboproepen om de Custom Vision Service te trainen. Elke aanroep wordt in detail uitgelegd, direct boven de code.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de map Scripts en klik vervolgens opC#-scriptmaken>. Roep het script CustomVisionTrainer aan.

  2. Dubbelklik op het nieuwe CustomVisionTrainer-script om het te openen met Visual Studio.

  3. Voeg de volgende naamruimten toe boven de klasse CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Voeg vervolgens de volgende variabelen toe in de klasse CustomVisionTrainer, boven de methode Start().

    Notitie

    De trainings-URL die hier wordt gebruikt, is opgenomen in de documentatie van Custom Vision Training 1.2 en heeft een structuur van:https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Ga voor meer informatie naar de naslag-API voor Custom Vision Training v1.2.

    Waarschuwing

    Het is belangrijk dat u het eindpunt noteert dat de Custom Vision Service u biedt voor de trainingsmodus, omdat de gebruikte JSON-structuur (binnen de klasse CustomVisionObjects) is ingesteld voor gebruik met Custom Vision Training v1.2. Als u een andere versie hebt, moet u mogelijk de structuur Objecten bijwerken.

        /// <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;
    

    Belangrijk

    Zorg ervoor dat u de waarde van de servicesleutel (trainingssleutel) en de waarde voor project-id toevoegt, die u eerder hebt genoteerd; dit zijn de waarden die u eerder in de cursus (hoofdstuk 2, stap 10 en hoger) hebt verzameld in de portal.

  5. Voeg de volgende methoden Start() en Awake() toe. Deze methoden worden aangeroepen bij initialisatie en bevatten de aanroep voor het instellen van de gebruikersinterface:

        /// <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);
        }
    
  6. Verwijder de methode Update(). Deze klasse heeft deze niet nodig.

  7. Voeg de methode RequestTagSelection() toe. Deze methode is de eerste die wordt aangeroepen wanneer een afbeelding is vastgelegd en opgeslagen op het apparaat en nu gereed is om te worden verzonden naar de Custom Vision Service om deze te trainen. Met deze methode wordt in de trainingsinterface een reeks trefwoorden weergegeven die de gebruiker kan gebruiken om de vastgelegde afbeelding te taggen. Ook wordt de VoiceRecognizer-klasse gewaarschuwd om naar de gebruiker te luisteren voor spraakinvoer.

        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();
        }
    
  8. Voeg de methode VerifyTag() toe. Deze methode ontvangt de spraakinvoer die wordt herkend door de klasse VoiceRecognizer en controleert de geldigheid ervan en start vervolgens het trainingsproces.

        /// <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));
            }
        }
    
  9. Voeg de methode SubmitImageForTraining() toe. Met deze methode wordt het trainingsproces Custom Vision Service gestart. De eerste stap is het ophalen van de tag-id van de service die is gekoppeld aan de gevalideerde spraakinvoer van de gebruiker. De tag-id wordt vervolgens samen met de afbeelding geüpload.

        /// <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());
        }
    
  10. Voeg de methode TrainCustomVisionProject() toe. Zodra de afbeelding is verzonden en gelabeld, wordt deze methode aangeroepen. Er wordt een nieuwe iteratie gemaakt die wordt getraind met alle vorige installatiekopieën die naar de service zijn verzonden, plus de afbeelding die zojuist is geüpload. Zodra de training is voltooid, roept deze methode een methode aan om de zojuist gemaakte iteratie in te stellen als Standaard, zodat het eindpunt dat u gebruikt voor analyse de meest recente getrainde iteratie is.

        /// <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)); 
                }
            }
        }
    
  11. Voeg de methode SetDefaultIteration() toe. Met deze methode wordt de eerder gemaakte en getrainde iteratie ingesteld als Standaard. Zodra dit is voltooid, moet deze methode de vorige iteratie verwijderen die in de Service bestaat. Op het moment van het schrijven van deze cursus is er een limiet van maximaal tien (10) iteraties die tegelijkertijd in de Service mogen bestaan.

        /// <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));
                }
            }
        }
    
  12. Voeg de methode DeletePreviousIteration() toe. Met deze methode wordt de vorige niet-standaarditeratie gevonden en verwijderd:

        /// <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();
            }
        }
    
  13. De laatste methode die in deze klasse moet worden toegevoegd, is de methode GetImageAsByteArray(), die wordt gebruikt in de web-aanroepen om de vastgelegde afbeelding te converteren naar een bytematrix.

        /// <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);
        }
    
  14. Zorg ervoor dat u uw wijzigingen opslaat in Visual Studio voordat u terugkeert naar Unity.

Hoofdstuk 10 - De klasse SceneOrganiser maken

Deze klasse doet het volgende:

  • Maak een Cursor-object om aan de hoofdcamera te koppelen.

  • Maak een Label-object dat wordt weergegeven wanneer de service de echte objecten herkent.

  • Stel de hoofdcamera in door de juiste onderdelen eraan te koppelen.

  • Spawn in de analysemodus de labels tijdens runtime, in de juiste wereldruimte ten opzichte van de positie van de hoofdcamera, en geef de gegevens weer die zijn ontvangen van de Custom Vision Service.

  • Spawn in de trainingsmodus de gebruikersinterface die de verschillende fasen van het trainingsproces weergeeft.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de map Scripts en klik vervolgens opC#-scriptmaken>. Noem het script SceneOrganiser.

  2. Dubbelklik op het nieuwe ScèneOrganiser-script om het te openen met Visual Studio.

  3. U hebt slechts één naamruimte nodig, verwijder de andere boven de klasse SceneOrganiser :

    using UnityEngine;
    
  4. Voeg vervolgens de volgende variabelen toe in de klasse SceneOrganiser , boven de methode 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;
    
  5. Verwijder de methoden Start() en Update().

  6. Voeg direct onder de variabelen de methode Awake() toe, waarmee de klasse wordt geïnitialiseerd en de scène wordt ingesteld.

        /// <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");
        }
    
  7. Voeg nu de methode CreateCameraCursor() toe waarmee de hoofdcameracursor wordt gemaakt en geplaatst, en de methode CreateLabel(), waarmee het object Analysis Label wordt gemaakt.

        /// <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;
        }
    
  8. Voeg de methode SetCameraStatus() toe, waarmee berichten worden verwerkt die zijn bedoeld voor de tekstvergaas en de status van de camera opgeven.

        /// <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>";
            }
        }
    
  9. Voeg de methoden PlaceAnalysisLabel() en SetTagsToLastLabel() toe. Hiermee worden de gegevens van de Custom Vision Service weergegeven in de 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")}");
                    }
                }
            }
        }
    
  10. Voeg ten slotte de methode CreateTrainingUI() toe, waarmee de gebruikersinterface wordt geactiveerd en de meerdere fasen van het trainingsproces worden weergegeven wanneer de toepassing zich in de trainingsmodus bevindt. Deze methode wordt ook gebruikt om het camerastatusobject te maken.

        /// <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;
        }
    
  11. Zorg ervoor dat u uw wijzigingen opslaat in Visual Studio voordat u terugkeert naar Unity.

Belangrijk

Voordat u doorgaat, opent u de klasse CustomVisionAnalyser en schakelt u in de methode AnalyseLastImageCaptured()de opmerkingen bij de volgende regels uit:

  AnalysisObject analysisObject = new AnalysisObject();
  analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
  SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);

Hoofdstuk 11- De klasse ImageCapture maken

De volgende klasse die u gaat maken, is de klasse ImageCapture .

Deze klasse is verantwoordelijk voor:

  • Een afbeelding vastleggen met de HoloLens-camera en deze opslaan in de map App .

  • Tikbewegingen van de gebruiker verwerken.

  • Het onderhouden van de Enum-waarde die bepaalt of de toepassing wordt uitgevoerd in de analysemodus of trainingsmodus .

Ga als volgt te werk om deze klasse te maken:

  1. Ga naar de map Scripts die u eerder hebt gemaakt.

  2. Klik met de rechtermuisknop in de map en klik vervolgens op C#-script maken>. Geef het script de naam ImageCapture.

  3. Dubbelklik op het nieuwe ImageCapture-script om het te openen met Visual Studio.

  4. Vervang de naamruimten boven aan het bestand door het volgende:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Voeg vervolgens de volgende variabelen toe in de klasse ImageCapture , boven de methode 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;
    
  6. Code voor de methoden Awake() en Start() moet nu worden toegevoegd:

        /// <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");
        }
    
  7. Implementeer een handler die wordt aangeroepen wanneer een tikbeweging plaatsvindt.

        /// <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;
            }     
        }
    

    Notitie

    In de analysemodus fungeert de Methode TapHandler als schakeloptie om de lus voor het vastleggen van foto's te starten of te stoppen.

    In de trainingsmodus wordt een afbeelding van de camera vastgelegd.

    Als de cursor groen is, betekent dit dat de camera beschikbaar is om de afbeelding te maken.

    Als de cursor rood is, betekent dit dat de camera bezet is.

  8. Voeg de methode toe die de toepassing gebruikt om het proces voor het vastleggen van afbeeldingen te starten en de installatiekopieën op te slaan.

        /// <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);              
                });
            });   
        }
    
  9. Voeg de handlers toe die worden aangeroepen wanneer de foto is vastgelegd en voor wanneer deze gereed is om te worden geanalyseerd. Het resultaat wordt vervolgens doorgegeven aan de CustomVisionAnalyser of de CustomVisionTrainer , afhankelijk van de modus waarin de code is ingesteld.

        /// <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();
        }
    
  10. Zorg ervoor dat u uw wijzigingen opslaat in Visual Studio voordat u terugkeert naar Unity.

  11. Nu alle scripts zijn voltooid, gaat u terug in de Unity-editor en sleept u de klasse ScèneOrganiser vanuit de map Scripts naar het object Hoofdcamera in het deelvenster Hiërarchie.

Hoofdstuk 12 - Voor de bouw

Als u een grondige test van uw toepassing wilt uitvoeren, moet u deze sideloaden op uw HoloLens.

Voordat u dit doet, moet u ervoor zorgen dat:

  • Alle instellingen die in hoofdstuk 2 worden vermeld, zijn correct ingesteld.

  • Alle velden in de hoofdcamera, het deelvenster Inspector, zijn correct toegewezen.

  • Het script ScèneOrganiser is gekoppeld aan het object Hoofdcamera .

  • Zorg ervoor dat u de voorspellingssleutel in de variabele predictionKey invoegt .

  • U hebt het voorspellingseindpunt ingevoegd in de variabele predictionEndpoint .

  • U hebt uw trainingssleutel ingevoegd in de variabele trainingKey van de klasse CustomVisionTrainer .

  • U hebt uw project-id ingevoegd in de variabele projectId van de klasse CustomVisionTrainer .

Hoofdstuk 13 - Uw toepassing bouwen en sideloaden

Ga als volgt te werk om het buildproces te starten:

  1. Ga naar Instellingen voor het maken > van bestanden.

  2. Selecteer Unity C#-projecten.

  3. Klik op Bouwen. Unity start een Bestandenverkenner venster, waarin u een map moet maken en selecteren waarin u de app wilt bouwen. Maak die map nu en geef deze de naam App. Klik vervolgens met de map App geselecteerd op Map selecteren.

  4. Unity begint met het bouwen van uw project in de map App .

  5. Zodra Unity klaar is met bouwen (dit kan enige tijd duren), wordt er een Bestandenverkenner venster geopend op de locatie van uw build (controleer de taakbalk, omdat deze mogelijk niet altijd boven uw vensters wordt weergegeven, maar u op de hoogte wordt gesteld van de toevoeging van een nieuw venster).

Implementeren op HoloLens:

  1. U hebt het IP-adres van uw HoloLens nodig (voor remote deploy) en om ervoor te zorgen dat uw HoloLens zich in de ontwikkelaarsmodus bevindt. Om dit te doen:

    1. Open de Instellingen terwijl u uw HoloLens draagt.

    2. Ga naar Netwerk & Internet>Wi-Fi>Geavanceerde opties

    3. Noteer het IPv4-adres .

    4. Ga vervolgens terug naar Instellingen en vervolgens naar Update & Security>for Developers

    5. Stel de ontwikkelaarsmodus in.

  2. Navigeer naar uw nieuwe Unity-build (de map App ) en open het oplossingsbestand met Visual Studio.

  3. Selecteer foutopsporing in oplossingsconfiguratie.

  4. Selecteer in solution platformx86, Remote Machine. U wordt gevraagd het IP-adres van een extern apparaat in te voegen (in dit geval de HoloLens die u hebt genoteerd).

    IP-adres instellen

  5. Ga naar het menu Bouwen en klik op Oplossing implementeren om de toepassing te sideloaden naar uw HoloLens.

  6. Uw app wordt nu weergegeven in de lijst met geïnstalleerde apps op uw HoloLens, klaar om te worden gestart.

Notitie

Als u een insluitende headset wilt implementeren, stelt u Solution Platform in op Lokale computer en stelt u configuratie in op Foutopsporing, met x86als platform. Implementeer vervolgens op de lokale computer met behulp van het menu-item Build en selecteer Oplossing implementeren.

De toepassing gebruiken:

Als u de app-functionaliteit wilt overschakelen tussen trainingsmodus en voorspellingsmodus , moet u de variabele AppMode bijwerken, die zich in de methode Awake() bevindt binnen de klasse ImageCapture .

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Training;

of

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Analysis;

In de trainingsmodus :

  • Bekijk Muis of toetsenbord en gebruik de tikbeweging.

  • Vervolgens wordt er tekst weergegeven waarin u wordt gevraagd om een tag op te geven.

  • Zeg muis of toetsenbord.

In de voorspellingsmodus :

  • Bekijk een object en gebruik de tikbeweging.

  • Tekst wordt weergegeven met het gedetecteerde object, met de hoogste waarschijnlijkheid (dit is genormaliseerd).

Hoofdstuk 14 - Uw Custom Vision-model evalueren en verbeteren

Als u uw service nauwkeuriger wilt maken, moet u doorgaan met het trainen van het model dat wordt gebruikt voor voorspelling. Dit wordt bereikt door uw nieuwe toepassing te gebruiken, met zowel de trainings - als voorspellingsmodus , waarbij u voor de laatste de portal moet bezoeken, wat in dit hoofdstuk wordt behandeld. Wees erop voorbereid om uw portal vaak opnieuw te bezoeken om uw model voortdurend te verbeteren.

  1. Ga opnieuw naar uw Azure Custom Vision Portal en selecteer het tabblad Voorspellingen (in het midden boven aan de pagina):

    Tabblad Voorspellingen selecteren

  2. U ziet alle installatiekopieën die naar uw service zijn verzonden terwijl uw toepassing werd uitgevoerd. Als u de muisaanwijzer op de afbeeldingen plaatst, ziet u de voorspellingen die voor die afbeelding zijn gemaakt:

    Lijst met voorspellingsafbeeldingen

  3. Selecteer een van uw afbeeldingen om deze te openen. Eenmaal geopend, ziet u aan de rechterkant de voorspellingen die zijn gemaakt voor die afbeelding. Als u de voorspellingen juist hebt en u deze afbeelding wilt toevoegen aan het trainingsmodel van uw service, klikt u op het invoervak Mijn tags en selecteert u de tag die u wilt koppelen. Wanneer u klaar bent, klikt u rechtsonder op de knop Opslaan en sluiten en gaat u verder met de volgende afbeelding.

    Afbeelding selecteren om te openen

  4. Zodra u weer bij het raster met afbeeldingen bent, ziet u dat de afbeeldingen waaraan u tags hebt toegevoegd (en die u hebt opgeslagen), worden verwijderd. Als u afbeeldingen vindt waarvan u denkt dat ze geen getagd item bevatten, kunt u ze verwijderen door op de afbeelding te klikken (dit kan voor meerdere afbeeldingen) en vervolgens op Verwijderen te klikken in de rechterbovenhoek van de rasterpagina. In de pop-up die volgt, kunt u respectievelijk op Ja, verwijderen of Nee klikken om de verwijdering te bevestigen of te annuleren.

    Installatiekopieën verwijderen

  5. Wanneer u klaar bent om verder te gaan, klikt u op de groene knop Trainen in de rechterbovenhoek. Uw servicemodel wordt getraind met alle afbeeldingen die u nu hebt opgegeven (waardoor het nauwkeuriger wordt). Zodra de training is voltooid, moet u nogmaals op de knop Standaard maken klikken, zodat uw voorspellings-URL de meest recente iteratie van uw service blijft gebruiken.

    Trainingsservicemodel startenSelecteer de optie Standaard maken

Uw voltooide Custom Vision API-toepassing

Gefeliciteerd, u hebt een mixed reality-app gebouwd die gebruikmaakt van de Azure Custom Vision API om objecten uit de echte wereld te herkennen, het servicemodel te trainen en vertrouwen te tonen van wat er is gezien.

Voorbeeld van voltooid project

Bonusoefeningen

Oefening 1

Train uw Custom Vision Service om meer objecten te herkennen.

Oefening 2

Als een manier om verder te gaan met wat u hebt geleerd, voert u de volgende oefeningen uit:

Een geluid afspelen wanneer een object wordt herkend.

Oefening 3

Gebruik de API om uw service opnieuw te trainen met dezelfde afbeeldingen die uw app analyseert, zodat de service nauwkeuriger wordt (zowel voorspelling als training tegelijk uitvoeren).