Delen via


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


Notitie

De Mixed Reality Academy-zelfstudies zijn ontworpen met HoloLens (1e generatie) en Mixed Reality Immersive Headsets in gedachten. Daarom vinden we het belangrijk om deze zelfstudies te behouden voor ontwikkelaars die nog steeds op zoek zijn naar richtlijnen bij het ontwikkelen voor die apparaten. Deze zelfstudies worden niet bijgewerkt met de nieuwste toolsets of interacties die worden gebruikt voor HoloLens 2. Ze blijven behouden om door te gaan met het werken op de ondersteunde apparaten. Er is een nieuwe reeks zelfstudies die in de toekomst zullen worden gepost en die laten zien hoe u zich ontwikkelt voor HoloLens 2. Deze kennisgeving wordt bijgewerkt met een koppeling naar deze zelfstudies wanneer ze worden gepost.


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

Met deze service kunt u een machine learning-model trainen met behulp van objectinstallatiekopieën. Vervolgens gebruikt u het getrainde model om vergelijkbare objecten te herkennen, zoals wordt geleverd door de camera-opname van Microsoft HoloLens of een camera die is verbonden met uw pc voor insluitende headsets (VR).

cursusresultaat

Azure Custom Vision is een Microsoft Cognitive Service waarmee ontwikkelaars aangepaste afbeeldingsclassificaties kunnen bouwen. Deze classificaties kunnen vervolgens worden gebruikt met nieuwe afbeeldingen voor het herkennen of classificeren van objecten in die nieuwe afbeelding. De Service biedt een eenvoudige, eenvoudig te gebruiken online portal om het proces te stroomlijnen. Ga naar de azure Custom Vision Service-pagina 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 deze objecten in de echte wereld te herkennen.

  • Trainingsmodus: u implementeert code waarmee de trainingsmodus in uw app wordt ingeschakeld. Met de trainingsmodus kunt u afbeeldingen vastleggen met behulp van de HoloLens-camera, 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 op Unity gebaseerde voorbeeldtoepassing. Het is aan u om deze concepten toe te passen op een aangepaste toepassing die u mogelijk bouwt.

Ondersteuning voor apparaten

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

Notitie

Hoewel deze cursus voornamelijk gericht is op HoloLens, kunt u ook toepassen wat u in deze cursus leert op Vr-headsets (Windows Mixed Reality immersive). Omdat insluitende headsets (VR) 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 gebruiken om insluitende headsets (VR) te ondersteunen.

Vereisten

Notitie

Deze zelfstudie is ontworpen voor ontwikkelaars die basiservaring hebben met Unity en C#. Houd er ook rekening mee dat de vereisten en schriftelijke instructies in dit document staan voor wat er op het moment van schrijven is getest en geverifieerd (juli 2018). U bent vrij om de nieuwste software te gebruiken, zoals vermeld in het artikel over de installatie van de hulpprogramma's , hoewel wordt ervan uitgegaan dat de informatie in deze cursus perfect overeenkomt met wat u vindt in nieuwere software dan 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 genoemd in een hoofdmap of in een near-rootmap (lange mappaden kunnen problemen veroorzaken tijdens de build).
  2. Uw HoloLens instellen en testen. Als u ondersteuning nodig hebt voor het instellen van uw HoloLens, gaat u naar het installatieartikel van HoloLens.
  3. Het is een goed idee om kalibratie en sensorafstemming uit te voeren bij het ontwikkelen van een nieuwe HoloLens-app (soms kan het helpen om deze taken voor elke gebruiker uit te voeren).

Voor hulp bij kalibratie volgt u deze koppeling naar het HoloLens-kalibratieartikel.

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 voor 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 proctors voor 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. Nadat 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 voor uw project in.

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

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

    4. De projecttypen instellen op classificatie

    5. Stel de domeinen in als Algemeen.

      De domeinen instellen

      Als u meer wilt weten over Azure-resourcegroepen, gaat u naar het artikel over de resourcegroep.

  7. Zodra u klaar bent, klikt u op Project maken. U wordt omgeleid naar de custom Vision-service, de projectpagina.

Hoofdstuk 2: Uw Custom Vision-project trainen

Eenmaal in de Custom Vision-portal is het 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 wilt herkennen aan uw toepassing. 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 uw tag is toegevoegd (mogelijk moet u de pagina opnieuw laden om deze weer te geven). Klik op het selectievakje naast de nieuwe tag als deze nog niet is ingeschakeld.

    Nieuwe tag inschakelen

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

    Afbeeldingen toevoegen

  5. Klik op Lokale bestanden bladeren en zoek en selecteer vervolgens de afbeeldingen die u wilt uploaden, met minimaal vijf (5). Onthoud dat al deze afbeeldingen het object moeten bevatten dat u traint.

    Notitie

    U kunt verschillende afbeeldingen tegelijk selecteren om te uploaden.

  6. Zodra u de afbeeldingen op het tabblad ziet, 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 upload hebt bevestigd, 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 ervoor te uploaden. Schakel muis uit zodra u de nieuwe tags hebt gemaakt, dus om het venster Afbeeldingen toevoegen weer te geven.

  9. Zodra u beide tags hebt ingesteld, klikt u op Trainen en begint de eerste trainingsiteratie te bouwen.

    Trainingsiteratie inschakelen

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

    Standaard- en voorspellings-URL maken

    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 uw code niet te wijzigen.

  11. Nadat u op de 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 in de rechterbovenhoek van het scherm.

    Klik op tandwielpictogram om instellingen te openen

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

    Trainingssleutel kopiëren

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

    Project-id kopiëren

Hoofdstuk 3 - Het Unity-project instellen

Hier volgt een typische opzet voor het ontwikkelen met mixed reality en is als zodanig 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 (vergeet niet, dichter bij hoofdmappen is beter). 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 Instellingen voor bestandsbuild > en selecteer Universeel Windows-platform en klik vervolgens op de knop Platform wisselen om uw selectie toe te passen.

    Build-instellingen configureren

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

    1. Doelapparaat is ingesteld op HoloLens

      Voor de insluitende 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 Meest recent 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. Doe 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 voor elke toekomst scène, 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 klik vervolgens in de bestandsnaam: tekstveld, typ CustomVisionScene en klik vervolgens op Opslaan.

        Geef een nieuwe scènebestand een naam

        Houd er rekening mee dat u uw Unity-scènes in de map Assets moet opslaan, 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 standaard blijven staan.

      Standaardinstellingen voor build

  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 scripting moet experimenteel (.NET 4.6 Equivalent) zijn, waardoor de editor opnieuw moet worden opgestart.

      2. Back-end voor scripts moet .NET zijn

      3. API-compatibiliteitsniveau moet .NET 4.6 zijn

      API-compantibliteit instellen

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

      1. InternetClient

      2. Webcam

      3. Microfoon

      Publicatie-instellingen configureren

    3. Schakel verderop in het deelvenster in XR-instellingen (hieronder Publicatie-instellingen) het selectievakje Virtual Reality Ondersteund in, zorg ervoor dat de Windows Mixed Reality SDK is toegevoegd.

    XR-instellingen configureren

  8. Terug in Build Settings Unity C# Projects wordt niet meer grijs weergegeven. Schakel het selectievakje naast dit selectievakje in.

  9. Sluit het venster Build Settings.

  10. Sla uw scène en project op (FILE > SAVE SCENE/ FILE > SAVE PROJECT).

Hoofdstuk 4 - De Newtonsoft DLL importeren in Unity

Belangrijk

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

Voor deze cursus is het gebruik van de Newtonsoft-bibliotheek vereist, die u als DLL 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 Package dat bij deze cursus is geleverd.

  1. Voeg de .unitypackage toe aan Unity met behulp van de menuoptie CustomPackage Import Package> van assets.>

  2. Controleer in het vak Import Unity Package dat verschijnt of alles onder (en inclusief) invoegtoepassingen is geselecteerd.

    Alle pakketitems importeren

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

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

    Newtonsoft-invoegtoepassing selecteren

  5. Zorg ervoor dat Any Platform is uitgeschakeld terwijl de Newtonsoft.Json-invoegtoepassing is geselecteerd en zorg ervoor dat WSAPlayer ook is uitgeschakeld en klik 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 gebruikt in de Unity Editor. Er is een andere set van deze items in de WSA-map die wordt gebruikt nadat het project is geëxporteerd uit Unity.

  6. Vervolgens moet u de WSA-map 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
    • alleen WSAPlayer is ingeschakeld
    • Niet-proces is ingeschakeld

    Platforminstellingen voor Newtonsoft-invoegtoepassingen configureren

Hoofdstuk 5 - Camera-instelling

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

  2. Zodra deze optie is geselecteerd, kunt u alle onderdelen van de hoofdcamera in het inspectorpaneel zien.

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

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

    3. Zorg ervoor dat de positie van de transformatie is ingesteld op 0, 0, 0

    4. Stel Clear Flags in op Solid Color (negeer dit voor insluitende headset).

    5. Stel de achtergrondkleur van het cameraonderdeel in op Zwart, Alpha 0 (Hex Code: #000000000) (negeer dit voor insluitende 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 die in de onderstaande code wordt weergegeven, worden uitgevoerd met behulp van de Custom Vision REST API. Door dit te gebruiken, ziet u hoe u deze API implementeert en gebruikt (handig om te begrijpen hoe u iets op uw eigen manier 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. Ga voor meer informatie naar het artikel over de Custom Vision Service SDK .

Deze klasse is verantwoordelijk voor:

  • Het laden van de meest recente afbeelding die is 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.

  • Deserialiseren van het antwoord en het doorgeven van de resulterende voorspelling aan de klasse SceneOrganiser , die zorgt voor de weergave van het antwoord.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de assetmap in het deelvenster Project en klik vervolgens op Map maken>. Roep de mapscripts aan.

    Map Scripts maken

  2. Dubbelklik op de zojuist gemaakte map om deze te openen.

  3. Klik met de rechtermuisknop in de map en klik vervolgens op C#-script maken>. 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 for Awake() moet nu worden toegevoegd om de instantievariabele 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 opgehaald van de analyse van de afbeelding die is vastgelegd door de klasse ImageCapture .

    Notitie

    In de AnalyseImageCapture-coroutine is er een aanroep naar de ScèneOrganiser-klasse die u nog moet maken. Laat deze regels daarom voorlopig commentaar geven.

        /// <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. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Hoofdstuk 7: de klasse CustomVisionObjects maken

De klasse die u nu maakt, 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 deserialiseren.

Waarschuwing

Het is belangrijk dat u rekening houdt met het eindpunt 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 op C#-script maken>. 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 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 VoiceRecognizer-klasse 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 op C#-script maken>. 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, waarvan 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. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Notitie

U hoeft zich geen zorgen te maken over code die mogelijk een fout bevat, omdat u binnenkort verdere klassen opgeeft, waardoor 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 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 op C#-script maken>. 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 hier gebruikte trainings-URL vindt u 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 naar de naslag-API voor Custom Vision Training v1.2 voor meer informatie.

    Waarschuwing

    Het is belangrijk dat u rekening houdt met het eindpunt dat de Custom Vision-service u biedt voor de trainingsmodus, omdat de JSON-structuur die wordt gebruikt (binnen de klasse CustomVisionObjects) is ingesteld voor gebruik met Custom Vision Training v1.2. Als u een andere versie hebt, moet u mogelijk de objectstructuur 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 uw servicesleutel (trainingssleutel) en project-id toevoegt, die u eerder in de cursus hebt genoteerd. Dit zijn de waarden die u eerder in de cursus hebt verzameld (hoofdstuk 2, stap 10 en hoger).

  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 moet worden aangeroepen wanneer een afbeelding is vastgelegd en opgeslagen op het apparaat en nu klaar is om te worden verzonden naar de Custom Vision-service om deze te trainen. Met deze methode wordt in de trainingsgebruikersinterface een set 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 VoiceRecognizer-klasse 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 van 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 installatiekopieën zijn 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 deze methode is voltooid, moet u de vorige iteratie verwijderen die in de Service bestaat. Op het moment van 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 in de weboproepen wordt gebruikt 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. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Hoofdstuk 10 - De klasse SceneOrganiser maken

In deze klasse wordt het volgende weergegeven:

  • Maak een Cursor-object dat moet worden gekoppeld aan de hoofdcamera.

  • Maak een labelobject dat wordt weergegeven wanneer de service de echte objecten herkent.

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

  • Wanneer u zich in de analysemodus bevindt, spawt u de labels tijdens runtime, in de juiste wereldruimte ten opzichte van de positie van de hoofdcamera en geeft u de gegevens weer die zijn ontvangen van de Custom Vision-service.

  • Wanneer u zich in de trainingsmodus bevindt, wordt de gebruikersinterface weergegeven 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 op C#-script maken>. Geef het script de naam ScèneOrganiser.

  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 uit 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 cursor van de hoofdcamera 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 tekstmaas die de status van de camera biedt.

        /// <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, waarmee de gegevens van de Custom Vision-service in de scène worden weergegeven.

        /// <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 tot slot de methode CreateTrainingUI() toe, waarmee de gebruikersinterface wordt weergegeven met de meerdere fasen van het trainingsproces 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. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Belangrijk

Voordat u doorgaat, opent u de klasse CustomVisionAnalyser en in de methode AnalyseLastImageCaptured() moet u de volgende regels verwijderen:

  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 behulp van de HoloLens-camera en deze opslaan in de app-map .

  • 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>. Noem het script 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. De methoden Code for Awake() en Start() moeten 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 fotoopnamelus te starten of te stoppen.

    In de trainingsmodus wordt een afbeelding van de camera vastgelegd.

    Wanneer 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 waarop 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. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

  11. Nu alle scripts zijn voltooid, gaat u terug in de Unity Editor en klikt u en sleept u de klasse SceneOrganiser van de map Scripts naar het object Main Camera in het deelvenster Hierarchy.

Hoofdstuk 12 - Voor het gebouw

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 genoemd, zijn correct ingesteld.

  • Alle velden in het hoofdcamera, inspectorpaneel, worden correct toegewezen.

  • Het script SceneOrganiser is gekoppeld aan het object Main Camera .

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

  • U hebt uw 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. Tick Unity C#- projecten.

  3. Klik op Opbouwen. Unity start een Bestandenverkenner-venster waarin u een map moet maken en vervolgens een map selecteert waarin u de app wilt inbouwen. Maak die map nu en noem deze app. Klik vervolgens met de map App geselecteerd op Map selecteren.

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

  5. Zodra Unity klaar is met bouwen (het 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 ontvangt een melding over de toevoeging van een nieuw venster).

Implementeren op HoloLens:

  1. U hebt het IP-adres van uw HoloLens nodig (voor Extern implementeren) en om ervoor te zorgen dat uw HoloLens zich in de ontwikkelaarsmodus bevindt. Dit doet u als volgt:

    1. Open de instellingen terwijl u uw HoloLens draagt.

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

    3. Noteer het IPv4-adres .

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

    5. Stel de ontwikkelaarsmodus in.

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

  3. Selecteer foutopsporing in de oplossingsconfiguratie.

  4. Selecteer x86, Remote Machine in het Solution Platform. 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 sideloaden naar uw HoloLens.

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

Notitie

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

De toepassing gebruiken:

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

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

In de trainingsmodus :

  • Kijk naar 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 op basis van 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 gebruik te maken van uw nieuwe toepassing, met zowel de trainings - als voorspellingsmodi , waarbij u de portal moet bezoeken. Dit is wat in dit hoofdstuk wordt behandeld. Wees voorbereid om uw portal meerdere keren 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 van de pagina) zodra u zich in uw project bevindt:

    Tabblad Voorspellingen selecteren

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

    Lijst met voorspellingsafbeeldingen

  3. Selecteer een van uw afbeeldingen om deze te openen. Zodra deze is geopend, ziet u de voorspellingen voor die afbeelding aan de rechterkant. 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 op de knop Opslaan en sluit u rechtsonder en gaat u verder met de volgende afbeelding.

    Afbeelding selecteren om te openen

  4. Zodra u weer terug bent in het raster met afbeeldingen, ziet u dat de afbeeldingen waaraan u tags hebt toegevoegd (en opgeslagen) worden verwijderd. Als u afbeeldingen vindt waarvan u denkt dat u het gelabelde item niet binnen het item hebt, kunt u ze verwijderen door op het vinkje op die afbeelding te klikken (dit kan voor verschillende afbeeldingen worden uitgevoerd) en vervolgens in de rechterbovenhoek van de rasterpagina op Verwijderen te klikken. In het pop-upvenster dat volgt, kunt u 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 echte objecten te herkennen, het servicemodel te trainen en het vertrouwen weer te geven van wat er is gezien.

Voltooid projectvoorbeeld

Bonusoefeningen

Oefening 1

Train uw Custom Vision-service om meer objecten te herkennen.

Oefening 2

Voltooi de volgende oefeningen om verder te gaan met wat u hebt geleerd:

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, dus om de service nauwkeuriger te maken (doe zowel voorspellingen als trainen tegelijk).