Urządzenia HoloLens (1. generacji) i Azure 310: wykrywanie obiektów

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 i jej położenie przestrzenne w udostępnionym obrazie przy użyciu funkcji wykrywania obiektów w aplikacji rzeczywistości mieszanej na platformie Azure Custom Vision.

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 i przybliżonej ich lokalizacji w świecie rzeczywistym, jak zapewnia przechwytywanie przez aparat Microsoft HoloLens lub podłączanie kamery do komputera w celu uzyskania immersywnych zestawów słuchawkowych (VR).

wynik kursu

Usługa Azure Custom Vision wykrywania obiektów to usługa firmy Microsoft, która umożliwia deweloperom tworzenie niestandardowych klasyfikatorów obrazów. Klasyfikatory te mogą być następnie używane z nowymi obrazami do wykrywania obiektów na tym nowym obrazie, zapewniając granice box w samym obrazie. Usługa udostępnia prosty, łatwy w użyciu portal online, aby usprawnić ten proces. Aby uzyskać więcej informacji, odwiedź następujące linki:

Po ukończeniu tego kursu będziesz mieć aplikację rzeczywistości mieszanej, która będzie mogła wykonywać następujące czynności:

  1. Użytkownik będzie mógł spojrzeć na obiekt, który wytrenowali przy użyciu usługi Azure Custom Vision Service, wykrywania obiektów.
  2. Użytkownik użyje gestu Naciśnięcie , aby przechwycić obraz tego, co patrzy.
  3. Aplikacja wyśle obraz do usługi Azure Custom Vision Service.
  4. Zostanie wyświetlona odpowiedź z usługi, która wyświetli wynik rozpoznawania jako tekst w przestrzeni światowej. Będzie to realizowane przy użyciu śledzenia przestrzennego Microsoft HoloLens, jako sposobu zrozumienia położenia na świecie rozpoznanego obiektu, a następnie przy użyciu tagu skojarzonego z wykrytymi elementami na obrazie, aby podać tekst etykiety.

Kurs obejmuje również ręczne przekazywanie obrazów, tworzenie tagów i szkolenie usługi w celu rozpoznawania różnych obiektów (w podanym przykładzie kubka), ustawiając pole granicy na przesłanym obrazie.

Ważne

Po utworzeniu i użyciu aplikacji deweloper powinien wrócić do usługi Azure Custom Vision Service i zidentyfikować przewidywania dokonane przez usługę oraz określić, czy są poprawne, czy nie (przez tagowanie niczego, czego usługa nieodebrała, i dostosowanie pól ograniczenia). Następnie usługę można ponownie wytrenować, co zwiększy prawdopodobieństwo rozpoznawania obiektów w świecie rzeczywistym.

W tym kursie dowiesz się, jak uzyskać wyniki z usługi Azure Custom Vision Service, wykrywania obiektów, do 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 310: wykrywanie obiektów ✔️

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 tej funkcji, 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 Custom Vision

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

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

  2. Kliknij pozycję Wprowadzenie.

    Zrzut ekranu przedstawiający przycisk Wprowadzenie.

  3. Zaloguj się do portalu Custom Vision.

    Zrzut ekranu przedstawiający przycisk Zaloguj się.

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

  5. 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 przycisk Zgadzam się.

    Zrzut ekranu przedstawiający panel Warunki użytkowania usługi.

  6. Po zaakceptowaniu warunków jesteś teraz w sekcji Moje projekty . Kliknij pozycję Nowy projekt.

    Zrzut ekranu pokazujący, gdzie wybrać pozycję Nowy projekt.

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

      Zrzut ekranu przedstawiający miejsce dodawania szczegółów dla nowego projektu.

    4. Ustaw typy projektów jako Wykrywanie obiektów (wersja zapoznawcza).

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

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ętnaście (15) obrazów dla każdego obiektu, który ma rozpoznawać aplikacja. Możesz użyć obrazów dostarczonych z tym kursem (seria filiżanek).

Aby wytrenować projekt Custom Vision:

  1. + Kliknij przycisk obok pozycji Tagi.

    Zrzut ekranu przedstawiający przycisk + obok pozycji Tagi.

  2. Dodaj nazwę tagu, który będzie używany do kojarzenia obrazów. W tym przykładzie używamy obrazów kubków do rozpoznawania, dlatego nazwaliśmy tag dla tego kubka. Kliknij przycisk Zapisz po zakończeniu.

    Zrzut ekranu pokazujący, gdzie dodać nazwę tagu.

  3. Zauważysz, że tag został dodany (może być konieczne ponowne załadowanie strony w celu wyświetlenia go).

    Zrzut ekranu przedstawiający miejsce dodania tagu.

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

    Zrzut ekranu przedstawiający miejsce dodawania obrazów.

  5. Kliknij pozycję Przeglądaj pliki lokalne i przejdź do obrazów, które chcesz przekazać dla jednego obiektu, a minimalna wartość to piętnaście (15).

    Porada

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

    Zrzut ekranu przedstawiający obrazy, które można przekazać.

  6. Naciśnij pozycję Przekaż pliki po wybraniu wszystkich obrazów, za pomocą których chcesz wytrenować projekt. Pliki rozpoczną przekazywanie. Po potwierdzeniu przekazania kliknij przycisk Gotowe.

    Zrzut ekranu przedstawiający postęp przekazanych obrazów.

  7. W tym momencie obrazy są przekazywane, ale nie oznaczane.

    Zrzut ekranu przedstawiający nieotagowany obraz.

  8. Aby oznaczyć obrazy, użyj myszy. Po umieszczeniu wskaźnika myszy na obrazie wyróżnienie zaznaczenia ułatwi Ci automatyczne rysowanie zaznaczenia wokół obiektu. Jeśli nie jest to dokładne, możesz rysować własne. Jest to realizowane przez przytrzymanie lewym kliknięciem myszy i przeciągnięciem regionu zaznaczenia w celu objęcia obiektu.

    Zrzut ekranu przedstawiający sposób tagowania obrazu.

  9. Po wybraniu obiektu na obrazie zostanie wyświetlony mały monit z prośbą o dodanie tagu regionu. Wybierz wcześniej utworzony tag ('Cup', w powyższym przykładzie) lub jeśli dodasz więcej tagów, wpisz ten tag i kliknij przycisk + (plus).

    Zrzut ekranu przedstawiający tag dodany do obrazu.

  10. Aby oznaczyć następny obraz, możesz kliknąć strzałkę po prawej stronie bloku lub zamknąć blok tagu (klikając znak X w prawym górnym rogu bloku), a następnie kliknij następny obraz. Gdy będzie gotowy następny obraz, powtórz tę samą procedurę. Zrób to dla wszystkich przekazanych obrazów, dopóki nie zostaną oznaczone.

    Uwaga

    Możesz wybrać kilka obiektów na tym samym obrazie, jak na poniższej ilustracji:

    Zrzut ekranu przedstawiający wiele obiektów na obrazie.

  11. Po oznaczeniu ich wszystkich kliknij przycisk oznakowany po lewej stronie ekranu, aby wyświetlić oznakowane obrazy.

    Zrzut ekranu z wyróżnionym przyciskiem Tag.

  12. Teraz możesz przystąpić do trenowania usługi. Kliknij przycisk Train (Trenowanie ), a pierwsza iteracja trenowania rozpocznie się.

    Zrzut ekranu z wyróżnionym przyciskiem Train (Trenowanie).

    Zrzut ekranu przedstawiający pierwszą iterację trenowania.

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

    Zrzut ekranu z wyróżnionym przyciskiem Ustaw jako domyślny.

    Uwaga

    Punkt końcowy 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.

  14. Po kliknięciu pozycji Adres URL przewidywania otwórz Notatnik i skopiuj i wklej adres URL (nazywany również punktem końcowym przewidywania) i klucz przewidywania usługi, aby można było go pobrać, gdy będzie potrzebny później w kodzie.

    Zrzut ekranu przedstawiający punkt końcowy przewidywania i klucz predycji.

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.

    Zrzut ekranu z wyróżnionym przyciskiem Nowy.

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

    Zrzut ekranu przedstawiający szczegóły projektu i miejsce, w którym należy wybrać pozycję Utwórz projekt.

  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. Zamknij okno Preferencje .

    Zrzut ekranu przedstawiający miejsce zmiany edytora skryptów zewnętrznych na program Visual Studio.

  4. Następnie przejdź do pozycji Ustawienia kompilacji plików > i przełącz platformę na platforma uniwersalna systemu Windows, a następnie kliknij przycisk Przełącz platformę.

    Zrzut ekranu z wyróżnionym przyciskiem Przełącz platformę.

  5. W tym samym oknie Ustawienia kompilacji upewnij się, że ustawiono następujące ustawienia:

    1. Urządzenie docelowe ma ustawioną wartość HoloLens

    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. Pozostałe ustawienia w obszarze Ustawienia kompilacji powinny być pozostawione jako domyślne na razie.

      Zrzut ekranu przedstawiający opcje konfiguracji ustawienia kompilacji.

  6. W tym samym 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.

        Zrzut ekranu przedstawiający opcję Poziom zgodności interfejsu API ustawiony na platformę .NET 4.6.

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

      1. InternetClient

      2. Kamerka internetowa

      3. SpatialPerception

        Zrzut ekranu przedstawiający połowę opcji konfiguracji możliwości.Zrzut ekranu przedstawiający niższą połowę opcji konfiguracji możliwości.

    3. W dalszej części panelu w obszarze Ustawienia XR (znaleziono poniżej ustawień publikowania), zaznacz opcję Obsługiwana rzeczywistość wirtualna, a następnie upewnij się, że dodano zestaw SDK Windows Mixed Reality.

      Zrzut ekranu przedstawiający dodanie zestawu SDK Windows Mixed Reality.

  8. Po powrocie do ustawień kompilacjiprojekty języka C# aparatu Unity nie są już wyszarzone: zaznacz pole wyboru obok tego.

  9. Zamknij okno Build Settings (Ustawienia kompilacji).

  10. W edytorze kliknij pozycję Edytuj>grafikęustawień> projektu.

    Zrzut ekranu przedstawiający wybraną opcję menu Grafika.

  11. W Panelu inspektoraustawienia grafiki zostaną otwarte. Przewiń w dół, aż zobaczysz tablicę o nazwie Zawsze uwzględnij cieniowania. Dodaj miejsce, zwiększając zmienną Size o jedną (w tym przykładzie było to 8, więc zrobiliśmy to 9). Zostanie wyświetlone nowe miejsce w ostatniej pozycji tablicy, jak pokazano poniżej:

    Zrzut ekranu przedstawiający tablicę Always Included Shaders.

  12. W miejscu kliknij mały okrąg docelowy obok miejsca, aby otworzyć listę cieniowania. Poszukaj starszego cieniowania/przezroczystego/rozproszonego cieniowania i kliknij go dwukrotnie.

    Zrzut ekranu przedstawiający starsze cieniowania/przezroczyste/rozproszone cieniowania.

Rozdział 4 . Importowanie pakietu Aparatu Unity CustomVisionObjDetection

Na potrzeby tego kursu otrzymasz pakiet zasobów aparatu Unity o nazwie Azure-MR-310.unitypackage.

[PORADA] Wszystkie obiekty obsługiwane przez aparat Unity, w tym całe sceny, można spakować do pliku unitypackage i wyeksportować /zaimportować w innych projektach. Jest to najbezpieczniejszy i najbardziej wydajny sposób przenoszenia zasobów między różnymi projektami aparatu Unity.

Pakiet Azure-MR-310, który należy pobrać tutaj.

  1. Przed tobą pulpit nawigacyjny aparatu Unity kliknij pozycję Zasoby w menu w górnej części ekranu, a następnie kliknij pozycję Importuj pakiet > niestandardowy pakietu.

    Zrzut ekranu przedstawiający opcję menu Pakiet niestandardowy.

  2. Użyj selektora plików, aby wybrać pakiet Azure-MR-310.unitypackage i kliknij przycisk Otwórz. Zostanie wyświetlona lista składników tego zasobu. Potwierdź importowanie, klikając przycisk Importuj .

    Zrzut ekranu przedstawiający listę składników zasobów, które chcesz zaimportować.

  3. Po zakończeniu importowania zauważysz, że foldery z pakietu zostały dodane do folderu Assets . Taka struktura folderów jest typowa dla projektu aparatu Unity.

    Zrzut ekranu przedstawiający zawartość folderu Assets.

    1. Folder Materials zawiera materiał używany przez kursor spojrzenia.

    2. Folder Plugins zawiera bibliotekę DLL Newtonsoft używaną przez kod do deserializacji odpowiedzi internetowej usługi. Dwie (2) różne wersje zawarte w folderze i podfolder są niezbędne do umożliwienia użycia i skompilowania biblioteki zarówno przez Edytor aparatu Unity, jak i kompilację platformy UWP.

    3. Folder Prefabs zawiera prefabryki zawarte w scenie. Są to:

      1. GazeCursor, kursor używany w aplikacji. Będzie współpracować z prefabem SpatialMapping, aby można było umieścić w scenie na obiektach fizycznych.
      2. Etykieta, czyli obiekt interfejsu użytkownika używany do wyświetlania tagu obiektu w scenie, gdy jest to wymagane.
      3. Obiekt SpatialMapping, który umożliwia aplikacji tworzenie mapy wirtualnej przy użyciu śledzenia przestrzennego Microsoft HoloLens.
    4. Folder Sceny , który obecnie zawiera wstępnie utworzoną scenę dla tego kursu.

  4. Otwórz folder Sceny w panelu projektu i kliknij dwukrotnie ikonę ObjDetectionScene, aby załadować scenę, która będzie używana na potrzeby tego kursu.

    Zrzut ekranu przedstawiający element ObjDetectionScene w folderze Sceny.

    Uwaga

    Nie dołączono żadnego kodu. Napiszesz kod, postępując zgodnie z tym kursem.

Rozdział 5 — Tworzenie klasy CustomVisionAnalyser.

Na tym etapie możesz napisać kod. Rozpoczniesz od klasy CustomVisionAnalyser .

Uwaga

Wywołania usługi Custom Vision Service 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 Custom Vision, który może być również używany do nawiązywania wywołań do usługi. Aby uzyskać więcej informacji, odwiedź artykuł Custom Vision SDK.

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 elementów zawartości znajdujący się w panelu projektu, a następnie kliknij pozycję Utwórz>folder. Wywołaj folder Scripts.

    Zrzut ekranu przedstawiający sposób tworzenia folderu Scripts.

  2. Kliknij dwukrotnie nowo 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. Upewnij się, że w górnej części pliku istnieją następujące przestrzenie nazw:

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  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>
        /// Bite array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Uwaga

    Upewnij się, że wstawiasz klucz przewidywania usługi do zmiennej predictionKey i punktu końcowego przewidywania do zmiennej predictionEndpoint . Skopiowano je do Notatnika wcześniej, w rozdziale 2, krok 14.

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

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Dodaj koprocedurę (przy użyciu metody static GetImageAsByteArray(), która uzyska wyniki analizy obrazu przechwyconą 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)
        {
            Debug.Log("Analyzing...");
    
            WWWForm webForm = new WWWForm();
    
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                Debug.Log("response: " + jsonResponse);
    
                // Create a texture. Texture size does not matter, since
                // LoadImage will replace with the incoming image size.
                //Texture2D tex = new Texture2D(1, 1);
                //tex.LoadImage(imageBytes);
                //SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
                // The response will be in JSON format, therefore it needs to be deserialized
                //AnalysisRootObject analysisRootObject = new AnalysisRootObject();
                //analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
                //SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  9. Usuń metody Start() i Update(), ponieważ nie będą używane.

  10. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

Ważne

Jak wspomniano wcześniej, 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ł 6 — 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.

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. Upewnij się, że w górnej części pliku istnieją następujące przestrzenie nazw:

    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.

    Ostrzeżenie

    Ważne jest, aby uważnie postępować zgodnie z następną instrukcją. Jeśli umieścisz nowe deklaracje klas w klasie CustomVisionObjects , w rozdziale 10 wystąpią błędy kompilacji z informacją, że klasy AnalysisRootObject i BoundingBox nie zostaną znalezione.

  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
    /// Includes Bounding Box
    /// </summary>
    public class AnalysisRootObject
    {
        public string id { get; set; }
        public string project { get; set; }
        public string iteration { get; set; }
        public DateTime created { get; set; }
        public List<Prediction> predictions { get; set; }
    }
    
    public class BoundingBox
    {
        public double left { get; set; }
        public double top { get; set; }
        public double width { get; set; }
        public double height { get; set; }
    }
    
    public class Prediction
    {
        public double probability { get; set; }
        public string tagId { get; set; }
        public string tagName { get; set; }
        public BoundingBox boundingBox { get; set; }
    }
    
  6. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

Rozdział 7 — Tworzenie klasy SpatialMapping

Ta klasa ustawi zderzacz mapowania przestrzennego w scenie, aby móc wykrywać kolizje między obiektami wirtualnymi i rzeczywistymi obiektami.

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

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

  3. Upewnij się, że masz następujące przestrzenie nazw, do których odwołuje się powyżej klasy SpatialMapping :

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

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SpatialMapping Instance;
    
        /// <summary>
        /// Used by the GazeCursor as a property with the Raycast call
        /// </summary>
        internal static int PhysicsRaycastMask;
    
        /// <summary>
        /// The layer to use for spatial mapping collisions
        /// </summary>
        internal int physicsLayer = 31;
    
        /// <summary>
        /// Creates environment colliders to work with physics
        /// </summary>
        private SpatialMappingCollider spatialMappingCollider;
    
  5. Dodaj pozycje Awake() i Start():

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Initialize and configure the collider
            spatialMappingCollider = gameObject.GetComponent<SpatialMappingCollider>();
            spatialMappingCollider.surfaceParent = this.gameObject;
            spatialMappingCollider.freezeUpdates = false;
            spatialMappingCollider.layer = physicsLayer;
    
            // define the mask
            PhysicsRaycastMask = 1 << physicsLayer;
    
            // set the object as active one
            gameObject.SetActive(true);
        }
    
  6. Usuń metodę Update().

  7. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

Rozdział 8 — Tworzenie klasy GazeCursor

Ta klasa jest odpowiedzialna za skonfigurowanie kursora w prawidłowej lokalizacji w przestrzeni rzeczywistej przez użycie metody SpatialMappingCollider utworzonej w poprzednim rozdziale.

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ływanie skryptu GazeCursor

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

  3. Upewnij się, że masz następującą przestrzeń nazw, do której odwołuje się powyżej klasy GazeCursor :

    using UnityEngine;
    
  4. Następnie dodaj następującą zmienną w klasie GazeCursor powyżej metody Start().

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Zaktualizuj metodę Start() przy użyciu następującego kodu:

        /// <summary>
        /// Runs at initialization right after the Awake method
        /// </summary>
        void Start()
        {
            // Grab the mesh renderer that is on the same object as this script.
            meshRenderer = gameObject.GetComponent<MeshRenderer>();
    
            // Set the cursor reference
            SceneOrganiser.Instance.cursor = gameObject;
            gameObject.GetComponent<Renderer>().material.color = Color.green;
    
            // If you wish to change the size of the cursor you can do so here
            gameObject.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
        }
    
  6. Zaktualizuj metodę Update() przy użyciu następującego kodu:

        /// <summary>
        /// Update is called once per frame
        /// </summary>
        void Update()
        {
            // Do a raycast into the world based on the user's head position and orientation.
            Vector3 headPosition = Camera.main.transform.position;
            Vector3 gazeDirection = Camera.main.transform.forward;
    
            RaycastHit gazeHitInfo;
            if (Physics.Raycast(headPosition, gazeDirection, out gazeHitInfo, 30.0f, SpatialMapping.PhysicsRaycastMask))
            {
                // If the raycast hit a hologram, display the cursor mesh.
                meshRenderer.enabled = true;
                // Move the cursor to the point where the raycast hit.
                transform.position = gazeHitInfo.point;
                // Rotate the cursor to hug the surface of the hologram.
                transform.rotation = Quaternion.FromToRotation(Vector3.up, gazeHitInfo.normal);
            }
            else
            {
                // If the raycast did not hit a hologram, hide the cursor mesh.
                meshRenderer.enabled = false;
            }
        }
    

    Uwaga

    Nie martw się o błąd dla nieznajdowania klasy SceneOrganiser . Zostanie on utworzony w następnym rozdziale.

  7. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

Rozdział 9 — Tworzenie klasy SceneOrganiser

Ta klasa będzie:

  • Skonfiguruj kamerę główną , dołączając do niej odpowiednie składniki.

  • Po wykryciu obiektu będzie on odpowiedzialny za obliczanie jego pozycji w świecie rzeczywistym i umieszczenie etykiety tagu w pobliżu z odpowiednią nazwą tagu.

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy wewnątrz folderu Skrypty , a następnie kliknij polecenie 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. Upewnij się, że masz następujące przestrzenie nazw, do których odwołuje się klasa SceneOrganiser :

    using System.Collections.Generic;
    using System.Linq;
    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 Main Camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        public GameObject label;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.8f;
    
        /// <summary>
        /// The quad object hosting the imposed image captured
        /// </summary>
        private GameObject quad;
    
        /// <summary>
        /// Renderer of the quad object
        /// </summary>
        internal Renderer quadRenderer;
    
  5. Usuń metody Start() i Update().

  6. Poniżej zmiennych 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 CustomVisionObjects class to this Gameobject
            gameObject.AddComponent<CustomVisionObjects>();
        }
    
  7. Dodaj metodę PlaceAnalysisLabel(), która utworzy wystąpienie etykiety w scenie (co w tym momencie jest niewidoczne dla użytkownika). Umieszcza również czworokąt (również niewidoczny), gdzie znajduje się obraz i nakłada się na świat rzeczywisty. Jest to ważne, ponieważ współrzędne pola pobrane z usługi po analizie są śledzone z powrotem do tego czworokąta w celu określenia przybliżonej lokalizacji obiektu w świecie rzeczywistym.

        /// <summary>
        /// Instantiate a Label in the appropriate location relative to the Main Camera.
        /// </summary>
        public void PlaceAnalysisLabel()
        {
            lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
            lastLabelPlacedText.text = "";
            lastLabelPlaced.transform.localScale = new Vector3(0.005f,0.005f,0.005f);
    
            // Create a GameObject to which the texture can be applied
            quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
            quadRenderer = quad.GetComponent<Renderer>() as Renderer;
            Material m = new Material(Shader.Find("Legacy Shaders/Transparent/Diffuse"));
            quadRenderer.material = m;
    
            // Here you can set the transparency of the quad. Useful for debugging
            float transparency = 0f;
            quadRenderer.material.color = new Color(1, 1, 1, transparency);
    
            // Set the position and scale of the quad depending on user position
            quad.transform.parent = transform;
            quad.transform.rotation = transform.rotation;
    
            // The quad is positioned slightly forward in font of the user
            quad.transform.localPosition = new Vector3(0.0f, 0.0f, 3.0f);
    
            // The quad scale as been set with the following value following experimentation,  
            // to allow the image on the quad to be as precisely imposed to the real world as possible
            quad.transform.localScale = new Vector3(3f, 1.65f, 1f);
            quad.transform.parent = null;
        }
    
  8. Dodaj metodę FinaliseLabel(). Odpowiada za:

    • Ustawianie tekstu etykiety za pomocą tagu przewidywania z najwyższą ufnością.
    • Wywołanie obliczenia pola ograniczenia dla obiektu czworokąta, umieszczonego wcześniej i umieszczenie etykiety w scenie.
    • Dostosowanie głębokości etykiety za pomocą Raycast w kierunku pola ograniczenia, które powinno zderzać się z obiektem w świecie rzeczywistym.
    • Zresetowanie procesu przechwytywania w celu umożliwienia użytkownikowi przechwytywania innego obrazu.
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void FinaliseLabel(AnalysisRootObject analysisObject)
        {
            if (analysisObject.predictions != null)
            {
                lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
                // Sort the predictions to locate the highest one
                List<Prediction> sortedPredictions = new List<Prediction>();
                sortedPredictions = analysisObject.predictions.OrderBy(p => p.probability).ToList();
                Prediction bestPrediction = new Prediction();
                bestPrediction = sortedPredictions[sortedPredictions.Count - 1];
    
                if (bestPrediction.probability > probabilityThreshold)
                {
                    quadRenderer = quad.GetComponent<Renderer>() as Renderer;
                    Bounds quadBounds = quadRenderer.bounds;
    
                    // Position the label as close as possible to the Bounding Box of the prediction 
                    // At this point it will not consider depth
                    lastLabelPlaced.transform.parent = quad.transform;
                    lastLabelPlaced.transform.localPosition = CalculateBoundingBoxPosition(quadBounds, bestPrediction.boundingBox);
    
                    // Set the tag text
                    lastLabelPlacedText.text = bestPrediction.tagName;
    
                    // Cast a ray from the user's head to the currently placed label, it should hit the object detected by the Service.
                    // At that point it will reposition the label where the ray HL sensor collides with the object,
                    // (using the HL spatial tracking)
                    Debug.Log("Repositioning Label");
                    Vector3 headPosition = Camera.main.transform.position;
                    RaycastHit objHitInfo;
                    Vector3 objDirection = lastLabelPlaced.position;
                    if (Physics.Raycast(headPosition, objDirection, out objHitInfo, 30.0f,   SpatialMapping.PhysicsRaycastMask))
                    {
                        lastLabelPlaced.position = objHitInfo.point;
                    }
                }
            }
            // Reset the color of the cursor
            cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the analysis process
            ImageCapture.Instance.ResetImageCapture();        
        }
    
  9. Dodaj metodę CalculateBoundingBoxPosition(), która hostuje wiele obliczeń niezbędnych do tłumaczenia współrzędnych pola ograniczenia pobranych z usługi i utwórz je proporcjonalnie na czworokącie.

        /// <summary>
        /// This method hosts a series of calculations to determine the position 
        /// of the Bounding Box on the quad created in the real world
        /// by using the Bounding Box received back alongside the Best Prediction
        /// </summary>
        public Vector3 CalculateBoundingBoxPosition(Bounds b, BoundingBox boundingBox)
        {
            Debug.Log($"BB: left {boundingBox.left}, top {boundingBox.top}, width {boundingBox.width}, height {boundingBox.height}");
    
            double centerFromLeft = boundingBox.left + (boundingBox.width / 2);
            double centerFromTop = boundingBox.top + (boundingBox.height / 2);
            Debug.Log($"BB CenterFromLeft {centerFromLeft}, CenterFromTop {centerFromTop}");
    
            double quadWidth = b.size.normalized.x;
            double quadHeight = b.size.normalized.y;
            Debug.Log($"Quad Width {b.size.normalized.x}, Quad Height {b.size.normalized.y}");
    
            double normalisedPos_X = (quadWidth * centerFromLeft) - (quadWidth/2);
            double normalisedPos_Y = (quadHeight * centerFromTop) - (quadHeight/2);
    
            return new Vector3((float)normalisedPos_X, (float)normalisedPos_Y, 0);
        }
    
  10. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

    Ważne

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

    // Create a texture. Texture size does not matter, since 
    // LoadImage will replace with the incoming image size.
    Texture2D tex = new Texture2D(1, 1);
    tex.LoadImage(imageBytes);
    SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
    // The response will be in JSON format, therefore it needs to be deserialized
    AnalysisRootObject analysisRootObject = new AnalysisRootObject();
    analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
    SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
    

Uwaga

Nie martw się o komunikat klasy ImageCapture "nie można odnaleźć", utworzysz go w następnym rozdziale.

Rozdział 10 — Tworzenie klasy ImageCapture

Następną klasą, którą zamierzasz utworzyć, jest klasa ImageCapture .

Ta klasa jest odpowiedzialna za:

  • Przechwytywanie obrazu przy użyciu aparatu HoloLens i przechowywanie go w folderze Aplikacja .
  • Obsługa gestów naciśnięcia od użytkownika.

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>
        /// 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;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the Microsoft HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  7. Zaimplementuj procedurę obsługi, która będzie wywoływana po wystąpieniu gestu naciśnięcia:

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            if (!captureIsActive)
            {
                captureIsActive = true;
    
                // Set the cursor color to red
                SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                // Begin the capture loop
                Invoke("ExecuteImageCaptureAndAnalysis", 0);
            }
        }
    

    Ważne

    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 obrazów i zapisania obrazu:

        /// <summary>
        /// Begin process of image capturing and send to Azure Custom Vision Service.
        /// </summary>
        private void ExecuteImageCaptureAndAnalysis()
        {
            // Create a label in world space using the ResultsLabel class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending
                ((res) => res.width * res.height).First();
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(true, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 1.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });
        }
    
  9. 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 elementu CustomVisionAnalyser na potrzeby analizy.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
            try
            {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
            }
            catch (Exception e)
            {
                Debug.LogFormat("Exception capturing photo to disk: {0}", e.Message);
            }
        }
    
        /// <summary>
        /// The camera photo mode has stopped after the capture.
        /// Begin the image analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            Debug.LogFormat("Stopped Photo Mode");
    
            // Dispose from the object in memory and request the image analysis 
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            // Call the image analysis
            StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath)); 
        }
    
        /// <summary>
        /// Stops all capture pending actions
        /// </summary>
        internal void ResetImageCapture()
        {
            captureIsActive = false;
    
            // Set the cursor color to green
            SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Pamiętaj, aby zapisać zmiany w programie Visual Studio przed powrotem do aparatu Unity.

Rozdział 11 — Konfigurowanie skryptów w scenie

Teraz, po napisaniu całego kodu niezbędnego dla tego projektu, nadszedł czas na skonfigurowanie skryptów w scenie i na prefab, aby działały prawidłowo.

  1. W edytorze aparatu Unity w panelu hierarchii wybierz aparat główny.

  2. W Panelu inspektora z wybraną główną kamerą kliknij pozycję Dodaj składnik, a następnie wyszukaj skrypt SceneOrganiser i kliknij dwukrotnie, aby go dodać.

    Zrzut ekranu przedstawiający skrypt SceneOrganizer.

  3. W panelu projektu otwórz folder Prefabs, przeciągnij prefabrykator Etykieta do obszaru wejściowego Puste odwołanie etykieta w skry skryptie SceneOrganiser , który właśnie został dodany do aparatu głównego, jak pokazano na poniższej ilustracji:

    Zrzut ekranu przedstawiający skrypt dodany do aparatu głównego.

  4. W panelu hierarchii wybierz element podrzędny GazeCursor głównego aparatu.

  5. W Panelu inspektora z wybraną pozycją GazeCursor kliknij pozycję Dodaj składnik, a następnie wyszukaj skrypt GazeCursor i kliknij dwukrotnie, aby go dodać.

    Zrzut ekranu przedstawiający miejsce dodawania skryptu GazeCursor.

  6. Ponownie w panelu hierarchii wybierz element podrzędnySpatialMapping aparatu głównego.

  7. W panelu Inspector (Panel inspektora) z wybranym polem SpatialMapping kliknij pozycję Dodaj składnik, a następnie wyszukaj skrypt SpatialMapping i kliknij dwukrotnie, aby go dodać.

    Zrzut ekranu przedstawiający miejsce dodawania skryptu SpatialMapping.

Pozostałe skrypty, które nie zostały ustawione, zostaną dodane przez kod w skryscie SceneOrganiser podczas wykonywania.

Rozdział 12 - Przed budynkiem

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

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

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

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

  • Skrypt GazeCursor jest dołączony do obiektu GazeCursor .

  • Skrypt SpatialMapping jest dołączony do obiektu SpatialMapping .

  • W rozdziale 5, krok 6:

    • Upewnij się, że wstawiasz klucz przewidywania usługi do zmiennej predictionKey .
    • Punkt końcowy przewidywania został wstawiony do klasy predictionEndpoint .

Rozdział 13 — Tworzenie rozwiązania platformy UWP i ładowanie bezpośrednie aplikacji

Teraz możesz przystąpić do tworzenia aplikacji jako rozwiązania platformy uniwersalnej systemu Windows, które będzie można wdrożyć w Microsoft HoloLens. Aby rozpocząć proces kompilacji:

  1. Przejdź do pozycji Ustawienia kompilacji pliku>.

  2. Zaznacz projekty c# aparatu Unity.

  3. Kliknij pozycję Dodaj otwarte sceny. Spowoduje to dodanie aktualnie otwartej sceny do kompilacji.

    Zrzut ekranu przedstawiający przycisk Dodaj otwarte sceny.

  4. 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 ten folder teraz i nadaj mu nazwę Aplikacja. Następnie po wybraniu folderu Aplikacja kliknij pozycję Wybierz folder.

  5. Aparat Unity rozpocznie kompilowanie projektu w folderze Aplikacja .

  6. Po zakończeniu kompilowania środowiska Unity (może to trochę potrwać), 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).

  7. Aby przeprowadzić wdrożenie na Microsoft HoloLens, musisz mieć adres IP tego urządzenia (w przypadku wdrożenia zdalnego) i upewnić się, że ma on również ustawiony tryb dewelopera. W tym celu:

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

    2. Przejdź do pozycji Opcjezaawansowanesieci & Internetu>Wi-Fi>

    3. Zanotuj adres IPv4 .

    4. Następnie przejdź z powrotem do obszaru Ustawienia, a następnie wybierz pozycję Aktualizuj & Zabezpieczenia>dla deweloperów

    5. Ustaw tryb deweloperawłączony.

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

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

  10. W polu Platforma rozwiązania wybierz pozycję x86, Maszyna zdalna. Zostanie wyświetlony monit o wstawienie adresu IP urządzenia zdalnego (w tym przypadku zanotowanego Microsoft HoloLens).

    Zrzut ekranu przedstawiający miejsce wstawienia adresu IP.

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

  12. Aplikacja powinna być teraz wyświetlana na liście zainstalowanych aplikacji na Microsoft HoloLens, które będą gotowe do uruchomienia.

Aby użyć aplikacji:

  • Przyjrzyj się obiektowi, który został wytrenowany za pomocą usługi Azure Custom Vision Service, wykrywania obiektów i użyj gestu Naciśnięcie.
  • Jeśli obiekt zostanie pomyślnie wykryty, zostanie wyświetlony tekst etykiety na świecie z nazwą tagu.

Ważne

Za każdym razem, gdy przechwycisz zdjęcie i wyślesz je do usługi, możesz wrócić do strony Usługi i ponownie przeszkolić usługę przy użyciu nowo przechwyconych obrazów. Na początku prawdopodobnie trzeba będzie również poprawić pola ograniczenia , aby były bardziej dokładne i ponownie przeszkolić usługę.

Uwaga

Umieszczony tekst etykiety może nie pojawić się w pobliżu obiektu, gdy czujniki Microsoft HoloLens i/lub element SpatialTrackingComponent w a unity nie mogą umieścić odpowiednich zderzaków względem obiektów w świecie rzeczywistym. Spróbuj użyć aplikacji na innej powierzchni, jeśli tak jest.

Aplikacja do wykrywania obiektów Custom Vision

Gratulacje, utworzono aplikację rzeczywistości mieszanej, która korzysta z usługi Azure Custom Vision, interfejsu API wykrywania obiektów, który może rozpoznać obiekt na podstawie obrazu, a następnie udostępnić przybliżoną pozycję dla tego obiektu w przestrzeni 3D.

Zrzut ekranu przedstawiający aplikację rzeczywistości mieszanej, która korzysta z interfejsu API wykrywania obiektów platformy Azure Custom Vision.

Ćwiczenia dodatkowe

Ćwiczenie 1

Dodawanie do etykiety tekstu za pomocą półprzezroczystego modułu w celu opakowania rzeczywistego obiektu w polu ograniczenia 3D.

Ćwiczenie 2

Przeszkolij usługę Custom Vision, aby rozpoznawać więcej obiektów.

Ćwiczenie 3

Odtwarzaj dźwięk po rozpoznaniu obiektu.

Ćwiczenie 4

Użyj interfejsu API, aby ponownie wytrenować usługę przy użyciu tych samych obrazów, które analizuje aplikacja, aby zwiększyć dokładność usługi (wykonaj jednocześnie przewidywanie i trenowanie).