Sdílet prostřednictvím


HoloLens (1. generace) a Azure 302b: Custom Vision


Poznámka

Kurzy Mixed Reality Academy byly navrženy s ohledem na HoloLens (1. generace) a Mixed Reality Asistivní náhlavní soupravy. Proto se domníváme, že je důležité ponechat tyto kurzy pro vývojáře, kteří stále hledají pokyny při vývoji pro tato zařízení. Tyto kurzy nebudou aktualizovány nejnovějšími sadami nástrojů nebo interakcemi používanými pro HoloLens 2. Budou zachovány, aby mohly pokračovat v práci na podporovaných zařízeních. V budoucnu bude k dispozici nová série kurzů, které předvedou vývoj pro HoloLens 2. Toto oznámení bude aktualizováno odkazem na tyto kurzy, jakmile budou zveřejněny.


V tomto kurzu se naučíte rozpoznávat vlastní vizuální obsah v rámci poskytnutého obrázku pomocí funkcí Azure Custom Vision v aplikaci hybridní reality.

Tato služba vám umožní vytrénovat model strojového učení pomocí obrázků objektů. Vytrénovaný model pak použijete k rozpoznávání podobných objektů, které jsou poskytovány zachycením Microsoft HoloLens z fotoaparátu nebo kamerou připojenou k počítači pro imerzivní náhlavní soupravy (VR).

výsledek kurzu

Azure Custom Vision je služba Microsoft Cognitive Service, která vývojářům umožňuje vytvářet vlastní klasifikátory obrázků. Tyto klasifikátory se pak dají použít s novými obrázky k rozpoznávání nebo klasifikaci objektů v rámci tohoto nového obrázku. Služba poskytuje jednoduchý a snadno použitelný online portál pro zjednodušení procesu. Další informace najdete na stránce Azure Custom Vision Service.

Po dokončení tohoto kurzu budete mít aplikaci hybridní reality, která bude moct pracovat ve dvou režimech:

  • Režim analýzy: Ruční nastavení služby Custom Vision tak, že nahrajete obrázky, vytvoříte značky a vytrénuje službu tak, aby rozpoznávala různé objekty (v tomto případě myš a klávesnice). Pak vytvoříte aplikaci HoloLens, která bude pořizovat snímky pomocí fotoaparátu a pokusí se tyto objekty rozpoznat v reálném světě.

  • Režim trénování: Implementujete kód, který ve vaší aplikaci povolí režim trénování. Režim trénování vám umožní zachytit snímky pomocí kamery HoloLensu, nahrát pořízené snímky do služby a vytrénovat vlastní model zpracování obrazu.

V tomto kurzu se naučíte, jak získat výsledky ze služby Custom Vision Service do ukázkové aplikace založené na Unity. Bude na vás, abyste tyto koncepty použili na vlastní aplikaci, kterou možná vytváříte.

Podpora zařízení

Kurz HoloLens Imerzivní náhlavní soupravy
MR a Azure 302b: Custom Vision ✔️ ✔️

Poznámka

I když se tento kurz zaměřuje primárně na HoloLens, můžete to, co se v tomto kurzu naučíte, použít také k Windows Mixed Reality imerzivních náhlavních souprav (VR). Vzhledem k tomu, že imerzivní náhlavní soupravy (VR) nemají přístupné kamery, budete potřebovat externí kameru připojenou k počítači. V průběhu kurzu uvidíte poznámky ke změnám, které budete možná muset použít pro podporu imerzivních náhlavních souprav (VR).

Požadavky

Poznámka

Tento kurz je určený pro vývojáře, kteří mají základní zkušenosti s Unity a C#. Mějte také na paměti, že požadavky a písemné pokyny v tomto dokumentu představují to, co bylo testováno a ověřeno v době psaní tohoto dokumentu (červenec 2018). Můžete používat nejnovější software, jak je uvedeno v článku instalace nástrojů , i když by se nemělo předpokládat, že informace v tomto kurzu budou dokonale odpovídat tomu, co najdete v novějším softwaru, než je uvedeno níže.

Pro tento kurz doporučujeme následující hardware a software:

Než začnete

  1. Aby nedocházelo k problémům při sestavování tohoto projektu, důrazně doporučujeme vytvořit projekt uvedený v tomto kurzu v kořenové nebo téměř kořenové složce (dlouhé cesty ke složkám můžou způsobit problémy při sestavování).
  2. Nastavte a otestujte HoloLens. Pokud potřebujete podporu pro nastavení HoloLensu, nezapomeňte navštívit článek o nastavení HoloLensu.
  3. Při zahájení vývoje nové aplikace HoloLens je vhodné provést kalibraci a ladění senzorů (někdy může pomoct provádět tyto úlohy pro každého uživatele).

Nápovědu k kalibraci potřebujete pomocí tohoto odkazu na článek Kalibrace HoloLensu.

Pokud potřebujete pomoc s laděním senzorů, použijte tento odkaz na článek o ladění senzorů HoloLens.

Kapitola 1 – Portál služby Custom Vision

Pokud chcete používat službu Custom Vision Service v Azure, budete muset nakonfigurovat instanci služby tak, aby byla k dispozici vaší aplikaci.

  1. Nejprve přejděte na hlavní stránku Custom Vision Service.

  2. Klikněte na tlačítko Začínáme .

    Začínáme se službou Custom Vision Service

  3. Přihlaste se k portálu služby Custom Vision.

    Přihlášení k portálu

    Poznámka

    Pokud ještě nemáte účet Azure, budete si ho muset vytvořit. Pokud tento kurz sledujete v situaci ve třídě nebo testovacím prostředí, požádejte o pomoc s nastavením nového účtu svého instruktora nebo některého z specialistů.

  4. Jakmile se poprvé přihlásíte, zobrazí se výzva k zobrazení panelu Podmínky služby . Kliknutím na zaškrtávací políčko odsouhlaste podmínky. Pak klikněte na Souhlasím.

    Podmínky služby

  5. Po vyjádření souhlasu s podmínkami přejdete na portálu do části Projekty . Klikněte na Nový projekt.

    Vytvoření nového projektu

  6. Na pravé straně se zobrazí karta s výzvou k zadání některých polí pro projekt.

    1. Vložte název projektu.

    2. Vložte popis projektu (volitelné).

    3. Zvolte skupinu prostředků nebo vytvořte novou. Skupina prostředků poskytuje způsob, jak monitorovat, řídit přístup, zřizovat a spravovat fakturaci kolekce prostředků Azure. Doporučujeme udržovat všechny služby Azure přidružené k jednomu projektu (např. tyto kurzy) ve společné skupině prostředků.

    4. Nastavte typy projektů na Klasifikaci.

    5. Nastavte Domény na Obecné.

      Nastavení domén

      Pokud si chcete přečíst další informace o skupinách prostředků Azure, přečtěte si článek o skupinách prostředků.

  7. Po dokončení klikněte na Vytvořit projekt a budete přesměrováni na stránku Custom Vision Service, projekt.

Kapitola 2 : Trénování Custom Vision projektu

Na portálu Custom Vision je vaším primárním cílem vytrénovat projekt tak, aby rozpoznával konkrétní objekty na obrázcích. Potřebujete alespoň pět (5) obrázků, ale u každého objektu, který má vaše aplikace rozpoznat, je vhodnější deset (10). Můžete použít obrázky, které jsou součástí tohoto kurzu (počítačová myš a klávesnice).

Trénujte projekt služby Custom Vision Service:

  1. Klikněte na + tlačítko vedle položky Značky.

    Přidání nové značky

  2. Přidejte název objektu, který chcete rozpoznat. Klikněte na Uložit.

    Přidání názvu objektu a uložení

  3. Všimněte si, že vaše značka byla přidána (možná budete muset stránku znovu načíst, aby se zobrazila). Zaškrtněte políčko vedle nové značky, pokud ještě není zaškrtnuté.

    Povolit novou značku

  4. Uprostřed stránky klikněte na Přidat obrázky .

    Přidání obrázků

  5. Klikněte na Procházet místní soubory a vyhledejte a vyberte obrázky, které chcete nahrát, minimálně pět (5). Mějte na paměti, že všechny tyto obrázky by měly obsahovat objekt, který trénujete.

    Poznámka

    K nahrání můžete vybrat několik obrázků najednou.

  6. Jakmile uvidíte obrázky na kartě, vyberte příslušnou značku v poli Moje značky .

    Vybrat značky

  7. Klikněte na Nahrát soubory. Soubory se začnou nahrávat. Jakmile budete mít potvrzení nahrávání, klikněte na Hotovo.

    Nahrání souborů

  8. Stejným postupem vytvořte novou značku s názvem Klávesnice a nahrajte pro ni příslušné fotky. Po vytvoření nových značek nezapomeňte zrušit zaškrtnutí políčkaMyš , aby se zobrazilo okno Přidat obrázky .

  9. Jakmile budete mít obě značky nastavené, klikněte na Train (Trénovat) a začne se vytvářet první iterace trénování.

    Povolení iterace trénování

  10. Po sestavení uvidíte dvě tlačítka s názvem Nastavit jako výchozí a Adresa URL předpovědi. Nejprve klikněte na Nastavit jako výchozí a pak klikněte na Prediktivní adresa URL.

    Nastavit výchozí a prediktivní adresu URL

    Poznámka

    Adresa URL koncového bodu, která je z ní poskytnuta, je nastavená na tu, která iterace byla označena jako výchozí. Pokud tedy později provedete novou iteraci a aktualizujete ji jako výchozí, nebudete muset kód měnit.

  11. Po kliknutí na prediktivní adresu URL otevřete Poznámkový blok a zkopírujte a vložte adresu URL a prediktivní klíč, abyste je mohli později v kódu načíst, až je budete potřebovat.

    Kopírování a vložení adresy URL a prediktivního klíče

  12. Klikněte na ozubené kolo v pravém horním rohu obrazovky.

    Kliknutím na ikonu ozubeného kola otevřete nastavení.

  13. Zkopírujte trénovací klíč a vložte ho do Poznámkového bloku pro pozdější použití.

    Kopírování trénovacího klíče

  14. Zkopírujte také ID projektu a vložte ho do souboru Poznámkového bloku pro pozdější použití.

    Kopírovat ID projektu

Kapitola 3 – Nastavení projektu Unity

Následující příklad je typickým nastavením pro vývoj s hybridní realitou a jako takový je dobrou šablonou pro jiné projekty.

  1. Otevřete Unity a klikněte na Nový.

    Vytvoření nového projektu Unity

  2. Teď budete muset zadat název projektu Unity. Vložte AzureCustomVision. Ujistěte se, že je šablona projektu nastavená na 3D. Umístění nastavte na místo, které je pro vás vhodné (nezapomeňte, že lepší je blíž ke kořenovým adresářům). Potom klikněte na Vytvořit projekt.

    Konfigurace nastavení projektu

  3. Když je Unity otevřená, stojí za to zkontrolovat, jestli je výchozí Editor skriptů nastavený na Visual Studio. Přejděte na Upravit>předvolby a v novém okně přejděte na Externí nástroje. Změňte Editor externích skriptů na Visual Studio 2017. Zavřete okno Předvolby .

    Konfigurace externích nástrojů

  4. Pak přejděte na Nastavení sestavení souboru>, vyberte Univerzální platforma Windows a pak kliknutím na tlačítko Přepnout platformu svůj výběr použijte.

    Konfigurace nastavení sestavení

  5. Zůstaňte v nastavení sestavení souboru > a ujistěte se, že:

    1. Cílové zařízení je nastavené na HoloLens.

      U imerzivních náhlavních souprav nastavte Cílové zařízení na Libovolné zařízení.

    2. Typ sestavení je nastavený na D3D.

    3. Sada SDK je nastavená na nejnovější nainstalovanou verzi.

    4. Verze sady Visual Studio je nastavená na nejnovější nainstalovanou verzi

    5. Sestavení a spuštění je nastavené na místní počítač.

    6. Uložte scénu a přidejte ji do sestavení.

      1. Uděláte to tak, že vyberete Přidat otevřené scény. Zobrazí se okno pro uložení.

        Přidání otevřené scény do seznamu sestavení

      2. Vytvořte novou složku pro tuto a jakoukoli budoucí scénu a pak vyberte tlačítko Nová složka a vytvořte novou složku s názvem Scény.

        Vytvořit novou složku scény

      3. Otevřete nově vytvořenou složku Scény , do textového pole Název souboru: zadejte CustomVisionScene a klikněte na Uložit.

        Pojmenování nového souboru scény

        Mějte na paměti, že scény Unity musíte uložit do složky Assets , protože musí být přidružené k projektu Unity. Typickým způsobem strukturování projektu Unity je vytvoření složky scenes (a dalších podobných složek).

    7. Zbývající nastavení v nastavení sestavení by prozatím měla zůstat ve výchozím nastavení.

      Výchozí nastavení sestavení

  6. V okně Build Settings (Nastavení sestavení ) klikněte na tlačítko Player Settings (Nastavení přehrávače ). Tím se otevře související panel v prostoru, kde se inspektor nachází.

  7. Na tomto panelu je potřeba ověřit několik nastavení:

    1. Na kartě Další nastavení :

      1. Skriptovací modul runtime verze by měla být experimentální (ekvivalent .NET 4.6), což aktivuje potřebu restartování editoru.

      2. Skriptovací back-end by měl být .NET.

      3. Úroveň kompatibility rozhraní API by měla být .NET 4.6.

      Nastavení kompatibility rozhraní API

    2. Na kartě Nastavení publikování v části Schopnosti zaškrtněte:

      1. InternetClient

      2. Webcam

      3. Mikrofon

      Konfigurace nastavení publikování

    3. Dále na panelu v Nastavení XR (najdete pod nastavením publikování) zaškrtněte možnost Virtuální realita podporována a ujistěte se, že je přidaná sada SDK Windows Mixed Reality.

    Konfigurace nastavení XR

  8. Zpět v nastavení sestaveníProjekty Unity C# už nejsou neaktivní; zaškrtněte políčko vedle této položky.

  9. Zavřete okno Nastavení sestavení.

  10. Uložte scénu a projekt (FILE > SAVE SCENE / FILE > SAVE PROJECT).

Kapitola 4 – Import knihovny DLL Newtonsoft v Unity

Důležité

Pokud chcete komponentu Unity Set up v tomto kurzu přeskočit a pokračovat přímo do kódu, můžete si stáhnout tento balíček Azure-MR-302b.unitypackage, naimportovat ho do projektu jako vlastní balíček a pak pokračovat od Kapitoly 6.

Tento kurz vyžaduje použití knihovny Newtonsoft , kterou můžete přidat jako knihovnu DLL k prostředkům. Balíček obsahující tuto knihovnu lze stáhnout z tohoto odkazu. K importu knihovny Newtonsoft do projektu použijte balíček Unity, který byl součástí tohoto kurzu.

  1. Přidejte soubor .unitypackage do Unity pomocí možnosti nabídky Vlastníbalíčekimportubalíčků>prostředků>.

  2. V okně Import Unity Package (Importovat balíček Unity ), které se zobrazí, zkontrolujte, že je vybrané všechno v části Plugins (a včetně).

    Importovat všechny položky balíčku

  3. Kliknutím na tlačítko Importovat přidejte položky do projektu.

  4. V zobrazení projektu přejděte do složky Newtonsoft v části Moduly plug-in a vyberte modul plug-in Newtonsoft.Json.

    Vyberte modul plug-in Newtonsoft.

  5. S vybraným modulem plug-in Newtonsoft.Json se ujistěte, že není zaškrtnuté políčkoAny Platform (Libovolná platforma), pak se ujistěte, že není zaškrtnuté políčko WSAPlayer, a pak klikněte na Apply (Použít). Stačí jenom ověřit, jestli jsou soubory správně nakonfigurované.

    Konfigurace modulu plug-in Newtonsoft

    Poznámka

    Označení těchto modulů plug-in se nakonfiguruje tak, aby se používaly jenom v Unity Editoru. Ve složce WSA je jiná sada, která se použije po exportu projektu z Unity.

  6. Dále musíte otevřít složku WSA ve složce Newtonsoft . Zobrazí se kopie stejného souboru, který jste právě nakonfigurovali. Vyberte soubor a pak v inspektoru zkontrolujte, že

    • Žádná platforma není zaškrtnutá.
    • Je zaškrtnutopouzeWSAPlayer.
    • Je zaškrtnuté políčko Nepřidávejte proces.

    Konfigurace nastavení platformy modulu plug-in Newtonsoft

Kapitola 5 - Nastavení kamery

  1. Na panelu hierarchie vyberte hlavní kameru.

  2. Po výběru uvidíte všechny komponenty hlavní kamery na panelu inspektoru.

    1. Objekt kamery musí mít název Hlavní kamera (všimněte si pravopisu).)

    2. Značka Hlavní kamera musí být nastavená na MainCamera (všimněte si pravopisu!).

    3. Ujistěte se, že je pozice transformace nastavená na 0, 0, 0.

    4. Nastavte Vymazat příznaky na plnou barvu (u imerzivní náhlavní soupravy tuto možnost ignorujte).

    5. Nastavte barvu pozadí komponenty kamery na Black, Alpha 0 (hex code: #000000000) (u imerzivní náhlavní soupravy tuto možnost ignorujte).

    Konfigurace vlastností součásti Kamera

Kapitola 6 – Vytvoření třídy CustomVisionAnalyser

V tuto chvíli jste připraveni napsat nějaký kód.

Začnete třídou CustomVisionAnalyser .

Poznámka

Volání služby Custom Vision v následujícím kódu se provádějí pomocí rozhraní REST API Custom Vision. Pomocí tohoto rozhraní zjistíte, jak implementovat a používat toto rozhraní API (užitečné pro pochopení toho, jak implementovat něco podobného na vlastní pěst). Mějte na paměti, že Microsoft nabízí sadu SDK služby Custom Vision, kterou je možné použít také k volání služby. Další informace najdete v článku Custom Vision Service SDK.

Tato třída zodpovídá za:

  • Načítá se nejnovější image zachycená jako pole bajtů.

  • Odeslání pole bajtů do instance služby Azure Custom Vision Service k analýze

  • Příjem odpovědi jako řetězce JSON

  • Deserializace odpovědi a předání výsledné předpovědi do Třídy SceneOrganiser , která se postará o to, jak by měla být odpověď zobrazena.

Vytvoření této třídy:

  1. Klikněte pravým tlačítkem na složku Asset na panelu projektu a pak klikněte na Vytvořit > složku. Zavolejte složku Skripty.

    Vytvořit složku skriptů

  2. Poklikáním na právě vytvořenou složku otevřete.

  3. Klikněte pravým tlačítkem myši do složky a pak klikněte na Vytvořit>skript jazyka C#. Pojmenujte skript CustomVisionAnalyser.

  4. Poklikáním na nový skript CustomVisionAnalyser ho otevřete v sadě Visual Studio.

  5. Aktualizujte obory názvů v horní části souboru tak, aby odpovídaly následujícímu:

    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    using Newtonsoft.Json;
    
  6. Do třídy CustomVisionAnalyser přidejte následující proměnné:

        /// <summary>
        /// Unique instance of this class
        /// </summary>
        public static CustomVisionAnalyser Instance;
    
        /// <summary>
        /// Insert your Prediction Key here
        /// </summary>
        private string predictionKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your prediction endpoint here
        /// </summary>
        private string predictionEndpoint = "Insert your prediction endpoint here";
    
        /// <summary>
        /// Byte array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Poznámka

    Nezapomeňte vložit klíč předpovědi do proměnné predictionKey a koncový bod předpovědi do proměnné predictionEndpoint . Zkopírovali jste je do Poznámkového bloku dříve v kurzu.

  7. Pro inicializaci proměnné Instance je teď potřeba přidat kód pro Awake( ):

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

  9. Dále přidejte korutin (se statickou metodou GetImageAsByteArray(), která získá výsledky analýzy obrázku zachyceného třídou ImageCapture .

    Poznámka

    V korutinu AnalyzeImageCapture je volání třídy SceneOrganiser , kterou teprve chcete vytvořit. Proto prozatím nechte tyto řádky okomentované.

        /// <summary>
        /// Call the Computer Vision Service to submit the image.
        /// </summary>
        public IEnumerator AnalyseLastImageCaptured(string imagePath)
        {
            WWWForm webForm = new WWWForm();
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                // The response will be in JSON format, therefore it needs to be deserialized    
    
                // The following lines refers to a class that you will build in later Chapters
                // Wait until then to uncomment these lines
    
                //AnalysisObject analysisObject = new AnalysisObject();
                //analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
                //SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  10. Před návratem do Unity nezapomeňte uložit změny v sadě Visual Studio.

Kapitola 7 – Vytvoření třídy CustomVisionObjects

Třída, kterou teď vytvoříte, je Třída CustomVisionObjects .

Tento skript obsahuje řadu objektů používaných jinými třídami k serializaci a deserializaci volání Custom Vision Service.

Upozornění

Je důležité, abyste si poznamenali koncový bod, který vám služba Custom Vision poskytuje, protože níže uvedená struktura JSON je nastavená tak, aby fungovala s Custom Vision Prediction verze 2.0. Pokud máte jinou verzi, možná budete muset aktualizovat následující strukturu.

Vytvoření této třídy:

  1. Klikněte pravým tlačítkem do složky Scripts (Skripty ) a pak klikněte na Create C# Script (Vytvořit>skript jazyka C#). Zavolejte skript CustomVisionObjects.

  2. Poklikáním na nový skript CustomVisionObjects ho otevřete v sadě Visual Studio.

  3. Na začátek souboru přidejte následující obory názvů:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Odstraňte Metody Start() a Update() uvnitř třídy CustomVisionObjects ; tato třída by teď měla být prázdná.

  5. Přidejte následující třídy mimotřídu CustomVisionObjects . Tyto objekty používá knihovna Newtonsoft k serializaci a deserializaci dat odpovědi:

    // The objects contained in this script represent the deserialized version
    // of the objects used by this application 
    
    /// <summary>
    /// Web request object for image data
    /// </summary>
    class MultipartObject : IMultipartFormSection
    {
        public string sectionName { get; set; }
    
        public byte[] sectionData { get; set; }
    
        public string fileName { get; set; }
    
        public string contentType { get; set; }
    }
    
    /// <summary>
    /// JSON of all Tags existing within the project
    /// contains the list of Tags
    /// </summary> 
    public class Tags_RootObject
    {
        public List<TagOfProject> Tags { get; set; }
        public int TotalTaggedImages { get; set; }
        public int TotalUntaggedImages { get; set; }
    }
    
    public class TagOfProject
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int ImageCount { get; set; }
    }
    
    /// <summary>
    /// JSON of Tag to associate to an image
    /// Contains a list of hosting the tags,
    /// since multiple tags can be associated with one image
    /// </summary> 
    public class Tag_RootObject
    {
        public List<Tag> Tags { get; set; }
    }
    
    public class Tag
    {
        public string ImageId { get; set; }
        public string TagId { get; set; }
    }
    
    /// <summary>
    /// JSON of Images submitted
    /// Contains objects that host detailed information about one or more images
    /// </summary> 
    public class ImageRootObject
    {
        public bool IsBatchSuccessful { get; set; }
        public List<SubmittedImage> Images { get; set; }
    }
    
    public class SubmittedImage
    {
        public string SourceUrl { get; set; }
        public string Status { get; set; }
        public ImageObject Image { get; set; }
    }
    
    public class ImageObject
    {
        public string Id { get; set; }
        public DateTime Created { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public string ImageUri { get; set; }
        public string ThumbnailUri { get; set; }
    }
    
    /// <summary>
    /// JSON of Service Iteration
    /// </summary> 
    public class Iteration
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public bool IsDefault { get; set; }
        public string Status { get; set; }
        public string Created { get; set; }
        public string LastModified { get; set; }
        public string TrainedAt { get; set; }
        public string ProjectId { get; set; }
        public bool Exportable { get; set; }
        public string DomainId { get; set; }
    }
    
    /// <summary>
    /// Predictions received by the Service after submitting an image for analysis
    /// </summary> 
    [Serializable]
    public class AnalysisObject
    {
        public List<Prediction> Predictions { get; set; }
    }
    
    [Serializable]
    public class Prediction
    {
        public string TagName { get; set; }
        public double Probability { get; set; }
    }
    

Kapitola 8 – Vytvoření třídy VoiceRecognizer

Tato třída rozpozná hlasový vstup od uživatele.

Vytvoření této třídy:

  1. Klikněte pravým tlačítkem do složky Scripts (Skripty ) a pak klikněte na Create C# Script (Vytvořit>skript jazyka C#). Zavolejte skript VoiceRecognizer.

  2. Poklikáním na nový skript VoiceRecognizer ho otevřete v sadě Visual Studio.

  3. Přidejte následující obory názvů nad třídu VoiceRecognizer :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Windows.Speech;
    
  4. Pak přidejte následující proměnné do třídy VoiceRecognizer nad metodu Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static VoiceRecognizer Instance;
    
        /// <summary>
        /// Recognizer class for voice recognition
        /// </summary>
        internal KeywordRecognizer keywordRecognizer;
    
        /// <summary>
        /// List of Keywords registered
        /// </summary>
        private Dictionary<string, Action> _keywords = new Dictionary<string, Action>();
    
  5. Přidejte metody Awake() a Start(), které nastaví uživatelská klíčová slova , která se mají rozpoznat při přidružování značky k obrázku:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start ()
        {
    
            Array tagsArray = Enum.GetValues(typeof(CustomVisionTrainer.Tags));
    
            foreach (object tagWord in tagsArray)
            {
                _keywords.Add(tagWord.ToString(), () =>
                {
                    // When a word is recognized, the following line will be called
                    CustomVisionTrainer.Instance.VerifyTag(tagWord.ToString());
                });
            }
    
            _keywords.Add("Discard", () =>
            {
                // When a word is recognized, the following line will be called
                // The user does not want to submit the image
                // therefore ignore and discard the process
                ImageCapture.Instance.ResetImageCapture();
                keywordRecognizer.Stop();
            });
    
            //Create the keyword recognizer 
            keywordRecognizer = new KeywordRecognizer(_keywords.Keys.ToArray());
    
            // Register for the OnPhraseRecognized event 
            keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
        }
    
  6. Odstraňte metodu Update().

  7. Přidejte následující obslužnou rutinu, která se volá pokaždé, když se rozpozná hlasový vstup:

        /// <summary>
        /// Handler called when a word is recognized
        /// </summary>
        private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
        {
            Action keywordAction;
            // if the keyword recognized is in our dictionary, call that Action.
            if (_keywords.TryGetValue(args.text, out keywordAction))
            {
                keywordAction.Invoke();
            }
        }
    
  8. Před návratem do Unity nezapomeňte uložit změny v sadě Visual Studio.

Poznámka

Nedělejte si starosti s kódem, který může vypadat jako chyba, protože brzy poskytnete další třídy, které je opraví.

Kapitola 9 – Vytvoření třídy CustomVisionTrainer

Tato třída zřetědí řadu webových volání pro trénování služby Custom Vision. Každé volání bude podrobně vysvětleno přímo nad kódem.

Vytvoření této třídy:

  1. Klikněte pravým tlačítkem do složky Scripts (Skripty ) a pak klikněte na Create C# Script (Vytvořit>skript jazyka C#). Zavolejte skript CustomVisionTrainer.

  2. Poklikáním na nový skript CustomVisionTrainer ho otevřete v sadě Visual Studio.

  3. Přidejte následující obory názvů nad třídu CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Potom přidejte následující proměnné do třídy CustomVisionTrainer nad metodu Start().

    Poznámka

    Zde použitá trénovací adresa URL je k dispozici v dokumentaci Custom Vision Training 1.2 a má strukturu:https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Další informace najdete v referenčním rozhraní API Custom Vision Training v1.2.

    Upozornění

    Je důležité, abyste si poznamenali koncový bod, který vám služba Custom Vision poskytuje pro režim trénování, protože použitá struktura JSON (v rámci třídy CustomVisionObjects) je nastavená tak, aby fungovala s Custom Vision Training v1.2. Pokud máte jinou verzi, možná budete muset aktualizovat strukturu Objects .

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static CustomVisionTrainer Instance;
    
        /// <summary>
        /// Custom Vision Service URL root
        /// </summary>
        private string url = "https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/";
    
        /// <summary>
        /// Insert your prediction key here
        /// </summary>
        private string trainingKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your Project Id here
        /// </summary>
        private string projectId = "- Insert your Project Id here -";
    
        /// <summary>
        /// Byte array of the image to submit for analysis
        /// </summary>
        internal byte[] imageBytes;
    
        /// <summary>
        /// The Tags accepted
        /// </summary>
        internal enum Tags {Mouse, Keyboard}
    
        /// <summary>
        /// The UI displaying the training Chapters
        /// </summary>
        private TextMesh trainingUI_TextMesh;
    

    Důležité

    Ujistěte se, že jste přidali hodnotu klíč služby (trénovací klíč) a hodnotu ID projektu, kterou jste si poznamenali dříve. jedná se o hodnoty, které jste shromáždili na portálu dříve v kurzu (kapitola 2, krok 10 a vyšší).

  5. Přidejte následující metody Start() a Awake(). Tyto metody se volají při inicializaci a obsahují volání pro nastavení uživatelského rozhraní:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        private void Start()
        { 
            trainingUI_TextMesh = SceneOrganiser.Instance.CreateTrainingUI("TrainingUI", 0.04f, 0, 4, false);
        }
    
  6. Odstraňte metodu Update(). Tato třída ji nebude potřebovat.

  7. Přidejte metodu RequestTagSelection(). Tato metoda je první, která se volá, když je image zachycena a uložena v zařízení, a je teď připravená k odeslání službě Custom Vision Service, aby ji vytrénovala. Tato metoda zobrazí v uživatelském rozhraní pro trénování sadu klíčových slov, která uživatel může použít k označení zachyceného obrázku. Upozorní také třídu VoiceRecognizer , aby začala poslouchat uživatele pro hlasový vstup.

        internal void RequestTagSelection()
        {
            trainingUI_TextMesh.gameObject.SetActive(true);
            trainingUI_TextMesh.text = $" \nUse voice command \nto choose between the following tags: \nMouse\nKeyboard \nor say Discard";
    
            VoiceRecognizer.Instance.keywordRecognizer.Start();
        }
    
  8. Přidejte metodu VerifyTag(). Tato metoda obdrží hlasový vstup rozpoznaný VoiceRecognizer třídy a ověří jeho platnost a pak zahájí proces trénování.

        /// <summary>
        /// Verify voice input against stored tags.
        /// If positive, it will begin the Service training process.
        /// </summary>
        internal void VerifyTag(string spokenTag)
        {
            if (spokenTag == Tags.Mouse.ToString() || spokenTag == Tags.Keyboard.ToString())
            {
                trainingUI_TextMesh.text = $"Tag chosen: {spokenTag}";
                VoiceRecognizer.Instance.keywordRecognizer.Stop();
                StartCoroutine(SubmitImageForTraining(ImageCapture.Instance.filePath, spokenTag));
            }
        }
    
  9. Přidejte metodu SubmitImageForTraining(). Tato metoda zahájí proces trénování služby Custom Vision Service. Prvním krokem je načtení ID značky ze služby, které je přidružené k ověřenému vstupu řeči od uživatele. Id značky se pak nahraje spolu s obrázkem.

        /// <summary>
        /// Call the Custom Vision Service to submit the image.
        /// </summary>
        public IEnumerator SubmitImageForTraining(string imagePath, string tag)
        {
            yield return new WaitForSeconds(2);
            trainingUI_TextMesh.text = $"Submitting Image \nwith tag: {tag} \nto Custom Vision Service";
            string imageId = string.Empty;
            string tagId = string.Empty;
    
            // Retrieving the Tag Id relative to the voice input
            string getTagIdEndpoint = string.Format("{0}{1}/tags", url, projectId);
            using (UnityWebRequest www = UnityWebRequest.Get(getTagIdEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
    
                Tags_RootObject tagRootObject = JsonConvert.DeserializeObject<Tags_RootObject>(jsonResponse);
    
                foreach (TagOfProject tOP in tagRootObject.Tags)
                {
                    if (tOP.Name == tag)
                    {
                        tagId = tOP.Id;
                    }             
                }
            }
    
            // Creating the image object to send for training
            List<IMultipartFormSection> multipartList = new List<IMultipartFormSection>();
            MultipartObject multipartObject = new MultipartObject();
            multipartObject.contentType = "application/octet-stream";
            multipartObject.fileName = "";
            multipartObject.sectionData = GetImageAsByteArray(imagePath);
            multipartList.Add(multipartObject);
    
            string createImageFromDataEndpoint = string.Format("{0}{1}/images?tagIds={2}", url, projectId, tagId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(createImageFromDataEndpoint, multipartList))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);           
    
                //unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                www.SetRequestHeader("Training-Key", trainingKey);
    
                // The upload handler will help uploading the byte array with the request
                www.uploadHandler = new UploadHandlerRaw(imageBytes);
    
                // The download handler will help receiving the analysis from Azure
                www.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                ImageRootObject m = JsonConvert.DeserializeObject<ImageRootObject>(jsonResponse);
                imageId = m.Images[0].Image.Id;
            }
            trainingUI_TextMesh.text = "Image uploaded";
            StartCoroutine(TrainCustomVisionProject());
        }
    
  10. Přidejte metodu TrainCustomVisionProject(). Po odeslání a označení obrázku bude tato metoda volána. Vytvoří se nová iterace, která se vytrénuje se všemi předchozími obrázky odeslanými do služby a s právě nahraným obrázkem. Po dokončení trénování tato metoda zavolá metodu, která nastaví nově vytvořenou iteraci jako výchozí, aby koncový bod, který používáte k analýze, byl nejnovější vytrénovanou iterací.

        /// <summary>
        /// Call the Custom Vision Service to train the Service.
        /// It will generate a new Iteration in the Service
        /// </summary>
        public IEnumerator TrainCustomVisionProject()
        {
            yield return new WaitForSeconds(2);
    
            trainingUI_TextMesh.text = "Training Custom Vision Service";
    
            WWWForm webForm = new WWWForm();
    
            string trainProjectEndpoint = string.Format("{0}{1}/train", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(trainProjectEndpoint, webForm))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
                Debug.Log($"Training - JSON Response: {jsonResponse}");
    
                // A new iteration that has just been created and trained
                Iteration iteration = new Iteration();
                iteration = JsonConvert.DeserializeObject<Iteration>(jsonResponse);
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Custom Vision Trained";
    
                    // Since the Service has a limited number of iterations available,
                    // we need to set the last trained iteration as default
                    // and delete all the iterations you dont need anymore
                    StartCoroutine(SetDefaultIteration(iteration)); 
                }
            }
        }
    
  11. Přidejte metodu SetDefaultIteration(). Tato metoda nastaví dříve vytvořenou a natrénovanou iteraci jako Výchozí. Po dokončení bude tato metoda muset odstranit předchozí iteraci existující ve službě. V době psaní tohoto kurzu existuje limit maximálního počtu deseti (10) iterací, které mohou existovat ve službě současně.

        /// <summary>
        /// Set the newly created iteration as Default
        /// </summary>
        private IEnumerator SetDefaultIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
            trainingUI_TextMesh.text = "Setting default iteration";
    
            // Set the last trained iteration to default
            iteration.IsDefault = true;
    
            // Convert the iteration object as JSON
            string iterationAsJson = JsonConvert.SerializeObject(iteration);
            byte[] bytes = Encoding.UTF8.GetBytes(iterationAsJson);
    
            string setDefaultIterationEndpoint = string.Format("{0}{1}/iterations/{2}", 
                                                            url, projectId, iteration.Id);
    
            using (UnityWebRequest www = UnityWebRequest.Put(setDefaultIterationEndpoint, bytes))
            {
                www.method = "PATCH";
                www.SetRequestHeader("Training-Key", trainingKey);
                www.SetRequestHeader("Content-Type", "application/json");
                www.downloadHandler = new DownloadHandlerBuffer();
    
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Default iteration is set \nDeleting Unused Iteration";
                    StartCoroutine(DeletePreviousIteration(iteration));
                }
            }
        }
    
  12. Přidejte metodu DeletePreviousIteration(). Tato metoda najde a odstraní předchozí nevýchozí iteraci:

        /// <summary>
        /// Delete the previous non-default iteration.
        /// </summary>
        public IEnumerator DeletePreviousIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
    
            trainingUI_TextMesh.text = "Deleting Unused \nIteration";
    
            string iterationToDeleteId = string.Empty;
    
            string findAllIterationsEndpoint = string.Format("{0}{1}/iterations", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Get(findAllIterationsEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                // The iteration that has just been trained
                List<Iteration> iterationsList = new List<Iteration>();
                iterationsList = JsonConvert.DeserializeObject<List<Iteration>>(jsonResponse);
    
                foreach (Iteration i in iterationsList)
                {
                    if (i.IsDefault != true)
                    {
                        Debug.Log($"Cleaning - Deleting iteration: {i.Name}, {i.Id}");
                        iterationToDeleteId = i.Id;
                        break;
                    }
                }
            }
    
            string deleteEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iterationToDeleteId);
    
            using (UnityWebRequest www2 = UnityWebRequest.Delete(deleteEndpoint))
            {
                www2.SetRequestHeader("Training-Key", trainingKey);
                www2.downloadHandler = new DownloadHandlerBuffer();
                yield return www2.SendWebRequest();
                string jsonResponse = www2.downloadHandler.text;
    
                trainingUI_TextMesh.text = "Iteration Deleted";
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "Ready for next \ncapture";
    
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "";
                ImageCapture.Instance.ResetImageCapture();
            }
        }
    
  13. Poslední metodou, která je přidána v této třídě, je GetImageAsByteArray() metoda, která se používá na webových voláních k převodu zachyceného obrázku na bajtové pole.

        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
            BinaryReader binaryReader = new BinaryReader(fileStream);
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  14. Před návratem do Unity nezapomeňte uložit změny v sadě Visual Studio.

Kapitola 10 – Vytvoření třídy SceneOrganiser

Tato třída:

  • Vytvořte objekt Kurzor , který se připojí k hlavní kameře.

  • Vytvořte objekt Label , který se zobrazí, když služba rozpozná reálné objekty.

  • Nastavte hlavní kameru tak, že k ní připojíte příslušné komponenty.

  • V režimu analýzy můžete popisky vytvořit za běhu v odpovídajícím prostoru ve světě vzhledem k poloze hlavní kamery a zobrazit data přijatá ze služby Custom Vision.

  • V režimu trénování spusťte uživatelské rozhraní, které bude zobrazovat různé fáze procesu trénování.

Vytvoření této třídy:

  1. Klikněte pravým tlačítkem do složky Scripts (Skripty) a pak klikněte na CreateC# Script (Vytvořit > skript jazyka C#). Skript pojmenujte SceneOrganiser.

  2. Poklikejte na nový skript SceneOrganiser a otevřete ho v sadě Visual Studio.

  3. Budete potřebovat pouze jeden obor názvů, ostatní odeberte z výše uvedené třídy SceneOrganiser :

    using UnityEngine;
    
  4. Pak přidejte následující proměnné do třídy SceneOrganiser nad metodu Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SceneOrganiser Instance;
    
        /// <summary>
        /// The cursor object attached to the camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        internal GameObject label;
    
        /// <summary>
        /// Object providing the current status of the camera.
        /// </summary>
        internal TextMesh cameraStatusIndicator;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.5f;
    
  5. Odstraňte metody Start() a Update().

  6. Přímo pod proměnné přidejte metodu Awake(), která inicializuje třídu a nastaví scénu.

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            // Use this class instance as singleton
            Instance = this;
    
            // Add the ImageCapture class to this GameObject
            gameObject.AddComponent<ImageCapture>();
    
            // Add the CustomVisionAnalyser class to this GameObject
            gameObject.AddComponent<CustomVisionAnalyser>();
    
            // Add the CustomVisionTrainer class to this GameObject
            gameObject.AddComponent<CustomVisionTrainer>();
    
            // Add the VoiceRecogniser class to this GameObject
            gameObject.AddComponent<VoiceRecognizer>();
    
            // Add the CustomVisionObjects class to this GameObject
            gameObject.AddComponent<CustomVisionObjects>();
    
            // Create the camera Cursor
            cursor = CreateCameraCursor();
    
            // Load the label prefab as reference
            label = CreateLabel();
    
            // Create the camera status indicator label, and place it above where predictions
            // and training UI will appear.
            cameraStatusIndicator = CreateTrainingUI("Status Indicator", 0.02f, 0.2f, 3, true);
    
            // Set camera status indicator to loading.
            SetCameraStatus("Loading");
        }
    
  7. Teď přidejte metodu CreateCameraCursor(), která vytvoří a umístí kurzor hlavní kamery, a metodu CreateLabel(), která vytvoří objekt Analysis Label .

        /// <summary>
        /// Spawns cursor for the Main Camera
        /// </summary>
        private GameObject CreateCameraCursor()
        {
            // Create a sphere as new cursor
            GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    
            // Attach it to the camera
            newCursor.transform.parent = gameObject.transform;
    
            // Resize the new cursor
            newCursor.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f);
    
            // Move it to the correct position
            newCursor.transform.localPosition = new Vector3(0, 0, 4);
    
            // Set the cursor color to red
            newCursor.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse"));
            newCursor.GetComponent<Renderer>().material.color = Color.green;
    
            return newCursor;
        }
    
        /// <summary>
        /// Create the analysis label object
        /// </summary>
        private GameObject CreateLabel()
        {
            // Create a sphere as new cursor
            GameObject newLabel = new GameObject();
    
            // Resize the new cursor
            newLabel.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
    
            // Creating the text of the label
            TextMesh t = newLabel.AddComponent<TextMesh>();
            t.anchor = TextAnchor.MiddleCenter;
            t.alignment = TextAlignment.Center;
            t.fontSize = 50;
            t.text = "";
    
            return newLabel;
        }
    
  8. Přidejte metodu SetCameraStatus(), která bude zpracovávat zprávy určené pro textovou síť poskytující stav kamery.

        /// <summary>
        /// Set the camera status to a provided string. Will be coloured if it matches a keyword.
        /// </summary>
        /// <param name="statusText">Input string</param>
        public void SetCameraStatus(string statusText)
        {
            if (string.IsNullOrEmpty(statusText) == false)
            {
                string message = "white";
    
                switch (statusText.ToLower())
                {
                    case "loading":
                        message = "yellow";
                        break;
    
                    case "ready":
                        message = "green";
                        break;
    
                    case "uploading image":
                        message = "red";
                        break;
    
                    case "looping capture":
                        message = "yellow";
                        break;
    
                    case "analysis":
                        message = "red";
                        break;
                }
    
                cameraStatusIndicator.GetComponent<TextMesh>().text = $"Camera Status:\n<color={message}>{statusText}..</color>";
            }
        }
    
  9. Přidejte metody PlaceAnalysisLabel() a SetTagsToLastLabel(), které vytvoří a zobrazí data ze služby Custom Vision Service do scény.

        /// <summary>
        /// Instantiate a label in the appropriate location relative to the Main Camera.
        /// </summary>
        public void PlaceAnalysisLabel()
        {
            lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
        }
    
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void SetTagsToLastLabel(AnalysisObject analysisObject)
        {
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
    
            if (analysisObject.Predictions != null)
            {
                foreach (Prediction p in analysisObject.Predictions)
                {
                    if (p.Probability > 0.02)
                    {
                        lastLabelPlacedText.text += $"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}";
                        Debug.Log($"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}");
                    }
                }
            }
        }
    
  10. Nakonec přidejte metodu CreateTrainingUI(), která vytvoří uživatelské rozhraní zobrazující více fází procesu trénování, když je aplikace v režimu trénování. Tato metoda bude také využita k vytvoření stavového objektu kamery.

        /// <summary>
        /// Create a 3D Text Mesh in scene, with various parameters.
        /// </summary>
        /// <param name="name">name of object</param>
        /// <param name="scale">scale of object (i.e. 0.04f)</param>
        /// <param name="yPos">height above the cursor (i.e. 0.3f</param>
        /// <param name="zPos">distance from the camera</param>
        /// <param name="setActive">whether the text mesh should be visible when it has been created</param>
        /// <returns>Returns a 3D text mesh within the scene</returns>
        internal TextMesh CreateTrainingUI(string name, float scale, float yPos, float zPos, bool setActive)
        {
            GameObject display = new GameObject(name, typeof(TextMesh));
            display.transform.parent = Camera.main.transform;
            display.transform.localPosition = new Vector3(0, yPos, zPos);
            display.SetActive(setActive);
            display.transform.localScale = new Vector3(scale, scale, scale);
            display.transform.rotation = new Quaternion();
            TextMesh textMesh = display.GetComponent<TextMesh>();
            textMesh.anchor = TextAnchor.MiddleCenter;
            textMesh.alignment = TextAlignment.Center;
            return textMesh;
        }
    
  11. Před návratem do Unity nezapomeňte uložit změny v sadě Visual Studio.

Důležité

Než budete pokračovat, otevřete třídu CustomVisionAnalyser a v rámci metody AnalyzeLastImageCaptured()odkomentujte následující řádky:

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

Kapitola 11 – Vytvoření třídy ImageCapture

Další třídou, kterou vytvoříte, je třída ImageCapture .

Tato třída zodpovídá za:

  • Zachycení obrázku pomocí kamery HoloLensu a jeho uložení do složky aplikace

  • Zpracování gest klepnutí od uživatele.

  • Zachování hodnoty výčtu , která určuje, zda bude aplikace spuštěna v režimu analýzy nebo režimu trénování .

Vytvoření této třídy:

  1. Přejděte do složky Scripts , kterou jste vytvořili dříve.

  2. Klikněte pravým tlačítkem do složky a pak klikněte na Vytvořit > skript C#. Skript pojmenujte ImageCapture.

  3. Poklikáním na nový skript ImageCapture ho otevřete v sadě Visual Studio.

  4. Obory názvů v horní části souboru nahraďte následujícím kódem:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Pak přidejte následující proměnné do třídy ImageCapture nad metodu Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static ImageCapture Instance;
    
        /// <summary>
        /// Keep counts of the taps for image renaming
        /// </summary>
        private int captureCount = 0;
    
        /// <summary>
        /// Photo Capture object
        /// </summary>
        private PhotoCapture photoCaptureObject = null;
    
        /// <summary>
        /// Allows gestures recognition in HoloLens
        /// </summary>
        private GestureRecognizer recognizer;
    
        /// <summary>
        /// Loop timer
        /// </summary>
        private float secondsBetweenCaptures = 10f;
    
        /// <summary>
        /// Application main functionalities switch
        /// </summary>
        internal enum AppModes {Analysis, Training }
    
        /// <summary>
        /// Local variable for current AppMode
        /// </summary>
        internal AppModes AppMode { get; private set; }
    
        /// <summary>
        /// Flagging if the capture loop is running
        /// </summary>
        internal bool captureIsActive;
    
        /// <summary>
        /// File path of current analysed photo
        /// </summary>
        internal string filePath = string.Empty;
    
  6. Teď je potřeba přidat kód pro metody Awake() a Start():

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
    
            // Change this flag to switch between Analysis Mode and Training Mode 
            AppMode = AppModes.Training;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
    
            SceneOrganiser.Instance.SetCameraStatus("Ready");
        }
    
  7. Implementujte obslužnou rutinu, která bude volána, když se objeví gesto klepnutí.

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            switch (AppMode)
            {
                case AppModes.Analysis:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to looping capture.
                        SceneOrganiser.Instance.SetCameraStatus("Looping Capture");
    
                        // Begin the capture loop
                        InvokeRepeating("ExecuteImageCaptureAndAnalysis", 0, secondsBetweenCaptures);
                    }
                    else
                    {
                        // The user tapped while the app was analyzing 
                        // therefore stop the analysis process
                        ResetImageCapture();
                    }
                    break;
    
                case AppModes.Training:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Call the image capture
                        ExecuteImageCaptureAndAnalysis();
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to uploading image.
                        SceneOrganiser.Instance.SetCameraStatus("Uploading Image");
                    }              
                    break;
            }     
        }
    

    Poznámka

    V režimu analýzy funguje metoda TapHandler jako přepínač pro spuštění nebo zastavení smyčky pořizování fotek.

    V režimu trénování zachytí snímek z kamery.

    Když je kurzor zelený, znamená to, že kamera je k dispozici pro pořízení snímku.

    Když je kurzor červený, znamená to, že kamera je zaneprázdněná.

  8. Přidejte metodu, kterou aplikace použije ke spuštění procesu zachycení image a uložení image.

        /// <summary>
        /// Begin process of Image Capturing and send To Azure Custom Vision Service.
        /// </summary>
        private void ExecuteImageCaptureAndAnalysis()
        {
            // Update camera status to analysis.
            SceneOrganiser.Instance.SetCameraStatus("Analysis");
    
            // Create a label in world space using the SceneOrganiser class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
    
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 0.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });   
        }
    
  9. Přidejte obslužné rutiny, které se budou volat, až bude fotka pořízena a kdy bude připravená k analýze. Výsledek se pak předá CustomVisionAnalyser nebo CustomVisionTrainer v závislosti na tom, ve kterém režimu je kód nastaven.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
        }
    
    
        /// <summary>
        /// The camera photo mode has stopped after the capture.
        /// Begin the Image Analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            Debug.LogFormat("Stopped Photo Mode");
    
            // Dispose from the object in memory and request the image analysis 
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            switch (AppMode)
            {
                case AppModes.Analysis:
                    // Call the image analysis
                    StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath));
                    break;
    
                case AppModes.Training:
                    // Call training using captured image
                    CustomVisionTrainer.Instance.RequestTagSelection();
                    break;
            }
        }
    
        /// <summary>
        /// Stops all capture pending actions
        /// </summary>
        internal void ResetImageCapture()
        {
            captureIsActive = false;
    
            // Set the cursor color to green
            SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Update camera status to ready.
            SceneOrganiser.Instance.SetCameraStatus("Ready");
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Před návratem do Unity nezapomeňte uložit změny v sadě Visual Studio.

  11. Teď, když jsou všechny skripty dokončené, vraťte se do Unity Editoru, klikněte na třídu SceneOrganiser a přetáhněte ji ze složky Scripts do objektu Hlavní kamera na panelu hierarchie.

Kapitola 12 - Před zahájením stavby

Pokud chcete provést důkladný test aplikace, budete ji muset zkušebně načíst do HoloLensu.

Než to uděláte, ujistěte se, že:

  • Všechna nastavení uvedená v kapitole 2 jsou správně nastavena.

  • Všechna pole v hlavní kameře na panelu inspektorů jsou správně přiřazená.

  • Skript SceneOrganiser je připojený k objektu Hlavní kamera .

  • Nezapomeňte vložit prediktivní klíč do proměnné predictionKey .

  • Do proměnné predictionEndpoint jste vložili koncový bod předpovědi.

  • Vložili jste trénovací klíč do proměnné trainingKey třídy CustomVisionTrainer .

  • Vložili jste ID projektu do proměnné projectId třídy CustomVisionTrainer .

Kapitola 13 – Sestavení a zkušební načtení aplikace

Postup zahájení procesu sestavení :

  1. Přejděte na Nastavení sestavení souboru>.

  2. Zaškrtněte Projekty Unity C#.

  3. Klikněte na Sestavit. Unity spustí Průzkumník souborů okno, ve kterém musíte vytvořit a vybrat složku, do které chcete aplikaci sestavit. Vytvořte teď složku a pojmenujte ji App. Pak s vybranou složkou Aplikace klikněte na Vybrat složku.

  4. Unity začne váš projekt vytvářet do složky Aplikace .

  5. Jakmile Unity dokončí sestavování (může to nějakou dobu trvat), otevře se okno Průzkumník souborů v umístění sestavení (zkontrolujte hlavní panel, protože se nemusí vždy zobrazovat nad okny, ale upozorní vás na přidání nového okna).

Nasazení na HoloLens:

  1. Budete potřebovat IP adresu vašeho HoloLensu (pro vzdálené nasazení) a zajistit, aby byl HoloLens ve vývojářském režimu. Použijte následující postup:

    1. Při nošení HoloLensu otevřete Nastavení.

    2. Přejděte naUpřesnit možnostisíťového & internetu>Wi-Fi>.

    3. Poznamenejte si adresu IPv4 .

    4. Pak přejděte zpět na Nastavení a pak na Aktualizace zabezpečení> &pro vývojáře.

    5. Nastavte režim vývojáře na Zapnuto.

  2. Přejděte do nového sestavení Unity (složka Aplikace ) a otevřete soubor řešení v sadě Visual Studio.

  3. V části Konfigurace řešení vyberte Ladit.

  4. Na platformě řešení vyberte x86, Vzdálený počítač. Zobrazí se výzva k vložení IP adresy vzdáleného zařízení (v tomto případě HoloLens, který jste si poznamenali).

    Nastavení IP adresy

  5. Přejděte do nabídky Build (Sestavení ) a klikněte na Deploy Solution (Nasadit řešení ) a načtěte aplikaci do HoloLensu bokem.

  6. Vaše aplikace by se teď měla zobrazit v seznamu nainstalovaných aplikací na holoLensu a měla by být připravená ke spuštění.

Poznámka

Pokud chcete nasadit imerzivní náhlavní soupravu, nastavte platformu řešení na Místní počítač a konfiguracinastavte na Ladění s platformou x86. Pak proveďte nasazení na místní počítač pomocí položky nabídky Sestavení a výběrem možnosti Nasadit řešení.

Použití aplikace:

Pokud chcete přepnout funkce aplikace mezi režimem trénování a režimem predikce , musíte aktualizovat proměnnou AppMode umístěnou v metodě Awake() umístěné ve třídě ImageCapture .

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

nebo

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

V režimu trénování :

  • Podívejte se na myš nebo klávesnici a použijte gesto klepnutí.

  • Dále se zobrazí text s výzvou k zadání značky.

  • Řekněte myš nebo klávesnici.

V režimu predikce :

  • Podívejte se na objekt a použijte gesto klepnutí.

  • Zobrazí se text se zjištěným objektem s nejvyšší pravděpodobností (normalizováno).

Kapitola 14 : Vyhodnocení a vylepšení modelu Custom Vision

Aby byla vaše služba přesnější, budete muset pokračovat v trénování modelu používaného k predikci. Toho dosáhnete pomocí nové aplikace s režimem trénování i predikce , přičemž druhý režim vyžaduje, abyste navštívili portál, což je to, co je popsáno v této kapitole. Připravte se na to, že se k portálu budete moct mnohokrát znovu vrátit, abyste mohli model neustále vylepšovat.

  1. Přejděte znovu na portál Azure Custom Vision a až budete v projektu, vyberte kartu Predictions (Predikce) (v horní části stránky uprostřed):

    Výběr karty predikce

  2. Zobrazí se všechny image, které byly odeslány do služby, když byla aplikace spuštěná. Pokud najedete myší na obrázky, zobrazí se vám předpovědi, které byly pro tento obrázek vytvořené:

    Seznam obrázků predikcí

  3. Vyberte jeden z obrázků a otevřete ho. Po otevření uvidíte vpravo předpovědi vytvořené pro tento obrázek. Pokud jste předpovědi zadali správně a chcete tento obrázek přidat do trénovacího modelu služby, klikněte na vstupní pole Moje značky a vyberte značku, kterou chcete přidružit. Až budete hotovi, klikněte vpravo dole na tlačítko Uložit a zavřít a pokračujte k dalšímu obrázku.

    Výběr obrázku k otevření

  4. Jakmile se vrátíte do mřížky obrázků, všimnete si, že obrázky, ke kterým jste přidali značky (a uložili), budou odebrány. Pokud najdete obrázky, u kterých se domníváte, že neobsahují vaši označenou položku, můžete je odstranit kliknutím na zaškrtnutí na obrázku (můžete to udělat u několika obrázků) a následným kliknutím na Odstranit v pravém horním rohu stránky mřížky. V následujícím automaticky otevírané nabídce můžete kliknutím na Ano, odstranit nebo Ne potvrdit odstranění nebo zrušit.

    Odstranění imagí

  5. Až budete připraveni pokračovat, klikněte v pravém horním rohu na zelené tlačítko Trénovat . Váš model služby se vytrénuje se všemi imagemi, které jste teď poskytli (díky tomu bude přesnější). Po dokončení trénování nezapomeňte ještě jednou kliknout na tlačítko Nastavit jako výchozí , aby adresa URL předpovědi dál používala nejaktuálnější iteraci vaší služby.

    Spustit model služby trénování: Vyberte výchozí možnost.

Dokončená aplikace Custom Vision API

Blahopřejeme, vytvořili jste aplikaci pro hybridní realitu, která využívá rozhraní API azure Custom Vision k rozpoznávání objektů z reálného světa, trénování modelu služby a k zobrazení jistoty toho, co bylo vidět.

Příklad dokončený projekt

Bonusová cvičení

Cvičení 1

Vytrénujte službu Custom Vision, aby rozpoznala více objektů.

Cvičení 2

Jako způsob, jak rozšířit to, co jste se naučili, proveďte následující cvičení:

Přehraje zvuk, když je objekt rozpoznán.

Cvičení 3

Pomocí rozhraní API znovu vytrénujte službu se stejnými obrázky, které vaše aplikace analyzuje, aby byla služba přesnější (proveďte predikci i trénování současně).