HoloLens (1e generatie) en Azure 310: objectdetectie

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 en de ruimtelijke positie ervan binnen een opgegeven afbeelding kunt herkennen met behulp van Azure Custom Vision mogelijkheden voor objectdetectie 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 en hun locatie in de echte wereld te benaderen, zoals opgegeven door de camera-opname van Microsoft HoloLens of een camera die verbinding maakt met een pc voor immersive (VR)-headsets.

cursusresultaat

Azure Custom Vision objectdetectie is een Microsoft-service waarmee ontwikkelaars aangepaste classificaties voor afbeeldingen kunnen bouwen. Deze classificaties kunnen vervolgens worden gebruikt met nieuwe afbeeldingen om objecten in die nieuwe afbeelding te detecteren door vakgrenzen in de afbeelding zelf op te geven. De Service biedt een eenvoudig, eenvoudig te gebruiken online portal om dit proces te stroomlijnen. Ga naar de volgende koppelingen voor meer informatie:

Na het voltooien van deze cursus hebt u een mixed reality-toepassing die het volgende kan doen:

  1. De gebruiker kan naar een object staren, dat ze hebben getraind met behulp van de Azure Custom Vision Service, Objectdetectie.
  2. De gebruiker gebruikt de tikbeweging om een afbeelding vast te leggen van wat hij of zij bekijkt.
  3. De app verzendt de installatiekopieën naar de Azure Custom Vision Service.
  4. Er komt een antwoord van de Service waarin het resultaat van de herkenning wordt weergegeven als wereldruimtetekst. Dit wordt bereikt door gebruik te maken van de ruimtelijke tracering van de Microsoft HoloLens, als een manier om de wereldpositie van het herkende object te begrijpen en vervolgens de tag te gebruiken die is gekoppeld aan wat in de afbeelding is gedetecteerd, om de labeltekst op te geven.

De cursus behandelt ook het handmatig uploaden van afbeeldingen, het maken van tags en het trainen van de Service om verschillende objecten te herkennen (in het opgegeven voorbeeld een kopje) door het grensvak in te stellen binnen de afbeelding die u verzendt.

Belangrijk

Nadat de app is gemaakt en gebruikt, moet de ontwikkelaar teruggaan naar de Azure Custom Vision Service en de voorspellingen identificeren die door de service zijn gedaan en bepalen of deze juist waren of niet (door iets wat de service heeft gemist te taggen en de begrenzingsvakken aan te passen). De Service kan vervolgens opnieuw worden getraind, waardoor de kans op het herkennen van objecten in de echte wereld toeneemt.

In deze cursus leert u hoe u de resultaten van de Azure Custom Vision Service, ObjectDetectie, 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 Immersive headsets
MR en Azure 310: Objectdetectie ✔️

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 hiervoor ondersteuning nodig hebt, gaat u naar 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-portal

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

  1. Ga naar de hoofdpagina van Custom Vision Service.

  2. Klik op Aan de slag.

    Schermopname waarin de knop Aan de slag is gemarkeerd.

  3. Meld u aan bij de Custom Vision Portal.

    Schermopname van de knop Aanmelden.

  4. 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.

  5. 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.

    Schermopname van het deelvenster Servicevoorwaarden.

  6. Nadat u akkoord bent gegaan met de voorwaarden, bevindt u zich nu in de sectie Mijn projecten . Klik op Nieuw project.

    Schermopname die laat zien waar u Nieuw project kunt selecteren.

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

    1. Een naam voor uw project invoegen

    2. Een beschrijving voor uw project invoegen (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 (bijvoorbeeld deze cursussen) onder een gemeenschappelijke resourcegroep te houden.

      Schermopname die laat zien waar u details voor het nieuwe project kunt toevoegen.

    4. Stel de projecttypen in als Objectdetectie (preview).

  8. 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 vijftien (15) afbeeldingen nodig voor elk object dat u door uw toepassing wilt laten herkennen. U kunt de afbeeldingen gebruiken die bij deze cursus worden geleverd (een reeks bekers).

Uw Custom Vision-project trainen:

  1. Klik op de + knop naast Tags.

    Schermopname van de knop + naast Tags.

  2. Voeg een naam toe voor de tag die wordt gebruikt om uw afbeeldingen aan te koppelen. In dit voorbeeld gebruiken we afbeeldingen van bekers voor herkenning, dus hebben we de tag hiervoor, Cup, benoemd. Klik op Opslaan zodra u klaar bent.

    Schermopname die laat zien waar een naam voor de tag moet worden toegevoegd.

  3. U ziet dat de tag is toegevoegd (mogelijk moet u de pagina opnieuw laden om deze weer te geven).

    Schermopname die laat zien waar uw tag is toegevoegd.

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

    Schermopname die laat zien waar afbeeldingen moeten worden toegevoegd.

  5. Klik op Bladeren in lokale bestanden en blader naar de afbeeldingen die u wilt uploaden voor één object, met een minimum van vijftien (15).

    Tip

    U kunt meerdere afbeeldingen tegelijk selecteren om te uploaden.

    Schermopname van de afbeeldingen die u kunt uploaden.

  6. Druk op Bestanden uploaden zodra u alle afbeeldingen hebt geselecteerd waarmee u het project wilt trainen. De bestanden worden geüpload. Zodra u de bevestiging van het uploaden hebt ontvangen, klikt u op Gereed.

    Schermopname van de voortgang van de geüploade afbeeldingen.

  7. Op dit moment worden uw afbeeldingen geüpload, maar niet getagd.

    Schermopname van een afbeelding zonder vlag.

  8. Gebruik de muis om uw afbeeldingen te taggen. Wanneer u de muisaanwijzer over de afbeelding beweegt, kunt u met een selectiemarkering automatisch een selectie rond uw object tekenen. Als het niet nauwkeurig is, kunt u zelf tekenen. Dit wordt bereikt door de linkermuisknop ingedrukt te houden en het selectiegebied te slepen om het object te omvatten.

    Schermopname van het labelen van een afbeelding.

  9. Nadat u het object in de afbeelding hebt geselecteerd, wordt u gevraagd om regiotag toe te voegen. Selecteer uw eerder gemaakte tag ('Cup', in het bovenstaande voorbeeld) of als u meer tags toevoegt, typt u die in en klikt u op de knop + (plus).

    Schermopname van de tag die u aan de afbeelding hebt toegevoegd.

  10. Als u de volgende afbeelding wilt taggen, klikt u op de pijl rechts van de blade of sluit u de tagblade (door op de X in de rechterbovenhoek van de blade te klikken) en vervolgens op de volgende afbeelding te klikken. Zodra u de volgende afbeelding klaar hebt, herhaalt u dezelfde procedure. Doe dit voor alle afbeeldingen die u hebt geüpload, totdat ze allemaal zijn getagd.

    Notitie

    U kunt verschillende objecten in dezelfde afbeelding selecteren, zoals in de onderstaande afbeelding:

    Schermopname van meerdere objecten in een afbeelding.

  11. Zodra u ze allemaal hebt getagd, klikt u op de knop Getagd aan de linkerkant van het scherm om de getagde afbeeldingen weer te geven.

    Schermopname waarin de knop Getagd is gemarkeerd.

  12. U bent nu klaar om uw service te trainen. Klik op de knop Trainen om de eerste trainingsiteratie te starten.

    Schermopname waarin de knop Trainen is gemarkeerd.

    Schermopname van de eerste trainingsiteratie.

  13. Zodra het is gebouwd, kunt u twee knoppen zien met de naam Standaard maken en Voorspellings-URL. Klik eerst op Standaard instellen en klik vervolgens op Voorspellings-URL.

    Schermopname waarin de knop Standaard maken is gemarkeerd.

    Notitie

    Het eindpunt dat hieruit wordt opgegeven, 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 dus niet te wijzigen.

  14. Nadat u op Voorspellings-URL hebt geklikt, opent u Kladblok en kopieert en plakt u de URL (ook wel uw voorspellingseindpunt genoemd) en de servicevoorspellingssleutel, zodat u deze later in de code kunt ophalen wanneer u deze nodig hebt.

    Schermopname van het voorspellingseindpunt en de predition-sleutel.

Hoofdstuk 3- Het Unity-project instellen

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

  1. Open Unity en klik op Nieuw.

    Schermopname met de knop Nieuw gemarkeerd.

  2. U moet nu een Unity-projectnaam opgeven. CustomVisionObjDetection invoegen. Zorg ervoor dat het projecttype is ingesteld op 3D en stel Locatie in op een locatie die geschikt is voor u (denk eraan dat dichter bij hoofdmappen beter is). Klik vervolgens op Project maken.

    Schermopname van de projectdetails en waar u Project maken kunt selecteren.

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

    Schermopname die laat zien waar u de externe scripteditor kunt wijzigen in Visual Studio.

  4. Ga vervolgens naar Instellingen voor bestandsbuilding>, zet platform opUniverseel Windows-platform en klik vervolgens op de knop Platform wisselen.

    Schermopname waarin de knop Schakelen tussen platformen is gemarkeerd.

  5. Zorg ervoor dat in hetzelfde venster Build-instellingen het volgende is ingesteld:

    1. Doelapparaat is ingesteld op HoloLens

    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. De overige instellingen, in Build-instellingen, moeten voorlopig als standaard worden gelaten.

      Schermopname van de configuratieopties voor build-instellingen.

  6. Klik in hetzelfde venster Build-instellingen op de knop Spelerinstellingen . Hiermee opent u het bijbehorende 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. Scripting Runtime-versie moet experimenteel (.NET 4.6-equivalent) zijn, waardoor de editor opnieuw moet worden gestart.

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

      3. Api-compatibiliteitsniveau moet .NET 4.6 zijn.

        Schermopname van de optie API-compatibiliteitsniveau ingesteld op .NET 4.6.

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

      1. InternetClient

      2. Webcam

      3. SpatialPerception

        Schermopname van de bovenste helft van de configuratieopties voor mogelijkheden.Schermopname van de onderste helft van de configuratieopties voor mogelijkheden.

    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.

      Schermopname die laat zien dat de Windows Mixed Reality SDK is toegevoegd.

  8. Terug in build-instellingen wordt Unity C#-projecten niet langer grijs weergegeven: schakel het selectievakje naast dit selectievakje in.

  9. Sluit het venster Build Settings.

  10. Klik in de Editor opProjectinstellingen>bewerken>Afbeeldingen.

    Schermopname met de geselecteerde menuoptie Afbeeldingen.

  11. In het deelvenster Inspector zijn de grafische instellingen geopend. Schuif omlaag totdat u een matrix ziet met de naam Always Include Shaders. Voeg een sleuf toe door de variabele Grootte met één te verhogen (in dit voorbeeld was dit 8, dus we hebben deze 9 gemaakt). Er wordt een nieuwe sleuf weergegeven op de laatste positie van de matrix, zoals hieronder wordt weergegeven:

    Schermopname met de matrix Always Included Shaders gemarkeerd.

  12. Klik in de sleuf op de kleine doelcirkel naast de sleuf om een lijst met shaders te openen. Zoek de verouderde shaders/transparent/diffuse arcering en dubbelklik erop.

    Schermopname waarin de verouderde shaders/transparante/diffuse arcering zijn gemarkeerd.

Hoofdstuk 4: Het CustomVisionObjDetection Unity-pakket importeren

Voor deze cursus krijgt u een Unity Asset-pakket met de naam Azure-MR-310.unitypackage.

[TIP] Objecten die door Unity worden ondersteund, inclusief hele scènes, kunnen worden verpakt in een .unitypackage-bestand en worden geëxporteerd/geïmporteerd in andere projecten. Het is de veiligste en meest efficiënte manier om assets tussen verschillende Unity-projecten te verplaatsen.

U vindt het Azure-MR-310-pakket dat u hier moet downloaden.

  1. Klik met het Unity-dashboard voor u op Assets in het menu bovenaan het scherm en klik vervolgens op Aangepast pakket > importeren.

    Schermopname met de menuoptie Aangepast pakket gemarkeerd.

  2. Gebruik de bestandskiezer om het pakket Azure-MR-310.unitypackage te selecteren en klik op Openen. Er wordt een lijst met onderdelen voor deze asset weergegeven. Bevestig het importeren door op de knop Importeren te klikken.

    Schermopname van de lijst met assetonderdelen die u wilt importeren.

  3. Zodra het importeren is voltooid, ziet u dat mappen uit het pakket nu zijn toegevoegd aan de map Assets . Dit type mapstructuur is typisch voor een Unity-project.

    Schermopname van de inhoud van de map Assets.

    1. De map Materialen bevat het materiaal dat door de Gaze Cursor wordt gebruikt.

    2. De map Plugins bevat de Newtonsoft DLL die door de code wordt gebruikt om het webantwoord van de service te deserialiseren. De twee (2) verschillende versies in de map en submap zijn nodig om de bibliotheek te kunnen gebruiken en bouwen door zowel de Unity Editor als de UWP-build.

    3. De map Prefabs bevat de prefabs in de scène. Dit zijn:

      1. De GazeCursor, de cursor die in de toepassing wordt gebruikt. Werkt samen met de SpatialMapping-prefab om in de scène op fysieke objecten te kunnen worden geplaatst.
      2. Het label, het UI-object dat wordt gebruikt om de objecttag in de scène weer te geven wanneer dat nodig is.
      3. De SpatialMapping, het object waarmee de toepassing een virtuele kaart kan maken met behulp van de ruimtelijke tracering van de Microsoft HoloLens.
    4. De map Scènes die momenteel de vooraf gemaakte scène voor deze cursus bevat.

  4. Open de map Scènes in het deelvenster Project en dubbelklik op ObjDetectionScene om de scène te laden die u voor deze cursus wilt gebruiken.

    Schermopname van de ObjDetectionScene in de map Scènes.

    Notitie

    Er is geen code opgenomen. U schrijft de code door deze cursus te volgen.

Hoofdstuk 5: 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 worden weergegeven, 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 SDK biedt die ook kan worden gebruikt om de service aan te roepen. Raadpleeg het artikel Custom Vision 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 map Activa in het projectvenster en klik vervolgens op Map maken>. Roep de map Scripts aan.

    Schermopname van het maken van de map Scripts.

  2. Dubbelklik op de zojuist gemaakte map 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. Zorg ervoor dat er boven aan het bestand naar de volgende naamruimten wordt verwezen:

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  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>
        /// Bite array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Notitie

    Zorg ervoor dat u uw Service Prediction-Key in de variabele predictionKey en uw Prediction-Endpoint in de variabele predictionEndpoint invoegt . U hebt deze eerder in hoofdstuk 2, stap 14, gekopieerd naar Kladblok.

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

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Voeg daaronder de coroutine (met de statische methode GetImageAsByteArray() toe, waarmee de resultaten worden verkregen van de analyse van de afbeelding, 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)
        {
            Debug.Log("Analyzing...");
    
            WWWForm webForm = new WWWForm();
    
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                Debug.Log("response: " + jsonResponse);
    
                // Create a texture. Texture size does not matter, since
                // LoadImage will replace with the incoming image size.
                //Texture2D tex = new Texture2D(1, 1);
                //tex.LoadImage(imageBytes);
                //SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
                // The response will be in JSON format, therefore it needs to be deserialized
                //AnalysisRootObject analysisRootObject = new AnalysisRootObject();
                //analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
                //SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  9. Verwijder de methoden Start() en Update(), omdat deze niet worden gebruikt.

  10. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Belangrijk

Zoals eerder vermeld, hoeft u zich geen zorgen te maken over code die een fout lijkt te hebben, omdat u binnenkort meer klassen opgeeft, waardoor deze worden opgelost.

Hoofdstuk 6: 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.

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. Zorg ervoor dat er boven aan het bestand naar de volgende naamruimten wordt verwezen:

    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.

    Waarschuwing

    Het is belangrijk dat u de volgende instructie zorgvuldig volgt. Als u de nieuwe klassedeclaraties in de klasse CustomVisionObjects plaatst, krijgt u compileerfouten in hoofdstuk 10, waarin wordt aangegeven dat AnalysisRootObject en BoundingBox niet zijn gevonden.

  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
    /// Includes Bounding Box
    /// </summary>
    public class AnalysisRootObject
    {
        public string id { get; set; }
        public string project { get; set; }
        public string iteration { get; set; }
        public DateTime created { get; set; }
        public List<Prediction> predictions { get; set; }
    }
    
    public class BoundingBox
    {
        public double left { get; set; }
        public double top { get; set; }
        public double width { get; set; }
        public double height { get; set; }
    }
    
    public class Prediction
    {
        public double probability { get; set; }
        public string tagId { get; set; }
        public string tagName { get; set; }
        public BoundingBox boundingBox { get; set; }
    }
    
  6. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Hoofdstuk 7: De klasse SpatialMapping maken

Deze klasse stelt de Spatial Mapping Collider in de scène in om conflicten tussen virtuele objecten en echte objecten te kunnen detecteren.

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 SpatialMapping aan.

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

  3. Zorg ervoor dat er boven de klasse SpatialMapping naar de volgende naamruimten wordt verwezen:

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. Voeg vervolgens de volgende variabelen toe in de klasse SpatialMapping, boven de methode Start():

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

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

  7. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Hoofdstuk 8: De klasse GazeCursor maken

Deze klasse is verantwoordelijk voor het instellen van de cursor op de juiste locatie in de echte ruimte, door gebruik te maken van de SpatialMappingCollider, gemaakt in het vorige hoofdstuk.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de map Scripts en klik vervolgens opC#-scriptmaken>. Het script GazeCursor aanroepen

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

  3. Zorg ervoor dat naar de volgende naamruimte wordt verwezen boven de klasse GazeCursor :

    using UnityEngine;
    
  4. Voeg vervolgens de volgende variabele toe in de klasse GazeCursor, boven de methode Start().

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Werk de methode Start() bij met de volgende code:

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

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

    Notitie

    U hoeft zich geen zorgen te maken dat de fout voor de klasse SceneOrganiser niet wordt gevonden. U maakt deze in het volgende hoofdstuk.

  7. Sla uw wijzigingen op in Visual Studio voordat u terugkeert naar Unity.

Hoofdstuk 9- De klasse SceneOrganiser maken

Met deze klasse kunt u:

  • Stel de hoofdcamera in door er de juiste onderdelen aan te koppelen.

  • Wanneer een object wordt gedetecteerd, is het verantwoordelijk voor het berekenen van de positie in de echte wereld en wordt er een labellabel in de buurt geplaatst met de juiste tagnaam.

Ga als volgt te werk om deze klasse te maken:

  1. Klik met de rechtermuisknop in de map Scripts en klik vervolgens opC#-scriptmaken>. Geef het script de naam ScèneOrganiser.

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

  3. Zorg ervoor dat er boven de klasse SceneOrganiser naar de volgende naamruimten wordt verwezen:

    using System.Collections.Generic;
    using System.Linq;
    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 Main Camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        public GameObject label;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.8f;
    
        /// <summary>
        /// The quad object hosting the imposed image captured
        /// </summary>
        private GameObject quad;
    
        /// <summary>
        /// Renderer of the quad object
        /// </summary>
        internal Renderer quadRenderer;
    
  5. Verwijder de methoden Start() en Update().

  6. Voeg 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 CustomVisionObjects class to this Gameobject
            gameObject.AddComponent<CustomVisionObjects>();
        }
    
  7. Voeg de methode PlaceAnalysisLabel() toe, waarmee het label in de scène wordt geïnstitueerd (dat op dit moment onzichtbaar is voor de gebruiker). Het plaatst ook de quad (ook onzichtbaar) waar de afbeelding wordt geplaatst en overlapt met de echte wereld. Dit is belangrijk omdat de boxcoördinaten die na analyse uit de service zijn opgehaald, worden teruggeleid naar deze quad om de locatie van het object in de echte wereld bij benadering te bepalen.

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

    • De labeltekst instellen met de tag van de voorspelling met de hoogste betrouwbaarheid.
    • De berekening van het begrenzingsvak aanroepen op het quad-object, dat eerder is geplaatst, en het label in de scène plaatsen.
    • Het aanpassen van de labeldiepte met behulp van een Raycast naar de begrenzingsdoos, die in de echte wereld tegen het object moet botsen.
    • Het vastleggen opnieuw instellen zodat de gebruiker een andere afbeelding kan vastleggen.
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void FinaliseLabel(AnalysisRootObject analysisObject)
        {
            if (analysisObject.predictions != null)
            {
                lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
                // Sort the predictions to locate the highest one
                List<Prediction> sortedPredictions = new List<Prediction>();
                sortedPredictions = analysisObject.predictions.OrderBy(p => p.probability).ToList();
                Prediction bestPrediction = new Prediction();
                bestPrediction = sortedPredictions[sortedPredictions.Count - 1];
    
                if (bestPrediction.probability > probabilityThreshold)
                {
                    quadRenderer = quad.GetComponent<Renderer>() as Renderer;
                    Bounds quadBounds = quadRenderer.bounds;
    
                    // Position the label as close as possible to the Bounding Box of the prediction 
                    // At this point it will not consider depth
                    lastLabelPlaced.transform.parent = quad.transform;
                    lastLabelPlaced.transform.localPosition = CalculateBoundingBoxPosition(quadBounds, bestPrediction.boundingBox);
    
                    // Set the tag text
                    lastLabelPlacedText.text = bestPrediction.tagName;
    
                    // Cast a ray from the user's head to the currently placed label, it should hit the object detected by the Service.
                    // At that point it will reposition the label where the ray HL sensor collides with the object,
                    // (using the HL spatial tracking)
                    Debug.Log("Repositioning Label");
                    Vector3 headPosition = Camera.main.transform.position;
                    RaycastHit objHitInfo;
                    Vector3 objDirection = lastLabelPlaced.position;
                    if (Physics.Raycast(headPosition, objDirection, out objHitInfo, 30.0f,   SpatialMapping.PhysicsRaycastMask))
                    {
                        lastLabelPlaced.position = objHitInfo.point;
                    }
                }
            }
            // Reset the color of the cursor
            cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the analysis process
            ImageCapture.Instance.ResetImageCapture();        
        }
    
  9. Voeg de methode CalculateBoundingBoxPosition() toe, die als host fungeert voor een aantal berekeningen die nodig zijn om de coördinaten van het begrenzingsvak te vertalen die zijn opgehaald uit de service en deze proportioneel opnieuw te maken op de quad.

        /// <summary>
        /// This method hosts a series of calculations to determine the position 
        /// of the Bounding Box on the quad created in the real world
        /// by using the Bounding Box received back alongside the Best Prediction
        /// </summary>
        public Vector3 CalculateBoundingBoxPosition(Bounds b, BoundingBox boundingBox)
        {
            Debug.Log($"BB: left {boundingBox.left}, top {boundingBox.top}, width {boundingBox.width}, height {boundingBox.height}");
    
            double centerFromLeft = boundingBox.left + (boundingBox.width / 2);
            double centerFromTop = boundingBox.top + (boundingBox.height / 2);
            Debug.Log($"BB CenterFromLeft {centerFromLeft}, CenterFromTop {centerFromTop}");
    
            double quadWidth = b.size.normalized.x;
            double quadHeight = b.size.normalized.y;
            Debug.Log($"Quad Width {b.size.normalized.x}, Quad Height {b.size.normalized.y}");
    
            double normalisedPos_X = (quadWidth * centerFromLeft) - (quadWidth/2);
            double normalisedPos_Y = (quadHeight * centerFromTop) - (quadHeight/2);
    
            return new Vector3((float)normalisedPos_X, (float)normalisedPos_Y, 0);
        }
    
  10. Sla uw wijzigingen op 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:

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

Notitie

U hoeft zich geen zorgen te maken over het bericht 'Kan niet worden gevonden' van de ImageCapture-klasse . U maakt dit in het volgende hoofdstuk.

Hoofdstuk 10- 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.

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 opC#-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>
        /// 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;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the Microsoft HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  7. Implementeer een handler die wordt aangeroepen wanneer een tikbeweging plaatsvindt:

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

    Belangrijk

    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 de installatiekopieën 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()
        {
            // Create a label in world space using the ResultsLabel class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending
                ((res) => res.width * res.height).First();
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(true, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 1.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });
        }
    
  9. 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 voor analyse.

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

Hoofdstuk 11 - De scripts in de scène instellen

Nu u alle code hebt geschreven die nodig is voor dit project, is het tijd om de scripts in de scène en op de prefabs in te stellen, zodat ze zich correct gedragen.

  1. Selecteer in de Unity-editor in het deelvenster Hiërarchie de hoofdcamera.

  2. Klik in het deelvenster Inspector, met de hoofdcamera geselecteerd, op Onderdeel toevoegen, zoek naar ScèneOrganiser-script en dubbelklik erop om het toe te voegen.

    Schermopname van het scèneorganizer-script.

  3. Open in het deelvenster Project de map Prefabs en sleep de prefab Label naar het invoergebied Label leeg referentiedoel in het script ScèneOrganiser dat u zojuist hebt toegevoegd aan de hoofdcamera, zoals wordt weergegeven in de onderstaande afbeelding:

    Schermopname van het script dat u hebt toegevoegd aan de hoofdcamera.

  4. Selecteer in het deelvenster Hiërarchie het onderliggende element GazeCursor van de hoofdcamera.

  5. Klik in het deelvenster Inspector, met de GazeCursor geselecteerd, op Component toevoegen, zoek vervolgens naar Het script GazeCursor en dubbelklik erop om het toe te voegen.

    Schermopname die laat zien waar u het GazeCursor-script toevoegt.

  6. Selecteer nogmaals in het deelvenster Hierarchy het onderliggende element SpatialMapping van de hoofdcamera.

  7. Klik in het deelvenster Inspector, met SpatialMapping geselecteerd, op Component toevoegen, zoek naar het script SpatialMapping en dubbelklik erop om het toe te voegen.

    Schermopname die laat zien waar u het SpatialMapping-script toevoegt.

De resterende scripts die u niet hebt ingesteld, worden tijdens runtime toegevoegd door de code in het ScèneOrganiser-script .

Hoofdstuk 12 - Voor de bouw

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

Voordat u dit doet, moet u ervoor zorgen dat:

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

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

  • Het script GazeCursor is gekoppeld aan het object GazeCursor .

  • Het script SpatialMapping is gekoppeld aan het object SpatialMapping .

  • In hoofdstuk 5, stap 6:

    • Zorg ervoor dat u uw servicevoorspellingssleutel in de variabele predictionKey invoegt .
    • U hebt het voorspellingseindpunt ingevoegd in de klasse predictionEndpoint .

Hoofdstuk 13- De UWP-oplossing bouwen en uw toepassing sideloaden

U bent nu klaar om uw toepassing te bouwen als een UWP-oplossing die u kunt implementeren op de Microsoft HoloLens. 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 Open scènes toevoegen. Hiermee wordt de momenteel geopende scène toegevoegd aan de build.

    Schermopname met de knop Open Scènes toevoegen gemarkeerd.

  4. 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.

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

  6. 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).

  7. Als u wilt implementeren op Microsoft HoloLens, hebt u het IP-adres van dat apparaat nodig (voor extern implementeren) en om ervoor te zorgen dat er ook ontwikkelaarsmodus is ingesteld. 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.

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

  9. Selecteer foutopsporing in oplossingsconfiguratie.

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

    Schermopname die laat zien waar het IP-adres moet worden ingevoegd.

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

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

De toepassing gebruiken:

  • Bekijk een object dat u hebt getraind met uw Azure Custom Vision Service, Objectdetectie en gebruik de tikbeweging.
  • Als het object is gedetecteerd, wordt er een labeltekst met wereldruimte weergegeven met de tagnaam.

Belangrijk

Telkens wanneer u een foto maakt en deze naar de Service verzendt, kunt u teruggaan naar de pagina Service en de Service opnieuw trainen met de nieuw gemaakte afbeeldingen. In het begin moet u waarschijnlijk ook de begrenzingsvakken corrigeren om nauwkeuriger te zijn en de service opnieuw te trainen.

Notitie

De geplaatste labeltekst wordt mogelijk niet weergegeven in de buurt van het object wanneer de Microsoft HoloLens sensoren en/of de SpatialTrackingComponent in Unity de juiste colliers niet kunnen plaatsen ten opzichte van de echte objecten. Probeer de toepassing op een ander oppervlak te gebruiken als dat het geval is.

Uw Custom Vision, objectdetectietoepassing

Gefeliciteerd, u hebt een mixed reality-app gemaakt die gebruikmaakt van de Azure Custom Vision, objectdetectie-API, die een object van een afbeelding kan herkennen en vervolgens een geschatte positie voor dat object in de 3D-ruimte kan bieden.

Schermopname van een mixed reality-app die gebruikmaakt van de Azure Custom Vision, Objectdetectie-API.

Bonusoefeningen

Oefening 1

Voeg toe aan het tekstlabel en gebruik een semitransparante kubus om het echte object in een 3D-begrenzingsvak te laten teruglopen.

Oefening 2

Train uw Custom Vision Service om meer objecten te herkennen.

Oefening 3

Een geluid afspelen wanneer een object wordt herkend.

Oefening 4

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 voorspelling als training tegelijk).