Udostępnij za pośrednictwem


Urządzenia HoloLens (1. generacji) i Azure 302b: Custom Vision


Uwaga

Samouczki akademii rzeczywistości mieszanej zostały zaprojektowane z myślą o urządzeniach HoloLens (1. generacji) i zestawach słuchawkowych immersyjnych rzeczywistości mieszanej. W związku z tym uważamy, że ważne jest pozostawienie tych samouczków na miejscu dla deweloperów, którzy nadal szukają wskazówek dotyczących opracowywania tych urządzeń. Te samouczki nie zostaną zaktualizowane przy użyciu najnowszych zestawów narzędzi ani interakcji używanych dla urządzenia HoloLens 2. Będą one utrzymywane w celu kontynuowania pracy na obsługiwanych urządzeniach. W przyszłości zostanie opublikowana nowa seria samouczków, które pokażą, jak opracowywać urządzenia HoloLens 2. To powiadomienie zostanie zaktualizowane za pomocą linku do tych samouczków po ich opublikowaniu.


W tym kursie dowiesz się, jak rozpoznawać zawartość wizualizacji niestandardowej w ramach dostarczonego obrazu przy użyciu funkcji usługi Azure Custom Vision w aplikacji rzeczywistości mieszanej.

Ta usługa umożliwia trenowanie modelu uczenia maszynowego przy użyciu obrazów obiektów. Następnie użyjesz wytrenowanego modelu do rozpoznawania podobnych obiektów, jak zapewnia przechwycenie aparatu Microsoft HoloLens lub aparat połączony z komputerem w celu uzyskania immersyjnych zestawów słuchawkowych (VR).

wynik kursu

Azure Custom Vision to usługa Microsoft Cognitive Service, która umożliwia deweloperom tworzenie niestandardowych klasyfikatorów obrazów. Klasyfikatory te mogą być następnie używane z nowymi obrazami do rozpoznawania lub klasyfikowania obiektów w tym nowym obrazie. Usługa zapewnia prosty, łatwy w użyciu portal online, aby usprawnić proces. Aby uzyskać więcej informacji, odwiedź stronę usługi Azure Custom Vision Service.

Po ukończeniu tego kursu będziesz mieć aplikację rzeczywistości mieszanej, która będzie mogła działać w dwóch trybach:

  • Tryb analizy: ręczne konfigurowanie usługi Custom Vision Przez przekazywanie obrazów, tworzenie tagów i trenowanie usługi w celu rozpoznawania różnych obiektów (w tym przypadku myszy i klawiatury). Następnie utworzysz aplikację HoloLens, która będzie przechwytywać obrazy przy użyciu aparatu i próbuje rozpoznać te obiekty w świecie rzeczywistym.

  • Tryb trenowania: zaimplementujesz kod, który włączy tryb trenowania w aplikacji. Tryb trenowania umożliwia przechwytywanie obrazów za pomocą aparatu HoloLens, przekazywanie przechwyconych obrazów do usługi i trenowanie modelu custom vision.

W tym kursie nauczysz się, jak uzyskać wyniki z usługi Custom Vision Service w przykładowej aplikacji opartej na środowisku Unity. Zastosowanie tych pojęć do niestandardowej aplikacji, którą można utworzyć, będzie możliwe.

Obsługa urządzeń

Kurs HoloLens Immersyjne zestawy nagłowne
MR i Azure 302b: Custom Vision ✔️ ✔️

Uwaga

Chociaż ten kurs koncentruje się głównie na urządzeniu HoloLens, możesz również zastosować to, czego nauczysz się w tym kursie, do zestawów słuchawkowych immersywnych (VR) systemu Windows Mixed Reality. Ponieważ immersyjne zestawy nagłowne (VR) nie mają dostępnych kamer, potrzebny jest zewnętrzny aparat podłączony do komputera. Wraz z kursem zobaczysz notatki dotyczące wszelkich zmian, które mogą być potrzebne do obsługi immersywnych zestawów słuchawkowych (VR).

Wymagania wstępne

Uwaga

Ten samouczek jest przeznaczony dla deweloperów, którzy mają podstawowe doświadczenie w językach Unity i C#. Należy również pamiętać, że wymagania wstępne i pisemne instrukcje zawarte w tym dokumencie reprezentują to, co zostało przetestowane i zweryfikowane w momencie pisania dokumentu (lipiec 2018 r.). Możesz bezpłatnie korzystać z najnowszego oprogramowania, jak wymieniono w artykule dotyczącym instalacji narzędzi , choć nie należy zakładać, że informacje zawarte w tym kursie doskonale pasują do tego, co znajdziesz w nowszym oprogramowaniu niż to, co zostało wymienione poniżej.

Na potrzeby tego kursu zalecamy następujące oprogramowanie i sprzęt:

Przed rozpoczęciem

  1. Aby uniknąć problemów podczas tworzenia tego projektu, zdecydowanie zaleca się utworzenie projektu wymienionego w tym samouczku w folderze głównym lub niemal głównym (długie ścieżki folderów mogą powodować problemy w czasie kompilacji).
  2. Skonfiguruj i przetestuj urządzenie HoloLens. Jeśli potrzebujesz pomocy technicznej dotyczącej konfigurowania urządzenia HoloLens, zapoznaj się z artykułem dotyczącym konfiguracji urządzenia HoloLens.
  3. Dobrym pomysłem jest przeprowadzenie kalibracji i dostrajania czujników podczas tworzenia nowej aplikacji HoloLens (czasami może to pomóc w wykonywaniu tych zadań dla każdego użytkownika).

Aby uzyskać pomoc dotyczącą kalibracji, skorzystaj z tego linku do artykułu Kalibracja urządzenia HoloLens.

Aby uzyskać pomoc dotyczącą dostrajania czujników, skorzystaj z tego linku do artykułu Dostrajanie czujników HoloLens.

Rozdział 1 — Portal usługi Custom Vision Service

Aby korzystać z usługi Custom Vision Service na platformie Azure, należy skonfigurować wystąpienie usługi, które ma zostać udostępnione aplikacji.

  1. Najpierw przejdź do strony głównej usługi Custom Vision Service.

  2. Kliknij przycisk Rozpocznij.

    Wprowadzenie do usługi Custom Vision Service

  3. Zaloguj się do portalu usługi Custom Vision Service .

    Zaloguj się do portalu

    Uwaga

    Jeśli nie masz jeszcze konta platformy Azure, musisz je utworzyć. Jeśli obserwujesz ten samouczek w sytuacji w klasie lub laboratorium, poproś instruktora lub jednego z opiekunów o pomoc przy konfigurowaniu nowego konta.

  4. Po pierwszym zalogowaniu się zostanie wyświetlony monit z panelem Warunki świadczenia usługi . Kliknij pole wyboru, aby wyrazić zgodę na warunki. Następnie kliknij pozycję Zgadzam się.

    Warunki świadczenia usług

  5. Po zaakceptowaniu warunków nastąpi przejście do sekcji Projekty w portalu. Kliknij pozycję Nowy projekt.

    Tworzenie nowego projektu

  6. Po prawej stronie zostanie wyświetlona karta z monitem o podanie niektórych pól dla projektu.

    1. Wstaw nazwę projektu.

    2. Wstaw opis projektu (opcjonalnie).

    3. Wybierz grupę zasobów lub utwórz nową. Grupa zasobów umożliwia monitorowanie, kontrolowanie dostępu, aprowizowania i zarządzania rozliczeniami dla kolekcji zasobów platformy Azure. Zaleca się zachowanie wszystkich usług platformy Azure skojarzonych z jednym projektem (np. takimi jak te kursy) w ramach wspólnej grupy zasobów.

    4. Ustawianie typów projektów na klasyfikację

    5. Ustaw wartość Domeny jako Ogólne.

      Ustawianie domen

      Jeśli chcesz dowiedzieć się więcej na temat grup zasobów platformy Azure, odwiedź artykuł grupy zasobów.

  7. Po zakończeniu kliknij pozycję Utwórz projekt, nastąpi przekierowanie do usługi Custom Vision Service, strony projektu.

Rozdział 2 . Szkolenie projektu usługi Custom Vision

Po przejściu do portalu Usługi Custom Vision głównym celem jest trenowanie projektu w celu rozpoznawania określonych obiektów na obrazach. Potrzebujesz co najmniej pięciu (5) obrazów, choć preferowane jest dziesięć (10) dla każdego obiektu, który ma być rozpoznawany przez aplikację. Możesz użyć obrazów dostarczonych z tym kursem (mysz komputerowa i klawiatura).

Aby wytrenować projekt usługi Custom Vision Service:

  1. + Kliknij przycisk obok pozycji Tagi.

    Dodawanie nowego tagu

  2. Dodaj nazwę obiektu, który chcesz rozpoznać. Kliknij przycisk Zapisz.

    Dodawanie nazwy obiektu i zapisywanie

  3. Zauważysz , że tag został dodany (może być konieczne ponowne załadowanie strony w celu wyświetlenia go). Kliknij pole wyboru obok nowego tagu, jeśli nie zostało jeszcze zaznaczone.

    Włącz nowy tag

  4. Kliknij pozycję Dodaj obrazy w środku strony.

    Dodawanie obrazów

  5. Kliknij pozycję Przeglądaj pliki lokalne i wyszukaj, a następnie wybierz obrazy, które chcesz przekazać, z co najmniej pięcioma (5). Pamiętaj, że wszystkie te obrazy powinny zawierać obiekt, który trenujesz.

    Uwaga

    Możesz jednocześnie wybrać kilka obrazów, aby je przekazać.

  6. Po wyświetleniu obrazów na karcie wybierz odpowiedni tag w polu Moje tagi .

    Wybieranie tagów

  7. Kliknij pozycję Przekaż pliki. Pliki rozpoczną przekazywanie. Po potwierdzeniu przekazania kliknij przycisk Gotowe.

    Przekaż pliki

  8. Powtórz ten sam proces, aby utworzyć nowy tag o nazwie Klawiatura i przekazać odpowiednie zdjęcia. Pamiętaj, aby usunąć zaznaczenie pola Myszy po utworzeniu nowych tagów, aby wyświetlić okno Dodawanie obrazów .

  9. Po skonfigurowaniu obu tagów kliknij pozycję Train (Trenowanie), a pierwsza iteracja szkoleniowa rozpocznie kompilowanie.

    Włączanie iteracji trenowania

  10. Po utworzeniu będzie można zobaczyć dwa przyciski o nazwie Ustaw jako domyślny i Adres URL przewidywania. Kliknij pozycję Ustaw jako domyślne, a następnie kliknij pozycję Adres URL przewidywania.

    Ustaw domyślny i adres URL przewidywania

    Uwaga

    Adres URL punktu końcowego podany w tym celu jest ustawiony na niezależnie od tego, która iteracja została oznaczona jako domyślna. W związku z tym, jeśli później wprowadzisz nową iterację i zaktualizujesz ją jako domyślną, nie musisz zmieniać kodu.

  11. Po kliknięciu pozycji Adres URL przewidywania otwórz Notatnik i skopiuj i wklej adres URL oraz klucz przewidywania, aby można było go pobrać, gdy będzie potrzebny później w kodzie.

    Kopiowanie i wklejanie adresu URL oraz klucza przewidywania

  12. Kliknij koła zębatego w prawym górnym rogu ekranu.

    Kliknij ikonę koła zębatego, aby otworzyć ustawienia

  13. Skopiuj klucz trenowania i wklej go do Notatnika, aby później go użyć.

    Kopiowanie klucza trenowania

  14. Skopiuj również identyfikator projektu, a także wklej go do pliku Notatnika, aby później go użyć.

    Kopiowanie identyfikatora projektu

Rozdział 3 . Konfigurowanie projektu aparatu Unity

Poniżej przedstawiono typową konfigurację do opracowywania za pomocą rzeczywistości mieszanej, a w związku z tym jest to dobry szablon dla innych projektów.

  1. Otwórz aparat Unity i kliknij pozycję Nowy.

    Tworzenie nowego projektu aparatu Unity

  2. Teraz musisz podać nazwę projektu aparatu Unity. Wstaw azureCustomVision . Upewnij się, że szablon projektu jest ustawiony na 3D. Ustaw lokalizację na odpowiednią dla Ciebie (pamiętaj, że bliżej katalogów głównych jest lepiej). Następnie kliknij pozycję Utwórz projekt.

    Konfigurowanie ustawień projektu

  3. Po otwarciu aparatu Unity warto sprawdzić, czy domyślny edytor skryptów jest ustawiony na program Visual Studio. Przejdź do pozycji Edytuj>preferencje, a następnie w nowym oknie przejdź do pozycji Narzędzia zewnętrzne. Zmień edytor skryptów zewnętrznych na Visual Studio 2017. Zamknij okno Preferencje.

    Konfigurowanie narzędzi zewnętrznych

  4. Następnie przejdź do pozycji Ustawienia kompilacji pliku > i wybierz pozycję platforma uniwersalna systemu Windows, a następnie kliknij przycisk Przełącz platformę, aby zastosować wybór.

    Konfigurowanie ustawień kompilacji

  5. Nadal w obszarze Ustawienia kompilacji pliku > i upewnij się, że:

    1. Urządzenie docelowe jest ustawione na urządzenie HoloLens

      W przypadku immersyjnych zestawów słuchawkowych ustaw opcję Urządzenie docelowe na dowolne urządzenie.

    2. Typ kompilacji jest ustawiony na D3D

    3. Zestaw SDK jest ustawiony na najnowszą zainstalowaną

    4. Dla wersji programu Visual Studio jest ustawiona wartość Najnowsza zainstalowana

    5. Kompilowanie i uruchamianie jest ustawione na komputer lokalny

    6. Zapisz scenę i dodaj ją do kompilacji.

      1. W tym celu wybierz pozycję Dodaj otwarte sceny. Zostanie wyświetlone okno zapisywania.

        Dodawanie otwartej sceny do listy kompilacji

      2. Utwórz nowy folder dla tego i dowolnej przyszłości sceny, a następnie wybierz przycisk Nowy folder , aby utworzyć nowy folder, nadaj mu nazwę Sceny.

        Utwórz nowy folder sceny

      3. Otwórz nowo utworzony folder Sceny , a następnie w polu Nazwa pliku: tekst wpisz CustomVisionScene, a następnie kliknij pozycję Zapisz.

        Nazwij nowy plik sceny

        Pamiętaj, że musisz zapisać sceny aparatu Unity w folderze Assets , ponieważ muszą być skojarzone z projektem aparatu Unity. Tworzenie folderu scen (i innych podobnych folderów) to typowy sposób tworzenia struktury projektu aparatu Unity.

    7. Pozostałe ustawienia w obszarze Ustawienia kompilacji powinny być pozostawione jako domyślne na razie.

      Domyślne ustawienia kompilacji

  6. W oknie Ustawienia kompilacji kliknij przycisk Ustawienia odtwarzacza, spowoduje to otwarcie powiązanego panelu w obszarze, w którym znajduje się inspektor.

  7. W tym panelu należy zweryfikować kilka ustawień:

    1. Na karcie Inne ustawienia:

      1. Wersja środowiska uruchomieniowego skryptów powinna być eksperymentalna (odpowiednik platformy.NET 4.6), co spowoduje konieczność ponownego uruchomienia edytora.

      2. Zaplecze skryptów powinno mieć wartość .NET

      3. Poziom zgodności interfejsu API powinien mieć wartość .NET 4.6

      Ustawianie compantiblity interfejsu API

    2. Na karcie Ustawienia publikowania w obszarze Możliwości sprawdź:

      1. InternetClient

      2. Kamera internetowa

      3. Mikrofon

      Konfigurowanie ustawień publikowania

    3. W dalszej części panelu w obszarze Ustawienia XR (znajdujące się poniżej ustawienia publikowania) zaznacz pole Virtual Reality Supported (Obsługiwane w rzeczywistości wirtualnej), upewnij się, że dodano zestaw WINDOWS Mixed Reality SDK .

    Konfigurowanie ustawień XR

  8. Po powrocie do ustawień kompilacji Projekty języka C# środowiska Unity nie są już wyszarzone; zaznacz pole wyboru obok tego.

  9. Zamknij okno Build Settings (Ustawienia kompilacji).

  10. Zapisz scenę i projekt (FILE > SAVE SCENE / FILE > SAVE PROJECT).

Rozdział 4 . Importowanie biblioteki DLL Newtonsoft w aporcie Unity

Ważne

Jeśli chcesz pominąć składnik Konfiguracji aparatu Unity tego kursu i kontynuować bezpośrednio w kodzie, możesz pobrać ten pakiet Azure-MR-302b.unitypackage, zaimportować go do projektu jako pakiet niestandardowy, a następnie kontynuować z rozdziału 6.

Ten kurs wymaga użycia biblioteki Newtonsoft , którą można dodać jako bibliotekę DLL do zasobów. Pakiet zawierający tę bibliotekę można pobrać z tego linku. Aby zaimportować bibliotekę Newtonsoft do projektu, użyj pakietu aparatu Unity, który został dołączony do tego kursu.

  1. Dodaj pakiet unitypackage do aparatu Unity przy użyciu opcji menu Importujpakiet>niestandardowy pakietu Assets.>

  2. W wyświetlonym oknie Importuj pakiet aparatu Unity upewnij się, że wybrano wszystkie elementy w obszarze (i w tym) Wtyczki .

    Importowanie wszystkich elementów pakietu

  3. Kliknij przycisk Importuj, aby dodać elementy do projektu.

  4. Przejdź do folderu Newtonsoft w obszarze Wtyczki w widoku projektu i wybierz wtyczkę Newtonsoft.Json.

    Wybieranie wtyczki Newtonsoft

  5. Po wybraniu wtyczki Newtonsoft.Json upewnij się, że pole wyboru Dowolna platforma jest niezaznaczone, a następnie upewnij się, że element WSAPlayer również nie jest zaznaczony, a następnie kliknij przycisk Zastosuj. Wystarczy potwierdzić, że pliki są poprawnie skonfigurowane.

    Konfigurowanie wtyczki Newtonsoft

    Uwaga

    Oznaczanie tych wtyczek konfiguruje je tak, aby były używane tylko w Edytorze aparatu Unity. Istnieje inny zestaw z nich w folderze WSA, który będzie używany po wyeksportowaniu projektu z aparatu Unity.

  6. Następnie należy otworzyć folder WSA w folderze Newtonsoft . Zobaczysz kopię tego samego pliku, który został właśnie skonfigurowany. Wybierz plik, a następnie w inspektorze upewnij się, że

    • Wszystkie platformyniezaznaczone
    • zaznaczono tylko WSAPlayer
    • Nie zaznaczono procesu

    Konfigurowanie ustawień platformy wtyczki Newtonsoft

Rozdział 5 — Konfiguracja aparatu

  1. W panelu hierarchii wybierz kamerę główną.

  2. Po wybraniu tej opcji będzie można zobaczyć wszystkie składniki głównego aparatu w Panelu inspektora.

    1. Obiekt aparatu musi mieć nazwę Main Camera (zanotuj pisownię!)

    2. Główny tag aparatu musi być ustawiony na MainCamera (zanotuj pisownię!)

    3. Upewnij się, że pozycja przekształcania jest ustawiona na 0, 0, 0

    4. Ustaw opcję Wyczyść flagi na Solid Color (ignoruj tę opcję dla immersywnych zestawów słuchawkowych).

    5. Ustaw kolor tła składnika aparatu na , Alfa 0 (kod szesnastkowy: #00000000) (zignoruj to w przypadku immersyjnego zestawu słuchawkowego).

    Konfigurowanie właściwości składnika aparatu

Rozdział 6 . Utwórz klasę CustomVisionAnalyser.

W tym momencie możesz napisać kod.

Rozpoczniesz od klasy CustomVisionAnalyser .

Uwaga

Wywołania usługi Custom Vision Service wykonane w kodzie przedstawionym poniżej są wykonywane przy użyciu interfejsu API REST usługi Custom Vision. Korzystając z tego, zobaczysz, jak zaimplementować i wykorzystać ten interfejs API (przydatne do zrozumienia, jak zaimplementować coś podobnego samodzielnie). Należy pamiętać, że firma Microsoft oferuje zestaw SDK usługi Custom Vision Service, który może być również używany do nawiązywania wywołań do usługi. Aby uzyskać więcej informacji, zobacz artykuł Custom Vision Service SDK (Zestaw SDK usługi Custom Vision Service).

Ta klasa jest odpowiedzialna za:

  • Ładowanie najnowszego obrazu przechwyconego jako tablica bajtów.

  • Wysyłanie tablicy bajtów do wystąpienia usługi Azure Custom Vision Service na potrzeby analizy.

  • Odbieranie odpowiedzi jako ciągu JSON.

  • Deserializowanie odpowiedzi i przekazanie wynikowej prognozy do klasy SceneOrganiser , która zajmie się sposobem wyświetlania odpowiedzi.

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy folder elementów zawartości znajdujący się w panelu projektu, a następnie kliknij pozycję Utwórz > folder. Wywołaj folder Scripts.

    Tworzenie folderu skryptów

  2. Kliknij dwukrotnie utworzony folder, aby go otworzyć.

  3. Kliknij prawym przyciskiem myszy wewnątrz folderu, a następnie kliknij polecenie Utwórz>skrypt języka C#. Nadaj skryptowi nazwę CustomVisionAnalyser.

  4. Kliknij dwukrotnie nowy skrypt CustomVisionAnalyser , aby otworzyć go za pomocą programu Visual Studio.

  5. Zaktualizuj przestrzenie nazw w górnej części pliku, aby pasować do następujących elementów:

    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    using Newtonsoft.Json;
    
  6. W klasie CustomVisionAnalyser dodaj następujące zmienne:

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

    Uwaga

    Pamiętaj, aby wstawić klucz przewidywania do zmiennej predictionKey i punktu końcowego przewidywania do zmiennej predictionEndpoint . Skopiowano je do Notatnika wcześniej w kursie.

  7. Teraz należy dodać kod dla aplikacji Awake(), aby zainicjować zmienną wystąpienia:

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Usuń metody Start() i Update().

  9. Następnie dodaj metodę coroutine (ze statyczną metodą GetImageAsByteArray(), która uzyska wyniki analizy obrazu przechwyconego przez klasę ImageCapture .

    Uwaga

    W coroutine AnalyseImageCapture istnieje wywołanie klasy SceneOrganiser, którą jeszcze utworzysz. W związku z tym pozostaw te wiersze komentarza na razie.

        /// <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);
        }
    
  10. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

Rozdział 7 . Tworzenie klasy CustomVisionObjects

Klasa, którą utworzysz teraz, to klasa CustomVisionObjects .

Ten skrypt zawiera wiele obiektów używanych przez inne klasy do serializacji i deserializacji wywołań wykonanych w usłudze Custom Vision Service.

Ostrzeżenie

Należy pamiętać o punkcie końcowym, który zapewnia usługa Custom Vision Service, ponieważ poniższa struktura JSON została skonfigurowana do pracy z usługą Custom Vision Prediction w wersji 2.0. Jeśli masz inną wersję, może być konieczne zaktualizowanie poniższej struktury.

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy wewnątrz folderu Skrypty, a następnie kliknij pozycję Utwórz>skrypt języka C#. Wywołaj skrypt CustomVisionObjects.

  2. Kliknij dwukrotnie nowy skrypt CustomVisionObjects , aby otworzyć go za pomocą programu Visual Studio.

  3. Dodaj następujące przestrzenie nazw na początku pliku:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Usuń metody Start() i Update() wewnątrz klasy CustomVisionObjects. Ta klasa powinna być teraz pusta.

  5. Dodaj następujące klasy poza klasą CustomVisionObjects. Te obiekty są używane przez bibliotekę Newtonsoft do serializacji i deserializacji danych odpowiedzi:

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

Rozdział 8 . Tworzenie klasy VoiceRecognizer

Ta klasa rozpozna dane wejściowe głosu od użytkownika.

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy wewnątrz folderu Skrypty, a następnie kliknij pozycję Utwórz>skrypt języka C#. Wywołaj skrypt VoiceRecognizer.

  2. Kliknij dwukrotnie nowy skrypt VoiceRecognizer , aby otworzyć go za pomocą programu Visual Studio.

  3. Dodaj następujące przestrzenie nazw powyżej klasy VoiceRecognizer :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Windows.Speech;
    
  4. Następnie dodaj następujące zmienne wewnątrz klasy VoiceRecognizer powyżej metody Start():

        /// <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>();
    
  5. Dodaj metody Awake() i Start(), z których druga skonfiguruje słowa kluczowe użytkownika do rozpoznawania podczas kojarzenia tagu z obrazem:

        /// <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;
        }
    
  6. Usuń metodę Update().

  7. Dodaj następującą procedurę obsługi, która jest wywoływana za każdym razem, gdy dane wejściowe głosowe są rozpoznawane:

        /// <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();
            }
        }
    
  8. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

Uwaga

Nie martw się o kod, który może wydawać się mieć błąd, ponieważ wkrótce udostępnisz kolejne klasy, co rozwiąże te problemy.

Rozdział 9 — Tworzenie klasy CustomVisionTrainer

Ta klasa będzie łączyć serię wywołań internetowych w celu wytrenowania usługi Custom Vision Service. Każde wywołanie zostanie szczegółowo wyjaśnione bezpośrednio nad kodem.

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy wewnątrz folderu Skrypty, a następnie kliknij pozycję Utwórz>skrypt języka C#. Wywołaj skrypt CustomVisionTrainer.

  2. Kliknij dwukrotnie nowy skrypt CustomVisionTrainer , aby otworzyć go za pomocą programu Visual Studio.

  3. Dodaj następujące przestrzenie nazw powyżej klasy CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Następnie dodaj następujące zmienne wewnątrz klasy CustomVisionTrainer powyżej metody Start().

    Uwaga

    Adres URL trenowania używany tutaj znajduje się w dokumentacji usługi Custom Vision Training 1.2 i ma strukturę: https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Aby uzyskać więcej informacji, odwiedź interfejs API referencyjnego trenowania usługi Custom Vision w wersji 1.2.

    Ostrzeżenie

    Należy pamiętać o punkcie końcowym, który usługa Custom Vision Service udostępnia dla trybu trenowania, ponieważ używana struktura JSON (w klasie CustomVisionObjects) została skonfigurowana do pracy z usługą Custom Vision Training w wersji 1.2. Jeśli masz inną wersję, może być konieczne zaktualizowanie struktury Objects .

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

    Ważne

    Upewnij się, że dodasz wartość klucza usługi (klucz trenowania) i wartość identyfikatora projektu, która została zanotowana wcześniej. Są to wartości zebrane z portalu wcześniej w kursie (rozdział 2, krok 10 do wewnątrz).

  5. Dodaj następujące metody Start() i Awake(). Metody te są wywoływane podczas inicjowania i zawierają wywołanie w celu skonfigurowania interfejsu użytkownika:

        /// <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);
        }
    
  6. Usuń metodę Update(). Ta klasa nie będzie jej potrzebować.

  7. Dodaj metodę RequestTagSelection(). Ta metoda jest pierwszą metodą wywoływaną, gdy obraz został przechwycony i przechowywany na urządzeniu, a teraz jest gotowy do przesłania do usługi Custom Vision Service, aby go wytrenować. Ta metoda wyświetla w interfejsie użytkownika trenowania zestaw słów kluczowych, których użytkownik może użyć do tagowania przechwyconego obrazu. Ostrzega również klasę VoiceRecognizer , aby rozpocząć nasłuchiwanie użytkownika na potrzeby danych głosowych.

        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();
        }
    
  8. Dodaj metodę VerifyTag(). Ta metoda otrzyma dane wejściowe głosu rozpoznane przez klasę VoiceRecognizer i zweryfikuje jego ważność, a następnie rozpocznie proces trenowania.

        /// <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));
            }
        }
    
  9. Dodaj metodę SubmitImageForTraining(). Ta metoda rozpocznie proces trenowania usługi Custom Vision Service. Pierwszym krokiem jest pobranie identyfikatora tagu z usługi skojarzonego z zweryfikowanymi danymi wejściowymi mowy od użytkownika. Identyfikator tagu zostanie następnie przekazany wraz z obrazem.

        /// <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());
        }
    
  10. Dodaj metodę TrainCustomVisionProject(). Po przesłaniu i oznaczeniu obrazu ta metoda zostanie wywołana. Spowoduje to utworzenie nowej iteracji, która zostanie wytrenowana przy użyciu wszystkich poprzednich obrazów przesłanych do usługi oraz właśnie przekazanego obrazu. Po zakończeniu trenowania ta metoda wywoła metodę w celu ustawienia nowo utworzonej iteracji jako domyślnej, aby punkt końcowy używany do analizy był najnowszą wytrenowanym iteracją.

        /// <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)); 
                }
            }
        }
    
  11. Dodaj metodę SetDefaultIteration(). Ta metoda ustawi wcześniej utworzoną i wytrenowana iterację jako Domyślna. Po zakończeniu ta metoda będzie musiała usunąć poprzednią iterację istniejącą w usłudze. W momencie pisania tego kursu istnieje limit 10 iteracji maksymalnie dziesięciu dozwolonych w tym samym czasie w usłudze.

        /// <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));
                }
            }
        }
    
  12. Dodaj metodę DeletePreviousIteration(). Ta metoda znajdzie i usunie poprzednią iterację inną niż domyślna:

        /// <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();
            }
        }
    
  13. Ostatnią metodą dodawania w tej klasie jest metoda GetImageAsByteArray() używana w wywołaniach internetowych w celu przekonwertowania obrazu przechwyconego na tablicę bajtów.

        /// <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);
        }
    
  14. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

Rozdział 10 — Tworzenie klasy SceneOrganiser

Ta klasa:

  • Utwórz obiekt Kursor, aby dołączyć do aparatu głównego.

  • Utwórz obiekt Label, który będzie wyświetlany, gdy usługa rozpoznaje obiekty w świecie rzeczywistym.

  • Skonfiguruj aparat główny, dołączając do niego odpowiednie składniki.

  • W trybie analizy zduplikuj etykiety w czasie wykonywania, w odpowiedniej przestrzeni świata względem pozycji aparatu głównego i wyświetl dane odebrane z usługi Custom Vision Service.

  • W trybie trenowania zduplikuj interfejs użytkownika, który będzie wyświetlał różne etapy procesu trenowania.

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy wewnątrz folderu Skrypty, a następnie kliknij pozycję Utwórz>skrypt języka C#. Nadaj skryptowi nazwę SceneOrganiser.

  2. Kliknij dwukrotnie nowy skrypt SceneOrganiser , aby otworzyć go za pomocą programu Visual Studio.

  3. Będziesz potrzebować tylko jednej przestrzeni nazw, usuń pozostałe z powyższej klasy SceneOrganiser :

    using UnityEngine;
    
  4. Następnie dodaj następujące zmienne wewnątrz klasy SceneOrganiser powyżej metody Start():

        /// <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;
    
  5. Usuń metody Start() i Update().

  6. Tuż pod zmiennymi dodaj metodę Awake(), która zainicjuje klasę i skonfiguruje scenę.

        /// <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");
        }
    
  7. Teraz dodaj metodę CreateCameraCursor(), która tworzy i umieszcza kursor main camera oraz metodę CreateLabel(), która tworzy obiekt Analysis Label .

        /// <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;
        }
    
  8. Dodaj metodę SetCameraStatus(), która będzie obsługiwać komunikaty przeznaczone dla siatki tekstowej, zapewniając stan aparatu.

        /// <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>";
            }
        }
    
  9. Dodaj metody PlaceAnalysisLabel() i SetTagsToLastLabel(), co spowoduje zduplikowania i wyświetlenia danych z usługi Custom Vision Service do sceny.

        /// <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")}");
                    }
                }
            }
        }
    
  10. Na koniec dodaj metodę CreateTrainingUI(), która będzie duplikować interfejs użytkownika wyświetlający wiele etapów procesu trenowania, gdy aplikacja jest w trybie trenowania. Ta metoda zostanie również wykorzystana do utworzenia obiektu stanu aparatu.

        /// <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;
        }
    
  11. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

Ważne

Przed kontynuowanym otwórz klasę CustomVisionAnalyser i w metodzie AnalysisLastImageCaptured() usuń komentarz z następujących wierszy:

  AnalysisObject analysisObject = new AnalysisObject();
  analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
  SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);

Rozdział 11 — Tworzenie klasy ImageCapture

Następną klasą , którą utworzysz, jest klasa ImageCapture .

Ta klasa jest odpowiedzialna za:

  • Przechwytywanie obrazu przy użyciu aparatu HoloLens i przechowywanie go w folderze aplikacji .

  • Obsługa gestów naciśnięcia od użytkownika.

  • Utrzymywanie wartości wyliczenia, która określa, czy aplikacja będzie działać w trybie analizy lub w trybie trenowania.

Aby utworzyć tę klasę:

  1. Przejdź do utworzonego wcześniej folderu Skrypty .

  2. Kliknij prawym przyciskiem myszy wewnątrz folderu, a następnie kliknij polecenie Utwórz > skrypt języka C#. Nadaj skryptowi nazwę ImageCapture.

  3. Kliknij dwukrotnie nowy skrypt ImageCapture , aby otworzyć go za pomocą programu Visual Studio.

  4. Zastąp przestrzenie nazw w górnej części pliku następującymi elementami:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Następnie dodaj następujące zmienne wewnątrz klasy ImageCapture powyżej metody Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static ImageCapture Instance;
    
        /// <summary>
        /// Keep counts of the taps for image renaming
        /// </summary>
        private int captureCount = 0;
    
        /// <summary>
        /// Photo Capture object
        /// </summary>
        private PhotoCapture photoCaptureObject = null;
    
        /// <summary>
        /// Allows gestures recognition in HoloLens
        /// </summary>
        private GestureRecognizer recognizer;
    
        /// <summary>
        /// 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;
    
  6. Teraz należy dodać kod dla metod Awake() i Start():

        /// <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");
        }
    
  7. Zaimplementuj procedurę obsługi, która będzie wywoływana po wystąpieniu gestu Naciśnięcie.

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

    Uwaga

    W trybie analizy metoda TapHandler działa jako przełącznik, aby uruchomić lub zatrzymać pętlę przechwytywania zdjęć.

    W trybie trenowania przechwytuje obraz z aparatu.

    Gdy kursor jest zielony, oznacza to, że aparat jest dostępny do wykonania obrazu.

    Gdy kursor jest czerwony, oznacza to, że aparat jest zajęty.

  8. Dodaj metodę używaną przez aplikację do uruchomienia procesu przechwytywania obrazu i zapisania obrazu.

        /// <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);              
                });
            });   
        }
    
  9. Dodaj programy obsługi, które będą wywoływane po uchwyceniu zdjęcia i gdy będzie gotowe do przeanalizowania. Wynik jest następnie przekazywany do klasy CustomVisionAnalyser lub CustomVisionTrainer w zależności od trybu, na którym jest ustawiony kod.

        /// <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();
        }
    
  10. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

  11. Teraz, gdy wszystkie skrypty zostały ukończone, wróć do Edytora aparatu Unity, a następnie kliknij i przeciągnij klasę SceneOrganiser z folderu Scripts do obiektu Main Camera w panelu hierarchii.

Rozdział 12 - Przed budynkiem

Aby przeprowadzić dokładny test aplikacji, należy załadować ją bezpośrednio na urządzenie HoloLens.

Przed wykonaniem upewnij się, że:

  • Wszystkie ustawienia wymienione w rozdziale 2 są ustawione poprawnie.

  • Wszystkie pola w głównym aparacie, Panel inspektora, są prawidłowo przypisane.

  • Skrypt SceneOrganiser jest dołączony do obiektu Main Camera .

  • Upewnij się, że wstawisz klucz przewidywania do zmiennej predictionKey .

  • Punkt końcowy przewidywania został wstawiony do zmiennej predictionEndpoint.

  • Klucz trenowania został wstawiony do zmiennej trainingKey klasy CustomVisionTrainer.

  • Identyfikator projektu został wstawiony do zmiennej projectId klasy CustomVisionTrainer.

Rozdział 13 — Kompilowanie i ładowanie bezpośrednie aplikacji

Aby rozpocząć proces kompilacji:

  1. Przejdź do pozycji Ustawienia kompilacji pliku>.

  2. Zaznacz projekty języka C# aparatu Unity.

  3. Kliknij pozycję Kompiluj. Aparat Unity uruchomi okno Eksplorator plików, w którym należy utworzyć, a następnie wybierz folder do skompilowania aplikacji. Utwórz teraz ten folder i nadaj mu nazwę Aplikacja. Następnie po wybraniu folderu Aplikacja kliknij pozycję Wybierz folder.

  4. Aparat Unity rozpocznie kompilowanie projektu w folderze App .

  5. Po zakończeniu kompilowania środowiska Unity (może to zająć trochę czasu), zostanie otwarte okno Eksplorator plików w lokalizacji kompilacji (sprawdź pasek zadań, ponieważ może nie zawsze pojawiać się nad oknami, ale powiadomi o dodaniu nowego okna).

Aby wdrożyć na urządzeniu HoloLens:

  1. Będziesz potrzebować adresu IP urządzenia HoloLens (na potrzeby zdalnego wdrażania) i upewnij się, że urządzenie HoloLens jest w trybie dewelopera. Czynność:

    1. Podczas noszenia urządzenia HoloLens otwórz ustawienia.

    2. Przejdź do pozycji Sieć i Internetowe>opcje zaawansowane sieci Wi-Fi>

    3. Zanotuj adres IPv4 .

    4. Następnie przejdź z powrotem do pozycji Ustawienia, a następnie przejdź do pozycji Aktualizuj i zabezpieczenia>dla deweloperów

    5. Ustaw tryb dewelopera wł.

  2. Przejdź do nowej kompilacji aparatu Unity ( folderu App ) i otwórz plik rozwiązania za pomocą programu Visual Studio.

  3. W obszarze Konfiguracja rozwiązania wybierz pozycję Debuguj.

  4. W polu Platforma rozwiązania wybierz pozycję x86, Maszyna zdalna. Zostanie wyświetlony monit o wstawienie adresu IP urządzenia zdalnego (w tym przypadku urządzenia HoloLens, który został zanotowany).

    Ustawianie adresu IP

  5. Przejdź do menu Kompilacja i kliknij pozycję Wdróż rozwiązanie, aby załadować aplikację bezpośrednio do urządzenia HoloLens.

  6. Aplikacja powinna być teraz wyświetlana na liście zainstalowanych aplikacji na urządzeniu HoloLens, gotowych do uruchomienia.

Uwaga

Aby wdrożyć w immersywnym zestawie słuchawkowym, ustaw wartość Platforma rozwiązania na Komputer lokalny i ustaw wartość Konfiguracja na Debugowanie z wartością x86 jako platforma. Następnie wdróż na komputerze lokalnym przy użyciu elementu menu Kompilacja , wybierając pozycję Wdróż rozwiązanie.

Aby użyć aplikacji:

Aby przełączyć funkcjonalność aplikacji między trybem trenowania i trybem przewidywania, należy zaktualizować zmienną AppMode znajdującą się w metodzie Awake() znajdującą się w klasie ImageCapture.

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Training;

lub

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Analysis;

W trybie trenowania :

  • Spójrz na mysz lub klawiaturę i użyj gestu Naciśnij.

  • Następnie zostanie wyświetlony tekst z prośbą o podanie tagu.

  • Powiedz myszą lub klawiaturą.

W trybie przewidywania :

  • Spójrz na obiekt i użyj gestu Naciśnij.

  • Zostanie wyświetlony tekst zawierający wykryty obiekt z najwyższym prawdopodobieństwem (jest to znormalizowane).

Rozdział 14 — Ocena i ulepszanie modelu usługi Custom Vision

Aby usługa była bardziej dokładna, należy kontynuować trenowanie modelu używanego do przewidywania. Jest to realizowane za pomocą nowej aplikacji, zarówno z trybami trenowania , jak i przewidywania , przy czym ten ostatni wymaga odwiedzenia portalu, co zostało omówione w tym rozdziale. Przygotuj się do wielokrotnego ponownego przejrzenia portalu, aby stale ulepszać model.

  1. Ponownie przejdź do witryny Azure Custom Vision Portal, a po przejściu do projektu wybierz kartę Przewidywania (w górnej części strony):

    Wybieranie karty przewidywania

  2. Zobaczysz wszystkie obrazy, które zostały wysłane do usługi, gdy aplikacja była uruchomiona. Po umieszczeniu wskaźnika myszy na obrazach zostaną one udostępnione przewidywaniom, które zostały wykonane dla tego obrazu:

    Lista obrazów przewidywania

  3. Wybierz jeden z obrazów, aby go otworzyć. Po otwarciu zobaczysz przewidywania wykonane dla tego obrazu po prawej stronie. Jeśli przewidywania były poprawne i chcesz dodać ten obraz do modelu trenowania usługi, kliknij pole Wprowadzanie moich tagów i wybierz tag, który chcesz skojarzyć. Po zakończeniu kliknij przycisk Zapisz i zamknij w prawym dolnym rogu i przejdź do następnego obrazu.

    Wybierz obraz do otwarcia

  4. Po powrocie do siatki obrazów zauważysz, że obrazy dodane do (i zapisane) zostaną usunięte. Jeśli znajdziesz jakiekolwiek obrazy, które uważasz, że nie masz w nich oznakowanego elementu, możesz je usunąć, klikając znacznik na tym obrazie (może to zrobić dla kilku obrazów), a następnie klikając przycisk Usuń w prawym górnym rogu strony siatki. W wyświetlonym oknie podręcznym możesz kliknąć pozycję Tak, usunąć lub Nie, aby potwierdzić usunięcie lub anulować je odpowiednio.

    Usuwanie obrazów

  5. Gdy wszystko będzie gotowe do kontynuowania, kliknij zielony przycisk Pociąg w prawym górnym rogu. Model usługi zostanie wytrenowany przy użyciu wszystkich udostępnionych obrazów (co zwiększy jego dokładność). Po zakończeniu trenowania ponownie kliknij przycisk Ustaw jako domyślny , aby adres URL przewidywania nadal korzystał z najbardziej aktualnej iteracji usługi.

    Rozpoczynanie trenowania modelu usługiWybierz opcję ustaw jako domyślną

Zakończona aplikacja interfejsu API usługi Custom Vision

Gratulacje, utworzono aplikację rzeczywistości mieszanej, która korzysta z interfejsu API usługi Azure Custom Vision do rozpoznawania obiektów w świecie rzeczywistym, trenowania modelu usługi i wyświetlania pewności co zostało zaobserwowane.

Przykład ukończonego projektu

Ćwiczenia dodatkowe

Ćwiczenie 1

Trenowanie usługi Custom Vision Service w celu rozpoznawania większej liczby obiektów.

Ćwiczenie 2

Aby rozwinąć zdobytą wiedzę, wykonaj następujące ćwiczenia:

Odtwarzaj dźwięk po rozpoznaniu obiektu.

Ćwiczenie 3

Użyj interfejsu API, aby ponownie wytrenować usługę przy użyciu tych samych obrazów, które analizuje aplikacja, aby usługa mogła być bardziej dokładna (wykonaj jednocześnie przewidywanie i trenowanie).