Udostępnij za pośrednictwem


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

Uwaga

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


W tym kursie dowiesz się, jak rozpoznawać zawartość wizualizacji niestandardowej i jej położenie przestrzenne w udostępnionym obrazie przy użyciu funkcji wykrywania obiektów 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, aby rozpoznać podobne obiekty i przybliżyć ich lokalizację w świecie rzeczywistym, jak zapewnia przechwytywanie aparatu Microsoft HoloLens lub aparat łączący się z komputerem na potrzeby immersywnych zestawów słuchawkowych (VR).

wynik kursu

Azure Custom Vision, wykrywanie 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 pola w samym obrazie. Usługa zapewnia 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 wykonać następujące czynności:

  1. Użytkownik będzie mógł patrzeć na obiekt, który trenował przy użyciu usługi Azure Custom Vision Service, wykrywania obiektów.
  2. Użytkownik użyje gestu Tap, 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. Zostanie to wykonane za pomocą śledzenia przestrzennego urządzenia Microsoft HoloLens jako sposobu zrozumienia pozycji światowej rozpoznanego obiektu, a następnie użycia tagu skojarzonego z wykrytymi elementami na obrazie, aby podać tekst etykiety.

Kurs obejmuje również ręczne przekazywanie obrazów, tworzenie tagów i trenowanie usługi w celu rozpoznawania różnych obiektów (w podanym przykładzie kubek), ustawiając pole granic w 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 dostosowywanie pól ograniczenia). Następnie usługa może zostać ponownie wytrenowana, co zwiększy prawdopodobieństwo rozpoznawania obiektów w świecie rzeczywistym.

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

Obsługa urządzeń

Kurs HoloLens Immersyjne zestawy nagłowne
MR i Azure 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ące oprogramowanie i sprzęt:

Przed rozpoczęciem

  1. Aby uniknąć problemów podczas tworzenia tego projektu, zdecydowanie zaleca się utworzenie projektu wymienionego w tym samouczku w folderze głównym lub niemal głównym (długie ścieżki folderów mogą powodować problemy w czasie kompilacji).
  2. Skonfiguruj i przetestuj urządzenie HoloLens. Jeśli potrzebujesz pomocy technicznej, 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 wykonywaniu tych zadań dla każdego użytkownika).

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

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

Rozdział 1 — Portal 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 Service.

  2. Kliknij pozycję Wprowadzenie.

    Zrzut ekranu z wyróżnionym przyciskiem Wprowadzenie.

  3. Zaloguj się do portalu Custom Vision.

    Zrzut ekranu przedstawiający przycisk Zaloguj.

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

  5. Po pierwszym zalogowaniu się zostanie wyświetlony monit z panelem Warunki świadczenia usługi . Kliknij pole wyboru, aby zaakceptować warunki. Następnie kliknij przycisk Zgadzam się.

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

  6. Po uzgodnieniu 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ę zachowanie wszystkich usług platformy Azure skojarzonych z jednym 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 usługi Custom Vision

Po przejściu do portalu Custom Vision głównym celem jest trenowanie projektu 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 być rozpoznawany przez aplikację. Możesz użyć obrazów dostarczonych z tym kursem (seria kubków).

Aby wytrenować projekt usługi 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 elementu Cup. Kliknij pozycję 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, przy czym minimalna wartość to piętnaście (15).

    Napiwek

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

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

  6. Po wybraniu wszystkich obrazów, za pomocą których chcesz wytrenować projekt, naciśnij pozycję Przekaż pliki . 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 otagowane.

    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 automatyczne rysowanie zaznaczenia wokół obiektu. Jeśli nie jest to dokładne, możesz narysować własne. Można to zrobić, trzymając lewym przyciskiem myszy i przeciągając region zaznaczenia, aby objąć obiekt.

    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 go 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ższym obrazie:

    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 wytrenować usługę. 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 przedstawiający przycisk Ustaw jako domyślny.

    Uwaga

    Punkt końcowy, który jest dostarczany z tego elementu, 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 predyscji.

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 wybierania pozycji Utwórz projekt.

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

    Zrzut ekranu pokazujący, gdzie zmienić edytor skryptów zewnętrznych na program Visual Studio.

  4. Następnie przejdź do pozycji Ustawienia kompilacji pliku > 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 jest ustawione na urządzenie HoloLens

    2. Typ kompilacji jest ustawiony na D3D

    3. Zestaw SDK jest ustawiony na najnowszą zainstalowaną

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

    5. Kompilowanie i uruchamianie jest ustawione na komputer lokalny

    6. 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 przestrzeni, w której 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 .NET 4.6.

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

      1. InternetClient

      2. Kamera 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 pole Virtual Reality Supported (Obsługiwane w rzeczywistości wirtualnej), a następnie upewnij się, że dodano zestaw WINDOWS Mixed Reality SDK .

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

  8. Po powrocie do ustawień kompilacji projekty 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 inspektora zostaną otwarte ustawienia grafiki. Przewiń w dół, aż zobaczysz tablicę o nazwie Zawsze uwzględnij cieniowania. Dodaj gniazdo, 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ę Zawsze dołączone cieniowania.

  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. Po wybraniu pulpitu nawigacyjnego 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 , a następnie 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 podfolderie są niezbędne, aby umożliwić używanie i kompilowanie 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 podstawie obiektów fizycznych.
      2. Etykieta, czyli obiekt interfejsu użytkownika używany do wyświetlania tagu obiektu w scenie, jeśli jest to wymagane.
      3. SpatialMapping, czyli obiekt, który umożliwia aplikacji tworzenie mapy wirtualnej przy użyciu śledzenia przestrzennego urządzenia 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ę używaną 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.

W tym momencie 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 usługi Custom Vision. Korzystając z tego, zobaczysz, jak zaimplementować i wykorzystać ten interfejs API (przydatne do zrozumienia, jak zaimplementować coś podobnego samodzielnie). Należy pamiętać, że firma Microsoft oferuje zestaw Custom Vision SDK , który może również służyć do nawiązywania wywołań do usługi. Aby uzyskać więcej informacji, zobacz artykuł Dotyczący zestawu 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.

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

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy folder elementów zawartości znajdujący się w panelu projektu, a następnie kliknij polecenie 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 wstawisz 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. Teraz należy dodać kod dla aplikacji Awake(), aby zainicjować zmienną wystąpienia:

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Dodaj metodę coroutine (ze statyczną metodą GetImageAsByteArray(), która uzyska wyniki analizy obrazu przechwyconą przez klasę ImageCapture .

    Uwaga

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

        /// <summary>
        /// Call the Computer Vision Service to submit the image.
        /// </summary>
        public IEnumerator AnalyseLastImageCaptured(string imagePath)
        {
            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. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

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 pozycję Utwórz>skrypt języka C#. Wywołaj skrypt CustomVisionObjects.

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

  3. 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 nie można odnaleźć klasy AnalysisRootObject i BoundingBox.

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

Rozdział 7 . Tworzenie klasy SpatialMapping

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

Aby utworzyć tę klasę:

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

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

  3. Upewnij się, że nad klasą SpatialMapping istnieją następujące przestrzenie nazw:

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

Rozdział 8 . Tworzenie klasy GazeCursor

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

Aby utworzyć tę klasę:

  1. Kliknij prawym przyciskiem myszy wewnątrz folderu Skrypty, a następnie kliknij pozycję Utwórz>skrypt języka C#. Wywoł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ę klasa GazeCursor :

    using UnityEngine;
    
  4. Następnie dodaj następującą zmienną wewnątrz klasy 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 nie znaleziono klasy SceneOrganiser , utworzysz go w następnym rozdziale.

  7. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

Rozdział 9 . Tworzenie klasy SceneOrganiser

Ta klasa:

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

  • Po wykryciu obiektu będzie on odpowiedzialny za obliczanie swojej 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 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. Upewnij się, że nad klasą SceneOrganiser istnieją następujące przestrzenie nazw:

    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), w którym 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, aby określić przybliżoną lokalizację 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 największą ufnością.
    • Wywołanie obliczeń pola ograniczenia na obiekcie czworokąt, umieszczonym wcześniej i umieszczeniu etykiety w scenie.
    • Dostosowanie głębokości etykiety przy użyciu Raycast w kierunku pola ograniczenia, które powinno zderzyć 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 przetł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. Przed powrotem do aparatu Unity pamiętaj o zapisaniu zmian w programie Visual Studio.

    Ważne

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

    // 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ą utworzysz, jest klasa ImageCapture .

Ta klasa jest odpowiedzialna za:

  • Przechwytywanie obrazu przy użyciu aparatu HoloLens i przechowywanie go w folderze App .
  • 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 zostanie wywołana, gdy wystąpi gest Tap:

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

Rozdział 11 — Konfigurowanie skryptów na scenie

Teraz, po napisaniu całego kodu niezbędnego dla tego projektu, nadszedł czas, aby skonfigurować skrypty w scenie i w 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ą pozycją Aparat główny 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 prefab etykietę do pustego obszaru wejściowego odwołania Etykieta, w skryscie 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 fotograficznego.

  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ędny SpatialMapping głównego aparatu.

  7. W panelu Inspector (Inspektor Panel) z wybraną pozycją 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 w czasie wykonywania.

Rozdział 12 - Przed budynkiem

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

Przed wykonaniem 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 wstawisz 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 UWP, które będzie można wdrożyć na urządzeniu Microsoft HoloLens. Aby rozpocząć proces kompilacji:

  1. Przejdź do pozycji Ustawienia kompilacji pliku>.

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

  5. Aparat Unity rozpocznie kompilowanie projektu w folderze App .

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

  7. Aby przeprowadzić wdrożenie na urządzeniu 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. Czynność:

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

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

    3. Zanotuj adres IPv4 .

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

    5. Ustaw tryb dewelopera wł.

  8. Przejdź do nowej kompilacji aparatu Unity ( folderu App ) 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 zanotowano urządzenie 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 urządzeniu 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śnij.
  • 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 wytrenować usługę przy użyciu nowo przechwyconych obrazów. Na początku prawdopodobnie trzeba będzie również poprawić pola ograniczenia, aby były dokładniejsze i ponownie wytrenować usługę.

Uwaga

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

Aplikacja custom vision, wykrywania obiektów

Gratulacje, utworzono aplikację rzeczywistości mieszanej, która korzysta z interfejsu API wykrywania obiektów usługi Azure Custom Vision, który może rozpoznać obiekt z 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 usługi Azure Custom Vision.

Ćwiczenia dodatkowe

Ćwiczenie 1

Dodanie do etykiety tekstowej umożliwia użycie półprzezroczystego modułu w celu opakowania rzeczywistego obiektu w polu ograniczenia 3D.

Ćwiczenie 2

Trenowanie usługi Custom Vision Service w celu rozpoznawania większej liczby 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 usługa mogła być bardziej dokładna (wykonaj jednocześnie przewidywanie i trenowanie).