Freigeben über


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 Lernprogrammen geben, die in Zukunft veröffentlicht werden, die zeigen, wie sie für HoloLens 2 entwickelt werden. Dieser Hinweis wird mit einem Link zu diesen Lernprogrammen aktualisiert, wenn sie veröffentlicht werden.


In diesem Kurs erfahren Sie, wie Sie benutzerdefinierte visuelle Inhalte und ihre räumliche Position innerhalb eines bereitgestellten Bilds erkennen, indem Sie die Funktionen von Azure Custom Vision "Object Detection" in einer Mixed Reality-Anwendung verwenden.

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 ihre Position in der realen Welt anzunähern, wie die Kameraaufnahme von Microsoft HoloLens oder eine Kamera, die mit einem PC für immersive Headsets (VR) verbunden ist.

Kursergebnis

Azure Custom Vision, Object Detection 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, einfach zu bedienendes Onlineportal, um diesen Prozess zu optimieren. Weitere Informationen finden Sie unter den folgenden Links:

Nach Abschluss dieses Kurses haben Sie eine Mixed Reality-Anwendung, die folgende Aktionen ausführen kann:

  1. Der Benutzer kann ein Objekt betrachten , das er mit dem Azure Custom Vision Service, Objekterkennung, trainiert hat.
  2. Der Benutzer verwendet die Tippgeste , um ein Bild davon zu erfassen, was er betrachtet.
  3. Die App sendet das Bild an den Azure Custom Vision Service.
  4. Es wird eine Antwort vom Dienst angezeigt, die das Ergebnis der Erkennung als Weltraumtext anzeigt. Dazu wird die räumliche Nachverfolgung von Microsoft HoloLens verwendet, um die Weltposition des erkannten Objekts zu verstehen und dann das Tag zu verwenden, das dem erkannten Bild zugeordnet ist, um den Beschriftungstext bereitzustellen.

Der Kurs behandelt außerdem das manuelle Hochladen von Bildern, das Erstellen von Tags und das Training des Dienstes, um verschiedene Objekte (im bereitgestellten Beispiel eine Tasse) zu erkennen, indem sie das Begrenzungsfeld innerhalb des von Ihnen übermittelten Bilds festlegen.

Wichtig

Nach der Erstellung und Verwendung der App sollte der Entwickler zurück zum Azure Custom Vision Service navigieren und die vom Dienst vorgenommenen Vorhersagen identifizieren und bestimmen, ob sie korrekt waren oder nicht (durch Kategorisieren eines verpassten Diensts und Anpassen der umgebenden Felder). Der Dienst kann dann neu trainiert werden, wodurch die Wahrscheinlichkeit erhöht wird, dass objekte der realen Welt erkannt werden.

In diesem Kurs erfahren Sie, wie Sie die Ergebnisse aus dem Azure Custom Vision Service, Object Detection, in einer Unity-basierten Beispielanwendung abrufen. Sie müssen diese Konzepte auf eine benutzerdefinierte Anwendung anwenden, die Sie möglicherweise erstellen.

Unterstützung für Geräte

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

Voraussetzungen

Hinweis

Dieses Lernprogramm wurde für Entwickler entwickelt, die grundlegende Erfahrung mit Unity und C# haben. Bitte beachten Sie auch, dass die Voraussetzungen und schriftlichen Anweisungen in diesem Dokument darstellen, was zum Zeitpunkt der Schriftlichkeit (Juli 2018) getestet und überprüft wurde. Sie können die neueste Software verwenden, wie im Artikel "Tools installieren" aufgeführt, aber es sollte nicht davon ausgegangen werden, dass die Informationen in diesem Kurs perfekt mit dem übereinstimmen, was Sie in neuerer Software finden werden als die unten aufgeführten.

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

Vor der Installation

  1. Um Probleme beim Erstellen dieses Projekts zu vermeiden, wird dringend empfohlen, das in diesem Lernprogramm erwähnte Projekt in einem Stamm- oder Near-Root-Ordner zu erstellen (lange Ordnerpfade können zu Buildzeit zu Problemen führen).
  2. Richten Sie Ihre HoloLens ein, und testen Sie sie. Wenn Sie Hierfür Unterstützung benötigen, besuchen Sie den HoloLens-Setupartikel.
  3. Es empfiehlt sich, beim Entwickeln einer neuen HoloLens-App Kalibrierung und Sensoroptimierung durchzuführen (manchmal kann es hilfreich sein, diese Aufgaben für jeden Benutzer auszuführen).

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

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

Kapitel 1 – Das benutzerdefinierte Vision-Portal

Um den Azure Custom Vision Service zu verwenden, müssen Sie eine Instanz davon konfigurieren, damit sie Ihrer Anwendung zur Verfügung gestellt wird.

  1. Navigieren Sie zur Hauptseite des benutzerdefinierten Vision-Diensts.

  2. Klicken Sie auf " Erste Schritte".

    Screenshot, der die Schaltfläche

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

    Screenshot der Schaltfläche

  4. Wenn Sie noch nicht über ein Azure-Konto verfügen, müssen Sie ein Konto erstellen. Wenn Sie diesem Lernprogramm in einer Unterrichts- oder Laborsituation folgen, bitten Sie Ihren Kursleiter oder einen der Betreuer, Hilfe beim Einrichten Ihres neuen Kontos zu erhalten.

  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 des Bereichs

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

    Screenshot, der zeigt, wo

  7. Auf der rechten Seite wird eine Registerkarte angezeigt, die Sie auffordert, 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, die Abrechnung für eine Sammlung von Azure-Ressourcen zu überwachen, zu steuern, den Zugriff zu steuern, bereitzustellen und zu verwalten. Es wird empfohlen, alle Azure-Dienste, die einem einzelnen Projekt (z. B. diesen Kursen) zugeordnet sind, unter einer gemeinsamen Ressourcengruppe zu halten.

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

      Hinweis

      Wenn Sie weitere Informationen zu Azure-Ressourcengruppen lesen möchten , navigieren Sie zu den zugehörigen Dokumenten.

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

  8. Nachdem Sie fertig sind, klicken Sie auf " Projekt erstellen", und Sie werden zur Projektseite "Custom Vision Service" umgeleitet.

Kapitel 2 – Schulung Ihres benutzerdefinierten Vision-Projekts

Sobald Sie sich im Custom Vision Portal befindet, besteht Ihr Hauptziel darin, Ihr Projekt zu schulen, um bestimmte Objekte in Bildern zu erkennen.

Sie benötigen mindestens 15 (15) Bilder für jedes Objekt, das Von der Anwendung erkannt werden soll. Sie können die mit 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 Schaltfläche

  2. Fügen Sie einen Namen für das Tag hinzu, das verwendet wird, um Ihre Bilder zuzuordnen. In diesem Beispiel verwenden wir Bilder von Tassen für die Erkennung, haben also das Tag für diesen Cup benannt. Klicken Sie nach Abschluss auf " Speichern ".

    Screenshot, der zeigt, wo ein Name für das Tag hinzugefügt werden soll.

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

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

    Tipp

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

    Screenshot der Bilder, die Sie hochladen können.

  6. Drücken Sie "Dateien hochladen", nachdem Sie alle Bilder ausgewählt haben, mit denen Sie das Projekt trainieren möchten. Die Dateien werden mit dem Hochladen beginnen. 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 eines nicht markierten Bilds.

  8. Verwenden Sie die Maus, um Ihre Bilder zu markieren. Wenn Sie mit dem Mauszeiger auf das Bild zeigen, unterstützt Sie eine Auswahlheraufhebung, indem Sie automatisch eine Auswahl um Ihr Objekt zeichnen. Wenn es nicht korrekt ist, können Sie Eigene zeichnen. Dazu klicken Sie mit der linken Maustaste, und ziehen Sie den Auswahlbereich, um das Objekt einzuschließen.

    Screenshot, der zeigt, wie sie ein Bild markieren.

  9. Nach der Auswahl des Objekts innerhalb des Bilds werden Sie mit einer kleinen Eingabeaufforderung aufgefordert, "Region-Tag hinzufügen" hinzuzufügen. Wählen Sie ihr zuvor erstelltes Tag ('Cup', im obigen Beispiel) aus, oder wenn Sie weitere Tags hinzufügen, geben Sie dies ein, und klicken Sie auf die Schaltfläche "+" (Plus).

    Screenshot des Tags, 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 in der oberen rechten Ecke des Blatts auf das X klicken) und dann auf das nächste Bild klicken. Sobald Sie das nächste Bild fertig 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, z. B. das folgende Bild:

    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, der die Schaltfläche

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

    Screenshot, der die Schaltfläche

    Screenshot, der die erste Iteration der Schulung zeigt.

  13. Nachdem sie erstellt wurde, können Sie zwei Schaltflächen mit dem Namen "Standard - und Vorhersage-URL erstellen" anzeigen. 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 die iterationsmarkiert, die als Standard gekennzeichnet wurde. Wenn Sie später eine neue Iteration vornehmen und als Standard aktualisieren, müssen Sie den Code nicht ändern.

  14. Nachdem Sie auf die Vorhersage-URL geklickt haben, öffnen Sie Editor, kopieren und einfügen Sie die URL (auch als " Vorhersageendpunkt" bezeichnet) und den Dienstvorhersageschlüssel, damit Sie sie später im Code abrufen können.

    Screenshot des Vorhersageendpunkts und des Präditionsschlüssels.

Kapitel 3 – Einrichten des Unity-Projekts

Im Folgenden sehen Sie eine typische Einrichtung 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, der die Schaltfläche

  2. Jetzt müssen Sie einen Unity-Projektnamen angeben. Insert CustomVisionObjDetection. Stellen Sie sicher, dass der Projekttyp auf 3D festgelegt ist, und legen Sie den Speicherort an einer für Sie geeigneten Stelle fest (denken Sie daran, dass die Stammverzeichnisse besser sind). Klicken Sie dann auf "Projekt erstellen".

    Screenshot mit den Projektdetails und der Auswahl von

  3. Wenn Unity geöffnet ist, lohnt es sich, den Standardmäßigen Skript-Editor auf Visual Studio festzulegen. Wechseln Sie zu "Einstellungen bearbeiten>", 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, der zeigt, wo der Externe Skript-Editor in Visual Studio geändert werden soll.

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

    Screenshot, der die Schaltfläche

  5. Stellen Sie im gleichen Fenster mit den Buildeinstellungen sicher, dass Folgendes festgelegt ist:

    1. Zielgerät ist auf HoloLens festgelegt

    2. Buildtyp ist auf D3D festgelegt

    3. SDK ist auf "Neueste Installation" festgelegt.

    4. Visual Studio-Version ist auf "Neueste Installation" festgelegt.

    5. Build und Ausführung ist auf den lokalen Computer festgelegt.

    6. Die übrigen Einstellungen in den Buildeinstellungen sollten jetzt als Standard beibehalten werden.

      Screenshot der Konfigurationsoptionen für die Buildeinstellung.

  6. Klicken Sie im selben Fenster "Buildeinstellungen " auf die Schaltfläche "Playereinstellungen ". Dadurch wird der zugehörige Bereich im 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" folgendes:

      1. Skripting Runtime Version sollte Experimental (.NET 4.6 Equivalent) sein, wodurch ein Neustart des Editors ausgelöst wird.

      2. Scripting Back-End sollte .NET sein.

      3. API-Kompatibilitätsstufe sollte .NET 4.6 sein.

        Screenshot der Option

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

      1. InternetClient

      2. Webcam

      3. SpatialPerception

        Screenshot der oberen Hälfte der Konfigurationsoptionen für Funktionen.Screenshot der unteren Hälfte der Konfigurationsoptionen für Funktionen.

    3. Klicken Sie weiter unten im Bereich unter "XR-Einstellungen" (unter "Veröffentlichungseinstellungen" zu finden) auf "Virtual Reality Unterstützt", und stellen Sie dann sicher, dass das Windows Mixed Reality SDK hinzugefügt wird.

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

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

  9. Schließen Sie das Fenster Buildeinstellungen.

  10. Klicken Sie im Editor auf ">Projekteinstellungen>bearbeiten".

    Screenshot der ausgewählten 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 Steckplatz hinzu, indem Sie die Größenvariable um eins erhöhen (in diesem Beispiel war es 8, sodass wir es 9 gemacht haben). Ein neuer Steckplatz wird in der letzten Position des Arrays angezeigt, wie unten dargestellt:

    Screenshot, der das Array

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

    Screenshot, der den Legacy-Shader/Transparent/Diffuse-Shader hervorhebung.

Kapitel 4 – Importieren des CustomVisionObjDetection Unity-Pakets

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

[TIPP] Alle von Unity unterstützten Objekte, einschließlich ganzer Szenen, können in eine Unitypackage-Datei verpackt und in anderen Projekten exportiert/importiert werden. Es ist die sicherste und effizienteste Möglichkeit, Ressourcen zwischen verschiedenen Unity-Projekten zu verschieben.

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

  1. Klicken Sie mit dem Unity-Dashboard vor Ihnen im Menü oben auf dem Bildschirm auf "Assets", und klicken Sie dann auf "Paket benutzerdefiniertes Paket > importieren".

    Screenshot, der die Menüoption

  2. Verwenden Sie die Dateiauswahl, um das Azure-MR-310.unitypackage-Paket auszuwählen, und klicken Sie auf " Öffnen". Ihnen wird eine Liste der Komponenten für dieses Objekt angezeigt. Bestätigen Sie den Import, indem Sie auf die Schaltfläche "Importieren " klicken.

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

  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, der den Inhalt des Ordners

    1. Der Ordner "Materialien " enthält das Material, das vom Blickcursor verwendet wird.

    2. Der Ordner Plugins enthält die Newtonsoft-DLL, die vom Code zum Deserialisieren der Dienstwebantwort verwendet wird. Die beiden (2) verschiedenen Versionen im Ordner und Unterordner 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 prefabs , die in der Szene enthalten sind. 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. The Label, which is the UI object used to display the object tag in the scene when required.
      3. Das SpatialMapping-Objekt, das es der Anwendung ermöglicht, mithilfe der räumlichen Nachverfolgung von 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 das ObjDetectionScene, um die Szene zu laden, die Sie für diesen Kurs verwenden werden.

    Screenshot des Ordners

    Hinweis

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

Kapitel 5 – Erstellen der CustomVisionAnalyser-Klasse.

An diesem Punkt sind Sie bereit, Code zu schreiben. Sie beginnen mit der CustomVisionAnalyser-Klasse .

Hinweis

Die Aufrufe des custom Vision Service, der im unten gezeigten Code ausgeführt wird, werden mithilfe der REST-API für benutzerdefinierte Vision ausgeführt. Mithilfe dieser Vorgehensweise erfahren Sie, wie Sie diese API implementieren und nutzen können (nützlich, um zu verstehen, wie Sie etwas ähnliches selbst implementieren). Beachten Sie, dass Microsoft ein custom Vision SDK anbietet, das auch zum Tätigen von Aufrufen an den Dienst verwendet werden kann. Weitere Informationen finden Sie im Artikel zum Custom Vision SDK.

Diese Klasse ist für Folgendes verantwortlich:

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

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

  • Empfangen der Antwort als JSON-Zeichenfolge.

  • Deserialisieren der Antwort und Übergeben der resultierenden Vorhersage an die SceneOrganiser-Klasse , die die Anzeige der Antwort übernimmt.

So erstellen Sie diese Klasse:

  1. Klicken Sie mit der rechten Maustaste in den Objektordner, der sich im Projektbereich befindet, und klicken Sie dann auf "Ordner erstellen>". Rufen Sie den Ordner Skripts auf.

    Screenshot, der zeigt, wie der Ordner

  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 auf "C#-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 weiter oben in Editor kopiert, in Kapitel 2, Schritt 14.

  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öchten. Lassen Sie diese Zeilen daher vorerst auskommentiert.

        /// <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 Methoden Start() und Update(), 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 Sorgen ü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 auf "C#-Skript erstellen>". 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 Methoden Start() und Update() 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 Kompilierungsfehler in Kapitel 10, 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 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 fest, damit Kollisionen 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 auf "C#-Skript erstellen>". 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 der SpatialMapping-Klasse auf die folgenden Namespaces 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. Add the Awake() and Start():

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Initialize and configure the collider
            spatialMappingCollider = gameObject.GetComponent<SpatialMappingCollider>();
            spatialMappingCollider.surfaceParent = this.gameObject;
            spatialMappingCollider.freezeUpdates = false;
            spatialMappingCollider.layer = physicsLayer;
    
            // define the mask
            PhysicsRaycastMask = 1 << physicsLayer;
    
            // set the object as active one
            gameObject.SetActive(true);
        }
    
  6. 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 das Einrichten des Cursors an der richtigen Position im realen Raum verantwortlich, indem sie den SpatialMappingCollider verwenden, 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 auf "C#-Skript erstellen>". 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 Sorgen über den Fehler für die SceneOrganiser-Klasse, die nicht gefunden wird, erstellen Sie 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 sieht wie folgt aus:

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

  • Wenn ein Objekt erkannt wird, ist es dafür verantwortlich, seine Position in der realen Welt zu berechnen und eine Tagbeschriftung in der Nähe des Objekts mit dem entsprechenden Tagnamen zu platzieren.

So erstellen Sie diese Klasse:

  1. Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann auf "C#-Skript erstellen>". 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 der SceneOrganiser-Klasse auf die folgenden Namespaces 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 Methoden Start() und Update().

  6. Fügen Sie unter den 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 Beschriftung in der Szene instanziiert (die an diesem Punkt für den Benutzer unsichtbar ist). Es platziert auch das Quad (auch unsichtbar), wo das Bild platziert wird, und überlappt mit der realen Welt. Dies ist wichtig, da die aus dem Dienst abgerufenen Boxkoordinaten nach der Analyse zurück 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 Beschriftungstexts mit dem Tag der Vorhersage mit der höchsten Vertrauenswürdigkeit.
    • Aufrufen der Berechnung des Umgebenden Felds auf dem Quad-Objekt, zuvor positioniert und die Beschriftung in der Szene platziert.
    • Anpassen der Beschriftungstiefe mithilfe eines Raycasts in Richtung der Begrenzungsbox, die gegen das Objekt in der realen Welt kollidieren sollte.
    • Zurücksetzen des Aufnahmevorgangs, 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 aus dem Dienst abgerufenen Begrenzungsfeldkoordinaten erforderlich sind, und erstellen Sie sie proportional auf dem Quad 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 Kommentare aus den 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 ImageCapture-Klasse "Konnte nicht gefunden" Nachricht, Sie erstellen sie im nächsten Kapitel.

Kapitel 10 – Erstellen der ImageCapture-Klasse

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

Diese Klasse ist für Folgendes verantwortlich:

  • Aufnehmen eines Bilds mithilfe der HoloLens-Kamera und Speichern im App-Ordner .
  • Behandeln von Tippgesten vom Benutzer.

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 auf "C#-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 oben in 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 for Awake() and Start() methods now to be added:

        /// <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 Tippbewegung 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 verwendet wird, und speichern Sie das Bild:

        /// <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 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 mit ausgewählter Hauptkamera auf " Komponente hinzufügen", suchen Sie dann nach "SceneOrganiser "-Skript, und doppelklicken Sie, um es hinzuzufügen.

    Screenshot des SceneOrganizer-Skripts.

  3. Öffnen Sie im Projektbereich den Ordner "Prefabs", ziehen Sie das Label Prefab in den Eingabebereich "Label empty reference target input" in the SceneOrganiser script that you have just added to the Main Camera,as shown in the image below:

    Screenshot des Skripts, das Sie der Hauptkamera hinzugefügt haben.

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

  5. Klicken Sie im Inspektorbereich mit ausgewähltem GazeCursor auf " Komponente hinzufügen", suchen Sie dann nach GazeCursor-Skript , und doppelklicken Sie, um es hinzuzufügen.

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

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

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

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

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

Kapitel 12 - Vor dem Gebäude

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

Bevor Sie vorgehen, stellen Sie folgendes sicher:

  • Alle im Kapitel 3 genannten Einstellungen sind korrekt festgelegt.

  • Das Skript SceneOrganiser ist an das Main Camera-Objekt angefügt.

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

  • Das Skript SpatialMapping ist mit dem SpatialMapping-Objekt verknüpft.

  • In Kapitel 5, Schritt 6:

    • Stellen Sie sicher, dass Sie Ihren Dienstvorhersageschlüssel in die Variable "predictionKey " einfügen.
    • Sie haben Ihren Vorhersageendpunkt in die predictionEndpoint-Klasse eingefügt.

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

Sie sind jetzt bereit, Ihre Anwendung als UWP-Lösung zu erstellen, die Sie auf microsoft HoloLens bereitstellen können. So beginnen Sie den Buildprozess:

  1. Wechseln Sie zu " Dateibuildeinstellungen > ".

  2. Tick Unity C#-Projekte.

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

    Screenshot, der die Schaltfläche

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

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

  6. Nachdem 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 sie möglicherweise nicht immer über Ihren Fenstern angezeigt wird, sondern Sie über dem Hinzufügen eines neuen Fensters informiert).

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

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

    2. Wechseln sie zu den erweiterten Optionen für Netzwerk- und Internet-WLAN>>

    3. Notieren Sie sich die IPv4-Adresse .

    4. Navigieren Sie als Nächstes zurück zu "Einstellungen" und dann zu "Update & Sicherheit>für Entwickler".

    5. Legen Sie den Entwicklermodus aktiviert fest.

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

  9. Wählen Sie in der Lösungskonfiguration "Debuggen" aus.

  10. Wählen Sie in der Lösungsplattform x86, Remotecomputer aus. Sie werden aufgefordert, die IP-Adresse eines Remotegeräts (die Microsoft HoloLens, in diesem Fall, die Sie angegeben haben) einzufügen.

    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 nun in der Liste der installierten Apps auf Ihrer Microsoft HoloLens angezeigt werden, damit sie gestartet werden können!

So verwenden Sie die Anwendung:

  • Sehen Sie sich ein Objekt an, das Sie mit Ihrem benutzerdefinierten Azure Vision-Dienst, der Objekterkennung trainiert haben, und verwenden Sie die Tippbewegung.
  • Wenn das Objekt erfolgreich erkannt wurde, wird ein Weltzeichenbeschriftungstext 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 neu trainieren. Am Anfang müssen Sie wahrscheinlich auch die Begrenzungsboxen korrigieren, um genauer zu sein und den Dienst neu zu trainieren.

Hinweis

Der platzierte Beschriftungstext wird möglicherweise nicht in der Nähe des Objekts angezeigt, wenn die Microsoft HoloLens-Sensoren und/oder "SpatialTrackingComponent" in Unity die entsprechenden Kollidierungen im Verhältnis zu den realen Objekten nicht platzieren. Versuchen Sie, die Anwendung auf einer anderen Oberfläche zu verwenden, wenn dies der Fall ist.

Ihre benutzerdefinierte Vision- und 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 kann, und dann eine ungefähre Position für dieses Objekt im 3D-Raum bereitstellen.

Screenshot einer Mixed Reality-App, die die Azure Custom Vision- und Objekterkennungs-API nutzt.

Zusatzübungen

Übung 1

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

Übung 2

Schulen Sie Ihren custom Vision Service, um weitere Objekte zu erkennen.

Übung 3

Wiedergeben eines Sounds, wenn ein Objekt erkannt wird.

Übung 4

Verwenden Sie die API, um Ihren Dienst mit denselben Bildern zu trainieren, die Ihre App analysiert, sodass der Dienst genauer ist (sowohl Vorhersage als auch Schulung gleichzeitig durchführen).