HoloLens (1. Generation) und Azure 302b: Benutzerdefinierte Vision
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 in einem bereitgestellten Bild mithilfe von Azure Custom Vision-Funktionen in einer Mixed Reality-Anwendung erkennen.
Mit diesem Dienst können Sie ein Machine Learning-Modell mithilfe von Objektbildern trainieren. Anschließend verwenden Sie das trainierte Modell, um ähnliche Objekte zu erkennen, wie die Kameraaufnahme von Microsoft HoloLens oder eine Kamera, die mit Ihrem PC verbunden ist, um immersive Headsets (VR) zu erkennen.
Azure Custom Vision ist ein Microsoft Cognitive Service, 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 oder zu klassifizieren. Der Dienst bietet ein einfaches, einfach zu bedienendes Onlineportal, um den Prozess zu optimieren. Weitere Informationen finden Sie auf der Seite "Azure Custom Vision Service".
Nach Abschluss dieses Kurses haben Sie eine Mixed Reality-Anwendung, die in zwei Modi arbeiten kann:
Analysemodus: Manuelles Einrichten des benutzerdefinierten Vision-Diensts durch Hochladen von Bildern, Erstellen von Tags und Schulung des Diensts zum Erkennen verschiedener Objekte (in diesem Fall Maus und Tastatur). Anschließend erstellen Sie eine HoloLens-App, die Bilder mithilfe der Kamera erfasst, und versuchen, diese Objekte in der realen Welt zu erkennen.
Schulungsmodus: Sie implementieren Code, der einen "Schulungsmodus" in Ihrer App aktiviert. Mit dem Schulungsmodus können Sie Bilder mithilfe der HoloLens-Kamera aufnehmen, die aufgenommenen Bilder in den Dienst hochladen und das benutzerdefinierte Visionsmodell trainieren.
In diesem Kurs erfahren Sie, wie Sie die Ergebnisse aus dem Custom Vision Service 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 302b: Benutzerdefinierte Vision | ✔️ | ✔️ |
Hinweis
Während sich dieser Kurs hauptsächlich auf HoloLens konzentriert, können Sie auch das, was Sie in diesem Kurs lernen, auf Windows Mixed Reality immersive Headsets (VR) anwenden. Da immersive Headsets (VR) keine barrierefreien Kameras haben, benötigen Sie eine externe Kamera, die mit Ihrem PC verbunden ist. Während Sie den Kurs befolgen, werden Notizen zu allen Änderungen angezeigt, die Sie möglicherweise verwenden müssen, um immersive Headsets (VR) zu unterstützen.
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:
- Ein Entwicklungs-PC, kompatibel mit Windows Mixed Reality für die Entwicklung von immersiven (VR)-Headsets
- Windows 10 Fall Creators Update (oder höher) mit aktivierter Entwicklermodus
- Das neueste Windows 10 SDK
- Unity 2017.4
- Visual Studio 2017
- Ein immersives Windows Mixed Reality-Headset (VR) oder Microsoft HoloLens mit aktiviertem Entwicklermodus
- Eine Kamera, die an Ihren PC angeschlossen ist (für die entwicklung immersive Headsets)
- Internetzugriff für Azure-Setup und Abruf der benutzerdefinierten Vision-API
- Eine Reihe von mindestens fünf (5) Bildern (zehn (10) empfohlen) für jedes Objekt, das vom Custom Vision Service erkannt werden soll. Wenn Sie möchten, können Sie die bilder verwenden , die bereits mit diesem Kurs (einer Computer maus und einer Tastatur) bereitgestellt wurden.
Vor der Installation
- 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).
- Richten Sie Ihre HoloLens ein, und testen Sie sie. Wenn Sie Unterstützung beim Einrichten Ihrer HoloLens benötigen, besuchen Sie den HoloLens-Setupartikel.
- 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 Portal für benutzerdefinierte Vision-Dienste
Um den benutzerdefinierten Vision-Dienst in Azure zu verwenden, müssen Sie eine Instanz des Diensts konfigurieren, die Ihrer Anwendung zur Verfügung gestellt wird.
Navigieren Sie zuerst zur Hauptseite des Custom Vision Service.
Klicken Sie auf die Schaltfläche "Erste Schritte ".
Melden Sie sich beim Custom Vision Service Portal an.
Hinweis
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.
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".
Nachdem Sie den Bedingungen zugestimmt haben, werden Sie zum Abschnitt "Projekte " des Portals navigiert. Klicken Sie auf Neues Projekt.
Auf der rechten Seite wird eine Registerkarte angezeigt, die Sie auffordert, einige Felder für das Projekt anzugeben.
Fügen Sie einen Namen für Ihr Projekt ein.
Fügen Sie eine Beschreibung für Ihr Projekt ein (optional).
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.
Festlegen der Projekttypen auf Klassifizierung
Legen Sie die Domänen als "Allgemein" fest.
Wenn Sie mehr über Azure-Ressourcengruppen erfahren möchten, besuchen Sie bitte den Artikel zur Ressourcengruppe.
Nachdem Sie fertig sind, klicken Sie auf " Projekt erstellen", werden Sie zur Projektseite "Custom Vision Service" umgeleitet.
Kapitel 2 – Schulung Ihres benutzerdefinierten Vision-Projekts
Sobald Sie sich im Portal "Custom Vision" befindet, besteht Ihr Hauptziel darin, Ihr Projekt zu schulen, um bestimmte Objekte in Bildern zu erkennen. Sie benötigen mindestens fünf (5) Bilder, obwohl zehn (10) bevorzugt werden, für jedes Objekt, das Ihre Anwendung erkennen soll. Sie können die in diesem Kurs bereitgestellten Bilder (eine Computer maus und eine Tastatur) verwenden.
So trainieren Sie Ihr Custom Vision Service-Projekt:
Klicken Sie auf die + Schaltfläche neben "Tags".
Fügen Sie den Namen des Objekts hinzu, das Sie erkennen möchten. Klicken Sie auf Speichern.
Sie werden feststellen, dass Ihr Tag hinzugefügt wurde (Möglicherweise müssen Sie Ihre Seite neu laden, damit sie angezeigt wird). Klicken Sie neben Dem neuen Tag auf das Kontrollkästchen, falls es noch nicht aktiviert ist.
Klicken Sie in der Mitte der Seite auf " Bilder hinzufügen".
Klicken Sie auf " Lokale Dateien durchsuchen", und suchen Sie dann die Bilder, die Sie hochladen möchten, mit mindestens fünf (5). Denken Sie daran, dass alle diese Bilder das Objekt enthalten sollten, das Sie trainieren.
Hinweis
Sie können mehrere Bilder gleichzeitig auswählen, um sie hochzuladen.
Sobald Sie die Bilder auf der Registerkarte sehen können, wählen Sie im Feld "Meine Tags" das entsprechende Tag aus.
Klicken Sie auf "Dateien hochladen". Die Dateien werden mit dem Hochladen beginnen. Nachdem Sie die Bestätigung des Uploads erhalten haben, klicken Sie auf "Fertig".
Wiederholen Sie denselben Vorgang, um ein neues Tag namens Tastatur zu erstellen und die entsprechenden Fotos dafür hochzuladen. Deaktivieren Sie die Maus, nachdem Sie die neuen Tags erstellt haben, damit das Fenster "Bilder hinzufügen" angezeigt wird.
Nachdem Sie beide Kategorien eingerichtet haben, klicken Sie auf "Trainieren", und die erste Schulungsiteration beginnt mit dem Erstellen.
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".
Hinweis
Die Endpunkt-URL, die von diesem bereitgestellt wird, wird auf die standardmäßige Iteration festgelegt. Wenn Sie später eine neue Iteration vornehmen und als Standard aktualisieren, müssen Sie den Code nicht ändern.
Nachdem Sie auf " Vorhersage-URL" geklickt haben, öffnen Sie Editor, kopieren Und einfügen Sie die URL und den Vorhersageschlüssel, damit Sie sie später im Code abrufen können.
Klicken Sie oben rechts auf dem Bildschirm auf die Kog .
Kopieren Sie den Schulungsschlüssel , und fügen Sie ihn zur späteren Verwendung in ein Editor ein.
Kopieren Sie auch Ihre Projekt-ID, und fügen Sie sie auch zur späteren Verwendung in Ihre Editordatei ein.
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.
Öffnen Sie Unity , und klicken Sie auf "Neu".
Jetzt müssen Sie einen Unity-Projektnamen angeben. Fügen Sie AzureCustomVision ein . Stellen Sie sicher, dass die Projektvorlage auf 3D festgelegt ist. 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".
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 2017. Schließen Sie das Fenster Einstellungen.
Wechseln Sie als Nächstes zu "Dateibuildeinstellungen>", und wählen Sie Universelle Windows-Plattform aus, und klicken Sie dann auf die Schaltfläche "Plattform wechseln", um Ihre Auswahl anzuwenden.
Während Sie sich noch in den Dateibuildeinstellungen > befinden , und stellen Sie sicher, dass:
Zielgerät ist auf HoloLens festgelegt
Legen Sie für die immersiven Headsets "Zielgerät " auf "Jedes Gerät" fest.
Buildtyp ist auf D3D festgelegt
SDK ist auf "Neueste Installation" festgelegt.
Visual Studio-Version ist auf "Neueste Installation" festgelegt.
Build und Ausführung ist auf den lokalen Computer festgelegt.
Speichern Sie die Szene, und fügen Sie sie dem Build hinzu.
Wählen Sie dazu "Offene Szenen hinzufügen" aus. Ein Speicherfenster wird angezeigt.
Erstellen Sie einen neuen Ordner für diese Und jede zukünftige Szene, und wählen Sie dann die Schaltfläche "Neuer Ordner " aus, um einen neuen Ordner zu erstellen, nennen Sie ihn "Szenen".
Öffnen Sie den neu erstellten Ordner "Szenen ", und klicken Sie dann im Feld "Dateiname: Textfeld", geben Sie "CustomVisionScene" ein, und klicken Sie dann auf "Speichern".
Beachten Sie, dass Sie Ihre Unity-Szenen im Ordner "Assets " speichern müssen, da sie dem Unity-Projekt zugeordnet sein müssen. Das Erstellen des Szenenordners (und andere ähnliche Ordner) ist eine typische Methode zum Strukturieren eines Unity-Projekts.
Die übrigen Einstellungen in den Buildeinstellungen sollten jetzt als Standard beibehalten werden.
Klicken Sie im Fenster "Buildeinstellungen " auf die Schaltfläche "Spielereinstellungen ". Dadurch wird der zugehörige Bereich im Bereich geöffnet, in dem sich der Inspektor befindet.
In diesem Bereich müssen einige Einstellungen überprüft werden:
Auf der Registerkarte "Andere Einstellungen" folgendes:
Die Skripting-Runtime-Version sollte experimental (.NET 4.6 Equivalent) sein, wodurch ein Neustart des Editors ausgelöst wird.
Scripting Back-End sollte .NET sein
API-Kompatibilitätsstufe sollte .NET 4.6 sein
Aktivieren Sie auf der Registerkarte "Veröffentlichungseinstellungen " unter "Funktionen" Folgendes:
InternetClient
Webcam
Mikrofon
Klicken Sie weiter unten im Bereich unter "XR-Einstellungen" (unter "Veröffentlichungseinstellungen" finden Sie auf "Virtual Reality Unterstützt"), und vergewissern Sie sich, dass das Windows Mixed Reality SDK hinzugefügt wird.
Zurück in Buildeinstellungen Unity C#-Projekte ist nicht mehr abgeblentet. Aktivieren Sie das Kontrollkästchen neben diesem.
Schließen Sie das Fenster Buildeinstellungen.
Speichern Sie Die Szene und das Projekt (FILE > SAVE SCENE / FILE > SAVE PROJECT).
Kapitel 4 – Importieren der Newtonsoft-DLL in Unity
Wichtig
Wenn Sie die Unity Set up-Komponente dieses Kurses überspringen und direkt in Code fortfahren möchten, können Sie dieses Azure-MR-302b.unitypackage herunterladen, es als benutzerdefiniertes Paket in Ihr Projekt importieren und dann von Kapitel 6 fortfahren.
Dieser Kurs erfordert die Verwendung der Newtonsoft-Bibliothek , die Sie als DLL zu Ihren Ressourcen hinzufügen können. Das Paket, das diese Bibliothek enthält , kann über diesen Link heruntergeladen werden. Um die Newtonsoft-Bibliothek in Ihr Projekt zu importieren, verwenden Sie das Unity-Paket, das in diesem Kurs enthalten ist.
Fügen Sie das UNITY-Paket mithilfe der Menüoption "Benutzerdefiniertes>Paket> importieren" zu Unity hinzu.
Stellen Sie im Popupfeld "Unity-Paket importieren" sicher, dass alles unter (und einschließlich) Plug-Ins ausgewählt ist.
Klicken Sie auf die Schaltfläche "Importieren ", um dem Projekt die Elemente hinzuzufügen.
Wechseln Sie in der Projektansicht zum Ordner Newtonsoft unter Plugins , und wählen Sie das Newtonsoft.Json-Plug-In aus.
Stellen Sie beim ausgewählten Newtonsoft.Json-Plug-In sicher, dass "Any Platform" deaktiviert ist, und stellen Sie sicher, dass WSAPlayer ebenfalls deaktiviert ist, und klicken Sie dann auf "Übernehmen". Dies ist nur zu bestätigen, dass die Dateien ordnungsgemäß konfiguriert sind.
Hinweis
Wenn Sie diese Plug-Ins markieren, werden sie nur im Unity-Editor verwendet. Es gibt einen anderen Satz davon im WSA-Ordner, der verwendet wird, nachdem das Projekt aus Unity exportiert wurde.
Als Nächstes müssen Sie den WSA-Ordner innerhalb des Newtonsoft-Ordners öffnen. Es wird eine Kopie derselben Datei angezeigt, die Sie gerade konfiguriert haben. Wählen Sie die Datei aus, und stellen Sie dann im Inspektor sicher, dass
- Jede Plattform ist deaktiviert.
- Nur WSAPlayer ist aktiviert.
- Der Vorgang wird überprüft.
Kapitel 5 – Kameraeinrichtung
Wählen Sie im Hierarchiebereich die Hauptkamera aus.
Nach der Auswahl können Sie alle Komponenten der Hauptkamera im Inspektorbereich anzeigen.
Das Kameraobjekt muss die Hauptkamera heißen (beachten Sie die Schreibweise!)
Das Hauptkameratag muss auf "MainCamera" festgelegt werden (beachten Sie die Schreibweise!)
Stellen Sie sicher, dass die Transformationsposition auf 0, 0, 0 festgelegt ist.
Legen Sie "Flags löschen" auf "Volltonfarbe" fest (dies wird für immersives Headset ignoriert).
Legen Sie die Hintergrundfarbe der Kamerakomponente auf Schwarz, Alpha 0 (Hex-Code: #00000000) fest (ignorieren Sie dies für immersives Headset).
Kapitel 6 – Erstellen der CustomVisionAnalyser-Klasse.
An diesem Punkt sind Sie bereit, Code zu schreiben.
Sie beginnen mit der CustomVisionAnalyser-Klasse .
Hinweis
Die Aufrufe des benutzerdefinierten Vision-Diensts im unten gezeigten Code werden mithilfe der REST-API "Custom 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 Service SDK anbietet, das auch zum Tätigen von Anrufen an den Dienst verwendet werden kann. Weitere Informationen finden Sie im Artikel zum Custom Vision Service 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:
Klicken Sie mit der rechten Maustaste auf den Ressourcenordner , der sich im Projektbereich befindet, und klicken Sie dann auf "Ordner erstellen > ". Rufen Sie den Ordner Skripts auf.
Doppelklicken Sie auf den soeben erstellten Ordner, um ihn zu öffnen.
Klicken Sie mit der rechten Maustaste in den Ordner, und klicken Sie dann auf "C#-Skript erstellen>". Nennen Sie das Skript CustomVisionAnalyser.
Doppelklicken Sie auf das neue CustomVisionAnalyser-Skript , um es mit Visual Studio zu öffnen.
Aktualisieren Sie die Namespaces oben in der Datei so, dass sie mit den folgenden Übereinstimmend übereinstimmen:
using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking; using Newtonsoft.Json;
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> /// Byte array of the image to submit for analysis /// </summary> [HideInInspector] public byte[] imageBytes;
Hinweis
Stellen Sie sicher, dass Sie Ihren Vorhersageschlüssel in die Variable "predictionKey " und ihren Vorhersageendpunkt in die Variable "predictionEndpoint " einfügen. Sie haben diese weiter oben im Kurs in Editor kopiert.
Code für Awake() muss jetzt hinzugefügt werden, um die Instanzvariable zu initialisieren:
/// <summary> /// Initialises this class /// </summary> private void Awake() { // Allows this instance to behave like a singleton Instance = this; }
Löschen Sie die Methoden Start() und Update().
Fügen Sie als Nächstes die Coroutine (mit der statischen GetImageAsByteArray()-Methode darunter hinzu , die die Ergebnisse der Analyse des Bilds abruft, das 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) { WWWForm webForm = new WWWForm(); using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm)) { // Gets a byte array out of the saved image imageBytes = GetImageAsByteArray(imagePath); unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream"); unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey); // The upload handler will help uploading the byte array with the request unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes); unityWebRequest.uploadHandler.contentType = "application/octet-stream"; // The download handler will help receiving the analysis from Azure unityWebRequest.downloadHandler = new DownloadHandlerBuffer(); // Send the request yield return unityWebRequest.SendWebRequest(); string jsonResponse = unityWebRequest.downloadHandler.text; // The response will be in JSON format, therefore it needs to be deserialized // The following lines refers to a class that you will build in later Chapters // Wait until then to uncomment these lines //AnalysisObject analysisObject = new AnalysisObject(); //analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse); //SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject); } } /// <summary> /// Returns the contents of the specified image file as a byte array. /// </summary> static byte[] GetImageAsByteArray(string imageFilePath) { FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read); BinaryReader binaryReader = new BinaryReader(fileStream); return binaryReader.ReadBytes((int)fileStream.Length); }
Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.
Kapitel 7 – 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.
Warnung
Es ist wichtig, dass Sie den Endpunkt beachten, den der Custom Vision Service ihnen bereitstellt, da die folgende JSON-Struktur für die Arbeit mit Custom Vision Prediction v2.0 eingerichtet wurde. Wenn Sie über eine andere Version verfügen, müssen Sie möglicherweise die folgende Struktur aktualisieren.
So erstellen Sie diese Klasse:
Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann auf "C#-Skript erstellen>". Rufen Sie das Skript CustomVisionObjects auf.
Doppelklicken Sie auf das neue CustomVisionObjects-Skript , um es mit Visual Studio zu öffnen.
Fügen Sie am Anfang der -Datei die folgenden Namespaces hinzu:
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking;
Löschen Sie die Methoden Start() und Update() innerhalb der CustomVisionObjects-Klasse . Diese Klasse sollte jetzt leer sein.
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 /// </summary> [Serializable] public class AnalysisObject { public List<Prediction> Predictions { get; set; } } [Serializable] public class Prediction { public string TagName { get; set; } public double Probability { get; set; } }
Kapitel 8 – Erstellen der VoiceRecognizer-Klasse
Diese Klasse erkennt die Spracheingabe des Benutzers.
So erstellen Sie diese Klasse:
Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann auf "C#-Skript erstellen>". Rufen Sie das Skript VoiceRecognizer auf.
Doppelklicken Sie auf das neue VoiceRecognizer-Skript , um es mit Visual Studio zu öffnen.
Fügen Sie die folgenden Namespaces oberhalb der VoiceRecognizer-Klasse hinzu:
using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Windows.Speech;
Fügen Sie dann die folgenden Variablen in der VoiceRecognizer-Klasse oberhalb der Start() -Methode hinzu:
/// <summary> /// Allows this class to behave like a singleton /// </summary> public static VoiceRecognizer Instance; /// <summary> /// Recognizer class for voice recognition /// </summary> internal KeywordRecognizer keywordRecognizer; /// <summary> /// List of Keywords registered /// </summary> private Dictionary<string, Action> _keywords = new Dictionary<string, Action>();
Fügen Sie die Methoden Awake() und Start() hinzu, von denen letztere die Benutzerstichwörter so einrichten, dass sie erkannt werden, wenn ein Tag einem Bild zugeordnet wird:
/// <summary> /// Called on initialization /// </summary> private void Awake() { Instance = this; } /// <summary> /// Runs at initialization right after Awake method /// </summary> void Start () { Array tagsArray = Enum.GetValues(typeof(CustomVisionTrainer.Tags)); foreach (object tagWord in tagsArray) { _keywords.Add(tagWord.ToString(), () => { // When a word is recognized, the following line will be called CustomVisionTrainer.Instance.VerifyTag(tagWord.ToString()); }); } _keywords.Add("Discard", () => { // When a word is recognized, the following line will be called // The user does not want to submit the image // therefore ignore and discard the process ImageCapture.Instance.ResetImageCapture(); keywordRecognizer.Stop(); }); //Create the keyword recognizer keywordRecognizer = new KeywordRecognizer(_keywords.Keys.ToArray()); // Register for the OnPhraseRecognized event keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized; }
Löschen Sie die Update()- Methode.
Fügen Sie den folgenden Handler hinzu, der immer dann aufgerufen wird, wenn Spracheingaben erkannt werden:
/// <summary> /// Handler called when a word is recognized /// </summary> private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args) { Action keywordAction; // if the keyword recognized is in our dictionary, call that Action. if (_keywords.TryGetValue(args.text, out keywordAction)) { keywordAction.Invoke(); } }
Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.
Hinweis
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 9 – Erstellen der CustomVisionTrainer-Klasse
Diese Klasse verkettet eine Reihe von Webaufrufen, um den Custom Vision Service zu trainieren. Jeder Aufruf wird direkt über dem Code ausführlich erläutert.
So erstellen Sie diese Klasse:
Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann auf "C#-Skript erstellen>". Rufen Sie das Skript CustomVisionTrainer auf.
Doppelklicken Sie auf das neue CustomVisionTrainer-Skript , um es mit Visual Studio zu öffnen.
Fügen Sie die folgenden Namespaces oberhalb der CustomVisionTrainer-Klasse hinzu:
using Newtonsoft.Json; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; using UnityEngine.Networking;
Fügen Sie dann die folgenden Variablen in der CustomVisionTrainer-Klasse oberhalb der Start() -Methode hinzu.
Hinweis
Die hier verwendete Schulungs-URL wird in der Dokumentation "Custom Vision Training 1.2 " bereitgestellt und weist eine Struktur von: https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
Weitere Informationen finden Sie in der Referenz-API für custom Vision Training v1.2.Warnung
Es ist wichtig, dass Sie den Endpunkt beachten, den der Custom Vision Service für den Schulungsmodus bereitstellt, da die verwendete JSON-Struktur (innerhalb der CustomVisionObjects-Klasse) für die Arbeit mit Custom Vision Training v1.2 eingerichtet wurde. Wenn Sie über eine andere Version verfügen, müssen Sie möglicherweise die Objektstruktur aktualisieren.
/// <summary> /// Allows this class to behave like a singleton /// </summary> public static CustomVisionTrainer Instance; /// <summary> /// Custom Vision Service URL root /// </summary> private string url = "https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/"; /// <summary> /// Insert your prediction key here /// </summary> private string trainingKey = "- Insert your key here -"; /// <summary> /// Insert your Project Id here /// </summary> private string projectId = "- Insert your Project Id here -"; /// <summary> /// Byte array of the image to submit for analysis /// </summary> internal byte[] imageBytes; /// <summary> /// The Tags accepted /// </summary> internal enum Tags {Mouse, Keyboard} /// <summary> /// The UI displaying the training Chapters /// </summary> private TextMesh trainingUI_TextMesh;
Wichtig
Stellen Sie sicher, dass Sie ihren Wert für Service Key (Schulungsschlüssel ) und den Wert der Projekt-ID hinzufügen, den Sie zuvor notiert haben. Dies sind die Werte, die Sie weiter oben im Kurs (Kapitel 2, Schritt 10) gesammelt haben.
Fügen Sie die folgenden Methoden Start() und Awake() hinzu. Diese Methoden werden für die Initialisierung aufgerufen und enthalten den Aufruf zum Einrichten der Benutzeroberfläche:
/// <summary> /// Called on initialization /// </summary> private void Awake() { Instance = this; } /// <summary> /// Runs at initialization right after Awake method /// </summary> private void Start() { trainingUI_TextMesh = SceneOrganiser.Instance.CreateTrainingUI("TrainingUI", 0.04f, 0, 4, false); }
Löschen Sie die Update()- Methode. Diese Klasse benötigt sie nicht.
Fügen Sie die RequestTagSelection()- Methode hinzu. Diese Methode wird zuerst aufgerufen, wenn ein Bild erfasst und im Gerät gespeichert wurde und jetzt an den Custom Vision Service übermittelt werden kann, um es zu trainieren. Diese Methode zeigt in der Schulungs-UI eine Reihe von Schlüsselwörtern an, mit denen der Benutzer das aufgenommene Bild kategorisieren kann. Außerdem wird die VoiceRecognizer-Klasse benachrichtigt, um dem Benutzer die Spracheingabe zuzuhören.
internal void RequestTagSelection() { trainingUI_TextMesh.gameObject.SetActive(true); trainingUI_TextMesh.text = $" \nUse voice command \nto choose between the following tags: \nMouse\nKeyboard \nor say Discard"; VoiceRecognizer.Instance.keywordRecognizer.Start(); }
Fügen Sie die VerifyTag()- Methode hinzu. Diese Methode empfängt die spracheingabe, die von der VoiceRecognizer-Klasse erkannt wird, und überprüft die Gültigkeit, und beginnen Sie dann mit dem Schulungsvorgang.
/// <summary> /// Verify voice input against stored tags. /// If positive, it will begin the Service training process. /// </summary> internal void VerifyTag(string spokenTag) { if (spokenTag == Tags.Mouse.ToString() || spokenTag == Tags.Keyboard.ToString()) { trainingUI_TextMesh.text = $"Tag chosen: {spokenTag}"; VoiceRecognizer.Instance.keywordRecognizer.Stop(); StartCoroutine(SubmitImageForTraining(ImageCapture.Instance.filePath, spokenTag)); } }
Fügen Sie die SubmitImageForTraining() -Methode hinzu. Diese Methode beginnt mit dem Schulungsprozess des Custom Vision Service. Der erste Schritt besteht darin, die Tag-ID aus dem Dienst abzurufen, der der überprüften Spracheingabe des Benutzers zugeordnet ist. Die Tag-ID wird dann zusammen mit dem Bild hochgeladen.
/// <summary> /// Call the Custom Vision Service to submit the image. /// </summary> public IEnumerator SubmitImageForTraining(string imagePath, string tag) { yield return new WaitForSeconds(2); trainingUI_TextMesh.text = $"Submitting Image \nwith tag: {tag} \nto Custom Vision Service"; string imageId = string.Empty; string tagId = string.Empty; // Retrieving the Tag Id relative to the voice input string getTagIdEndpoint = string.Format("{0}{1}/tags", url, projectId); using (UnityWebRequest www = UnityWebRequest.Get(getTagIdEndpoint)) { www.SetRequestHeader("Training-Key", trainingKey); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; Tags_RootObject tagRootObject = JsonConvert.DeserializeObject<Tags_RootObject>(jsonResponse); foreach (TagOfProject tOP in tagRootObject.Tags) { if (tOP.Name == tag) { tagId = tOP.Id; } } } // Creating the image object to send for training List<IMultipartFormSection> multipartList = new List<IMultipartFormSection>(); MultipartObject multipartObject = new MultipartObject(); multipartObject.contentType = "application/octet-stream"; multipartObject.fileName = ""; multipartObject.sectionData = GetImageAsByteArray(imagePath); multipartList.Add(multipartObject); string createImageFromDataEndpoint = string.Format("{0}{1}/images?tagIds={2}", url, projectId, tagId); using (UnityWebRequest www = UnityWebRequest.Post(createImageFromDataEndpoint, multipartList)) { // Gets a byte array out of the saved image imageBytes = GetImageAsByteArray(imagePath); //unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream"); www.SetRequestHeader("Training-Key", trainingKey); // The upload handler will help uploading the byte array with the request www.uploadHandler = new UploadHandlerRaw(imageBytes); // The download handler will help receiving the analysis from Azure www.downloadHandler = new DownloadHandlerBuffer(); // Send the request yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; ImageRootObject m = JsonConvert.DeserializeObject<ImageRootObject>(jsonResponse); imageId = m.Images[0].Image.Id; } trainingUI_TextMesh.text = "Image uploaded"; StartCoroutine(TrainCustomVisionProject()); }
Fügen Sie die TrainCustomVisionProject()- Methode hinzu. Sobald das Bild übermittelt und markiert wurde, wird diese Methode aufgerufen. Es wird eine neue Iteration erstellt, die mit allen vorherigen Bildern trainiert wird, die an den Dienst übermittelt wurden, sowie das soeben hochgeladene Bild. Nachdem die Schulung abgeschlossen wurde, ruft diese Methode eine Methode auf, um die neu erstellte Iteration als Standard festzulegen, sodass der Endpunkt, den Sie für die Analyse verwenden, die neueste trainierte Iteration ist.
/// <summary> /// Call the Custom Vision Service to train the Service. /// It will generate a new Iteration in the Service /// </summary> public IEnumerator TrainCustomVisionProject() { yield return new WaitForSeconds(2); trainingUI_TextMesh.text = "Training Custom Vision Service"; WWWForm webForm = new WWWForm(); string trainProjectEndpoint = string.Format("{0}{1}/train", url, projectId); using (UnityWebRequest www = UnityWebRequest.Post(trainProjectEndpoint, webForm)) { www.SetRequestHeader("Training-Key", trainingKey); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; Debug.Log($"Training - JSON Response: {jsonResponse}"); // A new iteration that has just been created and trained Iteration iteration = new Iteration(); iteration = JsonConvert.DeserializeObject<Iteration>(jsonResponse); if (www.isDone) { trainingUI_TextMesh.text = "Custom Vision Trained"; // Since the Service has a limited number of iterations available, // we need to set the last trained iteration as default // and delete all the iterations you dont need anymore StartCoroutine(SetDefaultIteration(iteration)); } } }
Fügen Sie die SetDefaultIteration() -Methode hinzu. Diese Methode legt die zuvor erstellte und trainierte Iteration als Standard fest. Nach Abschluss muss diese Methode die vorherige Iteration löschen, die im Dienst vorhanden ist. Zum Zeitpunkt des Schreibens dieses Kurses gibt es eine Beschränkung von maximal zehn (10) Iterationen, die gleichzeitig im Dienst vorhanden sein dürfen.
/// <summary> /// Set the newly created iteration as Default /// </summary> private IEnumerator SetDefaultIteration(Iteration iteration) { yield return new WaitForSeconds(5); trainingUI_TextMesh.text = "Setting default iteration"; // Set the last trained iteration to default iteration.IsDefault = true; // Convert the iteration object as JSON string iterationAsJson = JsonConvert.SerializeObject(iteration); byte[] bytes = Encoding.UTF8.GetBytes(iterationAsJson); string setDefaultIterationEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iteration.Id); using (UnityWebRequest www = UnityWebRequest.Put(setDefaultIterationEndpoint, bytes)) { www.method = "PATCH"; www.SetRequestHeader("Training-Key", trainingKey); www.SetRequestHeader("Content-Type", "application/json"); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; if (www.isDone) { trainingUI_TextMesh.text = "Default iteration is set \nDeleting Unused Iteration"; StartCoroutine(DeletePreviousIteration(iteration)); } } }
Fügen Sie die DeletePreviousIteration() -Methode hinzu. Diese Methode sucht und löscht die vorherige nicht standardmäßige Iteration:
/// <summary> /// Delete the previous non-default iteration. /// </summary> public IEnumerator DeletePreviousIteration(Iteration iteration) { yield return new WaitForSeconds(5); trainingUI_TextMesh.text = "Deleting Unused \nIteration"; string iterationToDeleteId = string.Empty; string findAllIterationsEndpoint = string.Format("{0}{1}/iterations", url, projectId); using (UnityWebRequest www = UnityWebRequest.Get(findAllIterationsEndpoint)) { www.SetRequestHeader("Training-Key", trainingKey); www.downloadHandler = new DownloadHandlerBuffer(); yield return www.SendWebRequest(); string jsonResponse = www.downloadHandler.text; // The iteration that has just been trained List<Iteration> iterationsList = new List<Iteration>(); iterationsList = JsonConvert.DeserializeObject<List<Iteration>>(jsonResponse); foreach (Iteration i in iterationsList) { if (i.IsDefault != true) { Debug.Log($"Cleaning - Deleting iteration: {i.Name}, {i.Id}"); iterationToDeleteId = i.Id; break; } } } string deleteEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iterationToDeleteId); using (UnityWebRequest www2 = UnityWebRequest.Delete(deleteEndpoint)) { www2.SetRequestHeader("Training-Key", trainingKey); www2.downloadHandler = new DownloadHandlerBuffer(); yield return www2.SendWebRequest(); string jsonResponse = www2.downloadHandler.text; trainingUI_TextMesh.text = "Iteration Deleted"; yield return new WaitForSeconds(2); trainingUI_TextMesh.text = "Ready for next \ncapture"; yield return new WaitForSeconds(2); trainingUI_TextMesh.text = ""; ImageCapture.Instance.ResetImageCapture(); } }
Die letzte Methode, die in dieser Klasse hinzugefügt werden soll, ist die GetImageAsByteArray() -Methode, die im Web aufruft, um das aufgenommene Bild in ein Bytearray zu konvertieren.
/// <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); }
Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.
Kapitel 10 – Erstellen der SceneOrganiser-Klasse
Diese Klasse sieht wie folgt aus:
Erstellen Sie ein Cursorobjekt , das an die Hauptkamera angefügt werden soll.
Erstellen Sie ein Label-Objekt , das angezeigt wird, wenn der Dienst die realen Objekte erkennt.
Richten Sie die Hauptkamera ein, indem Sie die entsprechenden Komponenten an sie anfügen.
Im Analysemodus werden die Beschriftungen zur Laufzeit im entsprechenden Weltbereich relativ zur Position der Hauptkamera eingeblendet und die vom Custom Vision Service empfangenen Daten angezeigt.
Wenn Sie sich im Schulungsmodus befinden, wird die Benutzeroberfläche angezeigt, in der die verschiedenen Phasen des Schulungsvorgangs angezeigt werden.
So erstellen Sie diese Klasse:
Klicken Sie mit der rechten Maustaste in den Ordner Skripts, und klicken Sie dann auf "C#-Skript erstellen>". Nennen Sie das Skript SceneOrganiser.
Doppelklicken Sie auf das neue SceneOrganiser-Skript , um es mit Visual Studio zu öffnen.
Sie benötigen nur einen Namespace, entfernen sie die anderen aus der SceneOrganiser-Klasse :
using UnityEngine;
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 camera /// </summary> internal GameObject cursor; /// <summary> /// The label used to display the analysis on the objects in the real world /// </summary> internal GameObject label; /// <summary> /// Object providing the current status of the camera. /// </summary> internal TextMesh cameraStatusIndicator; /// <summary> /// Reference to the last label positioned /// </summary> internal Transform lastLabelPlaced; /// <summary> /// Reference to the last label positioned /// </summary> internal TextMesh lastLabelPlacedText; /// <summary> /// Current threshold accepted for displaying the label /// Reduce this value to display the recognition more often /// </summary> internal float probabilityThreshold = 0.5f;
Löschen Sie die Methoden Start() und Update().
Fügen Sie direkt 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 CustomVisionTrainer class to this GameObject gameObject.AddComponent<CustomVisionTrainer>(); // Add the VoiceRecogniser class to this GameObject gameObject.AddComponent<VoiceRecognizer>(); // Add the CustomVisionObjects class to this GameObject gameObject.AddComponent<CustomVisionObjects>(); // Create the camera Cursor cursor = CreateCameraCursor(); // Load the label prefab as reference label = CreateLabel(); // Create the camera status indicator label, and place it above where predictions // and training UI will appear. cameraStatusIndicator = CreateTrainingUI("Status Indicator", 0.02f, 0.2f, 3, true); // Set camera status indicator to loading. SetCameraStatus("Loading"); }
Fügen Sie nun die CreateCameraCursor() -Methode hinzu, die den Hauptkameracursor erstellt und positioniert, und die CreateLabel() -Methode, die das Analysis Label-Objekt erstellt.
/// <summary> /// Spawns cursor for the Main Camera /// </summary> private GameObject CreateCameraCursor() { // Create a sphere as new cursor GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere); // Attach it to the camera newCursor.transform.parent = gameObject.transform; // Resize the new cursor newCursor.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f); // Move it to the correct position newCursor.transform.localPosition = new Vector3(0, 0, 4); // Set the cursor color to red newCursor.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse")); newCursor.GetComponent<Renderer>().material.color = Color.green; return newCursor; } /// <summary> /// Create the analysis label object /// </summary> private GameObject CreateLabel() { // Create a sphere as new cursor GameObject newLabel = new GameObject(); // Resize the new cursor newLabel.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f); // Creating the text of the label TextMesh t = newLabel.AddComponent<TextMesh>(); t.anchor = TextAnchor.MiddleCenter; t.alignment = TextAlignment.Center; t.fontSize = 50; t.text = ""; return newLabel; }
Fügen Sie die SetCameraStatus() -Methode hinzu, mit der Nachrichten verarbeitet werden, die für das Textgitter vorgesehen sind, die den Status der Kamera bereitstellen.
/// <summary> /// Set the camera status to a provided string. Will be coloured if it matches a keyword. /// </summary> /// <param name="statusText">Input string</param> public void SetCameraStatus(string statusText) { if (string.IsNullOrEmpty(statusText) == false) { string message = "white"; switch (statusText.ToLower()) { case "loading": message = "yellow"; break; case "ready": message = "green"; break; case "uploading image": message = "red"; break; case "looping capture": message = "yellow"; break; case "analysis": message = "red"; break; } cameraStatusIndicator.GetComponent<TextMesh>().text = $"Camera Status:\n<color={message}>{statusText}..</color>"; } }
Fügen Sie die Methoden PlaceAnalysisLabel() und SetTagsToLastLabel() hinzu, die die Daten aus dem Custom Vision Service in die Szene einblendet und anzeigen.
/// <summary> /// Instantiate a label in the appropriate location relative to the Main Camera. /// </summary> public void PlaceAnalysisLabel() { lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation); lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>(); } /// <summary> /// Set the Tags as Text of the last label created. /// </summary> public void SetTagsToLastLabel(AnalysisObject analysisObject) { lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>(); if (analysisObject.Predictions != null) { foreach (Prediction p in analysisObject.Predictions) { if (p.Probability > 0.02) { lastLabelPlacedText.text += $"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}"; Debug.Log($"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}"); } } } }
Fügen Sie schließlich die CreateTrainingUI() -Methode hinzu, die die Benutzeroberfläche mit den mehreren Phasen des Schulungsvorgangs anzeigt, wenn sich die Anwendung im Schulungsmodus befindet. Diese Methode wird auch verwendet, um das Kamerastatusobjekt zu erstellen.
/// <summary> /// Create a 3D Text Mesh in scene, with various parameters. /// </summary> /// <param name="name">name of object</param> /// <param name="scale">scale of object (i.e. 0.04f)</param> /// <param name="yPos">height above the cursor (i.e. 0.3f</param> /// <param name="zPos">distance from the camera</param> /// <param name="setActive">whether the text mesh should be visible when it has been created</param> /// <returns>Returns a 3D text mesh within the scene</returns> internal TextMesh CreateTrainingUI(string name, float scale, float yPos, float zPos, bool setActive) { GameObject display = new GameObject(name, typeof(TextMesh)); display.transform.parent = Camera.main.transform; display.transform.localPosition = new Vector3(0, yPos, zPos); display.SetActive(setActive); display.transform.localScale = new Vector3(scale, scale, scale); display.transform.rotation = new Quaternion(); TextMesh textMesh = display.GetComponent<TextMesh>(); textMesh.anchor = TextAnchor.MiddleCenter; textMesh.alignment = TextAlignment.Center; return textMesh; }
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:
AnalysisObject analysisObject = new AnalysisObject();
analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);
Kapitel 11 – 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.
Beibehalten des Enumerationswerts, der bestimmt, ob die Anwendung im Analysemodus oder im Schulungsmodus ausgeführt wird.
So erstellen Sie diese Klasse:
Wechseln Sie zum Ordner "Skripts", den Sie zuvor erstellt haben.
Klicken Sie mit der rechten Maustaste in den Ordner, und klicken Sie dann auf "C#-Skript erstellen>". Nennen Sie das Skript ImageCapture.
Doppelklicken Sie auf das neue ImageCapture-Skript , um es mit Visual Studio zu öffnen.
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;
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> /// Loop timer /// </summary> private float secondsBetweenCaptures = 10f; /// <summary> /// Application main functionalities switch /// </summary> internal enum AppModes {Analysis, Training } /// <summary> /// Local variable for current AppMode /// </summary> internal AppModes AppMode { get; private set; } /// <summary> /// Flagging if the capture loop is running /// </summary> internal bool captureIsActive; /// <summary> /// File path of current analysed photo /// </summary> internal string filePath = string.Empty;
Code for Awake() and Start() methods now to be added:
/// <summary> /// Called on initialization /// </summary> private void Awake() { Instance = this; // Change this flag to switch between Analysis Mode and Training Mode AppMode = AppModes.Training; } /// <summary> /// Runs at initialization right after Awake method /// </summary> void Start() { // Clean up the LocalState folder of this application from all photos stored DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath); var fileInfo = info.GetFiles(); foreach (var file in fileInfo) { try { file.Delete(); } catch (Exception) { Debug.LogFormat("Cannot delete file: ", file.Name); } } // Subscribing to the HoloLens API gesture recognizer to track user gestures recognizer = new GestureRecognizer(); recognizer.SetRecognizableGestures(GestureSettings.Tap); recognizer.Tapped += TapHandler; recognizer.StartCapturingGestures(); SceneOrganiser.Instance.SetCameraStatus("Ready"); }
Implementieren Sie einen Handler, der aufgerufen wird, wenn eine Tippbewegung auftritt.
/// <summary> /// Respond to Tap Input. /// </summary> private void TapHandler(TappedEventArgs obj) { switch (AppMode) { case AppModes.Analysis: if (!captureIsActive) { captureIsActive = true; // Set the cursor color to red SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red; // Update camera status to looping capture. SceneOrganiser.Instance.SetCameraStatus("Looping Capture"); // Begin the capture loop InvokeRepeating("ExecuteImageCaptureAndAnalysis", 0, secondsBetweenCaptures); } else { // The user tapped while the app was analyzing // therefore stop the analysis process ResetImageCapture(); } break; case AppModes.Training: if (!captureIsActive) { captureIsActive = true; // Call the image capture ExecuteImageCaptureAndAnalysis(); // Set the cursor color to red SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red; // Update camera status to uploading image. SceneOrganiser.Instance.SetCameraStatus("Uploading Image"); } break; } }
Hinweis
Im Analysemodus fungiert die TapHandler-Methode als Schalter, um die Fotoaufnahmeschleife zu starten oder zu beenden.
Im Schulungsmodus wird ein Bild von der Kamera aufgenommen.
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.
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() { // Update camera status to analysis. SceneOrganiser.Instance.SetCameraStatus("Analysis"); // Create a label in world space using the SceneOrganiser class // Invisible at this point but correctly positioned where the image was taken SceneOrganiser.Instance.PlaceAnalysisLabel(); // Set the camera resolution to be the highest possible Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First(); Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height); // Begin capture process, set the image format PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject) { photoCaptureObject = captureObject; CameraParameters camParameters = new CameraParameters { hologramOpacity = 0.0f, cameraResolutionWidth = targetTexture.width, cameraResolutionHeight = targetTexture.height, pixelFormat = CapturePixelFormat.BGRA32 }; // Capture the image from the camera and save it in the App internal folder captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result) { string filename = string.Format(@"CapturedImage{0}.jpg", captureCount); filePath = Path.Combine(Application.persistentDataPath, filename); captureCount++; photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk); }); }); }
Fügen Sie die Handler hinzu, die aufgerufen werden, wenn das Foto aufgenommen wurde und wann es analysiert werden kann. Das Ergebnis wird dann an den CustomVisionAnalyser oder den CustomVisionTrainer übergeben, je nachdem, auf welchem Modus der Code festgelegt ist.
/// <summary> /// Register the full execution of the Photo Capture. /// </summary> void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result) { // Call StopPhotoMode once the image has successfully captured photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode); } /// <summary> /// The camera photo mode has stopped after the capture. /// Begin the Image Analysis process. /// </summary> void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result) { Debug.LogFormat("Stopped Photo Mode"); // Dispose from the object in memory and request the image analysis photoCaptureObject.Dispose(); photoCaptureObject = null; switch (AppMode) { case AppModes.Analysis: // Call the image analysis StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath)); break; case AppModes.Training: // Call training using captured image CustomVisionTrainer.Instance.RequestTagSelection(); break; } } /// <summary> /// Stops all capture pending actions /// </summary> internal void ResetImageCapture() { captureIsActive = false; // Set the cursor color to green SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green; // Update camera status to ready. SceneOrganiser.Instance.SetCameraStatus("Ready"); // Stop the capture loop if active CancelInvoke(); }
Achten Sie darauf, ihre Änderungen in Visual Studio zu speichern, bevor Sie zu Unity zurückkehren.
Nachdem alle Skripts abgeschlossen wurden, wechseln Sie zurück im Unity-Editor, und ziehen Sie dann die SceneOrganiser-Klasse aus dem Ordner "Skripts " in das Hauptkameraobjekt im Hierarchiebereich.
Kapitel 12 - Vor dem Gebäude
Um einen gründlichen Test Ihrer Anwendung durchzuführen, müssen Sie sie auf Ihre HoloLens querladen.
Bevor Sie vorgehen, stellen Sie folgendes sicher:
Alle im Kapitel 2 genannten Einstellungen sind korrekt festgelegt.
Alle Felder in der Hauptkamera, Inspektorbereich, werden ordnungsgemäß zugewiesen.
Das Skript SceneOrganiser ist an das Main Camera-Objekt angefügt.
Stellen Sie sicher, dass Sie Ihren Vorhersageschlüssel in die Variable "predictionKey " einfügen.
Sie haben Ihren Vorhersageendpunkt in die Variable "predictionEndpoint " eingefügt.
Sie haben Ihren Schulungsschlüssel in die Variable "trainingKey " der CustomVisionTrainer-Klasse eingefügt.
Sie haben Ihre Projekt-ID in die ProjectId-Variable der CustomVisionTrainer-Klasse eingefügt.
Kapitel 13 – Erstellen und Querladen Ihrer Anwendung
So beginnen Sie den Buildprozess :
Wechseln Sie zu " Dateibuildeinstellungen > ".
Tick Unity C#-Projekte.
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".
Unity beginnt mit dem Erstellen Ihres Projekts im App-Ordner .
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).
So stellen Sie holoLens bereit:
Sie benötigen die IP-Adresse Ihrer HoloLens (für Remotebereitstellung) und um sicherzustellen, dass Sich Ihre HoloLens im Entwicklermodus befindet. Gehen Sie hierzu folgendermaßen vor:
Öffnen Sie beim Tragen Ihrer HoloLens die Einstellungen.
Wechseln sie zu den erweiterten Optionen für Netzwerk- und Internet-WLAN>>
Notieren Sie sich die IPv4-Adresse .
Navigieren Sie als Nächstes zurück zu "Einstellungen" und dann zu "Update & Sicherheit>für Entwickler".
Legen Sie den Entwicklermodus aktiviert fest.
Navigieren Sie zu Ihrem neuen Unity-Build (dem App-Ordner ), und öffnen Sie die Projektmappendatei mit Visual Studio.
Wählen Sie in der Lösungskonfiguration "Debuggen" aus.
Wählen Sie in der Lösungsplattform x86, Remotecomputer aus. Sie werden aufgefordert, die IP-Adresse eines Remotegeräts einzufügen (in diesem Fall die HoloLens, die Sie angegeben haben).
Wechseln Sie zum Menü "Erstellen ", und klicken Sie auf " Lösung bereitstellen", um die Anwendung in Ihre HoloLens querzuladen.
Ihre App sollte jetzt in der Liste der installierten Apps auf Ihrer HoloLens angezeigt werden, damit sie gestartet werden können!
Hinweis
Um das immersive Headset bereitzustellen, legen Sie die Lösungsplattform auf den lokalen Computer fest, und legen Sie die Konfiguration auf "Debuggen" fest, wobei x86 als Plattform verwendet wird. Stellen Sie dann mithilfe des Menüelements "Erstellen " die Option "Lösung bereitstellen" auf dem lokalen Computer bereit.
So verwenden Sie die Anwendung:
Um die App-Funktionalität zwischen dem Schulungsmodus und dem Vorhersagemodus zu wechseln, müssen Sie die AppMode-Variable aktualisieren, die sich in der Awake()- Methode befindet, die sich in der ImageCapture-Klasse befindet.
// Change this flag to switch between Analysis mode and Training mode
AppMode = AppModes.Training;
oder
// Change this flag to switch between Analysis mode and Training mode
AppMode = AppModes.Analysis;
Im Schulungsmodus :
Sehen Sie sich Maus oder Tastatur an, und verwenden Sie die Tippbewegung.
Als Nächstes wird Text angezeigt, in dem Sie aufgefordert werden, ein Tag anzugeben.
Sagen Sie entweder Maus oder Tastatur.
Im Vorhersagemodus :
Sehen Sie sich ein Objekt an, und verwenden Sie die Tippbewegung.
Text wird angezeigt, wenn das erkannte Objekt mit der höchsten Wahrscheinlichkeit (dies ist normalisiert) angezeigt wird.
Kapitel 14 – Bewerten und Verbessern Ihres benutzerdefinierten Vision-Modells
Um Ihren Dienst genauer zu gestalten, müssen Sie das modellieren, das für die Vorhersage verwendet wird. Dies wird durch die Verwendung Ihrer neuen Anwendung mit den Schulungs - und Vorhersagemodi erreicht, wobei letzteres erfordert, dass Sie das Portal besuchen müssen, was in diesem Kapitel behandelt wird. Seien Sie bereit, Ihr Portal mehrmals zu überarbeiten, um Ihr Modell kontinuierlich zu verbessern.
Wechseln Sie erneut zu Ihrem benutzerdefinierten Azure Vision-Portal, und wählen Sie die Registerkarte " Vorhersagen" (von der oberen Mitte der Seite) aus:
Sie sehen alle Bilder, die während der Ausführung Der Anwendung an Ihren Dienst gesendet wurden. Wenn Sie mit dem Mauszeiger auf die Bilder zeigen, erhalten Sie die Vorhersagen, die für dieses Bild erstellt wurden:
Wählen Sie eines Ihrer Bilder aus, um sie zu öffnen. Nach dem Öffnen sehen Sie die vorhersagen, die für dieses Bild auf der rechten Seite erstellt wurden. Wenn Die Vorhersagen korrekt waren und Sie dieses Bild dem Schulungsmodell Ihres Diensts hinzufügen möchten, klicken Sie auf das Eingabefeld "Meine Tags ", und wählen Sie das Tag aus, das Sie zuordnen möchten. Wenn Sie fertig sind, klicken Sie auf die Schaltfläche "Speichern und schließen" unten rechts, und fahren Sie mit dem nächsten Bild fort.
Sobald Sie wieder zum Raster der Bilder zurückkehren, werden die Bilder, denen Sie Tags hinzugefügt haben (und gespeichert wurden), entfernt. Wenn Sie Bilder finden, für die Sie denken, dass ihr markiertes Element darin nicht enthalten ist, können Sie sie löschen, indem Sie auf das Bild klicken (kann dies für mehrere Bilder tun) und dann in der oberen rechten Ecke der Rasterseite auf "Löschen" klicken. Im folgenden Popup können Sie auf "Ja", " Löschen " oder "Nein" klicken, um den Löschvorgang zu bestätigen oder abzubrechen.
Wenn Sie bereit sind, den Vorgang fortzusetzen, klicken Sie oben rechts auf die grüne Schaltfläche "Trainieren ". Ihr Servicemodell wird mit allen Bildern trainiert, die Sie jetzt bereitgestellt haben (wodurch es genauer wird). Nachdem die Schulung abgeschlossen ist, müssen Sie erneut auf die Standardschaltfläche "Standard erstellen" klicken, damit Ihre Vorhersage-URL weiterhin die aktuellste Iteration Ihres Diensts verwendet.
Ihre fertige benutzerdefinierte Vision-API-Anwendung
Herzlichen Glückwunsch, Sie haben eine Mixed Reality-App erstellt, die die Azure Custom Vision-API nutzt, um reale Objekte zu erkennen, das Dienstmodell zu trainieren und das Vertrauen zu zeigen, was gesehen wurde.
Zusatzübungen
Übung 1
Schulen Sie Ihren custom Vision Service , um weitere Objekte zu erkennen.
Übung 2
Um das gelernte Wissen zu erweitern, führen Sie die folgenden Übungen aus:
Wiedergeben eines Sounds, wenn ein Objekt erkannt wird.
Übung 3
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).