Dela via


HoloLens (första generationen) och Azure 302b: Custom vision


Kommentar

Självstudierna för Mixed Reality Academy har utformats med HoloLens (första generationen) och Mixed Reality Immersive Headsets i åtanke. Därför anser vi att det är viktigt att låta de här självstudierna vara kvar för utvecklare som fortfarande letar efter vägledning för att utveckla för dessa enheter. De här självstudierna uppdateras inte med de senaste verktygsuppsättningarna eller interaktionerna som används för HoloLens 2. De underhålls för att fortsätta arbeta med de enheter som stöds. Det kommer att finnas en ny serie självstudier som kommer att publiceras i framtiden som visar hur du utvecklar för HoloLens 2. Det här meddelandet uppdateras med en länk till de självstudierna när de publiceras.


I den här kursen får du lära dig hur du identifierar anpassat visuellt innehåll i en angivet bild med hjälp av Azure Custom Vision-funktioner i ett program för mixad verklighet.

Med den här tjänsten kan du träna en maskininlärningsmodell med hjälp av objektbilder. Du kommer sedan att använda den tränade modellen för att identifiera liknande objekt, enligt kamerainspelningen av Microsoft HoloLens eller en kamera som är ansluten till datorn för uppslukande (VR)-headset.

kursresultat

Azure Custom Vision är en Microsoft Cognitive Service som gör det möjligt för utvecklare att skapa anpassade bildklassificerare. Dessa klassificerare kan sedan användas med nya bilder för att identifiera eller klassificera objekt i den nya bilden. Tjänsten är en enkel onlineportal som är enkel att använda för att effektivisera processen. Mer information finns på sidan Azure Custom Vision Service.

När kursen är klar har du ett program för mixad verklighet som kan fungera i två lägen:

  • Analysläge: Konfigurera Custom Vision Service manuellt genom att ladda upp bilder, skapa taggar och träna tjänsten att identifiera olika objekt (i det här fallet mus och tangentbord). Sedan skapar du en HoloLens-app som tar bilder med kameran och försöker identifiera objekten i verkligheten.

  • Träningsläge: Du implementerar kod som aktiverar ett "träningsläge" i din app. Med träningsläget kan du ta bilder med HoloLens kamera, ladda upp de insamlade bilderna till tjänsten och träna custom vision-modellen.

Den här kursen lär dig hur du hämtar resultaten från Custom Vision Service till ett Unity-baserat exempelprogram. Det är upp till dig att tillämpa dessa begrepp på ett anpassat program som du kanske skapar.

Stöd för enheter

Kurs HoloLens Uppslukande headset
MR och Azure 302b: Custom Vision ✔️ ✔️

Kommentar

Den här kursen fokuserar främst på HoloLens, men du kan även tillämpa det du lär dig i den här kursen på Windows Mixed Reality-headset (VR). Eftersom integrerande (VR) headset inte har tillgängliga kameror behöver du en extern kamera ansluten till datorn. När du följer kursen visas anteckningar om eventuella ändringar som du kan behöva använda för att stödja uppslukande (VR) headset.

Förutsättningar

Kommentar

Den här självstudien är utformad för utvecklare som har grundläggande erfarenhet av Unity och C#. Tänk också på att kraven och de skriftliga instruktionerna i det här dokumentet representerar det som har testats och verifierats i skrivande stund (juli 2018). Du är fri att använda den senaste programvaran, som anges i artikeln installera verktyg , men det bör inte antas att informationen i den här kursen perfekt matchar vad du hittar i nyare programvara än vad som anges nedan.

Vi rekommenderar följande maskinvara och programvara för den här kursen:

Innan du börjar

  1. För att undvika problem med att skapa det här projektet rekommenderar vi starkt att du skapar projektet som nämns i den här självstudien i en rotmapp eller nära rotmapp (långa mappsökvägar kan orsaka problem vid byggtid).
  2. Konfigurera och testa dina HoloLens. Om du behöver stöd för att konfigurera HoloLens ska du gå till installationsartikeln för HoloLens.
  3. Det är en bra idé att utföra kalibrering och sensorjustering när du börjar utveckla en ny HoloLens-app (ibland kan det hjälpa till att utföra dessa uppgifter för varje användare).

Om du behöver hjälp med kalibrering kan du följa den här länken till artikeln HoloLens-kalibrering.

Om du vill ha hjälp med sensorjustering följer du den här länken till artikeln HoloLens Sensor Tuning.

Kapitel 1 – Custom Vision Service-portalen

Om du vill använda Custom Vision Service i Azure måste du konfigurera en instans av tjänsten som ska göras tillgänglig för ditt program.

  1. Gå först till Custom Vision Service-huvudsidan.

  2. Klicka på knappen Kom igång .

    Kom igång med Custom Vision Service

  3. Logga in på Custom Vision Service-portalen .

    Logga in på portalen

    Kommentar

    Om du inte redan har ett Azure-konto måste du skapa ett. Om du följer den här självstudien i en klassrums- eller labbsituation ber du din instruktör eller någon av rektorerna om hjälp med att konfigurera ditt nya konto.

  4. När du är inloggad för första gången uppmanas du att använda panelen Användarvillkor . Klicka på kryssrutan för att godkänna villkoren. Klicka sedan på Jag accepterar.

    Användarvillkor

  5. Efter att ha godkänt villkoren navigeras du till avsnittet Projekt i portalen. Klicka på Nytt projekt.

    Skapa nytt projekt

  6. En flik visas till höger, vilket uppmanar dig att ange några fält för projektet.

    1. Infoga ett namn för projektet.

    2. Infoga en beskrivning för projektet (valfritt).

    3. Välj en resursgrupp eller skapa en ny. En resursgrupp är ett sätt att övervaka, kontrollera åtkomst, etablera och hantera fakturering för en samling Azure-tillgångar. Vi rekommenderar att du behåller alla Azure-tjänster som är associerade med ett enskilt projekt (t.ex. dessa kurser) under en gemensam resursgrupp.

    4. Ställ in projekttyperna Klassificering

    5. Ange domänerna som Allmänt.

      Ange domänerna

      Om du vill läsa mer om Azure-resursgrupper kan du gå till resursgruppens artikel.

  7. När du är klar klickar du på Skapa projekt. Du omdirigeras till projektsidan Custom Vision Service.

Kapitel 2 – Träna ditt Custom Vision-projekt

I Custom Vision-portalen är ditt primära mål att träna projektet att identifiera specifika objekt i bilder. Du behöver minst fem (5) bilder, men tio (10) rekommenderas för varje objekt som du vill att programmet ska känna igen. Du kan använda de bilder som tillhandahålls med den här kursen (en datormus och ett tangentbord).

Så här tränar du ditt Custom Vision Service-projekt:

  1. Klicka på + knappen bredvid Taggar.

    Lägg till ny tagg

  2. Lägg till namnetdet objekt som du vill känna igen. Klicka på Spara.

    Lägg till objektnamn och spara

  3. Du kommer att märka att taggen har lagts till (du kan behöva läsa in sidan igen för att den ska visas). Klicka på kryssrutan tillsammans med den nya taggen om den inte redan är markerad.

    Aktivera ny tagg

  4. Klicka på Lägg till bilder i mitten av sidan.

    Lägg till bilder

  5. Klicka på Bläddra bland lokala filer och sök och välj sedan de bilder som du vill ladda upp, med minst fem (5). Kom ihåg att alla dessa bilder ska innehålla det objekt som du tränar.

    Kommentar

    Du kan välja flera bilder i taget för att ladda upp.

  6. När du kan se bilderna på fliken väljer du lämplig tagg i rutan Mina taggar .

    Välj taggar

  7. Klicka på Ladda upp filer. Filerna börjar laddas upp. När du har bekräftat uppladdningen klickar du på Klar.

    Ladda upp filer

  8. Upprepa samma process för att skapa en ny tagg med namnet Tangentbord och ladda upp lämpliga foton för den. Avmarkera Musen när du har skapat de nya taggarna så att fönstret Lägg till bilder visas.

  9. När du har konfigurerat båda taggarna klickar du på Träna, så börjar den första tränings-iterationen att börja byggas.

    Aktivera iteration för träning

  10. När den har skapats kan du se två knappar med namnet Gör standard och Förutsägelse-URL. Klicka på Gör standard först och klicka sedan på Förutsägelse-URL.

    Ange standard- och förutsägelse-URL

    Kommentar

    Slutpunkts-URL:en som tillhandahålls av detta är inställd på den iteration som har markerats som standard. Om du senare gör en ny iteration och uppdaterar den som standard behöver du inte ändra koden.

  11. När du har klickat på Förutsägelse-URL öppnar du Anteckningar och kopierar och klistrar in URL:en och förutsägelsenyckeln, så att du kan hämta den när du behöver den senare i koden.

    Kopiera och klistra in URL och förutsägelsenyckel

  12. Klicka på kugghjulet längst upp till höger på skärmen.

    Klicka på kugghjulsikonen för att öppna inställningar

  13. Kopiera träningsnyckeln och klistra in den i ett anteckningar för senare användning.

    Kopiera träningsnyckel

  14. Kopiera även ditt Projekt-ID och klistra in det i anteckningar-filen för senare användning.

    Kopiera projekt-ID

Kapitel 3 – Konfigurera Unity-projektet

Följande är en typisk konfiguration för utveckling med mixad verklighet och är därför en bra mall för andra projekt.

  1. Öppna Unity och klicka på Nytt.

    Skapa nytt Unity-projekt

  2. Nu måste du ange ett Unity-projektnamn. Infoga AzureCustomVision. Kontrollera att projektmallen är inställd på 3D. Ange platsen till någonstans som passar dig (kom ihåg att närmare rotkataloger är bättre). Klicka sedan på Skapa projekt.

    Konfigurera projektinställningar

  3. När Unity är öppet är det värt att kontrollera att standardskriptredigeraren är inställd på Visual Studio. Gå till Redigera>inställningar och gå sedan till Externa verktyg från det nya fönstret. Ändra extern skriptredigerare till Visual Studio 2017. Stäng fönstret Inställningar .

    Konfigurera externa verktyg

  4. Gå sedan till Inställningar för filbygge > och välj Universell Windows-plattform och klicka sedan på knappen Växla plattform för att tillämpa ditt val.

    Konfigurera bygginställningar

  5. När du fortfarande är i Filversionsinställningar > och se till att:

    1. Målenheten är inställd på HoloLens

      För de uppslukande headseten anger du Målenhet till Valfri enhet.

    2. Byggtyp är inställd på D3D

    3. SDK är inställt på Senaste installerat

    4. Visual Studio-versionen är inställd på Senaste installerad

    5. Build and Run är inställt på Lokal dator

    6. Spara scenen och lägg till den i bygget.

      1. Gör detta genom att välja Lägg till öppna scener. Ett spara-fönster visas.

        Lägg till öppen scen i bygglistan

      2. Skapa en ny mapp för detta, och eventuella framtida scenarier, välj sedan knappen Ny mapp , för att skapa en ny mapp, ge den namnet Scener.

        Skapa ny scenmapp

      3. Öppna den nyligen skapade mappen Scener och skriv CustomVisionScene i fältet Filnamn: text och klicka sedan på Spara.

        Namnge ny scenfil

        Tänk på att du måste spara Unity-scenerna i mappen Tillgångar , eftersom de måste associeras med Unity-projektet. Att skapa mappen scener (och andra liknande mappar) är ett typiskt sätt att strukturera ett Unity-projekt.

    7. De återstående inställningarna i Bygginställningar bör vara kvar som standard för tillfället.

      Standardinställningar för bygge

  6. I fönstret Bygginställningar klickar du på knappen Spelarinställningar. Då öppnas den relaterade panelen i det utrymme där inspektören finns.

  7. I den här panelen måste några inställningar verifieras:

    1. På fliken Andra inställningar :

      1. Skriptkörningsversionen bör vara experimentell (.NET 4.6-motsvarighet), vilket utlöser ett behov av att starta om redigeraren.

      2. Skriptserverdelen ska vara .NET

      3. API-kompatibilitetsnivån ska vara .NET 4.6

      Ange API-compantiblity

    2. På fliken Publiceringsinställningar går du till Funktioner och kontrollerar:

      1. InternetClient

      2. Webbkamera

      3. Mikrofon

      Konfigurera publiceringsinställningar

    3. Längre ned på panelen, i XR-inställningar (som finns under Publiceringsinställningar), markerar du Virtual Reality Supported (Virtual Reality Supported) och kontrollerar att Windows Mixed Reality SDK har lagts till.

    Konfigurera XR-inställningar

  8. Tillbaka i Bygginställningar Unity C#-projekt är inte längre nedtonade. Markera kryssrutan bredvid detta.

  9. Stäng fönstret Build Settings (Bygginställningar).

  10. Spara scen och projekt (FILE > SAVE SCENE/FILE > SAVE PROJECT).

Kapitel 4 – Importera Newtonsoft DLL i Unity

Viktigt!

Om du vill hoppa över unity-konfigurationskomponenten i den här kursen och fortsätta direkt till kod kan du ladda ned den här Azure-MR-302b.unitypackage, importera den till ditt projekt som ett anpassat paket och sedan fortsätta från kapitel 6.

Den här kursen kräver användning av Newtonsoft-biblioteket , som du kan lägga till som en DLL till dina tillgångar. Paketet som innehåller det här biblioteket kan laddas ned från den här länken. Om du vill importera Newtonsoft-biblioteket till projektet använder du Unity-paketet som medföljer den här kursen.

  1. Lägg till .unitypackage i Unity med menyalternativet Importera paket>för tillgångar.>

  2. I rutan Importera Unity-paket som visas kontrollerar du att allt under (och inklusive) plugin-program har valts .

    Importera alla paketobjekt

  3. Klicka på knappen Importera för att lägga till objekten i projektet.

  4. Gå till mappen Newtonsoft under Plugin-program i projektvyn och välj plugin-programmet Newtonsoft.Json.

    Välj Newtonsoft-plugin-program

  5. När plugin-programmet Newtonsoft.Json har valts kontrollerar du att Alla plattformar är avmarkerade och se sedan till att WSAPlayer också är avmarkerat och klicka sedan på Använd. Detta är bara för att bekräfta att filerna är korrekt konfigurerade.

    Konfigurera Newtonsoft-plugin-program

    Kommentar

    Om du markerar dessa plugin-program konfigureras de så att de endast används i Unity-redigeraren. Det finns en annan uppsättning av dem i WSA-mappen som ska användas när projektet har exporterats från Unity.

  6. Därefter måste du öppna WSA-mappen i mappen Newtonsoft . Du ser en kopia av samma fil som du just konfigurerade. Välj filen och se sedan till att du i inspektören

    • Alla plattformar är avmarkerade
    • endast WSAPlayer är markerat
    • Dont-processen är markerad

    Konfigurera inställningar för Newtonsoft-plugin-plattformen

Kapitel 5 – Kamerainstallation

  1. I hierarkipanelen väljer du huvudkameran.

  2. När du har valt kan du se alla komponenter i huvudkameran i kontrollpanelen.

    1. Kameraobjektet måste ha namnet Main Camera (notera stavningen!)

    2. Huvudkamerataggen måste vara inställd på MainCamera (observera stavningen!)

    3. Kontrollera att transformeringspositionen är inställd på 0, 0, 0

    4. Ange Clear Flags till Solid Color (ignorera detta för uppslukande headset).

    5. Ange bakgrundsfärgen för kamerakomponenten till Svart, Alpha 0 (Hex Code: #000000000) (ignorera detta för uppslukande headset).

    Konfigurera egenskaper för kamerakomponent

Kapitel 6 – Skapa klassen CustomVisionAnalyser.

Nu är du redo att skriva kod.

Du börjar med klassen CustomVisionAnalyser .

Kommentar

Anropen till Custom Vision Service som görs i koden som visas nedan görs med hjälp av Custom Vision REST-API:et. Med hjälp av detta får du se hur du implementerar och använder det här API:et (användbart för att förstå hur du implementerar något liknande på egen hand). Tänk på att Microsoft erbjuder en Custom Vision Service SDK som också kan användas för att göra anrop till tjänsten. Mer information finns i Artikeln om Custom Vision Service SDK .

Den här klassen ansvarar för:

  • Läser in den senaste avbildningen som tagits som en matris med byte.

  • Skicka bytematrisen till din Azure Custom Vision Service-instans för analys.

  • Tar emot svaret som en JSON-sträng.

  • Deserialisera svaret och skicka den resulterande förutsägelsen till klassen SceneOrganiser , som tar hand om hur svaret ska visas.

Så här skapar du den här klassen:

  1. Högerklicka i tillgångsmappen i projektpanelen och klicka sedan på Skapa > mapp. Anropa mappen Skript.

    Skapa skriptmapp

  2. Dubbelklicka på mappen som nyss skapades för att öppna den.

  3. Högerklicka i mappen och klicka sedan på Skapa>C#-skript. Ge skriptet namnet CustomVisionAnalyser.

  4. Dubbelklicka på det nya CustomVisionAnalyser-skriptet för att öppna det med Visual Studio.

  5. Uppdatera namnrymderna överst i filen så att de matchar följande:

    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    using Newtonsoft.Json;
    
  6. Lägg till följande variabler i klassen CustomVisionAnalyser :

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

    Kommentar

    Se till att du infogar din förutsägelsenyckel i variabeln predictionKey och förutsägelseslutpunkten i variabeln predictionEndpoint. Du kopierade dessa till Anteckningar tidigare i kursen.

  7. Kod för Awake() måste nu läggas till för att initiera instansvariabeln:

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Ta bort metoderna Start() och Update().

  9. Lägg sedan till coroutine -metoden (med den statiska Metoden GetImageAsByteArray() under den), som hämtar resultatet av analysen av den bild som avbildas av klassen ImageCapture .

    Kommentar

    I coroutinen AnalyseImageCapture finns det ett anrop till klassen SceneOrganiser som du ännu inte har skapat. Lämna därför dessa rader kommenterade för tillfället.

        /// <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. Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.

Kapitel 7 – Skapa klassen CustomVisionObjects

Klassen du skapar nu är klassen CustomVisionObjects .

Det här skriptet innehåller ett antal objekt som används av andra klasser för att serialisera och deserialisera anropen till Custom Vision Service.

Varning

Det är viktigt att du noterar slutpunkten som Custom Vision Service tillhandahåller, eftersom JSON-strukturen nedan har konfigurerats för att fungera med Custom Vision Prediction v2.0. Om du har en annan version kan du behöva uppdatera strukturen nedan.

Så här skapar du den här klassen:

  1. Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Anropa skriptet CustomVisionObjects.

  2. Dubbelklicka på det nya CustomVisionObjects-skriptet för att öppna det med Visual Studio.

  3. Lägg till följande namnområden överst i filen:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Ta bort metoderna Start() och Update() i klassen CustomVisionObjects. Den här klassen bör nu vara tom.

  5. Lägg till följande klasser utanför klassen CustomVisionObjects . Dessa objekt används av Newtonsoft-biblioteket för att serialisera och deserialisera svarsdata:

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

Kapitel 8 – Skapa klassen VoiceRecognizer

Den här klassen känner igen röstindata från användaren.

Så här skapar du den här klassen:

  1. Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Anropa skriptet VoiceRecognizer.

  2. Dubbelklicka på det nya VoiceRecognizer-skriptet för att öppna det med Visual Studio.

  3. Lägg till följande namnområden ovanför klassen VoiceRecognizer :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Windows.Speech;
    
  4. Lägg sedan till följande variabler i klassen VoiceRecognizer ovanför metoden 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. Lägg till metoderna Awake() och Start(), varav det senare konfigurerar de användarnyckelord som ska identifieras när en tagg associeras till en bild:

        /// <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. Ta bort metoden Update().

  7. Lägg till följande hanterare, som anropas när röstindata identifieras:

        /// <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. Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.

Kommentar

Oroa dig inte för kod som kan verka ha ett fel, eftersom du kommer att tillhandahålla ytterligare klasser snart, vilket kommer att åtgärda dessa.

Kapitel 9 – Skapa klassen CustomVisionTrainer

Den här klassen kedjar en serie webbanrop för att träna Custom Vision Service. Varje anrop förklaras i detalj precis ovanför koden.

Så här skapar du den här klassen:

  1. Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Anropa skriptet CustomVisionTrainer.

  2. Dubbelklicka på det nya CustomVisionTrainer-skriptet för att öppna det med Visual Studio.

  3. Lägg till följande namnområden ovanför klassen CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Lägg sedan till följande variabler i klassen CustomVisionTrainer ovanför metoden Start().

    Kommentar

    Utbildnings-URL:en som används här finns i Custom Vision Training 1.2-dokumentationen och har en struktur på:https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Mer information finns i referens-API:et för Custom Vision Training v1.2.

    Varning

    Det är viktigt att du noterar slutpunkten som Custom Vision Service tillhandahåller för träningsläget, eftersom den JSON-struktur som används (i klassen CustomVisionObjects) har konfigurerats för att fungera med Custom Vision Training v1.2. Om du har en annan version kan du behöva uppdatera objektstrukturen .

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

    Viktigt!

    Se till att du lägger till ditt servicenyckelvärde (träningsnyckel) och projekt-ID-värde , som du antecknade tidigare. Det här är de värden som du samlade in från portalen tidigare i kursen (kapitel 2, steg 10 och senare).

  5. Lägg till följande metoder för Start() och Awake(). Dessa metoder anropas vid initiering och innehåller anropet för att konfigurera användargränssnittet:

        /// <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. Ta bort metoden Update(). Den här klassen behöver den inte.

  7. Lägg till metoden RequestTagSelection(). Den här metoden är den första som anropas när en bild har avbildats och lagrats på enheten och nu är redo att skickas till Custom Vision Service för att träna den. Den här metoden visar i träningsgränssnittet en uppsättning nyckelord som användaren kan använda för att tagga den bild som har avbildats. Den varnar också klassen VoiceRecognizer för att börja lyssna på användaren efter röstinmatning.

        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. Lägg till metoden VerifyTag(). Den här metoden tar emot röstindata som identifieras av klassen VoiceRecognizer och verifierar dess giltighet och påbörjar sedan träningsprocessen.

        /// <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. Lägg till metoden SubmitImageForTraining(). Den här metoden påbörjar Custom Vision Service-utbildningsprocessen. Det första steget är att hämta tagg-ID :t från tjänsten som är associerad med verifierade talindata från användaren. Tagg-ID :t laddas sedan upp tillsammans med bilden.

        /// <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. Lägg till metoden TrainCustomVisionProject(). När avbildningen har skickats och taggats anropas den här metoden. Den skapar en ny iteration som tränas med alla tidigare bilder som skickats till tjänsten plus avbildningen som just laddats upp. När träningen har slutförts anropar den här metoden en metod för att ange den nyligen skapade iterationen som standard, så att slutpunkten som du använder för analys är den senaste tränade iterationen.

        /// <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. Lägg till metoden SetDefaultIteration(). Den här metoden anger den tidigare skapade och tränade iterationen som Standard. När den här metoden har slutförts måste den ta bort den tidigare iterationen som finns i tjänsten. I skrivande stund finns det en gräns på högst tio (10) iterationer som tillåts finnas samtidigt i tjänsten.

        /// <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. Lägg till metoden DeletePreviousIteration(). Den här metoden hittar och tar bort den tidigare iterationen som inte är standard:

        /// <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. Den sista metoden som ska läggas till i den här klassen är metoden GetImageAsByteArray(), som används på webbanropen för att konvertera avbildningen som avbildats till en bytematris.

        /// <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. Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.

Kapitel 10 – Skapa klassen SceneOrganiser

Den här klassen kommer att:

  • Skapa ett markörobjekt som ska kopplas till huvudkameran.

  • Skapa ett etikettobjekt som visas när tjänsten identifierar verkliga objekt.

  • Konfigurera huvudkameran genom att koppla lämpliga komponenter till den.

  • När du är i analysläge skapar du etiketterna vid körning, i lämpligt världsutrymme i förhållande till huvudkamerans position och visar de data som tas emot från Custom Vision Service.

  • När du är i träningsläge skapar du användargränssnittet som visar de olika stegen i träningsprocessen.

Så här skapar du den här klassen:

  1. Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Ge skriptet namnet SceneOrganiser.

  2. Dubbelklicka på det nya SceneOrganiser-skriptet för att öppna det med Visual Studio.

  3. Du behöver bara ett namnområde och tar bort de andra från klassen SceneOrganiser ovan:

    using UnityEngine;
    
  4. Lägg sedan till följande variabler i klassen SceneOrganiser ovanför metoden 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. Ta bort metoderna Start() och Update().

  6. Precis under variablerna lägger du till metoden Awake(), som initierar klassen och konfigurerar scenen.

        /// <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. Lägg nu till metoden CreateCameraCursor() som skapar och placerar huvudkameramarkören och metoden CreateLabel(), som skapar analysis label-objektet .

        /// <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. Lägg till metoden SetCameraStatus(), som hanterar meddelanden som är avsedda för textnätet och som ger kamerans status.

        /// <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. Lägg till metoderna PlaceAnalysisLabel() och SetTagsToLastLabel(), som skapar och visar data från Custom Vision Service i scenen.

        /// <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. Slutligen lägger du till metoden CreateTrainingUI() som skapar användargränssnittet som visar de flera stegen i träningsprocessen när programmet är i träningsläge. Den här metoden används också för att skapa kamerastatusobjektet.

        /// <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. Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.

Viktigt!

Innan du fortsätter öppnar du klassen CustomVisionAnalyser och i metoden AnalyseLastImageCaptured() avkommentarer du följande rader:

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

Kapitel 11 – Skapa klassen ImageCapture

Nästa klass som du ska skapa är klassen ImageCapture .

Den här klassen ansvarar för:

  • Avbilda en bild med HoloLens-kameran och lagra den i appmappen.

  • Hantera tryckgester från användaren.

  • Underhålla uppräkningsvärdet som avgör om programmet ska köras i analysläge eller träningsläge.

Så här skapar du den här klassen:

  1. Gå till mappen Skript som du skapade tidigare.

  2. Högerklicka i mappen och klicka sedan på Skapa > C#-skript. Ge skriptet namnet ImageCapture.

  3. Dubbelklicka på det nya ImageCapture-skriptet för att öppna det med Visual Studio.

  4. Ersätt namnrymderna överst i filen med följande:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Lägg sedan till följande variabler i klassen ImageCapture ovanför metoden 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. Kod för metoderna Awake() och Start() måste nu läggas till:

        /// <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. Implementera en hanterare som anropas när en Tryckgest inträffar.

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

    Kommentar

    I analysläge fungerar metoden TapHandler som en växel för att starta eller stoppa bildtagningsloopen.

    I träningsläge avbildas en bild från kameran.

    När markören är grön betyder det att kameran är tillgänglig för att ta bilden.

    När markören är röd betyder det att kameran är upptagen.

  8. Lägg till den metod som programmet använder för att starta avbildningsprocessen och lagra avbildningen.

        /// <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. Lägg till de hanterare som anropas när fotot har avbildats och för när det är redo att analyseras. Resultatet skickas sedan till CustomVisionAnalyser eller CustomVisionTrainer beroende på vilket läge koden är inställd på.

        /// <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. Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.

  11. Nu när alla skript har slutförts går du tillbaka till Unity-redigeraren och klickar och drar klassen SceneOrganiser från mappen Skript till huvudkameraobjektet i hierarkipanelen.

Kapitel 12 - Innan du bygger

För att utföra ett grundligt test av ditt program måste du läsa in det separat på HoloLens.

Innan du gör det kontrollerar du att:

  • Alla inställningar som anges i kapitel 2 är korrekt inställda.

  • Alla fält i huvudkameran, Kontrollpanelen, tilldelas korrekt.

  • Skriptet SceneOrganiser är kopplat till main camera-objektet .

  • Se till att du infogar din förutsägelsenyckel i variabeln predictionKey .

  • Du har infogat förutsägelseslutpunkten i variabeln predictionEndpoint.

  • Du har infogat träningsnyckeln i variabeln trainingKey i klassen CustomVisionTrainer.

  • Du har infogat ditt Projekt-ID i projectId-variabeln för klassen CustomVisionTrainer.

Kapitel 13 – Skapa och läsa in ditt program separat

Så här påbörjar du byggprocessen :

  1. Gå till Inställningar för filbygge>.

  2. Markera Unity C#-projekt.

  3. Klicka på Skapa. Unity startar ett Istraživač datoteka fönster där du behöver skapa och väljer sedan en mapp som appen ska byggas in i. Skapa mappen nu och ge den namnet App. Klicka sedan på Välj mapp med mappen App markerad.

  4. Unity börjar skapa projektet i mappen App .

  5. När Unity har byggt klart (det kan ta lite tid) öppnas ett Istraživač datoteka fönster på platsen för bygget (kontrollera aktivitetsfältet eftersom det kanske inte alltid visas ovanför dina fönster, men meddelar dig om att ett nytt fönster har lagts till).

Så här distribuerar du på HoloLens:

  1. Du behöver IP-adressen för dina HoloLens (för fjärrdistribution) och för att säkerställa att HoloLens är i utvecklarläge. Så här gör du:

    1. Öppna inställningarna när du använder HoloLens.

    2. Gå till Avancerade alternativ för nätverks- och Internet-Wi-Fi>>

    3. Observera IPv4-adressen.

    4. Gå sedan tillbaka till Inställningar och sedan till Uppdatera och säkerhet>för utvecklare

    5. Ange utvecklarläge på.

  2. Gå till din nya Unity-version (appmappen) och öppna lösningsfilen med Visual Studio.

  3. I Lösningskonfiguration väljer du Felsök.

  4. I Lösningsplattformen väljer du x86, Fjärrdator. Du uppmanas att infoga IP-adressen för en fjärrenhet (HoloLens, i det här fallet, som du noterade).

    Ange IP-adress

  5. Gå till menyn Skapa och klicka på Distribuera lösning för att separat läsa in programmet till dina HoloLens.

  6. Din app bör nu visas i listan över installerade appar på HoloLens, redo att startas!

Kommentar

Om du vill distribuera till ett uppslukande headset ställer du in Lösningsplattformen på Lokal dator och ställer in konfigurationen Felsökning med x86 som plattform. Distribuera sedan till den lokala datorn med hjälp av menyalternativet Skapa och välj Distribuera lösning.

Så här använder du programmet:

Om du vill växla appfunktioner mellan träningsläge och förutsägelseläge måste du uppdatera AppMode-variabeln, som finns i metoden Awake() som finns i klassen ImageCapture.

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

eller

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

I träningsläge :

  • Titta på mus eller tangentbord och använd tryckgesten.

  • Därefter visas texten där du uppmanas att ange en tagg.

  • Säg antingen mus eller tangentbord.

I förutsägelseläge :

  • Titta på ett objekt och använd gesten Tryck.

  • Texten visas med det identifierade objektet med högst sannolikhet (detta normaliseras).

Kapitel 14 – Utvärdera och förbättra din Custom Vision-modell

För att göra tjänsten mer exakt måste du fortsätta att träna den modell som används för förutsägelse. Detta uppnås genom att använda ditt nya program, med både tränings- och förutsägelselägena , där det senare kräver att du besöker portalen, vilket är vad som beskrivs i det här kapitlet. Var beredd att gå tillbaka till portalen många gånger för att kontinuerligt förbättra din modell.

  1. Gå till Azure Custom Vision-portalen igen och när du är i projektet väljer du fliken Förutsägelser (längst upp på sidan):

    Fliken Välj förutsägelser

  2. Du ser alla avbildningar som skickades till tjänsten medan programmet kördes. Om du hovrar över bilderna ger de dig de förutsägelser som gjordes för den bilden:

    Lista över förutsägelsebilder

  3. Välj en av dina bilder för att öppna den. När den är öppen visas de förutsägelser som gjorts för den bilden till höger. Om förutsägelserna var korrekta och du vill lägga till den här avbildningen i tjänstens träningsmodell klickar du på indatarutan Mina taggar och väljer den tagg som du vill associera. När du är klar klickar du på knappen Spara och stäng längst ned till höger och fortsätter till nästa bild.

    Välj bild som ska öppnas

  4. När du är tillbaka i rutnätet med bilder kommer du att märka att de bilder som du har lagt till taggar i (och sparat) kommer att tas bort. Om du hittar några bilder som du tror inte har taggat objekt i dem kan du ta bort dem genom att klicka på bockmarkeringen på bilden (kan göra detta för flera bilder) och sedan klicka på Ta bort i det övre högra hörnet på rutnätssidan. I popup-fönstret som följer kan du antingen klicka på Ja, ta bort eller Nej för att bekräfta borttagningen eller avbryta den.

    Ta bort avbildningar

  5. När du är redo att fortsätta klickar du på den gröna knappen Träna längst upp till höger. Din tjänstmodell tränas med alla bilder som du nu har angett (vilket gör den mer exakt). När träningen är klar måste du klicka på knappen Gör standard en gång till så att din förutsägelse-URL fortsätter att använda den senaste iterationen av tjänsten.

    Starta träningstjänstmodellenVälj alternativet Gör som standard

Ditt färdiga Custom Vision API-program

Grattis, du har skapat en app för mixad verklighet som använder Azure Custom Vision-API:et för att identifiera verkliga objekt, träna tjänstmodellen och visa förtroende för vad som har setts.

Färdigt projektexempel

Bonusövningar

Övning 1

Träna Din Custom Vision Service att identifiera fler objekt.

Övning 2

Slutför följande övningar som ett sätt att utöka det du har lärt dig:

Spela upp ett ljud när ett objekt känns igen.

Övning 3

Använd API:et för att träna om tjänsten med samma bilder som din app analyserar, så att tjänsten blir mer exakt (gör både förutsägelse och träning samtidigt).