HoloLens (1. generace) a Azure 310: Detekce objektů
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 a jeho prostorovou pozici v rámci poskytnutého obrázku pomocí funkcí Rozpoznávání objektů ve službě 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 vytrénovaný model k rozpoznávání podobných objektů a k přibližné poloze v reálném světě, jak poskytuje snímek kamery Microsoft HoloLensu nebo kamery, které se připojují k počítači pro imerzivní náhlavní soupravy (VR).
Azure Custom Vision, Rozpoznávání objektů je služba Microsoftu, 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 detekci objektů v rámci tohoto nového obrázku tím, že v samotném obrázku poskytnou hranice boxu . Služba poskytuje jednoduchý, snadno použitelný online portál pro zjednodušení tohoto procesu. Další informace najdete na následujících odkazech:
Po dokončení tohoto kurzu budete mít aplikaci hybridní reality, která bude moct provést následující akce:
- Uživatel bude moct upřet pohled na objekt, který vytrénoval pomocí služby Azure Custom Vision Service, rozpoznávání objektů.
- Uživatel použije gesto Klepnutí k zachycení obrázku toho, na co se dívá.
- Aplikace odešle obrázek do služby Azure Custom Vision Service.
- Ve službě bude odpověď, která zobrazí výsledek rozpoznávání jako světoprostorový text. Toho dosáhnete použitím prostorového sledování Microsoft HoloLens, jako způsobu pochopení pozice rozpoznaného objektu a následného použití značky přidružené k tomu, co je na obrázku zjištěno, k poskytnutí textu popisku.
Kurz se také bude zabývat ručním nahráváním obrázků, vytvářením značek a trénováním služby, aby rozpoznala různé objekty (v uvedeném příkladu cup) nastavením rámečku hranic v rámci odesílaného obrázku.
Důležité
Po vytvoření a použití aplikace by se vývojář měl vrátit ke službě Azure Custom Vision Service a identifikovat predikce provedené službou a určit, jestli byly správné nebo ne (pomocí označování čehokoli, co služba zmeškala, a úpravou ohraničujících polí). Služba se pak dá znovu vytrénovat, což zvýší pravděpodobnost, že rozpozná skutečné objekty světa.
V tomto kurzu se dozvíte, jak získat výsledky ze služby Azure Custom Vision Service, rozpoznávání objektů 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 310: Detekce objektů | ✔️ |
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:
- Vývojový počítač
- Windows 10 Fall Creators Update (nebo novější) s povoleným režimem vývojáře
- Nejnovější sada Windows 10 SDK
- Unity 2017.4 LTS
- Visual Studio 2017
- Microsoft HoloLens s povoleným režimem vývojáře
- Přístup k internetu pro nastavení Azure a načítání služby Custom Vision Service
- Pro každý objekt, který chcete, aby služba Custom Vision rozpoznala, se vyžaduje řada alespoň patnácti (15) obrázků. Pokud chcete, můžete použít obrázky, které jsou již k dispozici v tomto kurzu, série šáleků).
Než začnete
- 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í).
- Nastavte a otestujte HoloLens. Pokud k tomu potřebujete podporu, navštivte článek o nastavení HoloLensu.
- 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 Custom Vision
Pokud chcete použít službu Azure Custom Vision Service, budete muset nakonfigurovat instanci, která bude zpřístupněna vaší aplikaci.
Přejděte na hlavní stránku služby Custom Vision.
Klikněte na Začínáme.
Přihlaste se k portálu Custom Vision.
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.
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.
Když jste se dohodli na podmínkách, jste teď v části Moje projekty . Klikněte na Nový projekt.
Na pravé straně se zobrazí karta, která zobrazí výzvu k zadání některých polí pro projekt.
Vložení názvu projektu
Vložení popisu projektu (volitelné)
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ů).
Poznámka:
Pokud si chcete přečíst další informace o skupinách prostředků Azure, přejděte na přidruženou dokumentaci.
Nastavte typy projektů jako rozpoznávání objektů (Preview).
Jakmile budete hotovi, klikněte na Vytvořit projekt a budete přesměrováni na stránku projektu Custom Vision Service.
Kapitola 2 – trénování projektu Custom Vision
Jakmile se na portálu Custom Vision nacházíte, vaším primárním cílem je vytrénovat projekt tak, aby rozpoznával konkrétní objekty na obrázcích.
Pro každý objekt, který chcete, aby aplikace rozpoznala, potřebujete alespoň patnáct (15) obrázků. Obrázky, které jsou součástí tohoto kurzu (série šáleků), můžete použít.
Trénování projektu Custom Vision:
Klikněte na + tlačítko vedle značek.
Přidejte název značky, která se použije k přidružení obrázků. V tomto příkladu používáme obrázky šáleků k rozpoznávání, proto jsme pojmenovali značku pro tento Cup. Po dokončení klikněte na Uložit .
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).
Klikněte na Přidat obrázky uprostřed stránky.
Klikněte na Procházet místní soubory a přejděte k obrázkům, které chcete nahrát pro jeden objekt s minimálním 15 (15).
Tip
Můžete vybrat několik obrázků najednou a nahrát je.
Jakmile vyberete všechny obrázky, se kterými chcete projekt trénovat, stiskněte tlačítko Nahrát soubory . Soubory se začnou nahrávat. Jakmile potvrdíte nahrávání, klikněte na Hotovo.
V tuto chvíli se obrázky nahrají, ale nejsou označené.
Pokud chcete obrázky označit, použijte myš. Když na obrázek najedete myší, zvýraznění výběru vám pomůže automaticky nakreslit výběr kolem objektu. Pokud není přesný, můžete si nakreslit vlastní. Toho dosáhnete tak, že podržíte levým tlačítkem myši a přetáhnete oblast výběru tak, aby zahrnovala objekt.
Po výběru objektu na obrázku se zobrazí malá výzva k zadání značky Přidat oblast. Vyberte dříve vytvořenou značku ('Cup', v předchozím příkladu) nebo pokud přidáváte další značky, zadejte ji a klikněte na tlačítko + (plus ).
Pokud chcete označit další obrázek, můžete kliknout na šipku vpravo od okna nebo zavřít okno značky (kliknutím na symbol X v pravém horním rohu okna) a potom kliknout na další obrázek. Jakmile budete mít další obrázek připravený, opakujte stejný postup. Udělejte to u všech obrázků, které jste nahráli, dokud nebudou všechny označené.
Poznámka:
Na stejném obrázku můžete vybrat několik objektů, například na následujícím obrázku:
Jakmile je označíte, klikněte na označené tlačítko na levé straně obrazovky a zobrazte označené obrázky.
Teď jste připraveni službu trénovat. Klikněte na tlačítko Trénovat a zahájí se první iterace trénování.
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.
Poznámka:
Koncový bod, který je z tohoto parametru poskytnutý, 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.
Po kliknutí na adresu URL predikce otevřete Poznámkový blok a zkopírujte a vložte adresu URL (označovanou také jako predikce koncového bodu) a klíč predikce služby, abyste ji mohli načíst, až ji budete potřebovat později v kódu.
Kapitola 3 – Nastavení projektu Unity
Následuje typická sada pro vývoj s hybridní realitou a jako taková je vhodná šablona pro jiné projekty.
Otevřete Unity a klikněte na Nový.
Teď budete muset zadat název projektu Unity. Vložte CustomVisionObjDetection. Ujistěte se, že je typ projektu nastavený na 3D, a nastavte umístění na umístění, které je pro vás vhodné (mějte na paměti, že blíž ke kořenovým adresářům je lepší). Potom klikněte na Vytvořit projekt.
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. Zavřete okno Předvolby.
Dále přejděte do nastavení sestavení souboru > a přepněte platformu na Univerzální platforma Windows a potom klikněte na tlačítko Přepnout platformu.
Ve stejném okně Nastavení sestavení se ujistěte, že jsou nastavené následující položky:
Cílové zařízení je nastavené na HoloLens.
Typ sestavení je nastavený na D3D.
Sada SDK je nastavená na nejnovější nainstalovanou verzi.
Verze sady Visual Studio je nastavená na nejnovější nainstalovanou verzi.
Sestavení a spuštění je nastavené na místní počítač.
Zbývající nastavení v nastavení sestavení by teď měla zůstat ve výchozím nastavení.
Ve stejném 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.
Na tomto panelu je potřeba ověřit několik nastavení:
Na kartě Další nastavení:
Skriptovací verze modulu runtime by měla být experimentální (ekvivalent .NET 4.6), která aktivuje nutnost restartování editoru.
Back-end skriptování by měl být .NET.
Úroveň kompatibility rozhraní API by měla být .NET 4.6.
Na kartě Nastavení publikování v části Možnosti zaškrtněte:
InternetClient
Webová kamera
SpatialPerception
Dále na panelu v nastavení XR (v části Nastavení publikování najdete níže) zaškrtněte možnost Podpora virtuální reality a ujistěte se, že je přidaná sada WINDOWS Mixed Reality SDK.
Zpět v nastavení sestavení už projekty Unity C# nejsou šedě: zaškrtněte políčko vedle tohoto nastavení.
Zavřete okno Nastavení sestavení.
V editoru klikněte na Upravit>grafiku nastavení>projektu.
Na panelu inspektoru se otevře nastavení grafiky. Posuňte se dolů, dokud neuvidíte pole s názvem Vždy zahrnout shadery. Přidejte slot zvětšením proměnné Velikost o jeden (v tomto příkladu to bylo 8, takže jsme ho udělali 9). V poslední pozici pole se zobrazí nový slot, jak je znázorněno níže:
V slotu kliknutím na malý cílový kruh vedle slotu otevřete seznam shaderů. Vyhledejte starší shadery, průhledné nebo difuzní shader a poklikejte na něj.
Kapitola 4 – Import balíčku CustomVisionObjDetection Unity
Pro účely tohoto kurzu máte k dispozici balíček prostředků Unity s názvem Azure-MR-310.unitypackage.
[TIP] Všechny objekty podporované Unity, včetně celých scén, se dají zabalit do souboru .unitypackage a exportovat nebo importovat do jiných projektů. Je to nejbezpečnější a nejúčinnější způsob, jak přesouvat prostředky mezi různými projekty Unity.
Balíček Azure-MR-310, který potřebujete stáhnout, najdete zde.
Na řídicím panelu Unity před vámi klikněte na Prostředky v nabídce v horní části obrazovky a potom klikněte na Importovat balíček > vlastní balíček.
Pomocí nástroje pro výběr souborů vyberte balíček Azure-MR-310.unitypackage a klikněte na Otevřít. Zobrazí se vám seznam komponent pro tento prostředek. Kliknutím na tlačítko Importovat potvrďte import.
Po dokončení importu si všimnete, že složky z balíčku se teď přidaly do složky Assets . Tento typ struktury složek je typický pro projekt Unity.
Složka Materiály obsahuje materiál používaný kurzorem Pohled.
Složka Plugins obsahuje Newtonsoft DLL používaný kódem k deserializaci webové odpovědi služby. Dvě (2) různé verze obsažené ve složce a podsložky jsou nezbytné k tomu, aby knihovna mohla být použita a sestavena jak v Unity Editoru, tak v sestavení UPW.
Složka Prefabs obsahuje předfaby obsažené ve scéně. Toto jsou:
- GazeCursor, kurzor použitý v aplikaci. Bude spolupracovat s předfabem SpatialMapping, aby bylo možné umístit na scénu nad fyzické objekty.
- Popisek, což je objekt uživatelského rozhraní použitý k zobrazení značky objektu ve scéně v případě potřeby.
- SpatialMapping, což je objekt, který aplikaci umožňuje použít vytvoření virtuální mapy pomocí prostorového sledování Microsoft HoloLens.
Složka Scény , která aktuálně obsahuje předdefinované scény pro tento kurz.
Otevřete složku Scény na panelu projektu a poklikáním na ObjDetectionScene načtěte scénu, kterou použijete pro tento kurz.
Poznámka:
Žádný kód není zahrnutý, kód napíšete podle tohoto kurzu.
Kapitola 5 – 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, která je provedena v níže uvedeném kódu, se provádí 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 Custom Vision SDK , která se dá použít také k volání služby. Další informace najdete v článku o sadě Custom Vision SDK.
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:
Klikněte pravým tlačítkem myši do složky Asset, která se nachází na panelu projektu, a potom klikněte na Vytvořit>složku. Volejte skripty složky.
Poklikáním otevřete nově vytvořenou složku.
Klikněte pravým tlačítkem do složky a potom klikněte na Vytvořit>skript jazyka C#. Pojmenujte skript CustomVisionAnalyser.
Poklikáním otevřete nový skript CustomVisionAnalyser v sadě Visual Studio.
Ujistěte se, že v horní části souboru jsou odkazované následující obory názvů:
using Newtonsoft.Json; using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking;
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> /// Bite 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 predikce vložíte klíč predikce služby do proměnné predictionEndpoint. Zkopírovali jste je do Poznámkového bloku dříve, v kapitole 2, krok 14.
Pro inicializaci proměnné Instance je teď potřeba přidat kód pro probuzené( ):
/// <summary> /// Initializes this class /// </summary> private void Awake() { // Allows this instance to behave like a singleton Instance = this; }
Přidejte pod ni korutin (se statickou metodou GetImageAsByteArray(), která získá výsledky analýzy obrázku zachycené 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) { Debug.Log("Analyzing..."); WWWForm webForm = new WWWForm(); using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm)) { // Gets a byte array out of the saved image imageBytes = GetImageAsByteArray(imagePath); unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream"); unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey); // The upload handler will help uploading the byte array with the request unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes); unityWebRequest.uploadHandler.contentType = "application/octet-stream"; // The download handler will help receiving the analysis from Azure unityWebRequest.downloadHandler = new DownloadHandlerBuffer(); // Send the request yield return unityWebRequest.SendWebRequest(); string jsonResponse = unityWebRequest.downloadHandler.text; Debug.Log("response: " + jsonResponse); // Create a texture. Texture size does not matter, since // LoadImage will replace with the incoming image size. //Texture2D tex = new Texture2D(1, 1); //tex.LoadImage(imageBytes); //SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex); // The response will be in JSON format, therefore it needs to be deserialized //AnalysisRootObject analysisRootObject = new AnalysisRootObject(); //analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse); //SceneOrganiser.Instance.FinaliseLabel(analysisRootObject); } } /// <summary> /// Returns the contents of the specified image file as a byte array. /// </summary> static byte[] GetImageAsByteArray(string imageFilePath) { FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read); BinaryReader binaryReader = new BinaryReader(fileStream); return binaryReader.ReadBytes((int)fileStream.Length); }
Odstraňte metody Start() a Update(), protože nebudou použity.
Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.
Důležité
Jak už jsme zmínili dříve, 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 6 – 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.
Vytvoření této třídy:
Klikněte pravým tlačítkem do složky Skripty a potom klikněte na Vytvořit>skript jazyka C#. Volání skriptu CustomVisionObjects
Poklikáním otevřete nový skript CustomVisionObjects pomocí sady Visual Studio.
Ujistěte se, že v horní části souboru jsou odkazované následující obory názvů:
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking;
Odstraňte metody Start() a Update() uvnitř třídy CustomVisionObjects, tato třída by teď měla být prázdná.
Upozorňující
Je důležité pečlivě postupovat podle další instrukce. Pokud vložíte nové deklarace třídy do CustomVisionObjects třídy, zobrazí se chyby kompilace v kapitole 10, které hlásí, že AnalysisRootObject a BoundingBox nebyly nalezeny.
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 /// Includes Bounding Box /// </summary> public class AnalysisRootObject { public string id { get; set; } public string project { get; set; } public string iteration { get; set; } public DateTime created { get; set; } public List<Prediction> predictions { get; set; } } public class BoundingBox { public double left { get; set; } public double top { get; set; } public double width { get; set; } public double height { get; set; } } public class Prediction { public double probability { get; set; } public string tagId { get; set; } public string tagName { get; set; } public BoundingBox boundingBox { get; set; } }
Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.
Kapitola 7 – Vytvoření třídy SpatialMapping
Tato třída nastaví kolizi prostorového mapování ve scéně, aby bylo možné detekovat kolize mezi virtuálními objekty a skutečnými objekty.
Vytvoření této třídy:
Klikněte pravým tlačítkem do složky Skripty a potom klikněte na Vytvořit>skript jazyka C#. Zavolejte skript SpatialMapping.
Poklikáním otevřete nový skript SpatialMapping v sadě Visual Studio.
Ujistěte se, že jsou nad třídou SpatialMapping odkazované následující obory názvů:
using UnityEngine; using UnityEngine.XR.WSA;
Pak do třídy SpatialMapping nad metodu Start() přidejte následující proměnné:
/// <summary> /// Allows this class to behave like a singleton /// </summary> public static SpatialMapping Instance; /// <summary> /// Used by the GazeCursor as a property with the Raycast call /// </summary> internal static int PhysicsRaycastMask; /// <summary> /// The layer to use for spatial mapping collisions /// </summary> internal int physicsLayer = 31; /// <summary> /// Creates environment colliders to work with physics /// </summary> private SpatialMappingCollider spatialMappingCollider;
Přidejte probuzené() a Start():<
/a1> /// <summary> /// Initializes this class /// </summary> private void Awake() { // Allows this instance to behave like a singleton Instance = this; } /// <summary> /// Runs at initialization right after Awake method /// </summary> void Start() { // Initialize and configure the collider spatialMappingCollider = gameObject.GetComponent<SpatialMappingCollider>(); spatialMappingCollider.surfaceParent = this.gameObject; spatialMappingCollider.freezeUpdates = false; spatialMappingCollider.layer = physicsLayer; // define the mask PhysicsRaycastMask = 1 << physicsLayer; // set the object as active one gameObject.SetActive(true); }
Odstraňte metodu Update().
Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.
Kapitola 8 – Vytvoření třídy GazeCursor
Tato třída zodpovídá za nastavení kurzoru ve správném umístění v reálném prostoru pomocí SpatialMappingCollider vytvořené v předchozí kapitole.
Vytvoření této třídy:
Klikněte pravým tlačítkem do složky Skripty a potom klikněte na Vytvořit>skript jazyka C#. Volání skriptu GazeCursor
Poklikáním otevřete nový skript GazeCursor v sadě Visual Studio.
Ujistěte se, že je nad třídou GazeCursor odkazovaný následující obor názvů:
using UnityEngine;
Pak přidejte následující proměnnou do třídy GazeCursor nad Metodu Start().
/// <summary> /// The cursor (this object) mesh renderer /// </summary> private MeshRenderer meshRenderer;
Aktualizujte metodu Start() následujícím kódem:
/// <summary> /// Runs at initialization right after the Awake method /// </summary> void Start() { // Grab the mesh renderer that is on the same object as this script. meshRenderer = gameObject.GetComponent<MeshRenderer>(); // Set the cursor reference SceneOrganiser.Instance.cursor = gameObject; gameObject.GetComponent<Renderer>().material.color = Color.green; // If you wish to change the size of the cursor you can do so here gameObject.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f); }
Aktualizujte metodu Update() následujícím kódem:
/// <summary> /// Update is called once per frame /// </summary> void Update() { // Do a raycast into the world based on the user's head position and orientation. Vector3 headPosition = Camera.main.transform.position; Vector3 gazeDirection = Camera.main.transform.forward; RaycastHit gazeHitInfo; if (Physics.Raycast(headPosition, gazeDirection, out gazeHitInfo, 30.0f, SpatialMapping.PhysicsRaycastMask)) { // If the raycast hit a hologram, display the cursor mesh. meshRenderer.enabled = true; // Move the cursor to the point where the raycast hit. transform.position = gazeHitInfo.point; // Rotate the cursor to hug the surface of the hologram. transform.rotation = Quaternion.FromToRotation(Vector3.up, gazeHitInfo.normal); } else { // If the raycast did not hit a hologram, hide the cursor mesh. meshRenderer.enabled = false; } }
Poznámka:
Nedělejte si starosti s chybou třídy SceneOrganiser , která nebyla nalezena, vytvoříte ji v další kapitole.
Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.
Kapitola 9 - Vytvoření třídy SceneOrganiser
Tato třída:
Nastavte hlavní kameru tak, že k ní připojíte příslušné součásti.
Při zjištění objektu bude zodpovědný za výpočet jeho pozice v reálném světě a umístění popisku značky blízko objektu s odpovídajícím názvem značky.
Vytvoření této třídy:
Klikněte pravým tlačítkem do složky Skripty a potom klikněte na Vytvořit>skript jazyka C#. Pojmenujte skript SceneOrganiser.
Poklikáním otevřete nový skript SceneOrganiser v sadě Visual Studio.
Ujistěte se, že máte následující obory názvů odkazované nad SceneOrganiser třídy:
using System.Collections.Generic; using System.Linq; using UnityEngine;
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 Main Camera /// </summary> internal GameObject cursor; /// <summary> /// The label used to display the analysis on the objects in the real world /// </summary> public GameObject label; /// <summary> /// Reference to the last Label positioned /// </summary> internal Transform lastLabelPlaced; /// <summary> /// Reference to the last Label positioned /// </summary> internal TextMesh lastLabelPlacedText; /// <summary> /// Current threshold accepted for displaying the label /// Reduce this value to display the recognition more often /// </summary> internal float probabilityThreshold = 0.8f; /// <summary> /// The quad object hosting the imposed image captured /// </summary> private GameObject quad; /// <summary> /// Renderer of the quad object /// </summary> internal Renderer quadRenderer;
Odstraňte metody Start() a Update().
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 CustomVisionObjects class to this Gameobject gameObject.AddComponent<CustomVisionObjects>(); }
Přidejte metodu PlaceAnalysisLabel(), která vytvoří instanci popisku ve scéně (která je v tomto okamžiku pro uživatele neviditelná). Umístí také čtyřúhelník (také neviditelný), kde je obrázek umístěn, a překrývá se skutečným světem. To je důležité, protože souřadnice rámečku načtené ze služby po analýze jsou trasovány zpět do tohoto čtyřúhelníku, aby se určilo přibližné umístění objektu v reálném světě.
/// <summary> /// Instantiate a Label in the appropriate location relative to the Main Camera. /// </summary> public void PlaceAnalysisLabel() { lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation); lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>(); lastLabelPlacedText.text = ""; lastLabelPlaced.transform.localScale = new Vector3(0.005f,0.005f,0.005f); // Create a GameObject to which the texture can be applied quad = GameObject.CreatePrimitive(PrimitiveType.Quad); quadRenderer = quad.GetComponent<Renderer>() as Renderer; Material m = new Material(Shader.Find("Legacy Shaders/Transparent/Diffuse")); quadRenderer.material = m; // Here you can set the transparency of the quad. Useful for debugging float transparency = 0f; quadRenderer.material.color = new Color(1, 1, 1, transparency); // Set the position and scale of the quad depending on user position quad.transform.parent = transform; quad.transform.rotation = transform.rotation; // The quad is positioned slightly forward in font of the user quad.transform.localPosition = new Vector3(0.0f, 0.0f, 3.0f); // The quad scale as been set with the following value following experimentation, // to allow the image on the quad to be as precisely imposed to the real world as possible quad.transform.localScale = new Vector3(3f, 1.65f, 1f); quad.transform.parent = null; }
Přidejte metodu FinaleLabel(). Zodpovídá za:
- Nastavení textu Popisek se značkou predikce s nejvyšší jistotou
- Volání výpočtu ohraničujícího rámečku na čtyřúhelníku objektu, umístěné dříve a umístění popisku do scény.
- Úprava hloubky popisku pomocí Raycastu směrem k ohraničující rámeček, který by měl kolidovat s objektem ve skutečném světě.
- Resetováním procesu zachycení umožníte uživateli zachytit jinou image.
/// <summary> /// Set the Tags as Text of the last label created. /// </summary> public void FinaliseLabel(AnalysisRootObject analysisObject) { if (analysisObject.predictions != null) { lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>(); // Sort the predictions to locate the highest one List<Prediction> sortedPredictions = new List<Prediction>(); sortedPredictions = analysisObject.predictions.OrderBy(p => p.probability).ToList(); Prediction bestPrediction = new Prediction(); bestPrediction = sortedPredictions[sortedPredictions.Count - 1]; if (bestPrediction.probability > probabilityThreshold) { quadRenderer = quad.GetComponent<Renderer>() as Renderer; Bounds quadBounds = quadRenderer.bounds; // Position the label as close as possible to the Bounding Box of the prediction // At this point it will not consider depth lastLabelPlaced.transform.parent = quad.transform; lastLabelPlaced.transform.localPosition = CalculateBoundingBoxPosition(quadBounds, bestPrediction.boundingBox); // Set the tag text lastLabelPlacedText.text = bestPrediction.tagName; // Cast a ray from the user's head to the currently placed label, it should hit the object detected by the Service. // At that point it will reposition the label where the ray HL sensor collides with the object, // (using the HL spatial tracking) Debug.Log("Repositioning Label"); Vector3 headPosition = Camera.main.transform.position; RaycastHit objHitInfo; Vector3 objDirection = lastLabelPlaced.position; if (Physics.Raycast(headPosition, objDirection, out objHitInfo, 30.0f, SpatialMapping.PhysicsRaycastMask)) { lastLabelPlaced.position = objHitInfo.point; } } } // Reset the color of the cursor cursor.GetComponent<Renderer>().material.color = Color.green; // Stop the analysis process ImageCapture.Instance.ResetImageCapture(); }
Přidejte metodu CalculateBoundingBoxPosition(), která hostuje řadu výpočtů nezbytných k překladu souřadnic ohraničujícího boxu načtených ze služby a jejich opětovnému vytvoření úměrně na čtyřúhelníku.
/// <summary> /// This method hosts a series of calculations to determine the position /// of the Bounding Box on the quad created in the real world /// by using the Bounding Box received back alongside the Best Prediction /// </summary> public Vector3 CalculateBoundingBoxPosition(Bounds b, BoundingBox boundingBox) { Debug.Log($"BB: left {boundingBox.left}, top {boundingBox.top}, width {boundingBox.width}, height {boundingBox.height}"); double centerFromLeft = boundingBox.left + (boundingBox.width / 2); double centerFromTop = boundingBox.top + (boundingBox.height / 2); Debug.Log($"BB CenterFromLeft {centerFromLeft}, CenterFromTop {centerFromTop}"); double quadWidth = b.size.normalized.x; double quadHeight = b.size.normalized.y; Debug.Log($"Quad Width {b.size.normalized.x}, Quad Height {b.size.normalized.y}"); double normalisedPos_X = (quadWidth * centerFromLeft) - (quadWidth/2); double normalisedPos_Y = (quadHeight * centerFromTop) - (quadHeight/2); return new Vector3((float)normalisedPos_X, (float)normalisedPos_Y, 0); }
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:
// Create a texture. Texture size does not matter, since // LoadImage will replace with the incoming image size. Texture2D tex = new Texture2D(1, 1); tex.LoadImage(imageBytes); SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex); // The response will be in JSON format, therefore it needs to be deserialized AnalysisRootObject analysisRootObject = new AnalysisRootObject(); analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse); SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
Poznámka:
Nedělejte si starosti se zprávou "Nelze najít" třídy ImageCapture , vytvoříte ji v další kapitole.
Kapitola 10 – 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
Vytvoření této třídy:
Přejděte do složky Scripts , kterou jste vytvořili dříve.
Klikněte pravým tlačítkem do složky a potom klikněte na Vytvořit>skript jazyka C#. Pojmenujte skript ImageCapture.
Poklikáním otevřete nový skript ImageCapture v sadě Visual Studio.
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;
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> /// Flagging if the capture loop is running /// </summary> internal bool captureIsActive; /// <summary> /// File path of current analysed photo /// </summary> internal string filePath = string.Empty;
Teď je potřeba přidat kód pro metody Awake() a Start():
/// <summary> /// Called on initialization /// </summary> private void Awake() { Instance = this; } /// <summary> /// Runs at initialization right after Awake method /// </summary> void Start() { // Clean up the LocalState folder of this application from all photos stored DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath); var fileInfo = info.GetFiles(); foreach (var file in fileInfo) { try { file.Delete(); } catch (Exception) { Debug.LogFormat("Cannot delete file: ", file.Name); } } // Subscribing to the Microsoft HoloLens API gesture recognizer to track user gestures recognizer = new GestureRecognizer(); recognizer.SetRecognizableGestures(GestureSettings.Tap); recognizer.Tapped += TapHandler; recognizer.StartCapturingGestures(); }
Implementujte obslužnou rutinu, která bude volána, když dojde k gestu klepnutí:
/// <summary> /// Respond to Tap Input. /// </summary> private void TapHandler(TappedEventArgs obj) { if (!captureIsActive) { captureIsActive = true; // Set the cursor color to red SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red; // Begin the capture loop Invoke("ExecuteImageCaptureAndAnalysis", 0); } }
Důležité
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á.
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() { // Create a label in world space using the ResultsLabel class // Invisible at this point but correctly positioned where the image was taken SceneOrganiser.Instance.PlaceAnalysisLabel(); // Set the camera resolution to be the highest possible Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending ((res) => res.width * res.height).First(); Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height); // Begin capture process, set the image format PhotoCapture.CreateAsync(true, delegate (PhotoCapture captureObject) { photoCaptureObject = captureObject; CameraParameters camParameters = new CameraParameters { hologramOpacity = 1.0f, cameraResolutionWidth = targetTexture.width, cameraResolutionHeight = targetTexture.height, pixelFormat = CapturePixelFormat.BGRA32 }; // Capture the image from the camera and save it in the App internal folder captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result) { string filename = string.Format(@"CapturedImage{0}.jpg", captureCount); filePath = Path.Combine(Application.persistentDataPath, filename); captureCount++; photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk); }); }); }
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á do CustomVisionAnalyseru pro analýzu.
/// <summary> /// Register the full execution of the Photo Capture. /// </summary> void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result) { try { // Call StopPhotoMode once the image has successfully captured photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode); } catch (Exception e) { Debug.LogFormat("Exception capturing photo to disk: {0}", e.Message); } } /// <summary> /// The camera photo mode has stopped after the capture. /// Begin the image analysis process. /// </summary> void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result) { Debug.LogFormat("Stopped Photo Mode"); // Dispose from the object in memory and request the image analysis photoCaptureObject.Dispose(); photoCaptureObject = null; // Call the image analysis StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath)); } /// <summary> /// Stops all capture pending actions /// </summary> internal void ResetImageCapture() { captureIsActive = false; // Set the cursor color to green SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green; // Stop the capture loop if active CancelInvoke(); }
Než se vrátíte do Unity, nezapomeňte změny uložit v sadě Visual Studio.
Kapitola 11 - Nastavení skriptů ve scéně
Teď, když jste napsali veškerý kód potřebný pro tento projekt, je čas nastavit skripty ve scéně a na předfaktorech, aby se tyto skripty chovaly správně.
V Editoru Unity na panelu hierarchie vyberte hlavní kameru.
V panelu inspektoru s vybranou hlavní kamerou klikněte na Přidat komponentu, vyhledejte skript SceneOrganiser a poklikáním ho přidejte.
Na panelu projektů otevřete složku Prefabs, přetáhněte prefab Popisek do prázdné cílové vstupní oblasti Popisek ve skriptu SceneOrganiser, který jste právě přidali do hlavní kamery, jak je znázorněno na následujícím obrázku:
Na panelu hierarchie vyberte podřízenou položku GazeCursor hlavní kamery.
V panelu inspektoru s vybranou možností GazeCursor klikněte na Přidat komponentu, vyhledejte skript GazeCursor a poklikáním ho přidejte.
Znovu na panelu hierarchie vyberte podřízenou položku SpatialMapping hlavní kamery.
Na panelu inspektoru s vybranou možností SpatialMapping klikněte na Přidat komponentu, vyhledejte skript SpatialMapping a poklikáním ho přidejte.
Zbývající skripty, které jste nenastavili, se během běhu přidají kódem ve skriptu SceneOrganiser .
Kapitola 12 - Před budovou
Pokud chcete provést důkladný test vaší aplikace, budete ji muset načíst na Microsoft HoloLens.
Než to uděláte, ujistěte se, že:
Všechna nastavení uvedená v kapitole 3 jsou správně nastavena.
Skript SceneOrganiser je připojen k objektu hlavní kamery .
Skript GazeCursor je připojen k objektu GazeCursor .
Skript SpatialMapping je připojen k objektu SpatialMapping .
V kapitole 5 krok 6:
- Nezapomeňte vložit klíč predikce služby do proměnné predictionKey.
- Koncový bod předpovědi jste vložili do třídy predictionEndpoint.
Kapitola 13 – Sestavení řešení pro UPW a zkušební načtení aplikace
Teď jste připraveni vytvořit aplikaci jako řešení pro UPW, které budete moct nasadit do Microsoft HoloLensu. Zahájení procesu sestavení:
Přejděte do nastavení sestavení souboru>.
Zaškrtněte projekty Unity C#.
Klikněte na Přidat otevřené scény. Tím se do sestavení přidá aktuálně otevřená scéna.
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.
Unity začne sestavovat projekt do složky Aplikace .
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).
Pokud chcete nasadit na Microsoft HoloLens, budete potřebovat IP adresu tohoto zařízení (pro vzdálené nasazení) a zajistit, aby byl nastavený také režim vývojáře. Akce:
Když nosíte HoloLens, otevřete Nastavení.
Přejít na možnosti Rozšířené možnosti sítě a internetu>Wi-Fi>
Poznamenejte si adresu IPv4 .
Pak přejděte zpět na Nastavení a pak přejděte na Aktualizovat a zabezpečení>pro vývojáře.
Nastavte režim vývojáře.
Přejděte do nového sestavení Unity ( složka aplikace ) a otevřete soubor řešení v sadě Visual Studio.
V konfiguraci řešení vyberte Ladit.
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ě microsoft HoloLens, který jste si poznamenali).
Přejděte do nabídky Sestavení a kliknutím na Nasadit řešení načtěte aplikaci na HoloLens.
Vaše aplikace by se teď měla zobrazit v seznamu nainstalovaných aplikací na vašem Microsoft HoloLensu, připravených ke spuštění!
Použití aplikace:
- Podívejte se na objekt, který jste natrénovali pomocí služby Azure Custom Vision Service, rozpoznávání objektů a použijte gesto klepnutí.
- Pokud se objekt úspěšně zjistí, zobrazí se text popisku s názvem značky.
Důležité
Pokaždé, když zachytíte fotku a odešlete ji do služby, můžete se vrátit na stránku Služby a znovu natrénovat službu pomocí nově zachycených obrázků. Na začátku budete pravděpodobně také muset opravit ohraničující rámečky , aby byly přesnější a znovu natrénovaly službu.
Poznámka:
Umístěný text popisku nemusí být blízko objektu, když senzory Microsoft HoloLens nebo SpatialTrackingComponent v Unity nenasadí příslušné kolidátory vzhledem k skutečným objektům světa. Pokud tomu tak je, zkuste aplikaci použít na jiném povrchu.
Vaše aplikace Custom Vision, Rozpoznávání objektů
Blahopřejeme, vytvořili jste aplikaci hybridní reality, která využívá rozhraní API služby Azure Custom Vision, rozhraní API pro rozpoznávání objektů, které dokáže rozpoznat objekt z obrázku, a pak poskytnout přibližnou pozici pro daný objekt v 3D prostoru.
Bonusová cvičení
Cvičení 1
Přidáním do textového popisku použijte poloprůhlednou datovou krychli k zabalení skutečného objektu do 3D ohraničujícího rámečku.
Cvičení 2
Natrénujte službu Custom Vision Service tak, aby rozpoznala více objektů.
Cvičení 3
Přehrajte zvuk, když je objekt rozpoznán.
Cvičení 4
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ě).