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


Uwaga

Samouczki Mixed Reality Academy zostały zaprojektowane z myślą o urządzeniach HoloLens (1. generacji) i Mixed Reality immersywnych zestawów słuchawkowych. W związku z tym uważamy, że ważne jest pozostawienie tych samouczków w miejscu dla deweloperów, którzy nadal szukają wskazówek dotyczących opracowywania dla tych urządzeń. Te samouczki nie zostaną zaktualizowane o najnowsze zestawy narzędzi ani interakcje używane na potrzeby 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ć na potrzeby 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, zgodnie z opisem przechwycenia przez kamerę Microsoft HoloLens lub kamerą podłączoną do komputera w celu uzyskania immersywnych 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 na tym nowym obrazie. Usługa udostępnia 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 szkolenie 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óbujesz 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 przy użyciu aparatu HoloLens, przekazywanie przechwyconych obrazów do usługi i trenowanie modelu custom vision.

W tym kursie dowiesz się, jak uzyskać wyniki z usługi Custom Vision Service w przykładowej aplikacji opartej na a unity. Zastosowanie tych pojęć do aplikacji niestandardowej, 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, aby Windows Mixed Reality immersywny zestaw słuchawkowy (VR). Ponieważ immersyjne zestawy nagłowne (VR) nie mają dostępnych kamer, potrzebny będzie 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ący sprzęt i oprogramowanie:

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. Konfigurowanie i testowanie urządzenia HoloLens. Jeśli potrzebujesz pomocy technicznej dotyczącej konfigurowania urządzenia HoloLens, zapoznaj się z artykułem dotyczącym konfigurowania 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 wykonaniu 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 dotyczącego dostrajania czujników holoLens.

Rozdział 1 — portal usługi Custom Vision

Aby korzystać z usługi Custom Vision na platformie Azure, należy skonfigurować wystąpienie usługi, aby było dostępne dla aplikacji.

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

  2. Kliknij przycisk Rozpocznij .

    Wprowadzenie do usługi Custom Vision Service

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

    Zaloguj się do portalu

    Uwaga

    Jeśli nie masz jeszcze konta platformy Azure, musisz je utworzyć. Jeśli korzystasz z tego samouczka w sytuacji na potrzeby zajęć lub laboratorium, poproś instruktora lub jednego z opiekunów o pomoc przy konfigurowaniu nowego konta.

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

    Warunki użytkowania usługi

  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ę przechowywanie wszystkich usług platformy Azure skojarzonych z pojedynczym projektem (np. takimi jak te kursy) w ramach wspólnej grupy zasobów.

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

    5. W polu Domeny ustaw wartość 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 strony usługi Custom Vision, projektu.

Rozdział 2 — Szkolenie projektu Custom Vision

W portalu Custom Vision podstawowym celem jest wytrenować projekt 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 rozpoznawać aplikacja. 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 pozycję 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łączanie nowego tagu

  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

    Aby przekazać, możesz wybrać kilka obrazów jednocześnie.

  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.

    Przekazywanie plików

  8. Powtórz ten sam proces, aby utworzyć nowy tag o nazwie Klawiatura i przekazać odpowiednie zdjęcia. Pamiętaj, aby usunąć zaznaczenie polamyszy 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 trenowania rozpocznie tworzenie.

    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 adres URL domyślnego i przewidywania

    Uwaga

    Podany z tego adresu URL punktu końcowego 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 usługę AzureCustomVision. Upewnij się, że szablon projektu ma ustawioną wartość 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ć domyślny edytor skryptów 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 plików > 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 ma ustawioną wartość HoloLens

      W przypadku immersyjnych zestawów słuchawkowych ustaw wartość Urządzenie docelowe na Dowolne urządzenie.

    2. Typ kompilacji jest ustawiony na D3D

    3. Zestaw SDK jest ustawiony na najnowszą zainstalowaną

    4. Wersja programu Visual Studio jest ustawiona na najnowszą zainstalowaną

    5. Kompilowanie i uruchamianie jest ustawione na komputer lokalny

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

      1. Zrób to, wybierając pozycję Dodaj otwarte sceny. Zostanie wyświetlone okno zapisywania.

        Dodawanie otwartej sceny do listy kompilacji

      2. Utwórz nowy folder dla tego i dowolnego przyszłego 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. Kamerka internetowa

      3. Mikrofon

      Konfigurowanie ustawień publikowania

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

    Konfigurowanie ustawień XR

  8. W obszarze Ustawienia kompilacjiProjekty aparatu Unity C# 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 środowisku Unity

Ważne

Jeśli chcesz pominąć składnik Unity Set up 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>niestandardowypakietu Assets>.

  2. W oknie Importuj pakiet aparatu Unity wyskakującym upewnij się, że wybrano wszystkie elementy w obszarze (i włącznie) 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.

    Wybierz wtyczkę Newtonsoft

  5. Po wybraniu wtyczki Newtonsoft.Json upewnij się, że pole wyboru Dowolna platforma jest niezaznaczone, a następnie upewnij się, że program 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 umożliwia skonfigurowanie ich do użycia 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
    • zaznaczonotylkoWSAPlayer
    • Proces nie jest sprawdzany

    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 kamery głównej 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łcenia jest ustawiona na 0, 0, 0

    4. Ustaw opcję Clear Flags (Wyczyść flagi ) na Solid Color (zignoruj tę opcję dla immersywnych zestawów nagłownych).

    5. Ustaw kolor tła składnika aparatu na Czarny, Alfa 0 (kod szesnastkowy: #000000000) ( zignoruj to w przypadku immersywnych zestawów słuchawkowych).

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

Rozdział 6 — Tworzenie klasy CustomVisionAnalyser.

Na tym etapie możesz napisać kod.

Rozpoczniesz od klasy CustomVisionAnalyser .

Uwaga

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

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.

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

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy folder zasobów 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 odpowiadały następującym elementom:

    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

    Upewnij się, że wstawiasz klucz przewidywania do zmiennej predictionKey i punktu końcowego przewidywania do zmiennej predictionEndpoint . Skopiowano je do Notatnika wcześniej w trakcie.

  7. Aby zainicjować zmienną Wystąpienia, należy teraz dodać kod funkcji Awake():

        /// <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 koprocedurę (przy użyciu metody static GetImageAsByteArray(), która uzyska wyniki analizy obrazu przechwyconego przez klasę ImageCapture .

    Uwaga

    W koprocedurze AnalyseImageCapture istnieje wywołanie klasy SceneOrganiser , którą jeszcze nie utworzono. 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. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

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 Custom Vision Prediction v2.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 polecenie 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łosowe od użytkownika.

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy wewnątrz folderu Skrypty , a następnie kliknij polecenie 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, które będą rozpoznawane 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. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

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ć szereg 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 polecenie 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 jest udostępniany w dokumentacji 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, zapoznaj się z interfejsem API referencyjnym trenowania Custom Vision w wersji 1.2.

    Ostrzeżenie

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

        /// <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 dodano wartość klucza usługi (klucz trenowania) i wartość identyfikatora projektu zanotowaną wcześniej; są to wartości zebrane z portalu wcześniej w kursie (rozdział 2, krok 10).

  5. Dodaj następujące metody Start() i Awake(). Metody te są wywoływane przy inicjowaniu 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 i jest teraz gotowy do przesłania do usługi Custom Vision, 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. Alertuje również klasę VoiceRecognizer , aby rozpocząć nasłuchiwanie użytkownika pod kątem 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łosowe 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 skojarzonej 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 ze wszystkimi poprzednimi obrazami przesłanymi do usługi oraz właśnie przekazanym obrazem. Po zakończeniu trenowania ta metoda wywoła metodę, aby ustawić nowo utworzoną iterację jako domyślną, 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ślną. Po zakończeniu ta metoda będzie musiała usunąć poprzednią iterację istniejącą w usłudze. W momencie pisania tego kursu istnieje limit maksymalnie dziesięciu (10) iteracji, które mogą istnieć 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. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

Rozdział 10 — tworzenie klasy SceneOrganiser

Ta klasa będzie:

  • 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 głównego aparatu i wyświetl dane odebrane z usługi Custom Vision.

  • 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 w folderze 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. Potrzebna będzie tylko jedna przestrzeń nazw, usunięcie pozostałych z klasy SceneOrganiser :

    using UnityEngine;
    
  4. Następnie dodaj następujące zmienne w klasie 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ącej 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(), które będą pojawiać się i wyświetlać dane 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 zduplikuje 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. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

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 pozycję 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 w klasie 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 zostanie wywołana po wystąpieniu gestu naciśnięcia.

        /// <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 uruchamiania procesu przechwytywania obrazów i przechowywania 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, gdy zdjęcie zostało uchwycone, a gdy będzie gotowe do przeanalizowania. Wynik jest następnie przekazywany do klasy CustomVisionAnalyser lub CustomVisionTrainer w zależności od trybu ustawionego kodu.

        /// <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. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

  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 do urządzenia HoloLens.

Przed wykonaniem tej czynności upewnij się, że:

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

  • Wszystkie pola w głównym aparacie, Panelu 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ę Skompiluj. Środowisko Unity uruchomi okno Eksplorator plików, w którym należy utworzyć, a następnie wybierz folder, w którym ma zostać skompilowanie aplikacji. Utwórz teraz ten folder i nadaj mu nazwę Aplikacja. Następnie z wybraną pozycją Folder aplikacji kliknij pozycję Wybierz folder.

  4. Środowisko Unity rozpocznie kompilowanie projektu w folderze App .

  5. Po zakończeniu kompilowania aparatu Unity (może to zająć trochę czasu), zostanie otwarte okno Eksplorator plików w lokalizacji kompilacji (sprawdź pasek zadań, ponieważ może nie zawsze pojawić się nad oknami, ale powiadomi Cię 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 upewnić się, że urządzenie HoloLens jest w trybie dewelopera. W tym celu:

    1. Podczas noszenia urządzenia HoloLens otwórz pozycję Ustawienia.

    2. Przejdź do pozycji Sieć &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 & Zabezpieczenia>dla deweloperów

    5. Ustaw tryb dewelopera na włączony.

  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 obszarze 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, które będą gotowe do uruchomienia.

Uwaga

Aby wdrożyć na immersywny zestaw słuchawkowy, ustaw wartość Platforma rozwiązania na Komputer lokalny i ustaw wartość Konfiguracja na Debugowanie z x86 jako platformą. 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ącej 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 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 od Ciebie odwiedzenia portalu, co zostało omówione w tym rozdziale. Przygotuj się do ponownego przejrzenia portalu wiele razy, aby stale ulepszać model.

  1. Ponownie przejdź do witryny Azure Custom Vision Portal, a gdy jesteś w projekcie, 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, a następnie przejdź do następnego obrazu.

    Wybierz obraz, aby otworzyć

  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żna to zrobić dla kilku obrazów), a następnie klikając pozycję Usuń w prawym górnym rogu strony siatki. W wyświetlonym okienku podręcznym możesz kliknąć kolejno pozycje Tak, Usuń 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 sprawi, że będzie bardziej dokładne). Po zakończeniu trenowania ponownie kliknij przycisk Ustaw jako domyślny , aby adres URL przewidywania nadal korzystał z najbardziej aktualnej iteracji usługi.

    Rozpocznij model usługi szkoleniowejWybierz opcję ustaw jako domyślną

Zakończona aplikacja interfejsu API Custom Vision

Gratulacje, utworzono aplikację rzeczywistości mieszanej, która wykorzystuje interfejs 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

Wytrenuj usługę Custom Vision, aby rozpoznać więcej obiektów.

Ćwiczenie 2

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

Odtwarzanie dźwięku 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).