Sdílet prostřednictvím


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


Poznámka:

Kurzy Mixed Reality Academy byly navrženy s HoloLensem (1. generace) a imerzivními náhlavními soupravami hybridní reality. Proto máme pocit, že je důležité nechat tyto kurzy zavedené pro vývojáře, kteří stále hledají pokyny při vývoji těchto zařízení. Tyto kurzy nebudou aktualizovány nejnovějšími sadami nástrojů ani interakcemi používanými pro HoloLens 2. Budou zachovány, aby pokračovaly v práci na podporovaných zařízeních. Bude k dispozici nová série kurzů, které budou publikovány v budoucnu, které předvádějí, jak vyvíjet pro HoloLens 2. Toto oznámení se při publikování aktualizuje odkazem na tyto kurzy.


V tomto kurzu se dozvíte, jak rozpoznávat vlastní vizuální obsah v rámci poskytnutého obrázku pomocí funkcí služby Azure Custom Vision v aplikaci hybridní reality.

Tato služba vám umožní trénovat model strojového učení pomocí obrázků objektů. Potom použijete natrénovaný model k rozpoznávání podobných objektů, které poskytuje zachycení fotoaparátu Microsoft HoloLens nebo kamery připojené 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 je pak možné 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ý, snadno použitelný online portál pro zjednodušení procesu. Další informace najdete na stránce služby 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 Service nahráním obrázků, vytvářením značek a trénováním služby rozpoznávat různé objekty (v tomto případě myš a klávesnice). Pak vytvoříte aplikaci HoloLens, která zachytí obrázky pomocí kamery a pokusí se tyto objekty rozpoznat ve skutečném světě.

  • Režim trénování: Implementujete kód, který ve vaší aplikaci povolí režim trénování. Trénovací režim vám umožní zachytit obrázky pomocí kamery HoloLens, nahrát zachycené obrázky do služby a vytrénovat model vlastního zpracování obrazu.

V tomto kurzu se dozví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 byste mohli vytvářet.

Podpora zařízení

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

Poznámka:

I když se tento kurz primárně zaměřuje na HoloLens, můžete také použít to, co se v tomto kurzu naučíte, na imerzivní náhlavní soupravy Windows Mixed Reality (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. Jak budete postupovat podle kurzu, uvidíte poznámky o všech změnách, které možná budete 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 otestováno a ověřeno v době psaní (červenec 2018). Můžete používat nejnovější software, jak je uvedeno v článku o instalaci 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. Abyste se vyhnuli 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ůsobovat problémy v době sestavení).
  2. Nastavte a otestujte HoloLens. Pokud potřebujete podporu k nastavení HoloLens, nezapomeňte navštívit článek o nastavení HoloLens.
  3. Při vývoji nové aplikace HoloLens je vhodné provést kalibraci a ladění senzorů (někdy může pomoct tyto úlohy provádět pro každého uživatele).

Nápovědu k kalibraci najdete v tomto odkazu na článek o kalibraci HoloLens.

Nápovědu k ladění senzorů najdete v tomto odkazu na článek o ladění snímačů HoloLens.

Kapitola 1 – Portál služby Custom Vision

Pokud chcete službu Custom Vision Service používat v Azure, budete muset nakonfigurovat instanci služby, aby byla pro vaši aplikaci dostupná.

  1. Nejprve přejděte na hlavní stránku služby 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 ho muset vytvořit. Pokud tento kurz sledujete v situaci v učebně nebo testovacím prostředí, požádejte svého instruktora nebo některého z proktorů, aby vám pomohli nastavit nový účet.

  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 souhlasíte s podmínkami. Pak klikněte na souhlasím.

    Podmínky služby

  5. Když jste se dohodli na podmínkách, budete přesměrováni do části Projekty na portálu. Klikněte na Nový projekt.

    Vytvoření nového projektu

  6. Na pravé straně se zobrazí karta, která zobrazí výzvu 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 pro kolekci prostředků Azure. Doporučujeme zachovat všechny služby Azure přidružené k jednomu projektu (např. tyto kurzy) v rámci společné skupiny prostředků).

    4. Nastavení typů projektů na klasifikaci

    5. Nastavte domény jako obecné.

      Nastavení domén

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

  7. Jakmile budete hotovi, klikněte na Vytvořit projekt, budete přesměrováni na stránku projektu Custom Vision Service.

Kapitola 2 – trénování projektu Custom Vision

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 pro každý objekt, který chcete, aby aplikace rozpoznala deset (10). Obrázky, které jsou součástí tohoto kurzu (počítačová myš a klávesnice), můžete použít.

Trénování projektu Custom Vision Service:

  1. Klikněte na + tlačítko vedle značek.

    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). Pokud ještě není zaškrtnuté, klikněte na zaškrtávací políčko vedle nové značky.

    Povolit novou značku

  4. Klikněte na Přidat obrázky uprostřed stránky.

    Přidání obrázků

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

    Poznámka:

    Můžete vybrat několik obrázků najednou a nahrát je.

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

    Vybrat značky

  7. Klikněte na Nahrát soubory. Soubory se začnou nahrávat. Jakmile potvrdíte 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íčka Myš , aby se zobrazilo okno Přidat obrázky .

  9. Jakmile nastavíte obě značky, klikněte na Train (Trénovat) a první iterace trénování začne sestavovat.

    Povolení iterace trénování

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

    Nastavení výchozí adresy URL a adresy URL predikce

    Poznámka:

    Adresa URL koncového bodu, která je z této adresy zadána, je nastavená na to , která iterace byla označena jako výchozí. Pokud později provedete novou iteraci a aktualizujete ji jako výchozí, nebudete muset změnit kód.

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

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

  12. Klikněte na ozubené kolečko 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. Také zkopírujte ID projektu a vložte ho také do souboru Poznámkového bloku pro pozdější použití.

    Kopírování ID projektu

Kapitola 3 – Nastavení projektu Unity

Následuje typická sada pro vývoj s hybridní realitou a jako taková je vhodná šablona 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. Nastavte umístění na místo, které je pro vás vhodné (nezapomeňte, že blíže ke kořenovým adresářům je lepší). Potom klikněte na Vytvořit projekt.

    Konfigurace nastavení projektu

  3. Při otevření Unity stojí za to zkontrolovat, jestli je výchozí editor skriptů nastavený na Visual Studio. Přejděte na Upravit>předvolby a pak 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. Dále přejděte do Nastavení sestavení souboru > a vyberte Univerzální platforma Windows a kliknutím na tlačítko Přepnout platformu použijte svůj výběr.

    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, pojmenujte ji Scény.

        Vytvoření nové složky scény

      3. Otevřete nově vytvořenou složku Scény a potom v názvu souboru: textové pole, 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. Vytvoření složky scén (a dalších podobných složek) je typický způsob strukturování projektu Unity.

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

      Výchozí nastavení sestavení

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

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

    1. Na kartě Další nastavení:

      1. Skriptovací verze modulu runtime by měla být experimentální (ekvivalent .NET 4.6), která aktivuje nutnost restartování editoru.

      2. Back-end skriptování by měl být .NET.

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

      Nastavení compantiblity rozhraní API

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

      1. InternetClient

      2. Webová kamera

      3. Mikrofon

      Konfigurace nastavení publikování

    3. Dále na panelu v nastavení XR (v části Nastavení publikování níže) zaškrtněte možnost Podpora virtuální reality a ujistěte se, že je přidaná sada SDK pro Windows Mixed Reality.

    Konfigurace nastavení XR

  8. Zpět v projektech Unity Nastavení sestavení v jazyce C# se už nezobrazuje šedě. Zaškrtněte políčko vedle tohoto příkazu.

  9. Zavřete okno Nastavení sestavení.

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

Kapitola 4 – Import knihovny Newtonsoft DLL v Unity

Důležité

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

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

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

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

    Import všech položek balíčku

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

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

    Výběr modulu plug-in Newtonsoft

  5. Pokud je vybraný modul plug-in Newtonsoft.Json, ujistěte se, že je nezaškrtnutá žádná platforma, a potom klikněte na Použít. Stačí jenom ověřit, že jsou soubory správně nakonfigurované.

    Konfigurace modulu plug-in Newtonsoft

    Poznámka:

    Označení těchto modulů plug-in konfiguruje jejich použití pouze v Unity Editoru. Ve složce WSA existuje jiná sada, která se použije po exportu projektu z Unity.

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

    • Libovolná platforma není zaškrtnutá.
    • Je zaškrtnuto pouze WSAPlayer.
    • Proces dont je zaškrtnutý.

    Konfigurace nastavení platformy plug-in Newtonsoft

Kapitola 5 – Nastavení kamery

  1. Na panelu hierarchie vyberte hlavní kameru.

  2. Po výběru uvidíte všechny součásti hlavní kamery na panelu inspektoru.

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

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

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

    4. Nastavte vymazat příznaky na plnou barvu (ignorujte to pro imerzivní náhlavní soupravu).

    5. Nastavte barvu pozadí komponenty fotoaparátu na Černou, Alfa 0 (Šestnáctkový kód: #000000000) (ignorujte ji pro imerzivní náhlavní soupravu).

    Konfigurace vlastností komponenty Camera

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

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

Začnete s CustomVisionAnalyser třídy.

Poznámka:

Volání služby Custom Vision vytvořené v kódu uvedeném níže se provádějí pomocí rozhraní REST API služby Custom Vision. Díky tomu uvidíte, jak implementovat a používat toto rozhraní API (užitečné pro pochopení toho, jak implementovat něco podobného sami). Mějte na paměti, že Microsoft nabízí sadu SDK služby Custom Vision, která se dá použít také k volání služby. Další informace najdete v článku o sadě SDK služby Custom Vision.

Tato třída zodpovídá za:

  • Načtení nejnovější image zachycené jako pole bajtů

  • Odeslání pole bajtů do instance služby Azure Custom Vision Service pro účely analýzy

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

  • Deserializace odpovědi a předání výsledné predikce do třídy SceneOrganiser , která se postará o způsob zobrazení odpovědi.

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

  1. Klikněte pravým tlačítkem na složku assetu umístěnou na panelu projektu a potom klikněte na vytvořit > složku. Volejte skripty složky.

    Vytvoření složky skriptů

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

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

  4. Poklikáním otevřete nový skript CustomVisionAnalyser 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:

    Ujistěte se, že do proměnné predictionKey a koncového bodu předpovědi vložíte prediktivní klíč 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 probuzené( ):

        /// <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 analyzeImageCapture korutin, existuje volání SceneOrganiser třídy, kterou jste ještě vytvořit. Proto nechte tyto řádky okomentované prozatím.

        /// <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. Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.

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

Třída, kterou vytvoříte nyní, je CustomVisionObjects třída.

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

Upozorňující

Je důležité si uvědomit koncový bod, který vám služba Custom Vision Service poskytuje, protože následující struktura JSON je nastavená tak, aby fungovala s Custom Vision Prediction v2.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 Skripty a potom klikněte na Vytvořit>skript jazyka C#. Volání skriptu CustomVisionObjects

  2. Poklikáním otevřete nový skript CustomVisionObjects pomocí sady Visual Studio.

  3. Do horní části 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 mimo Tří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 Skripty a potom klikněte na Vytvořit>skript jazyka C#. Volání skriptu VoiceRecognizer.

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

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

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

        /// <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 Probuzení() a Start(), z nichž druhé 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á při každém rozpoznání hlasového vstupu:

        /// <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. Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.

Poznámka:

Nedělejte si starosti s kódem, který se může zdát, že obsahuje chybu, protože brzy poskytnete další třídy, které je opraví.

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

Tato třída zřetězí ř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 Skripty a potom klikněte na Vytvořit>skript jazyka C#. Volání skriptu CustomVisionTrainer.

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

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

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

    Poznámka:

    Zde použitá trénovací adresa URL je k dispozici v dokumentaci k 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 služby Custom Vision Training verze 1.2.

    Upozorňující

    Je důležité si uvědomit koncový bod, který služba Custom Vision Service poskytuje pro trénovací režim, protože struktura JSON používaná (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 Objektů .

        /// <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íče služby (trénovací klíč) a hodnotu ID projektu, kterou jste si poznamenali dříve. Jedná se o hodnoty, které jste shromáždili z portálu dříve v kurzu (kapitola 2, krok 10 atd.).

  5. Přidejte následující metody Start() and 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 má volat, když byla zachycena a uložena image v zařízení a je nyní připravená k odeslání do služby 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á může uživatel použít k označení obrázku, který byl zachycen. Upozorňuje také na třídu VoiceRecognizer , aby začala naslouchat uživateli 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ý třídou VoiceRecognizer 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ří novou iteraci, která se vytrénuje se všemi předchozími obrázky odeslanými do služby a obrázkem, který jste právě nahráli. 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 pro analýzu, 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 vytré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 platí omezení maximálního počtu deseti (10) iterací, které mohou existovat ve stejnou dobu ve službě.

        /// <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 vyhledá a odstraní předchozí ne vý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 přidání do této třídy je GetImageAsByteArray() metoda, která se používá na webových voláních k převodu obrázku zachyceného 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. Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.

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

Tato třída:

  • Vytvořte objekt Kurzor pro připojení k hlavní kameře.

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

  • Nastavte hlavní kameru tak, že k ní připojíte příslušné součásti.

  • Když jste v režimu analýzy, třete popisky za běhu, ve vhodném světě vzhledem k pozici hlavní kamery a zobrazte data přijatá ze služby Custom Vision Service.

  • Když v režimu trénování vytvoříte uživatelské rozhraní, které zobrazí různé fáze procesu trénování.

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

  1. Klikněte pravým tlačítkem do složky Skripty a potom klikněte na Vytvořit>skript jazyka C#. Pojmenujte skript SceneOrganiser.

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

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

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

        /// <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. Nyní přidejte CreateCameraCursor() metoda, která vytvoří a umístí kurzor Main Camera, a CreateLabel() metoda, 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í objektu stavu 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. Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.

Důležité

Než budete pokračovat, otevřete CustomVisionAnalyser třídy a v rámci AnalyzeLastImageCaptured() metoda zrušte komentář 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í fotoaparátu HoloLens a jeho uložení do složky aplikace

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

  • Udržování hodnoty Výčtu, která určuje, jestli se aplikace spustí v režimu analýzy nebo v 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 potom klikněte na Vytvořit > skript jazyka C#. Pojmenujte skript ImageCapture.

  3. Poklikáním otevřete nový skript ImageCapture 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ž dojde k gestu 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 zachycení fotek.

    V režimu trénování zachytí obrázek z fotoaparátu.

    Když je kurzor zelený, znamená to, že kamera je k dispozici k pořízení obrázku.

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

  8. Přidejte metodu, kterou aplikace používá 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, když byla fotka zachycena a kdy je připravena k analýze. Výsledek se pak předá CustomVisionAnalyser nebo CustomVisionTrainer v závislosti na tom, na 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. Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.

  11. Teď, když byly dokončeny všechny skripty, vraťte se v Unity Editoru a potom klikněte a přetáhněte třídu SceneOrganiser ze složky Scripts do objektu Main Camera na panelu hierarchie.

Kapitola 12 - Před budovou

Pokud chcete provést důkladný test aplikace, budete ji muset načíst na HoloLens bokem.

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, panelu inspektoru, jsou správně přiřazena.

  • Skript SceneOrganiser je připojen k objektu hlavní kamery .

  • Ujistěte se, že do proměnné predictionKey vložíte prediktivní klíč.

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

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

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

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

Zahájení procesu sestavení:

  1. Přejděte do nastavení sestavení souboru>.

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

  3. Klikněte na Sestavit. Unity spustí okno Průzkumník souborů, ve kterém potřebujete vytvořit a pak vybrat složku pro sestavení aplikace. Vytvořte teď složku a pojmenujte ji App. Potom s vybranou složkou Aplikace klikněte na Vybrat složku.

  4. Unity začne sestavovat projekt do složky Aplikace .

  5. Po dokončení sestavování Unity (může to nějakou dobu trvat), otevře se okno Průzkumník souborů na místě 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 v režimu vývojáře. Akce:

    1. Když nosíte HoloLens, otevřete Nastavení.

    2. Přejít na možnosti Rozšířené možnosti sítě a internetu>Wi-Fi>

    3. Poznamenejte si adresu IPv4 .

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

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

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

  3. V konfiguraci ř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 Sestavení a kliknutím na Nasadit řešení načtěte aplikaci na HoloLens.

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

Poznámka:

Pokud chcete nasadit do imerzivní náhlavní soupravy, nastavte platformu řešení na místní počítač a nastavte konfiguraci na Ladění s platformou x86. Potom nasaďte na místní počítač pomocí položky nabídky Sestavení a vyberte Nasadit řešení.

Použití aplikace:

Pokud chcete přepnout funkce aplikace mezi režimem trénování a predikčním režimem, musíte aktualizovat proměnnou AppMode umístěnou v metodě Awake(), která se nachází 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í.

  • V dalším kroku 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 s rozpoznaným objektem s nejvyšší pravděpodobností (to je normalizováno).

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

Aby byla služba přesnější, budete muset pokračovat v trénování modelu používaného k predikci. Toho se dosahuje pomocí nové aplikace s režimy trénování i predikce , které vyžadují, abyste navštívili portál, který je popsaný v této kapitole. Připravte se na opakované opakování portálu, abyste mohli model průběžně vylepšovat.

  1. Znovu přejděte na portál Azure Custom Vision a až budete v projektu, vyberte kartu Predikce (v horním rohu stránky):

    Výběr karty predikce

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

    Seznam předpovědí obrázků

  3. Vyberte jeden z obrázků a otevřete ho. Po otevření uvidíte předpovědi vytvořené pro tento obrázek napravo. Pokud byly předpovědi správné a chcete přidat tento obrázek 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 na tlačítko Uložit a zavřít vpravo dole a pokračujte k dalšímu obrázku.

    Výběr obrázku, který se má otevřít

  4. Jakmile se vrátíte do mřížky obrázků, všimnete si, že se odeberou obrázky, ke kterým jste přidali (a uložili) značky. Pokud najdete nějaké obrázky, u kterých si myslíte, že v nich nejsou označené položky, můžete je odstranit kliknutím na zaškrtnutí tohoto 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 tlačítko Ano, odstranit nebo Ne potvrdit odstranění nebo ho zrušit.

    Odstranění imagí

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

    Spuštění modelu trénovací službyVýběr možnosti Nastavit jako výchozí

Hotová aplikace rozhraní API služby Custom Vision

Blahopřejeme, vytvořili jste aplikaci hybridní reality, která využívá rozhraní API služby Azure Custom Vision k rozpoznávání reálných objektů, trénování modelu služby a zobrazení spolehlivosti toho, co bylo vidět.

Příklad dokončených projektů

Bonusová cvičení

Cvičení 1

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

Cvičení 2

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

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

Cvičení 3

Pomocí rozhraní API můžete službu znovu vytrénovat pomocí stejných obrázků, které vaše aplikace analyzuje, aby byla služba přesnější (predikce i trénování současně).