HoloLens (första generationen) och Azure 310: Objektidentifiering
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 känner igen anpassat visuellt innehåll och dess rumsliga position i en angivet bild med hjälp av azure Custom Vision-funktioner för objektidentifiering 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 och ungefärlig deras plats i den verkliga världen, enligt kamerainspelningen av Microsoft HoloLens eller en kameraanslutning till en dator för uppslukande (VR) headset.
Azure Custom Vision, Objektidentifiering är en Microsoft-tjänst 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 objekt i den nya bilden genom att ange Box Boundaries i själva bilden. Tjänsten är en enkel onlineportal som är enkel att använda för att effektivisera den här processen. Mer information finns på följande länkar:
När kursen är klar har du ett program för mixad verklighet som kan göra följande:
- Användaren kommer att kunna titta på ett objekt som de har tränat med hjälp av Azure Custom Vision Service, Object Detection.
- Användaren använder Tap-gesten för att avbilda en bild av vad de tittar på.
- Appen skickar avbildningen till Azure Custom Vision Service.
- Det kommer att finnas ett svar från tjänsten som visar resultatet av igenkänningen som text i världsrymden. Detta uppnås genom användning av Microsoft HoloLens spatial spårning, som ett sätt att förstå det identifierade objektets världsposition och sedan använda taggen som är associerad med det som identifieras i bilden, för att tillhandahålla etiketttexten.
Kursen går även igenom hur du laddar upp bilder manuellt, skapar taggar och tränar tjänsten att identifiera olika objekt (i det angivna exemplet en kopp) genom att ange gränsrutan i den bild som du skickar in.
Viktigt!
När appen har skapats och använts bör utvecklaren gå tillbaka till Azure Custom Vision Service och identifiera de förutsägelser som gjorts av tjänsten och avgöra om de var korrekta eller inte (genom att tagga något som tjänsten missade och justera avgränsningsrutorna). Tjänsten kan sedan tränas om, vilket ökar sannolikheten för att den identifierar verkliga objekt.
Den här kursen lär dig hur du hämtar resultaten från Azure Custom Vision Service, Object Detection, 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 310: Objektidentifiering | ✔️ |
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:
- En utvecklingsdator
- Windows 10 Fall Creators Update (eller senare) med utvecklarläge aktiverat
- Den senaste Windows 10 SDK
- Unity 2017.4 LTS
- Visual Studio 2017
- Ett Microsoft HoloLens med utvecklarläge aktiverat
- Internetåtkomst för Azure-konfiguration och hämtning av Custom Vision Service
- En serie med minst femton (15) bilder krävs för varje objekt som du vill att Custom Vision ska känna igen. Om du vill kan du använda de bilder som redan finns med i den här kursen, en serie koppar).
Innan du börjar
- 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).
- Konfigurera och testa dina HoloLens. Om du behöver stöd för detta kan du gå till installationsartikeln för HoloLens.
- 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-portalen
Om du vill använda Azure Custom Vision Service måste du konfigurera en instans av den som ska göras tillgänglig för ditt program.
Gå till huvudsidan för Custom Vision Service.
Klicka på Komma igång.
Logga in på Custom Vision-portalen.
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.
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 godkänner.
Efter att ha gått med på villkoren finns du nu i avsnittet Mina projekt . Klicka på Nytt projekt.
En flik visas till höger, vilket uppmanar dig att ange några fält för projektet.
Infoga ett namn för projektet
Infoga en beskrivning för projektet (valfritt)
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).
Kommentar
Om du vill läsa mer om Azure-resursgrupper går du till de associerade dokumenten
Ange Projekttyper som objektidentifiering (förhandsversion).
När du är klar klickar du på Skapa projekt så omdirigeras du till projektsidan för 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 femton (15) bilder för varje objekt som du vill att programmet ska känna igen. Du kan använda bilderna som medföljer den här kursen (en serie koppar).
Så här tränar du ditt Custom Vision-projekt:
Klicka på + knappen bredvid Taggar.
Lägg till ett namn för taggen som ska användas för att associera dina bilder med. I det här exemplet använder vi bilder av koppar för igenkänning, så har namngett taggen för den här cupen. Klicka på Spara när det är klart.
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å Lägg till bilder i mitten av sidan.
Klicka på Bläddra bland lokala filer och bläddra till de bilder som du vill ladda upp för ett objekt, med minst femton (15).
Dricks
Du kan välja flera bilder i taget för att ladda upp.
Tryck på Ladda upp filer när du har valt alla bilder som du vill träna projektet med. Filerna börjar laddas upp. När du har bekräftat uppladdningen klickar du på Klar.
Nu laddas dina bilder upp, men taggas inte.
Om du vill tagga dina bilder använder du musen. När du hovra över bilden hjälper en markeringsmarkering dig genom att automatiskt rita en markering runt objektet. Om det inte är korrekt kan du rita dina egna. Detta uppnås genom att hålla vänsterklicka på musen och dra markeringsregionen för att omfatta objektet.
När du har valt objektet i bilden uppmanas du att lägga till regiontagg i en liten fråga. Välj taggen som du skapade tidigare ('Cup', i exemplet ovan) eller om du lägger till fler taggar skriver du in den och klickar på knappen + (plus).
Om du vill tagga nästa bild kan du klicka på pilen till höger om bladet eller stänga taggbladet (genom att klicka på X i det övre högra hörnet på bladet) och sedan klicka på nästa bild. När du har nästa bild klar upprepar du samma procedur. Gör detta för alla bilder som du har laddat upp tills alla är taggade.
Kommentar
Du kan välja flera objekt i samma bild, till exempel bilden nedan:
När du har taggat dem alla klickar du på den taggade knappen till vänster på skärmen för att visa de taggade bilderna.
Nu är du redo att träna din tjänst. Klicka på knappen Träna så börjar den första tränings-iterationen.
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.
Kommentar
Slutpunkten som tillhandahålls från 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.
När du har klickat på Förutsägelse-URL öppnar du Anteckningar och kopierar och klistrar in URL:en (kallas även din förutsägelseslutpunkt) och tjänstens förutsägelsenyckel, så att du kan hämta den när du behöver den senare i koden.
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.
Öppna Unity och klicka på Nytt.
Nu måste du ange ett Unity-projektnamn. Infoga CustomVisionObjDetection. Kontrollera att projekttypen är inställd på 3D och ange platsen till någonstans som passar dig (kom ihåg att närmare rotkataloger är bättre). Klicka sedan på Skapa projekt.
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. Stäng fönstret Inställningar .
Gå sedan till Inställningar för filbygge > och växla plattform till Universell Windows-plattform och klicka sedan på knappen Växla plattform.
I samma fönster för bygginställningar kontrollerar du att följande är inställda:
Målenheten är inställd på HoloLens
Byggtyp är inställd på D3D
SDK är inställt på Senaste installerat
Visual Studio-versionen är inställd på Senaste installerad
Build and Run är inställt på Lokal dator
De återstående inställningarna i Bygginställningar bör vara kvar som standard för tillfället.
I samma fönster för bygginställningar klickar du på knappen Spelarinställningar. Då öppnas den relaterade panelen i det utrymme där kontrollanten finns.
I den här panelen måste några inställningar verifieras:
På fliken Andra inställningar :
Skriptkörningsversionen bör vara experimentell (.NET 4.6-motsvarighet), vilket utlöser ett behov av att starta om redigeraren.
Skriptserverdelen ska vara .NET.
API-kompatibilitetsnivån ska vara .NET 4.6.
På fliken Publiceringsinställningar går du till Funktioner och kontrollerar:
InternetClient
Webbkamera
SpatialPerception
Längre ned på panelen, i XR-inställningar (som finns under Publiceringsinställningar), markerar du Virtual Reality Supported (Virtual Reality Supported) och kontrollerar sedan att Windows Mixed Reality SDK har lagts till.
I Bygginställningar är Unity C# Projects inte längre nedtonat: markera kryssrutan bredvid detta.
Stäng fönstret Build Settings (Bygginställningar).
I redigeraren klickar du på Redigera>projektinställningar>Grafik.
I Kontrollpanelen är grafikinställningarna öppna. Rulla nedåt tills du ser en matris med namnet Always Include Shaders. Lägg till ett fack genom att öka variabeln Storlek med en (i det här exemplet var det 8 så vi gjorde det till 9). Ett nytt fack visas i matrisens sista position enligt nedan:
I facket klickar du på den lilla målcirkeln bredvid facket för att öppna en lista över skuggningar. Leta efter den äldre skuggningen /transparent/diffus skuggning och dubbelklicka på den.
Kapitel 4 – Importera CustomVisionObjDetection Unity-paketet
För den här kursen får du ett Unity Asset Package med namnet Azure-MR-310.unitypackage.
[TIPS] Alla objekt som stöds av Unity, inklusive hela scener, kan paketeras i en .unitypackage-fil och exporteras/importeras i andra projekt. Det är det säkraste och mest effektiva sättet att flytta tillgångar mellan olika Unity-projekt.
Du hittar det Azure-MR-310-paket som du behöver ladda ned här.
Med Unity-instrumentpanelen framför dig klickar du på Tillgångar i menyn överst på skärmen och klickar sedan på Importera paket > anpassat paket.
Använd filväljaren för att välja paketet Azure-MR-310.unitypackage och klicka på Öppna. En lista över komponenter för den här tillgången visas för dig. Bekräfta importen genom att klicka på knappen Importera .
När importen är klar ser du att mappar från paketet nu har lagts till i mappen Tillgångar . Den här typen av mappstruktur är typisk för ett Unity-projekt.
Mappen Material innehåller det material som används av blickmarkören.
Mappen Plugins innehåller newtonsoft DLL som används av koden för att deserialisera tjänstens webbsvar. De två (2) olika versionerna som finns i mappen och undermappen är nödvändiga för att biblioteket ska kunna användas och byggas av både Unity-redigeraren och UWP-versionen.
Prefabs-mappen innehåller de prefabs som finns i scenen. Dessa är:
- GazeCursor, markören som används i programmet. Kommer att arbeta tillsammans med SpatialMapping-prefab för att kunna placeras i scenen ovanpå fysiska objekt.
- Etiketten, som är det användargränssnittsobjekt som används för att visa objekttaggen i scenen vid behov.
- SpatialMapping, som är det objekt som gör att programmet kan använda för att skapa en virtuell karta, med hjälp av Microsoft HoloLens rumsliga spårning.
Mappen Scener som för närvarande innehåller den färdiga scenen för den här kursen.
Öppna mappen Scener i projektpanelen och dubbelklicka på ObjDetectionScene för att läsa in scenen som du ska använda för den här kursen.
Kommentar
Ingen kod ingår, du kommer att skriva koden genom att följa den här kursen.
Kapitel 5 – 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 SDK som också kan användas för att göra anrop till tjänsten. Mer information finns i artikeln om Custom Vision 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:
Högerklicka i tillgångsmappen i projektpanelen och klicka sedan på Skapa>mapp. Anropa mappen Skript.
Dubbelklicka på den nyligen skapade mappen för att öppna den.
Högerklicka i mappen och klicka sedan på Skapa>C#-skript. Ge skriptet namnet CustomVisionAnalyser.
Dubbelklicka på det nya CustomVisionAnalyser-skriptet för att öppna det med Visual Studio.
Kontrollera att du har följande namnområden som refereras längst upp i filen:
using Newtonsoft.Json; using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking;
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> /// Bite array of the image to submit for analysis /// </summary> [HideInInspector] public byte[] imageBytes;
Kommentar
Se till att du infogar din tjänstförutsägelsenyckel i variabeln predictionKey och förutsägelseslutpunkten i variabeln predictionEndpoint. Du kopierade dessa till Anteckningar tidigare, i kapitel 2, steg 14.
Kod för Awake() måste nu läggas till för att initiera instansvariabeln:
/// <summary> /// Initializes this class /// </summary> private void Awake() { // Allows this instance to behave like a singleton Instance = this; }
Lägg till coroutine -metoden (med den statiska metoden GetImageAsByteArray() under den), som hämtar resultatet av analysen av bilden, 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) { 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); }
Ta bort metoderna Start() och Update() eftersom de inte kommer att användas.
Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.
Viktigt!
Som tidigare nämnts bör du inte oroa dig 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 6 – 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.
Så här skapar du den här klassen:
Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Anropa skriptet CustomVisionObjects.
Dubbelklicka på det nya CustomVisionObjects-skriptet för att öppna det med Visual Studio.
Kontrollera att du har följande namnområden som refereras längst upp i filen:
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking;
Ta bort metoderna Start() och Update() i klassen CustomVisionObjects. Den här klassen bör nu vara tom.
Varning
Det är viktigt att du följer nästa instruktion noggrant. Om du placerar de nya klassdeklarationerna i klassen CustomVisionObjects får du kompileringsfel i kapitel 10 som anger att AnalysisRootObject och BoundingBox inte hittas.
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 /// 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; } }
Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.
Kapitel 7 – Skapa klassen SpatialMapping
Den här klassen anger Spatial Mapping Collider i scenen så att du kan identifiera kollisioner mellan virtuella objekt och verkliga objekt.
Så här skapar du den här klassen:
Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Anropa skriptet SpatialMapping.
Dubbelklicka på det nya SpatialMapping-skriptet för att öppna det med Visual Studio.
Kontrollera att du har följande namnområden som refereras ovanför klassen SpatialMapping :
using UnityEngine; using UnityEngine.XR.WSA;
Lägg sedan till följande variabler i klassen SpatialMapping ovanför metoden 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;
Lägg till Awake () och 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); }
Ta bort metoden Update().
Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.
Kapitel 8 – Skapa klassen GazeCursor
Den här klassen ansvarar för att konfigurera markören på rätt plats i det verkliga utrymmet genom att använda SpatialMappingCollider, som skapades i föregående kapitel.
Så här skapar du den här klassen:
Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Anropa skriptet GazeCursor
Dubbelklicka på det nya GazeCursor-skriptet för att öppna det med Visual Studio.
Kontrollera att du har följande namnområde som refereras ovanför klassen GazeCursor :
using UnityEngine;
Lägg sedan till följande variabel i klassen GazeCursor ovanför metoden Start().
/// <summary> /// The cursor (this object) mesh renderer /// </summary> private MeshRenderer meshRenderer;
Uppdatera metoden Start() med följande kod:
/// <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); }
Uppdatera metoden Update() med följande kod:
/// <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; } }
Kommentar
Oroa dig inte för att felet för klassen SceneOrganiser inte hittas. Du skapar det i nästa kapitel.
Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.
Kapitel 9 – Skapa klassen SceneOrganiser
Den här klassen kommer att:
Konfigurera huvudkameran genom att koppla lämpliga komponenter till den.
När ett objekt identifieras ansvarar det för att beräkna sin position i den verkliga världen och placera en taggetikett nära det med lämpligt taggnamn.
Så här skapar du den här klassen:
Högerklicka i mappen Skript och klicka sedan på Skapa>C#-skript. Ge skriptet namnet SceneOrganiser.
Dubbelklicka på det nya SceneOrganiser-skriptet för att öppna det med Visual Studio.
Kontrollera att du har följande namnområden som refereras ovanför klassen SceneOrganiser :
using System.Collections.Generic; using System.Linq; using UnityEngine;
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 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;
Ta bort metoderna Start() och Update().
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 CustomVisionObjects class to this Gameobject gameObject.AddComponent<CustomVisionObjects>(); }
Lägg till metoden PlaceAnalysisLabel(), som instansierar etiketten i scenen (som i det här läget är osynlig för användaren). Den placerar också fyrhjulingen (också osynlig) där bilden placeras och överlappar den verkliga världen. Detta är viktigt eftersom de lådkoordinater som hämtats från tjänsten efter analysen spåras tillbaka till den här fyrhjulingen för att fastställa den ungefärliga platsen för objektet i den verkliga världen.
/// <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; }
Lägg till metoden FinaliseLabel(). Den ansvarar för:
- Ange etiketttexten med taggen förutsägelse med högsta konfidens.
- Anropa beräkningen av avgränsningsrutan på quad-objektet, som placerats tidigare och placera etiketten i scenen.
- Justera etikettdjupet med hjälp av en Raycast mot avgränsningslådan, som ska kollidera mot objektet i den verkliga världen.
- Återställa avbildningsprocessen så att användaren kan avbilda en annan bild.
/// <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(); }
Lägg till metoden CalculateBoundingBoxPosition(), som är värd för ett antal beräkningar som krävs för att översätta avgränsningsrutans koordinater som hämtats från tjänsten och återskapa dem proportionellt på quaden.
/// <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); }
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:
// 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);
Kommentar
Oroa dig inte för imagecapture-klassen "det gick inte att hitta" meddelandet, du skapar det i nästa kapitel.
Kapitel 10 – 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 mappen App .
- Hantera tryckgester från användaren.
Så här skapar du den här klassen:
Gå till mappen Skript som du skapade tidigare.
Högerklicka i mappen och klicka sedan på Skapa>C#-skript. Ge skriptet namnet ImageCapture.
Dubbelklicka på det nya ImageCapture-skriptet för att öppna det med Visual Studio.
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;
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> /// Flagging if the capture loop is running /// </summary> internal bool captureIsActive; /// <summary> /// File path of current analysed photo /// </summary> internal string filePath = string.Empty;
Kod för metoderna Awake() och Start() måste nu läggas till:
/// <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(); }
Implementera en hanterare som anropas när en Tryckgest inträffar:
/// <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); } }
Viktigt!
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.
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() { // 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); }); }); }
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 för analys.
/// <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(); }
Se till att spara ändringarna i Visual Studio innan du återvänder till Unity.
Kapitel 11 – Konfigurera skripten i scenen
Nu när du har skrivit all kod som behövs för det här projektet är det dags att konfigurera skripten i scenen och på prefaberna så att de fungerar korrekt.
I Unity-redigeraren går du till hierarkipanelen och väljer huvudkameran.
I Kontrollpanelen, med huvudkameran markerad, klickar du på Lägg till komponent och söker sedan efter SceneOrganiser-skript och dubbelklickar för att lägga till det.
I projektpanelen öppnar du mappen Prefabs, drar prefab för etikett till området Etikett tom referensmålinmatning i skriptet SceneOrganiser som du just har lagt till i huvudkameran, som du ser i bilden nedan:
I hierarkipanelen väljer du GazeCursor-underordnad till huvudkameran.
I Kontrollpanelen, med GazeCursor markerat, klickar du på Lägg till komponent och söker sedan efter GazeCursor-skript och dubbelklickar för att lägga till det.
I hierarkipanelen väljer du spatialmapping-underordnad till huvudkameran.
I Kontrollpanelen, med SpatialMapping markerat, klickar du på Lägg till komponent, söker sedan efter SpatialMapping-skript och dubbelklickar för att lägga till det.
Återstående skript som du inte har angett läggs till av koden i SceneOrganiser-skriptet under körningen.
Kapitel 12 - Innan du bygger
För att kunna utföra ett grundligt test av ditt program måste du läsa in det separat på Microsoft HoloLens.
Innan du gör det kontrollerar du att:
Alla inställningar som anges i kapitel 3 är korrekt inställda.
Skriptet SceneOrganiser är kopplat till main camera-objektet .
Skriptet GazeCursor är kopplat till GazeCursor-objektet .
Skriptet SpatialMapping är kopplat till SpatialMapping-objektet .
I kapitel 5, steg 6:
- Se till att du infogar din tjänstförutsägelsenyckel i variabeln predictionKey .
- Du har infogat förutsägelseslutpunkten i klassen predictionEndpoint.
Kapitel 13 – Skapa UWP-lösningen och läs in programmet separat
Nu är du redo att skapa ditt program som en UWP-lösning som du kommer att kunna distribuera till Microsoft HoloLens. Så här påbörjar du byggprocessen:
Gå till Inställningar för filbygge>.
Markera Unity C#-projekt.
Klicka på Lägg till öppna scener. Då läggs den öppna scenen till i bygget.
Klicka på Skapa. Unity startar ett Utforskaren 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 appmappen markerad.
Unity börjar skapa projektet i mappen App .
När Unity har skapats (det kan ta lite tid) öppnas ett Utforskaren 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).
För att distribuera vidare till Microsoft HoloLens behöver du IP-adressen för den enheten (för fjärrdistribution) och för att säkerställa att den även har konfigurerat utvecklarläge . Så här gör du:
Öppna inställningarna när du använder HoloLens.
Gå till Avancerade alternativ för nätverks- och Internet-Wi-Fi>>
Observera IPv4-adressen.
Gå sedan tillbaka till Inställningar och sedan till Uppdatera och säkerhet>för utvecklare
Ange utvecklarläge på.
Gå till din nya Unity-version (appmappen) och öppna lösningsfilen med Visual Studio.
I Lösningskonfiguration väljer du Felsök.
I Lösningsplattformen väljer du x86, Fjärrdator. Du uppmanas att infoga IP-adressen för en fjärrenhet (Microsoft HoloLens, i det här fallet som du noterade).
Gå till menyn Skapa och klicka på Distribuera lösning för att separat läsa in programmet till dina HoloLens.
Din app bör nu visas i listan över installerade appar på Microsoft HoloLens, redo att startas!
Så här använder du programmet:
- Titta på ett objekt som du har tränat med Azure Custom Vision Service, Objektidentifiering och använd gesten Tryck.
- Om objektet har identifierats visas en etiketttext i världsrymden med taggnamnet.
Viktigt!
Varje gång du tar ett foto och skickar det till tjänsten kan du gå tillbaka till sidan Tjänst och träna om tjänsten med de nyligen insamlade bilderna. I början måste du förmodligen också korrigera avgränsningsrutorna för att vara mer exakta och träna om tjänsten.
Kommentar
Etiketttexten som placeras kanske inte visas i närheten av objektet när Microsoft HoloLens-sensorerna och/eller SpatialTrackingComponent i Unity inte kan placera lämpliga kolliderare i förhållande till verkliga objekt. Försök att använda programmet på en annan yta om så är fallet.
Ditt Custom Vision-program för objektidentifiering
Grattis, du har skapat en mixed reality-app som använder Azure Custom Vision, API för objektidentifiering, som kan identifiera ett objekt från en bild och sedan ge objektet en ungefärlig position i 3D-utrymme.
Bonusövningar
Övning 1
Lägg till i textetiketten och använd en halvtransparent kub för att omsluta det verkliga objektet i en 3D-avgränsningsruta.
Övning 2
Träna Din Custom Vision Service att identifiera fler objekt.
Övning 3
Spela upp ett ljud när ett objekt känns igen.
Övning 4
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).