HoloLens (1. Generation) und Azure 310: Objekterkennung

Hinweis

Die Tutorials der Mixed Reality Academy wurden im Hinblick auf HoloLens (1. Gen.) und immersive Mixed Reality-Headsets entworfen. Daher halten wir es für wichtig, diese Tutorials für Entwickler verfügbar zu halten, die noch nach Anleitung beim Entwickeln 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 gewartet, um weiterhin auf den unterstützten Geräten zu funktionieren. Es wird eine neue Reihe von Tutorials geben, die in Zukunft veröffentlicht werden, die veranschaulichen, wie für HoloLens 2 entwickelt werden kann. Dieser Hinweis wird mit einem Link zu diesen Tutorials aktualisiert, wenn sie veröffentlicht werden.


In diesem Kurs erfahren Sie, wie Sie benutzerdefinierte visuelle Inhalte und deren räumliche Position in einem bereitgestellten Bild mithilfe von Azure Custom Vision Funktionen "Objekterkennung" 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 ermitteln, 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 von Azure Custom Vision ist ein Microsoft-Dienst, mit dem Entwickler benutzerdefinierte Bildklassifizierungen 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 sich ein Objekt ansehen, das er mithilfe des Azure Custom Vision Service, Objekterkennung trainiert hat.
  2. Der Benutzer verwendet die Tippen-Geste , um ein Bild von dem zu erfassen, was er sieht.
  3. Die App sendet das Image an den Azure Custom Vision Service.
  4. Es wird eine Antwort vom Dienst angezeigt, in der das Ergebnis der Erkennung als Weltraumtext angezeigt wird. 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, das dem im Bild erkannten Objekt zugeordnet ist, verwendet wird, um den Bezeichnungstext bereitzustellen.

Der Kurs behandelt auch das manuelle Hochladen von Bildern, das Erstellen von Tags und das Training des Diensts, verschiedene Objekte (im bereitgestellten Beispiel eine Tasse) zu erkennen, 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 vorgenommenen Vorhersagen identifizieren und ermitteln, ob sie korrekt waren oder nicht (durch Taggen, was der Dienst verpasst hat, und die Begrenzungsrahmen anpassen). Der Dienst kann dann erneut trainiert werden, was die Wahrscheinlichkeit erhöht, dass er objekte aus der Realen Welt erkennt.

In diesem Kurs erfahren Sie, wie Sie die Ergebnisse von Azure Custom Vision Service, Objekterkennung, in einer Unity-basierten Beispielanwendung abrufen. Sie müssen diese Konzepte auf eine benutzerdefinierte Anwendung anwenden, 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 das darstellen, was zum Zeitpunkt der Erstellung (Juli 2018) getestet und überprüft wurde. Sie können die neueste Software verwenden, wie im Artikel Installieren der Tools aufgeführt, aber es sollte nicht davon ausgegangen werden, 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:

Vorbereitung

  1. Um Probleme beim Erstellen dieses Projekts zu vermeiden, wird dringend empfohlen, dass Sie das in diesem Tutorial erwähnte Projekt in einem Stamm- oder Fast-Stammordner 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, lesen 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 dies helfen, 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 Zur HoloLens-Sensoroptimierung.

Kapitel 1: Das Custom Vision-Portal

Um den Azure Custom Vision Service verwenden zu können, müssen Sie eine instance konfigurieren, die 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 Laborsituation befolgen, bitten Sie Ihren Kursleiter oder einen der Experten um Hilfe beim Einrichten Ihres neuen Kontos.

  5. Sobald Sie zum ersten Mal angemeldet sind, werden Sie mit dem Bereich Nutzungsbedingungen aufgefordert. Klicken Sie auf 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: Auswählen von

  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 (z. B. diesen Kursen) zugeordnet sind, unter einer gemeinsamen Ressourcengruppe zu speichern.

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

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

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

Kapitel 2: Training Ihres Custom Vision-Projekts

Sobald Sie sich im Custom Vision Portal befinden, besteht Ihr Hauptziel 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, der die Schaltfläche + neben Tags zeigt.

  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, also haben wir das Tag dafür , Cup genannt. Klicken Sie nach Abschluss auf Speichern .

    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 es angezeigt wird).

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

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

    Screenshot: Hinzufügen von Bildern

  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 , nachdem 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. Um Ihre Bilder zu markieren, verwenden Sie die Maus. 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 auf die Maus klicken und den Auswahlbereich ziehen, um Ihr Objekt einzuschließen.

    Screenshot: Markieren eines Bilds

  9. Nach der Auswahl Ihres Objekts innerhalb des Bilds werden Sie mit einer kleinen Eingabeaufforderung aufgefordert, regionstag 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 Bild bereit haben, wiederholen Sie dasselbe Verfahren. 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 links auf dem Bildschirm, um die markierten Bilder anzuzeigen.

    Screenshot: Hervorhebung der 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 erstellen und Vorhersage-URL angezeigt. Klicken Sie zuerst auf Standard festlegen , und klicken Sie dann auf Vorhersage-URL.

    Screenshot mit hervorgehobener Schaltfläche

    Hinweis

    Der Endpunkt, der von diesem bereitgestellt wird, wird auf die Iteration festgelegt, die als Standard markiert wurde. Wenn Sie also 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 Den Editor, und kopieren Sie die URL (auch als Ihr Prediction-Endpoint bezeichnet) und den Dienstvorhersageschlüssel, sodass Sie ihn bei Bedarf später im Code abrufen können.

    Screenshot: Vorhersageendpunkt und Präditionsschlüssel

Kapitel 3: Einrichten des Unity-Projekts

Im Folgenden ist ein typischer Aufbau für die Entwicklung mit Mixed Reality dargestellt und daher eine gute Vorlage für andere Projekte.

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

    Screenshot: Hervorgehobene Schaltfläche

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

    Screenshot, der die Projektdetails und die Auswahl von Projekt erstellen zeigt.

  3. Wenn Unity geöffnet ist, lohnt es sich, zu überprüfen, ob der Standardskript-Editor auf Visual Studio festgelegt ist. Navigieren Sie zuBearbeitungseinstellungen>, und navigieren Sie dann im neuen Fenster zu Externe Tools. Ändern Sie den externen Skript-Editor in Visual Studio. Schließen Sie das Fenster Einstellungen.

    Screenshot: Ändern des externen Skript-Editors in Visual Studio

  4. Wechseln Sie als Nächstes zu Dateierstellungseinstellungen>, und ä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 mit den 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 Latest installed (Neueste Installation) festgelegt.

    5. Build and Run 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 selben Buildeinstellungen-Fenster auf die Schaltfläche 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 Skriptlaufzeitversion sollte experimental (.NET 4.6 Equivalent) lauten, was einen Neustart des Editors auslöst.

      2. 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. Klicken Sie weiter unten im Bereich unter XR-Einstellungen (unter Veröffentlichungseinstellungen) auf Virtual Reality Unterstützt, und stellen Sie 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 ausgegraut: Aktivieren Sie das Kontrollkästchen neben diesem.

  9. Schließen Sie das Fenster Buildeinstellungen.

  10. Klicken Sie im Editor aufProjekteinstellungen>bearbeiten>Grafiken.

    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 eins 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: Hervorhebung des Arrays

  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: Hervorhebung des Legacy-Shaders/Transparent/Diffuse-Shaders

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 vor Ihnen im Menü oben auf dem Bildschirm auf Ressourcen, und klicken Sie dann auf Benutzerdefiniertes Paket > importieren.

    Screenshot: Hervorhebung der Menüoption

  2. Verwenden Sie die Dateiauswahl, um das Paket Azure-MR-310.unitypackage auszuwählen, 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 Zu importierenden Ressourcenkomponenten

  3. Nachdem der Import abgeschlossen ist, werden Sie feststellen, dass Ordner aus dem Paket jetzt 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 Gaze Cursor verwendet wird.

    2. Der Ordner Plugins 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. Dazu zählen:

      1. Der GazeCursor, der in der Anwendung verwendete Cursor. Arbeitet mit dem SpatialMapping-Prefab zusammen, um in der Szene auf physischen Objekten platziert werden zu können.
      2. Das Label, bei dem es sich um das UI-Objekt handelt, das bei Bedarf zum Anzeigen des Objekttags in der Szene verwendet wird.
      3. Das SpatialMapping-Objekt, das es der Anwendung ermöglicht, mithilfe der räumlichen Nachverfolgung der Microsoft HoloLens eine virtuelle Karte zu erstellen.
    4. Der Ordner Szenen , der derzeit die vordefinierte 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 Scenes

    Hinweis

    Es ist kein Code enthalten. Sie schreiben den Code, indem Sie diesen Kurs ausführen.

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 folgenden Code ausgeführt werden, werden mithilfe der Custom Vision REST-API ausgeführt. Dadurch sehen 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 zum Tätigen von Aufrufen des Diensts verwendet werden kann. Weitere Informationen finden Sie im Artikel Custom Vision SDK.

Diese Klasse ist verantwortlich für:

  • Laden des neuesten Bilds, das als Array von Bytes 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, der sich im Projektbereich befindet, und klicken Sie dann aufOrdnererstellen>. Rufen Sie den Ordner Skripts auf.

    Screenshot: Erstellen des Ordners Skripts

  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#-Skripterstellen>. 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

    Fügen Sie ihren Dienstvorhersageschlüssel in die variable predictionKey und den Prediction-Endpunkt in die variable predictionEndpoint ein. Sie haben diese zuvor in Kapitel 2, Schritt 14, in 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 erhält, die von der ImageCapture-Klasse erfasst wurde.

    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. Speichern Sie ihre Änderungen in Visual Studio, bevor Sie zu Unity zurückkehren.

Wichtig

Wie bereits erwähnt, machen Sie sich keine Sorgen um Code, bei dem möglicherweise ein Fehler auftritt, 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 des Custom Vision-Diensts 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 nun 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 werden.

  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. Speichern Sie ihre Änderungen in Visual Studio, bevor Sie zu Unity zurückkehren.

Kapitel 7: Erstellen der SpatialMapping-Klasse

Diese Klasse legt den Spatial Mapping-Collider in der Szene fest, um Kollisionen zwischen virtuellen Objekten und realen Objekten erkennen zu 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 die folgenden Namespaces über der SpatialMapping-Klasse verwiesen werden:

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. Fügen Sie dann die folgenden Variablen 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. Speichern Sie ihre Änderungen in Visual Studio, bevor Sie zu Unity zurückkehren.

Kapitel 8: Erstellen der GazeCursor-Klasse

Diese Klasse ist für das Einrichten des Cursors an der richtigen Position im realen Raum verantwortlich, indem sie den SpatialMappingCollider verwendet, 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 die 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 Sorgen darüber, dass der Fehler für die SceneOrganiser-Klasse nicht gefunden wird. Sie erstellen ihn im nächsten Kapitel.

  7. Speichern Sie ihre Änderungen in Visual Studio, 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 daran anbringen.

  • 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 entsprechenden Tagnamens.

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, mit der die -Klasse initialisiert und die Szene eingerichtet wird.

        /// <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 zu diesem Zeitpunkt 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 aus dem Dienst abgerufenen Feldkoordinaten in 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. IISConfigurator ist für Folgendes zuständig:

    • 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 in der realen Welt mit dem Objekt kollidieren sollte.
    • Zurücksetzen des Aufnahmeprozesses, damit der Benutzer ein anderes 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 zum Übersetzen der vom Dienst abgerufenen Begrenzungsrahmenkoordinaten erforderlich sind, und erstellen Sie sie proportional auf dem Quader neu.

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

    Öffnen Sie vor dem Fortfahren 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 Sorgen über die Meldung "Es wurde nicht gefunden" der ImageCapture-Klasse , sie wird im nächsten Kapitel erstellt.

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 mithilfe der HoloLens-Kamera und Speichern im Ordner "App ".
  • Behandeln von Tippen-Gesten 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 von der Anwendung zum Starten des Bildaufnahmeprozesses und zum Speichern des Bilds verwendet wird:

        /// <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 den 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: SceneOrganizer-Skript

  3. Öffnen Sie im Projektbereich den Ordner Prefabs, und ziehen Sie das Label-Prefab in den Eingabebereich Bezeichnung leeres Verweisziel im SceneOrganiser-Skript, das Sie soeben der Hauptkamera 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 mit ausgewähltem GazeCursor 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 erneut im Hierarchiebereich das untergeordnete SpatialMapping-Element der Hauptkamera aus.

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

    Screenshot: Hinzufügen des SpatialMapping-Skripts

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

Kapitel 12 – Vor dem Erstellen

Um einen gründlichen Test Ihrer Anwendung durchzuführen, müssen Sie sie auf Ihre Microsoft HoloLens querladen.

Stellen Sie zunächst Folgendes sicher:

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

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

  • Das Skript GazeCursor wird 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 der Microsoft HoloLens bereitstellen können. So starten Sie den Buildprozess:

  1. Wechseln Sie zu Dateibuildeinstellungen>.

  2. Ticken Sie Unity C#-Projekte.

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

    Screenshot: Hervorhebung der 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ählter App-Ordner auf Ordner auswählen.

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

  6. Sobald Unity den Bau 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 benachrichtigt).

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

    1. Öffnen Sie die Einstellungen, während Sie Ihre HoloLens tragen.

    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 Aktualisieren & Sicherheit>für Entwickler.

    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 in der Projektmappenkonfiguration debuggen aus.

  10. Wählen Sie auf der Lösungsplattform die Option 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. Navigieren Sie zum Menü Erstellen , und klicken Sie auf Lösung bereitstellen , um die Anwendung in Ihre HoloLens querzuladen.

  12. Ihre App sollte nun 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 wurde, wird ein Weltbereichsbezeichnungstext mit dem Tagnamen angezeigt.

Wichtig

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

Hinweis

Der platzierte Bezeichnungstext wird möglicherweise nicht in der Nähe des Objekts angezeigt, wenn die Microsoft HoloLens Sensoren und/oder die SpatialTrackingComponent in Unity die entsprechenden Collider im Verhältnis 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-Objekterkennungs-API nutzt, die ein Objekt aus einem Bild 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

Zusatzübungen

Übung 1

Wenn Sie der Textbeschriftung hinzufügen, verwenden Sie einen halbtransparenten Cube, um das reale Objekt in ein 3D-Begrenzungsfeld zu umschließen.

Übung 2

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

Übung 3

Geben Sie einen Sound ab, 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 (sowohl Vorhersage als auch Training gleichzeitig durchführen).