Teilen über


HoloLens (1. Generation) und Azure 310: Objekterkennung

Hinweis

Die Tutorials der Mixed Reality Academy wurden unter Berücksichtigung von HoloLens (1. Generation) und Mixed Reality Immersive Headsets entwickelt. Daher ist es uns wichtig, diese Tutorials für Entwickler zu belassen, die noch nach Anleitungen bei der Entwicklung für diese Geräte suchen. Diese Tutorials werden nicht mit den neuesten Toolsets oder Interaktionen aktualisiert, die für HoloLens 2 verwendet werden. Sie werden beibehalten, um weiterhin auf den unterstützten Geräten zu arbeiten. Es wird eine neue Reihe von Tutorials geben, die in Zukunft veröffentlicht werden, um zu veranschaulichen, wie für HoloLens 2 entwickelt werden kann. Dieser Hinweis wird mit einem Link zu diesen Tutorials aktualisiert, sobald sie veröffentlicht werden.


In diesem Kurs erfahren Sie, wie Sie benutzerdefinierte visuelle Inhalte und deren räumliche Position innerhalb eines bereitgestellten Bilds mithilfe von Azure Custom Vision "Objekterkennung"-Funktionen in einer Mixed Reality-Anwendung erkennen.

Mit diesem Dienst können Sie ein Machine Learning-Modell mithilfe von Objektbildern trainieren. Anschließend verwenden Sie das trainierte Modell, um ähnliche Objekte zu erkennen und deren Position in der realen Welt zu nähern, wie dies durch die Kameraaufnahme von Microsoft HoloLens oder einer Kamera bereitgestellt wird, die mit einem PC für immersive Headsets (VR) verbunden ist.

Kursergebnis

Die Objekterkennung in Azure Custom Vision ist ein Microsoft-Dienst, mit dem Entwickler benutzerdefinierte Bildklassifizierer erstellen können. Diese Klassifizierer können dann mit neuen Bildern verwendet werden, um Objekte innerhalb dieses neuen Bilds zu erkennen, indem Box-Grenzen innerhalb des Bilds selbst bereitgestellt werden. Der Dienst bietet ein einfaches, benutzerfreundliches Onlineportal, um diesen Prozess zu optimieren. Weitere Informationen finden Sie unter den folgenden Links:

Nach Abschluss dieses Kurses verfügen Sie über eine Mixed Reality-Anwendung, die folgende Aufgaben ausführen kann:

  1. Der Benutzer kann ein Objekt anvisieren, das er mithilfe des Azure Custom Vision-Diensts , Objekterkennung trainiert hat.
  2. Der Benutzer verwendet die Tippen-Geste , um ein Bild davon zu erfassen, was er gerade betrachtet.
  3. Die App sendet das Image an den Azure Custom Vision Service.
  4. Es wird eine Antwort vom Dienst angezeigt, die das Ergebnis der Erkennung als Weltraumtext anzeigt. Dies wird erreicht, indem die räumliche Nachverfolgung des Microsoft HoloLens verwendet wird, um die Weltposition des erkannten Objekts zu verstehen, und dann das Tag zu verwenden, das dem, was im Bild erkannt wird, zugeordnet ist, um den Bezeichnungstext bereitzustellen.

Der Kurs behandelt auch das manuelle Hochladen von Bildern, das Erstellen von Tags und das Trainieren des Diensts zum Erkennen verschiedener Objekte (im bereitgestellten Beispiel eine Tasse), indem das Begrenzungsfeld innerhalb des von Ihnen übermittelten Bilds festgelegt wird.

Wichtig

Nach der Erstellung und Verwendung der App sollte der Entwickler zurück zum Azure-Custom Vision-Dienst navigieren und die vom Dienst getroffenen Vorhersagen identifizieren und ermitteln, ob sie korrekt waren oder nicht (durch Taggen von Elementen, die der Dienst verpasst hat, und die Begrenzungsrahmen anpassen). Der Dienst kann dann neu trainiert werden, was die Wahrscheinlichkeit erhöht, dass er reale Objekte erkennt.

In diesem Kurs erfahren Sie, wie Sie die Ergebnisse des Azure Custom Vision-Diensts Objekterkennung in eine Unity-basierte Beispielanwendung abrufen. Es ist an Ihnen, diese Konzepte auf eine benutzerdefinierte Anwendung anzuwenden, die Sie möglicherweise erstellen.

Geräteunterstützung

Kurs HoloLens Immersive Headsets
MR und Azure 310: Objekterkennung ✔️

Voraussetzungen

Hinweis

Dieses Tutorial richtet sich an Entwickler, die über grundlegende Erfahrungen mit Unity und C# verfügen. Bitte beachten Sie auch, dass die Voraussetzungen und schriftlichen Anweisungen in diesem Dokument entsprechen, was zum Zeitpunkt des Schreibens (Juli 2018) getestet und überprüft wurde. Sie können die neueste Software verwenden, die im Artikel Installieren der Tools aufgeführt ist, obwohl nicht davon ausgegangen werden sollte, dass die Informationen in diesem Kurs perfekt dem entsprechen, was Sie in neuerer Software finden, als die unten aufgeführten.

Wir empfehlen die folgende Hardware und Software für diesen Kurs:

Vorbereitende Schritte

  1. Um Probleme beim Erstellen dieses Projekts zu vermeiden, wird dringend empfohlen, das in diesem Tutorial erwähnte Projekt in einem Stammordner oder in der Nähe des Stammordners zu erstellen (lange Ordnerpfade können zur Buildzeit Zu Problemen führen).
  2. Richten Sie Ihre HoloLens ein und testen Sie sie. Wenn Sie Unterstützung dafür benötigen, besuchen Sie den Artikel HoloLens-Setup.
  3. Es ist eine gute Idee, Kalibrierung und Sensoroptimierung durchzuführen, wenn Sie mit der Entwicklung einer neuen HoloLens-App beginnen (manchmal kann es hilfreich sein, diese Aufgaben für jeden Benutzer auszuführen).

Hilfe zur Kalibrierung finden Sie unter diesem Link zum Artikel HoloLens-Kalibrierung.

Hilfe zur Sensoroptimierung finden Sie unter diesem Link zum Artikel HoloLens Sensor Tuning.

Kapitel 1: Das Custom Vision-Portal

Um azure Custom Vision Service verwenden zu können, müssen Sie einen instance konfigurieren, der für Ihre Anwendung verfügbar gemacht wird.

  1. Navigieren Sie zur Seite Custom Vision Service Standard.

  2. Klicken Sie auf Erste Schritte.

    Screenshot: Hervorgehobene Schaltfläche

  3. Melden Sie sich beim Custom Vision-Portal an.

    Screenshot: Schaltfläche

  4. Wenn Sie noch nicht über ein Azure-Konto verfügen, müssen Sie eines erstellen. Wenn Sie dieses Tutorial in einer Unterrichts- oder Lab-Situation durcharbeiten, bitten Sie Ihren Kursleiter oder einen der Betreuer um Hilfe beim Einrichten Ihres neuen Kontos.

  5. Sobald Sie sich zum ersten Mal angemeldet haben, werden Sie zur Eingabe des Bereichs "Nutzungsbedingungen " aufgefordert. Aktivieren Sie das Kontrollkästchen, um den Bedingungen zuzustimmen. Klicken Sie dann auf Ich stimme zu.

    Screenshot: Bereich

  6. Nachdem Sie den Bedingungen zugestimmt haben, befinden Sie sich jetzt im Abschnitt Meine Projekte . Klicken Sie auf Neues Projekt.

    Screenshot, der zeigt, wo Sie Neues Projekt auswählen.

  7. Auf der rechten Seite wird eine Registerkarte angezeigt, in der Sie aufgefordert werden, einige Felder für das Projekt anzugeben.

    1. Einfügen eines Namens für Ihr Projekt

    2. Einfügen einer Beschreibung für Ihr Projekt (optional)

    3. Wählen Sie eine Ressourcengruppe aus, oder erstellen Sie eine neue. Eine Ressourcengruppe bietet eine Möglichkeit zum Überwachen, Steuern des Zugriffs, Bereitstellen und Verwalten der Abrechnung für eine Sammlung von Azure-Ressourcen. Es wird empfohlen, alle Azure-Dienste, die einem einzelnen Projekt zugeordnet sind (z. B. diese Kurse), in einer gemeinsamen Ressourcengruppe zu speichern.

      Screenshot, der zeigt, wo Details für das neue Projekt hinzugefügt werden.

    4. Legen Sie die Projekttypen auf Objekterkennung (Vorschau) fest.

  8. Wenn Sie fertig sind, klicken Sie auf Projekt erstellen, und Sie werden zur Seite Custom Vision Service-Projekt umgeleitet.

Kapitel 2 : Schulung Ihres Custom Vision Projekts

Sobald Sie sich im Custom Vision Portal befinden, besteht Ihr primäres Ziel darin, Ihr Projekt so zu trainieren, dass bestimmte Objekte in Bildern erkannt werden.

Sie benötigen mindestens fünfzehn (15) Bilder für jedes Objekt, das ihre Anwendung erkennen soll. Sie können die in diesem Kurs bereitgestellten Bilder (eine Reihe von Tassen) verwenden.

So trainieren Sie Ihr Custom Vision-Projekt:

  1. Klicken Sie auf die + Schaltfläche neben Tags.

    Screenshot: Schaltfläche

  2. Fügen Sie einen Namen für das Tag hinzu, mit dem Ihre Bilder zugeordnet werden. In diesem Beispiel verwenden wir Bilder von Tassen zur Erkennung. Daher haben wir das Tag dafür , Cup genannt. Klicken Sie auf Speichern, wenn Sie fertig sind.

    Screenshot: Hinzufügen eines Namens für das Tag

  3. Sie werden feststellen, dass Ihr Tag hinzugefügt wurde (Möglicherweise müssen Sie Ihre Seite neu laden, damit sie angezeigt wird).

    Screenshot, der zeigt, wo Ihr Tag hinzugefügt wird.

  4. Klicken Sie in der Mitte der Seite auf Bilder hinzufügen .

    Screenshot, der zeigt, wo Bilder hinzugefügt werden.

  5. Klicken Sie auf Lokale Dateien durchsuchen, und navigieren Sie zu den Bildern, die Sie für ein Objekt hochladen möchten, wobei mindestens fünfzehn (15) sind.

    Tipp

    Sie können mehrere Bilder gleichzeitig auswählen, um sie hochzuladen.

    Screenshot, der die Bilder zeigt, die Sie hochladen können.

  6. Klicken Sie auf Dateien hochladen , sobald Sie alle Bilder ausgewählt haben, mit denen Sie das Projekt trainieren möchten. Die Dateien werden hochgeladen. Nachdem Sie die Bestätigung des Uploads erhalten haben, klicken Sie auf Fertig.

    Screenshot, der den Fortschritt der hochgeladenen Bilder zeigt.

  7. An diesem Punkt werden Ihre Bilder hochgeladen, aber nicht markiert.

    Screenshot: Bild ohne Tags

  8. Verwenden Sie die Maus, um Ihre Bilder zu markieren. Wenn Sie mit dem Mauszeiger auf Ihr Bild zeigen, hilft Ihnen eine Auswahlmarkierungen, indem Sie automatisch eine Auswahl um Ihr Objekt zeichnen. Wenn es nicht genau ist, können Sie ihre eigenen zeichnen. Dies wird erreicht, indem Sie mit der linken Maustaste klicken und den Auswahlbereich ziehen, um Das Objekt einzuschließen.

    Screenshot: Markieren eines Bilds

  9. Nach der Auswahl ihres Objekts im Bild werden Sie in einer kleinen Eingabeaufforderung aufgefordert, Region-Tag hinzuzufügen. Wählen Sie Ihr zuvor erstelltes Tag aus ("Cup", im obigen Beispiel), oder wenn Sie weitere Tags hinzufügen, geben Sie dieses ein, und klicken Sie auf die Schaltfläche + (Plus).

    Screenshot: Tag, das Sie dem Bild hinzugefügt haben

  10. Um das nächste Bild zu markieren, können Sie auf den Pfeil rechts neben dem Blatt klicken oder das Tagblatt schließen (indem Sie auf das X in der oberen rechten Ecke des Blatts klicken) und dann auf das nächste Bild klicken. Sobald Sie das nächste Image bereit haben, wiederholen Sie denselben Vorgang. Führen Sie dies für alle bilder aus, die Sie hochgeladen haben, bis sie alle markiert sind.

    Hinweis

    Sie können mehrere Objekte in demselben Bild auswählen, wie in der folgenden Abbildung:

    Screenshot, der mehrere Objekte in einem Bild zeigt.

  11. Nachdem Sie alle markiert haben, klicken Sie auf die markierte Schaltfläche auf der linken Seite des Bildschirms, um die markierten Bilder anzuzeigen.

    Screenshot, in dem die Schaltfläche

  12. Sie sind jetzt bereit, Ihren Dienst zu trainieren. Klicken Sie auf die Schaltfläche Trainieren , und die erste Trainingsiteration beginnt.

    Screenshot: Hervorgehobene Schaltfläche

    Screenshot: Erste Trainingsiteration

  13. Nachdem sie erstellt wurde, werden zwei Schaltflächen namens Standard festlegen und Vorhersage-URL angezeigt. Klicken Sie zuerst auf Standard festlegen , und klicken Sie dann auf Vorhersage-URL.

    Screenshot, der die Schaltfläche

    Hinweis

    Der Endpunkt, der von diesem bereitgestellt wird, wird auf den wertfestgelegten Iterations-Wert festgelegt, der als Standard markiert wurde. Wenn Sie daher später eine neue Iteration vornehmen und diese als Standard aktualisieren, müssen Sie Ihren Code nicht ändern.

  14. Nachdem Sie auf Vorhersage-URL geklickt haben, öffnen Sie editor, und kopieren Sie die URL (auch Als Ihr Prediction-Endpoint bezeichnet) und den Dienstvorhersageschlüssel, damit Sie ihn abrufen können, wenn Sie ihn später im Code benötigen.

    Screenshot: Vorhersageendpunkt und Präditionsschlüssel

Kapitel 3: Einrichten des Unity-Projekts

Es folgt ein typisches Setup für die Entwicklung mit Mixed Reality und ist daher eine gute Vorlage für andere Projekte.

  1. Öffnen Sie Unity , und klicken Sie auf Neu.

    Screenshot, in dem die Schaltfläche

  2. Sie müssen nun einen Unity-Projektnamen angeben. Einfügen von CustomVisionObjDetection. Stellen Sie sicher, dass der Projekttyp auf 3D festgelegt ist, und legen Sie den Speicherort auf einen für Sie geeigneten Speicherort fest (denken Sie daran, dass näher an Stammverzeichnissen besser ist). Klicken Sie dann auf Projekt erstellen.

    Screenshot, der die Projektdetails zeigt und wo Sie Projekt erstellen auswählen.

  3. Wenn Unity geöffnet ist, empfiehlt es sich, die Standardskript Editor auf Visual Studio festgelegt ist, zu überprüfen. Wechseln Sie zu Einstellungen bearbeiten>, und navigieren Sie dann im neuen Fenster zu Externe Tools. Ändern Sie external script Editor in Visual Studio. Schließen Sie das Fenster Einstellungen .

    Screenshot: Ändern der Editor für externe Skripts in Visual Studio

  4. Wechseln Sie als Nächstes zu Dateibuildeinstellungen>, ändern Sie die Plattform auf Universelle Windows-Plattform, und klicken Sie dann auf die Schaltfläche Plattform wechseln.

    Screenshot: Hervorgehobene Schaltfläche

  5. Stellen Sie sicher, dass im selben Fenster build settings (Buildeinstellungen ) Folgendes festgelegt ist:

    1. Zielgerät ist auf HoloLens festgelegt

    2. Buildtyp ist auf D3D festgelegt.

    3. SDK ist auf Latest installed (Neueste Installation) festgelegt.

    4. Visual Studio-Version ist auf Zuletzt installiert festgelegt.

    5. Build und Ausführen ist auf Lokaler Computer festgelegt.

    6. Die restlichen Einstellungen in Buildeinstellungen sollten vorerst als Standard beibehalten werden.

      Screenshot: Konfigurationsoptionen für die Buildeinstellung

  6. Klicken Sie im gleichen Fenster build settings (Buildeinstellungen ) auf die Schaltfläche Player Settings (Playereinstellungen ). Dadurch wird der zugehörige Bereich in dem Bereich geöffnet, in dem sich der Inspektor befindet.

  7. In diesem Bereich müssen einige Einstellungen überprüft werden:

    1. Auf der Registerkarte Andere Einstellungen :

      1. Die Skriptruntimeversion sollte experimentell (.NET 4.6-Entsprechung) sein, wodurch ein Neustart des Editor ausgelöst wird.

      2. Das Skript-Back-End sollte .NET sein.

      3. Der API-Kompatibilitätsgrad sollte .NET 4.6 sein.

        Screenshot: Option

    2. Aktivieren Sie auf der Registerkarte Veröffentlichungseinstellungen unter Funktionen Folgendes:

      1. InternetClient

      2. Webcam

      3. SpatialPerception

        Screenshot, der die obere Hälfte der Konfigurationsoptionen für Funktionen zeigt. Screenshot, der die untere Hälfte der Konfigurationsoptionen für Funktionen zeigt.

    3. Aktivieren Sie weiter unten im Bereich unter XR-Einstellungen (unter Veröffentlichungseinstellungen) das Kontrollkästchen Virtual Reality Supported (Virtuelle Realität unterstützt), und stellen Sie dann sicher, dass das Windows Mixed Reality SDK hinzugefügt wurde.

      Screenshot, der zeigt, dass das Windows Mixed Reality SDK hinzugefügt wurde.

  8. Zurück in den Buildeinstellungen ist Unity C#-Projekte nicht mehr abgeblendet: Aktivieren Sie das Kontrollkästchen daneben.

  9. Schließen Sie das Fenster Buildeinstellungen .

  10. Klicken Sie im Editor aufGrafik fürProjekteinstellungen>bearbeiten>.

    Screenshot: Ausgewählte Menüoption

  11. Im Inspektorbereich werden die Grafikeinstellungen geöffnet. Scrollen Sie nach unten, bis ein Array namens Always Include Shader angezeigt wird. Fügen Sie einen Slot hinzu, indem Sie die Variable Size um 1 erhöhen (in diesem Beispiel war es 8, sodass wir es 9 gemacht haben). Ein neuer Slot wird an der letzten Position des Arrays angezeigt, wie unten gezeigt:

    Screenshot, der das Array

  12. Klicken Sie im Slot auf den kleinen Zielkreis neben dem Slot, um eine Liste mit Shadern zu öffnen. Suchen Sie nach legacy shader/Transparent/Diffuse shader, und doppelklicken Sie darauf.

    Screenshot: Legacy-Shader/Transparent/Diffuse-Shader

Kapitel 4: Importieren des Unity-Pakets CustomVisionObjDetection

Für diesen Kurs erhalten Sie ein Unity-Ressourcenpaket namens Azure-MR-310.unitypackage.

[TIPP] Alle von Unity unterstützten Objekte, einschließlich ganzer Szenen, können in eine UNITYPACKAGE-Datei gepackt und in andere Projekte exportiert bzw. importiert werden. Dies ist die sicherste und effizienteste Möglichkeit, Ressourcen zwischen verschiedenen Unity-Projekten zu verschieben.

Das Azure-MR-310-Paket, das Sie herunterladen müssen, finden Sie hier.

  1. Klicken Sie mit dem Unity-Dashboard oben auf dem Bildschirm im Menü auf Ressourcen, und klicken Sie dann auf Benutzerdefiniertes Paket > importieren.

    Screenshot, in dem die Menüoption

  2. Wählen Sie mit der Dateiauswahl das Paket Azure-MR-310.unitypackage aus, und klicken Sie auf Öffnen. Eine Liste der Komponenten für dieses Medienobjekt wird angezeigt. Bestätigen Sie den Import, indem Sie auf die Schaltfläche Importieren klicken.

    Screenshot: Liste der Ressourcenkomponenten, die Sie importieren möchten

  3. Nachdem der Import abgeschlossen ist, werden Sie feststellen, dass Ordner aus dem Paket nun ihrem Ordner Assets hinzugefügt wurden. Diese Art von Ordnerstruktur ist typisch für ein Unity-Projekt.

    Screenshot: Inhalt des Ordners

    1. Der Ordner Materials enthält das material, das vom Anvisierencursor verwendet wird.

    2. Der Ordner Plug-Ins enthält die Newtonsoft-DLL, die vom Code zum Deserialisieren der Service-Webantwort verwendet wird. Die zwei (2) verschiedenen Versionen, die im Ordner und Unterordner enthalten sind, sind erforderlich, damit die Bibliothek sowohl vom Unity-Editor als auch vom UWP-Build verwendet und erstellt werden kann.

    3. Der Ordner Prefabs enthält die in der Szene enthaltenen Prefabs. Dies sind:

      1. Der GazeCursor, der in der Anwendung verwendete Cursor. Arbeitet mit dem SpatialMapping-Prefab zusammen, um in der Szene über physischen Objekten platziert werden zu können.
      2. Die Bezeichnung, bei der es sich um das Benutzeroberflächenobjekt handelt, mit dem bei Bedarf das Objekttag in der Szene angezeigt wird.
      3. SpatialMapping ist das Objekt, das es der Anwendung ermöglicht, mithilfe der räumlichen Nachverfolgung der Microsoft HoloLens eine virtuelle Karte zu erstellen.
    4. Der Ordner Scenes , der derzeit die vorgefertigte Szene für diesen Kurs enthält.

  4. Öffnen Sie den Ordner Szenen im Projektbereich, und doppelklicken Sie auf ObjDetectionScene, um die Szene zu laden, die Sie für diesen Kurs verwenden werden.

    Screenshot: ObjDetectionScene im Ordner

    Hinweis

    Es ist kein Code enthalten. Sie schreiben den Code, indem Sie diesem Kurs folgen.

Kapitel 5: Erstellen der CustomVisionAnalyser-Klasse.

An diesem Punkt können Sie Code schreiben. Sie beginnen mit der CustomVisionAnalyser-Klasse .

Hinweis

Die Aufrufe des Custom Vision-Diensts, die im unten gezeigten Code erfolgen, werden mithilfe der rest-API Custom Vision ausgeführt. Dadurch erfahren Sie, wie Sie diese API implementieren und nutzen (nützlich, um zu verstehen, wie Sie etwas Ähnliches selbst implementieren können). Beachten Sie, dass Microsoft ein Custom Vision SDK anbietet, das auch für Aufrufe an den Dienst verwendet werden kann. Weitere Informationen finden Sie im Artikel Custom Vision SDK.

Diese Klasse ist für Folgendes zuständig:

  • Laden des neuesten Bilds, das als Bytearray erfasst wurde.

  • Senden des Bytearrays an Ihre Azure Custom Vision Service-instance zur Analyse.

  • Empfangen der Antwort als JSON-Zeichenfolge.

  • Deserialisieren der Antwort und Übergeben der resultierenden Prediction an die SceneOrganiser-Klasse , die sich darum kümmert, wie die Antwort angezeigt werden soll.

So erstellen Sie diese Klasse:

  1. Klicken Sie mit der rechten Maustaste auf den Ressourcenordner im Projektbereich, und klicken Sie dann aufOrdnererstellen>. Rufen Sie den Ordner Skripts auf.

    Screenshot: Erstellen des Ordners

  2. Doppelklicken Sie auf den neu erstellten Ordner, um ihn zu öffnen.

  3. Klicken Sie mit der rechten Maustaste in den Ordner, und klicken Sie dann aufC#-Skript erstellen>. Nennen Sie das Skript CustomVisionAnalyser.

  4. Doppelklicken Sie auf das neue CustomVisionAnalyser-Skript, um es mit Visual Studio zu öffnen.

  5. Stellen Sie sicher, dass oben in der Datei auf die folgenden Namespaces verwiesen wird:

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  6. Fügen Sie in der CustomVisionAnalyser-Klasse die folgenden Variablen hinzu:

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

    Hinweis

    Stellen Sie sicher, dass Sie Ihren Dienstvorhersageschlüssel in die Variable predictionKey und Ihren Prediction-Endpoint in die variable predictionEndpoint einfügen. Sie haben diese zuvor in Kapitel 2 Schritt 14 in Den Editor kopiert.

  7. Code für Awake() muss jetzt hinzugefügt werden, um die Instanzvariable zu initialisieren:

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Fügen Sie die Coroutine (mit der statischen GetImageAsByteArray() -Methode darunter hinzu, die die Ergebnisse der Analyse des Bilds abruft, die von der ImageCapture-Klasse erfasst wird.

    Hinweis

    In der AnalyseImageCapture-Coroutine gibt es einen Aufruf der SceneOrganiser-Klasse , die Sie noch erstellen müssen. Lassen Sie daher diese Zeilen vorerst kommentiert.

        /// <summary>
        /// Call the Computer Vision Service to submit the image.
        /// </summary>
        public IEnumerator AnalyseLastImageCaptured(string imagePath)
        {
            Debug.Log("Analyzing...");
    
            WWWForm webForm = new WWWForm();
    
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                Debug.Log("response: " + jsonResponse);
    
                // Create a texture. Texture size does not matter, since
                // LoadImage will replace with the incoming image size.
                //Texture2D tex = new Texture2D(1, 1);
                //tex.LoadImage(imageBytes);
                //SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
                // The response will be in JSON format, therefore it needs to be deserialized
                //AnalysisRootObject analysisRootObject = new AnalysisRootObject();
                //analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
                //SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  9. Löschen Sie die Start()- und Update() -Methoden, da sie nicht verwendet werden.

  10. Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.

Wichtig

Wie bereits erwähnt, machen Sie sich keine Gedanken über Code, der möglicherweise einen Fehler aufweist, da Sie in Kürze weitere Klassen bereitstellen werden, die diese beheben werden.

Kapitel 6: Erstellen der CustomVisionObjects-Klasse

Die Klasse, die Sie jetzt erstellen, ist die CustomVisionObjects-Klasse .

Dieses Skript enthält eine Reihe von Objekten, die von anderen Klassen zum Serialisieren und Deserialisieren der Aufrufe an den Custom Vision Service verwendet werden.

So erstellen Sie diese Klasse:

  1. Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann aufC#-Skripterstellen>. Rufen Sie das Skript CustomVisionObjects auf.

  2. Doppelklicken Sie auf das neue CustomVisionObjects-Skript, um es mit Visual Studio zu öffnen.

  3. Stellen Sie sicher, dass oben in der Datei auf die folgenden Namespaces verwiesen wird:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Löschen Sie die Start()- und Update() -Methoden innerhalb der CustomVisionObjects-Klasse . Diese Klasse sollte jetzt leer sein.

    Warnung

    Es ist wichtig, dass Sie die nächste Anweisung sorgfältig befolgen. Wenn Sie die neuen Klassendeklarationen in die CustomVisionObjects-Klasse einfügen, erhalten Sie in Kapitel 10 Kompilierungsfehler, die angeben, dass AnalysisRootObject und BoundingBox nicht gefunden wurden.

  5. Fügen Sie die folgenden Klassen außerhalb der CustomVisionObjects-Klasse hinzu. Diese Objekte werden von der Newtonsoft-Bibliothek verwendet, um die Antwortdaten zu serialisieren und zu deserialisieren:

    // The objects contained in this script represent the deserialized version
    // of the objects used by this application 
    
    /// <summary>
    /// Web request object for image data
    /// </summary>
    class MultipartObject : IMultipartFormSection
    {
        public string sectionName { get; set; }
    
        public byte[] sectionData { get; set; }
    
        public string fileName { get; set; }
    
        public string contentType { get; set; }
    }
    
    /// <summary>
    /// JSON of all Tags existing within the project
    /// contains the list of Tags
    /// </summary> 
    public class Tags_RootObject
    {
        public List<TagOfProject> Tags { get; set; }
        public int TotalTaggedImages { get; set; }
        public int TotalUntaggedImages { get; set; }
    }
    
    public class TagOfProject
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int ImageCount { get; set; }
    }
    
    /// <summary>
    /// JSON of Tag to associate to an image
    /// Contains a list of hosting the tags,
    /// since multiple tags can be associated with one image
    /// </summary> 
    public class Tag_RootObject
    {
        public List<Tag> Tags { get; set; }
    }
    
    public class Tag
    {
        public string ImageId { get; set; }
        public string TagId { get; set; }
    }
    
    /// <summary>
    /// JSON of images submitted
    /// Contains objects that host detailed information about one or more images
    /// </summary> 
    public class ImageRootObject
    {
        public bool IsBatchSuccessful { get; set; }
        public List<SubmittedImage> Images { get; set; }
    }
    
    public class SubmittedImage
    {
        public string SourceUrl { get; set; }
        public string Status { get; set; }
        public ImageObject Image { get; set; }
    }
    
    public class ImageObject
    {
        public string Id { get; set; }
        public DateTime Created { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public string ImageUri { get; set; }
        public string ThumbnailUri { get; set; }
    }
    
    /// <summary>
    /// JSON of Service Iteration
    /// </summary> 
    public class Iteration
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public bool IsDefault { get; set; }
        public string Status { get; set; }
        public string Created { get; set; }
        public string LastModified { get; set; }
        public string TrainedAt { get; set; }
        public string ProjectId { get; set; }
        public bool Exportable { get; set; }
        public string DomainId { get; set; }
    }
    
    /// <summary>
    /// Predictions received by the Service
    /// after submitting an image for analysis
    /// Includes Bounding Box
    /// </summary>
    public class AnalysisRootObject
    {
        public string id { get; set; }
        public string project { get; set; }
        public string iteration { get; set; }
        public DateTime created { get; set; }
        public List<Prediction> predictions { get; set; }
    }
    
    public class BoundingBox
    {
        public double left { get; set; }
        public double top { get; set; }
        public double width { get; set; }
        public double height { get; set; }
    }
    
    public class Prediction
    {
        public double probability { get; set; }
        public string tagId { get; set; }
        public string tagName { get; set; }
        public BoundingBox boundingBox { get; set; }
    }
    
  6. Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.

Kapitel 7: Erstellen der SpatialMapping-Klasse

Diese Klasse legt den Spatial Mapping Collider in der Szene so fest, dass Konflikte zwischen virtuellen Objekten und realen Objekten erkannt werden können.

So erstellen Sie diese Klasse:

  1. Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann aufC#-Skripterstellen>. Rufen Sie das Skript SpatialMapping auf.

  2. Doppelklicken Sie auf das neue SpatialMapping-Skript, um es mit Visual Studio zu öffnen.

  3. Stellen Sie sicher, dass über die folgenden Namespaces oberhalb der SpatialMapping-Klasse verwiesen wird:

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. Fügen Sie dann die folgenden Variablen in der SpatialMapping-Klasse oberhalb der Start()- Methode hinzu:

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

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

  7. Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.

Kapitel 8: Erstellen der GazeCursor-Klasse

Diese Klasse ist für die Einrichtung des Cursors an der richtigen Position im realen Raum verantwortlich, indem der SpatialMappingCollider verwendet wird, der im vorherigen Kapitel erstellt wurde.

So erstellen Sie diese Klasse:

  1. Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann aufC#-Skripterstellen>. Aufrufen des Skripts GazeCursor

  2. Doppelklicken Sie auf das neue GazeCursor-Skript, um es mit Visual Studio zu öffnen.

  3. Stellen Sie sicher, dass über der GazeCursor-Klasse auf den folgenden Namespace verwiesen wird:

    using UnityEngine;
    
  4. Fügen Sie dann die folgende Variable in der GazeCursor-Klasse oberhalb der Start() -Methode hinzu.

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Aktualisieren Sie die Start()- Methode mit dem folgenden Code:

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

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

    Hinweis

    Machen Sie sich keine Gedanken darüber, dass der Fehler für die SceneOrganiser-Klasse nicht gefunden wird. Sie erstellen ihn im nächsten Kapitel.

  7. Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.

Kapitel 9: Erstellen der SceneOrganiser-Klasse

Diese Klasse führt Folgendes aus:

  • Richten Sie die Hauptkamera ein, indem Sie die entsprechenden Komponenten an sie anfügen.

  • Wenn ein Objekt erkannt wird, ist es für die Berechnung seiner Position in der realen Welt verantwortlich und platziert eine Tagbeschriftung in der Nähe des Objekts mit dem entsprechenden Tagnamen.

So erstellen Sie diese Klasse:

  1. Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann aufC#-Skripterstellen>. Nennen Sie das Skript SceneOrganiser.

  2. Doppelklicken Sie auf das neue SceneOrganiser-Skript, um es mit Visual Studio zu öffnen.

  3. Stellen Sie sicher, dass über die folgenden Namespaces oberhalb der SceneOrganiser-Klasse verwiesen wird:

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
  4. Fügen Sie dann die folgenden Variablen in der SceneOrganiser-Klasse oberhalb der Start()- Methode hinzu:

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SceneOrganiser Instance;
    
        /// <summary>
        /// The cursor object attached to the Main Camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        public GameObject label;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.8f;
    
        /// <summary>
        /// The quad object hosting the imposed image captured
        /// </summary>
        private GameObject quad;
    
        /// <summary>
        /// Renderer of the quad object
        /// </summary>
        internal Renderer quadRenderer;
    
  5. Löschen Sie die Start()- und Update()- Methoden.

  6. Fügen Sie unterhalb der Variablen die Awake() -Methode hinzu, die die Klasse initialisiert und die Szene einrichtet.

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            // Use this class instance as singleton
            Instance = this;
    
            // Add the ImageCapture class to this Gameobject
            gameObject.AddComponent<ImageCapture>();
    
            // Add the CustomVisionAnalyser class to this Gameobject
            gameObject.AddComponent<CustomVisionAnalyser>();
    
            // Add the CustomVisionObjects class to this Gameobject
            gameObject.AddComponent<CustomVisionObjects>();
        }
    
  7. Fügen Sie die PlaceAnalysisLabel()- Methode hinzu, die die Bezeichnung in der Szene instanziiert (die an diesem Punkt für den Benutzer nicht sichtbar ist). Es platziert auch das Quad (auch unsichtbar) an der Stelle, an der das Bild platziert wird, und überschneidet sich mit der realen Welt. Dies ist wichtig, da die nach der Analyse vom Dienst abgerufenen Feldkoordinaten auf dieses Quad zurückverfolgt werden, um die ungefähre Position des Objekts in der realen Welt zu bestimmen.

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

    • Festlegen des Bezeichnungstexts mit dem Tag der Vorhersage mit der höchsten Zuverlässigkeit.
    • Aufrufen der Berechnung des Begrenzungsrahmens für das zuvor positionierte Quad-Objekt und Platzieren der Bezeichnung in der Szene.
    • Anpassen der Beschriftungstiefe mithilfe eines Raycasts in Richtung des Begrenzungsrahmens, der mit dem Objekt in der realen Welt kollidieren sollte.
    • Setzen Sie den Erfassungsprozess zurück, damit der Benutzer ein weiteres Bild erfassen kann.
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void FinaliseLabel(AnalysisRootObject analysisObject)
        {
            if (analysisObject.predictions != null)
            {
                lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
                // Sort the predictions to locate the highest one
                List<Prediction> sortedPredictions = new List<Prediction>();
                sortedPredictions = analysisObject.predictions.OrderBy(p => p.probability).ToList();
                Prediction bestPrediction = new Prediction();
                bestPrediction = sortedPredictions[sortedPredictions.Count - 1];
    
                if (bestPrediction.probability > probabilityThreshold)
                {
                    quadRenderer = quad.GetComponent<Renderer>() as Renderer;
                    Bounds quadBounds = quadRenderer.bounds;
    
                    // Position the label as close as possible to the Bounding Box of the prediction 
                    // At this point it will not consider depth
                    lastLabelPlaced.transform.parent = quad.transform;
                    lastLabelPlaced.transform.localPosition = CalculateBoundingBoxPosition(quadBounds, bestPrediction.boundingBox);
    
                    // Set the tag text
                    lastLabelPlacedText.text = bestPrediction.tagName;
    
                    // Cast a ray from the user's head to the currently placed label, it should hit the object detected by the Service.
                    // At that point it will reposition the label where the ray HL sensor collides with the object,
                    // (using the HL spatial tracking)
                    Debug.Log("Repositioning Label");
                    Vector3 headPosition = Camera.main.transform.position;
                    RaycastHit objHitInfo;
                    Vector3 objDirection = lastLabelPlaced.position;
                    if (Physics.Raycast(headPosition, objDirection, out objHitInfo, 30.0f,   SpatialMapping.PhysicsRaycastMask))
                    {
                        lastLabelPlaced.position = objHitInfo.point;
                    }
                }
            }
            // Reset the color of the cursor
            cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the analysis process
            ImageCapture.Instance.ResetImageCapture();        
        }
    
  9. Fügen Sie die CalculateBoundingBoxPosition()-Methode hinzu, die eine Reihe von Berechnungen hostet, die erforderlich sind, um die vom Dienst abgerufenen Begrenzungsrahmenkoordinaten zu übersetzen und sie proportional auf dem Quad neu zu erstellen.

        /// <summary>
        /// This method hosts a series of calculations to determine the position 
        /// of the Bounding Box on the quad created in the real world
        /// by using the Bounding Box received back alongside the Best Prediction
        /// </summary>
        public Vector3 CalculateBoundingBoxPosition(Bounds b, BoundingBox boundingBox)
        {
            Debug.Log($"BB: left {boundingBox.left}, top {boundingBox.top}, width {boundingBox.width}, height {boundingBox.height}");
    
            double centerFromLeft = boundingBox.left + (boundingBox.width / 2);
            double centerFromTop = boundingBox.top + (boundingBox.height / 2);
            Debug.Log($"BB CenterFromLeft {centerFromLeft}, CenterFromTop {centerFromTop}");
    
            double quadWidth = b.size.normalized.x;
            double quadHeight = b.size.normalized.y;
            Debug.Log($"Quad Width {b.size.normalized.x}, Quad Height {b.size.normalized.y}");
    
            double normalisedPos_X = (quadWidth * centerFromLeft) - (quadWidth/2);
            double normalisedPos_Y = (quadHeight * centerFromTop) - (quadHeight/2);
    
            return new Vector3((float)normalisedPos_X, (float)normalisedPos_Y, 0);
        }
    
  10. Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.

    Wichtig

    Bevor Sie fortfahren, öffnen Sie die CustomVisionAnalyser-Klasse , und heben Sie in der AnalyseLastImageCaptured() -Methode die Auskommentierung der folgenden Zeilen auf:

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

Hinweis

Machen Sie sich keine Gedanken über die Meldung "Es wurde nicht gefunden" der ImageCapture-Klasse . Sie erstellen sie im nächsten Kapitel.

Kapitel 10: Erstellen der ImageCapture-Klasse

Die nächste Klasse, die Sie erstellen, ist die ImageCapture-Klasse .

Diese Klasse ist für Folgendes zuständig:

  • Erfassen eines Bilds mit der HoloLens-Kamera und Speichern im Ordner App
  • Behandeln von Tippgesten des Benutzers.

So erstellen Sie diese Klasse:

  1. Wechseln Sie zum Ordner Skripts, den Sie zuvor erstellt haben.

  2. Klicken Sie mit der rechten Maustaste in den Ordner, und klicken Sie dann aufC#-Skript erstellen>. Nennen Sie das Skript ImageCapture.

  3. Doppelklicken Sie auf das neue ImageCapture-Skript, um es mit Visual Studio zu öffnen.

  4. Ersetzen Sie die Namespaces am Anfang der Datei durch Folgendes:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Fügen Sie dann die folgenden Variablen in der ImageCapture-Klasse oberhalb der Start() -Methode hinzu:

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static ImageCapture Instance;
    
        /// <summary>
        /// Keep counts of the taps for image renaming
        /// </summary>
        private int captureCount = 0;
    
        /// <summary>
        /// Photo Capture object
        /// </summary>
        private PhotoCapture photoCaptureObject = null;
    
        /// <summary>
        /// Allows gestures recognition in HoloLens
        /// </summary>
        private GestureRecognizer recognizer;
    
        /// <summary>
        /// Flagging if the capture loop is running
        /// </summary>
        internal bool captureIsActive;
    
        /// <summary>
        /// File path of current analysed photo
        /// </summary>
        internal string filePath = string.Empty;
    
  6. Code für die Methoden Awake() und Start() muss jetzt hinzugefügt werden:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the Microsoft HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  7. Implementieren Sie einen Handler, der aufgerufen wird, wenn eine Tippen-Geste auftritt:

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

    Wichtig

    Wenn der Cursor grün ist, bedeutet dies, dass die Kamera verfügbar ist, um das Bild aufzunehmen. Wenn der Cursor rot ist, bedeutet dies, dass die Kamera ausgelastet ist.

  8. Fügen Sie die Methode hinzu, die die Anwendung verwendet, um den Imageerfassungsprozess zu starten und das Bild zu speichern:

        /// <summary>
        /// Begin process of image capturing and send to Azure Custom Vision Service.
        /// </summary>
        private void ExecuteImageCaptureAndAnalysis()
        {
            // Create a label in world space using the ResultsLabel class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending
                ((res) => res.width * res.height).First();
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(true, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 1.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });
        }
    
  9. Fügen Sie die Handler hinzu, die aufgerufen werden, wenn das Foto aufgenommen wurde und wann es analysiert werden kann. Das Ergebnis wird dann zur Analyse an customVisionAnalyser übergeben.

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

Kapitel 11: Einrichten der Skripts in der Szene

Nachdem Sie nun den gesamten Code geschrieben haben, der für dieses Projekt erforderlich ist, ist es an der Zeit, die Skripts in der Szene und auf den Prefabs einzurichten, damit sie sich ordnungsgemäß verhalten.

  1. Wählen Sie im Unity-Editor im Hierarchiebereich die Hauptkamera aus.

  2. Klicken Sie im Inspektorbereich bei ausgewählter Hauptkamera auf Komponente hinzufügen, suchen Sie nach SceneOrganiser-Skript , und doppelklicken Sie darauf, um es hinzuzufügen.

    Screenshot des SceneOrganizer-Skripts

  3. Öffnen Sie im Projektbereich den Ordner Prefabs, und ziehen Sie das Prefab Label in den Eingabebereich Bezeichnung leeres Referenzziel im Skript SceneOrganiser , das Sie der Hauptkamera gerade hinzugefügt haben, wie in der folgenden Abbildung gezeigt:

    Screenshot: Skript, das Sie der Hauptkamera hinzugefügt haben

  4. Wählen Sie im Hierarchiebereich das untergeordnete GazeCursor-Element der Hauptkamera aus.

  5. Klicken Sie im Inspektorbereich bei ausgewählter GazeCursor-Option auf Komponente hinzufügen, suchen Sie nach GazeCursor-Skript , und doppelklicken Sie darauf, um es hinzuzufügen.

    Screenshot: Hinzufügen des GazeCursor-Skripts

  6. Wählen Sie im Hierarchiebereich das untergeordnete SpatialMapping-Element der Hauptkamera aus.

  7. Klicken Sie im Inspektorbereich bei ausgewählter SpatialMapping-Option auf Komponente hinzufügen, suchen Sie nach SpatialMapping-Skript , und doppelklicken Sie darauf, um es hinzuzufügen.

    Screenshot, der zeigt, wo Sie das SpatialMapping-Skript hinzufügen.

Die restlichen Skripts, die Sie nicht festgelegt haben, werden während der Laufzeit vom Code im SceneOrganiser-Skript hinzugefügt.

Kapitel 12 - Vor dem Erstellen

Für einen gründlichen Test Ihrer Anwendung müssen Sie sie auf Ihre Microsoft HoloLens querladen.

Bevor Sie dies tun, stellen Sie Folgendes sicher:

  • Alle in Kapitel 3 erwähnten Einstellungen sind richtig festgelegt.

  • Das Skript SceneOrganiser ist an das Hauptkameraobjekt angefügt.

  • Das Skript GazeCursor ist an das GazeCursor-Objekt angefügt.

  • Das Skript SpatialMapping ist an das SpatialMapping-Objekt angefügt.

  • In Kapitel 5, Schritt 6:

    • Stellen Sie sicher, dass Sie Ihren Dienstvorhersageschlüssel in die variable predictionKey einfügen.
    • Sie haben Ihren Prediction Endpoint in die predictionEndpoint-Klasse eingefügt.

Kapitel 13: Erstellen der UWP-Lösung und Querladen Ihrer Anwendung

Jetzt können Sie Ihre Anwendung als UWP-Lösung erstellen, die Sie auf dem Microsoft HoloLens bereitstellen können. So starten Sie den Buildprozess:

  1. Wechseln Sie zu Dateibuildeinstellungen>.

  2. Tick Unity C#-Projekte.

  3. Klicken Sie auf Geöffnete Szenen hinzufügen. Dadurch wird dem Build die derzeit geöffnete Szene hinzugefügt.

    Screenshot, in dem die Schaltfläche

  4. Klicken Sie auf Erstellen. Unity startet ein Explorer Fenster, in dem Sie einen Ordner erstellen und dann auswählen müssen, in dem die App erstellt werden soll. Erstellen Sie diesen Ordner jetzt, und nennen Sie ihn App. Klicken Sie dann bei ausgewähltem App-Ordner auf Ordner auswählen.

  5. Unity beginnt mit dem Erstellen Ihres Projekts im Ordner App .

  6. Sobald Unity das Erstellen abgeschlossen hat (es kann einige Zeit dauern), öffnet es ein Explorer Fenster an der Position Ihres Builds (überprüfen Sie ihre Taskleiste, da es möglicherweise nicht immer über Ihren Fenstern angezeigt wird, aber Sie über das Hinzufügen eines neuen Fensters informiert).

  7. Für die Bereitstellung auf Microsoft HoloLens benötigen Sie die IP-Adresse dieses Geräts (für Remote Deploy) und um sicherzustellen, dass auch der Entwicklermodus festgelegt ist. Gehen Sie dazu wie folgt vor:

    1. Öffnen Sie beim Tragen Ihrer HoloLens die Einstellungen.

    2. Wechseln Sie zu Netzwerk & Internet>Wi-Fi>Erweiterte Optionen

    3. Notieren Sie sich die IPv4-Adresse .

    4. Navigieren Sie als Nächstes zurück zu Einstellungen und dann zu Update & Security>For Developers

    5. Legen Sie den Entwicklermodusein.

  8. Navigieren Sie zu Ihrem neuen Unity-Build (dem Ordner App ), und öffnen Sie die Projektmappendatei mit Visual Studio.

  9. Wählen Sie unter Projektmappenkonfiguration die Option Debuggen aus.

  10. Wählen Sie auf der Lösungsplattform x86, Remotecomputer aus. Sie werden aufgefordert, die IP-Adresse eines Remotegeräts einzugeben (in diesem Fall die Microsoft HoloLens, die Sie sich notiert haben).

    Screenshot, der zeigt, wo die IP-Adresse eingefügt werden soll.

  11. Wechseln Sie zum Menü Erstellen , und klicken Sie auf Lösung bereitstellen , um die Anwendung in Ihre HoloLens querzuladen.

  12. Ihre App sollte jetzt in der Liste der installierten Apps auf Ihrem Microsoft HoloLens angezeigt werden, bereit für den Start!

So verwenden Sie die Anwendung:

  • Sehen Sie sich ein Objekt an, das Sie mit Ihrem Azure Custom Vision-Dienst trainiert haben, und verwenden Sie die Tippen-Geste.
  • Wenn das Objekt erfolgreich erkannt wird, wird ein Weltzeichen-Bezeichnungstext mit dem Tagnamen angezeigt.

Wichtig

Jedes Mal, wenn Sie ein Foto aufnehmen und an den Dienst senden, können Sie zur Seite Dienst zurückkehren und den Dienst mit den neu aufgenommenen Bildern erneut trainieren. Zu Beginn müssen Sie wahrscheinlich auch die Begrenzungsrahmen korrigieren, um genauer zu sein und den Dienst erneut zu trainieren.

Hinweis

Der platzierte Bezeichnungstext wird möglicherweise nicht in der Nähe des Objekts angezeigt, wenn die Microsoft HoloLens Sensoren und/oder spatialTrackingComponent in Unity die entsprechenden Collider relativ zu den realen Objekten nicht platzieren können. Versuchen Sie, die Anwendung auf einer anderen Oberfläche zu verwenden, wenn dies der Fall ist.

Ihre Custom Vision, Objekterkennungsanwendung

Herzlichen Glückwunsch, Sie haben eine Mixed Reality-App erstellt, die die Azure Custom Vision, die Objekterkennungs-API nutzt, die ein Objekt anhand eines Bilds erkennen und dann eine ungefähre Position für dieses Objekt im 3D-Raum bereitstellen kann.

Screenshot: Mixed Reality-App, die die Azure Custom Vision, Objekterkennungs-API nutzt

Bonusübungen

Übung 1

Wenn Sie der Textbeschriftung hinzufügen, verwenden Sie einen halbtransparenten Würfel, um das reale Objekt in einem 3D-Begrenzungsrahmen zu umschließen.

Übung 2

Trainieren Sie Ihren Custom Vision Service, um weitere Objekte zu erkennen.

Übung 3

Geben Sie einen Sound wieder, wenn ein Objekt erkannt wird.

Übung 4

Verwenden Sie die API, um Ihren Dienst mit denselben Bildern, die Ihre App analysiert, neu zu trainieren, um den Dienst genauer zu gestalten (gleichzeitige Vorhersage und Training).