Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Feljegyzés
A Mixed Reality Academy oktatóanyagait a HoloLens (1st gen) és a Mixed Reality modern headsetek szem előtt tartásával tervezték. Ezért fontosnak érezzük, hogy ezeket az oktatóanyagokat megőrizzük azoknak a fejlesztőknek, akik továbbra is útmutatást keresnek az eszközök fejlesztéséhez. Ezek az oktatóanyagok nem frissülnek a HoloLens 2-hez használt legújabb eszközkészletekkel vagy interakciókkal. A támogatott eszközökön továbbra is működni fognak. A jövőben egy új oktatóanyag-sorozat jelenik meg, amely bemutatja, hogyan fejleszthet a HoloLens 2-hez. Ez az értesítés a közzétételükkor az oktatóanyagokra mutató hivatkozással frissül.
Ebben a kurzusban megtanulhatja, hogyan ismerheti fel az egyéni vizualizációs tartalmakat és azok térbeli pozícióját egy megadott képen az Azure Custom Vision "Object Detection" képességeinek használatával egy vegyes valóságú alkalmazásban.
Ez a szolgáltatás lehetővé teszi egy gépi tanulási modell betanítása objektumképek használatával. Ezt követően a betanított modellel felismerheti a hasonló objektumokat, és megközelítheti a valós világban elfoglalt helyüket a Microsoft HoloLens kamerarögzítésével, vagy egy kamerával, amely a modern (VR) headsetekhez csatlakozik egy PC-hez.

Az Azure Custom Vision, Object Detection egy Microsoft-szolgáltatás, amely lehetővé teszi a fejlesztők számára egyéni képosztályozók létrehozását. Ezek az osztályozók ezután új rendszerképekkel is használhatók az új képen belüli objektumok észlelésére azáltal, hogy a dobozhatárokat a képen belül adhatja meg. A szolgáltatás egy egyszerű, könnyen használható online portált biztosít a folyamat gördülékenyebbé tételéhez. További információkért látogasson el a következő hivatkozásokra:
A tanfolyam elvégzése után egy vegyes valóság alkalmazással rendelkezik, amely a következőket fogja tudni elvégezni:
- A felhasználó megtekinthet egy objektumot, amelyet az Azure Custom Vision Service objektumészlelésével betanított.
- A felhasználó a Koppintás kézmozdulat segítségével rögzít egy képet arról, hogy mit néznek.
- Az alkalmazás elküldi a rendszerképet az Azure Custom Vision Service-nek.
- A szolgáltatás válasza megjeleníti a világűrbeli szövegként való felismerés eredményét. Ezt a Microsoft HoloLens térbeli nyomon követésével, a felismert objektum világhelyzetének megértéséhez, majd a képen észleltekhez társított címke használatával valósítjuk meg a címkeszöveg megadásához.
A tanfolyam magában foglalja a képek manuális feltöltését, a címkék létrehozását és a szolgáltatás betanítását a különböző objektumok felismerésére (a megadott példában egy csésze) a beküldött képen belül a Határ mező beállításával.
Fontos
Az alkalmazás létrehozását és használatát követően a fejlesztőnek vissza kell navigálnia az Azure Custom Vision Service-be, azonosítania kell a szolgáltatás által készített előrejelzéseket, és meg kell állapítania, hogy helyesek voltak-e vagy sem (a szolgáltatás által kihagyott adatok címkézésével és a határolókeretek módosításával). A szolgáltatás ezután újra betanításra kerül, ami növeli a valós objektumok felismerésének valószínűségét.
Ez a tanfolyam bemutatja, hogyan szerezheti be az eredményeket az Azure Custom Vision Service objektumészleléséből egy Unity-alapú mintaalkalmazásba. Önön múlik, hogy ezeket a fogalmakat alkalmazza-e egy egyéni alkalmazásra, amely esetleg létrejön.
Eszköztámogatás
| Tanfolyam | HoloLens | Modern fejhallgatók |
|---|---|---|
| MR és Azure 310: Objektumészlelés | ✔️ |
Előfeltételek
Feljegyzés
Ez az oktatóanyag olyan fejlesztőknek készült, akik alapszintű tapasztalattal rendelkeznek a Unity és a C# használatával kapcsolatban. Vegye figyelembe azt is, hogy a dokumentumban szereplő előfeltételek és írásos utasítások az íráskor (2018. július) tesztelt és ellenőrzött adatokat jelölik. Ön szabadon használhatja a legújabb szoftvert, ahogy az az eszközök telepítéséről szóló cikkben szerepel, bár nem szabad feltételezni, hogy az ebben a tanfolyamban szereplő információk tökéletesen megfelelnek az újabb szoftverekben találhatóaknak, mint az alább felsoroltak.
A tanfolyamhoz a következő hardvert és szoftvert javasoljuk:
- Fejlesztői számítógép
- Windows 10 Fall Creators Update (vagy újabb) fejlesztői mód engedélyezve
- A legújabb Windows 10 SDK
- Unity 2017.4 LTS
- Visual Studio 2017
- A Microsoft HoloLens engedélyezve van a fejlesztői móddal
- Internet-hozzáférés az Azure beállításához és a Custom Vision Service lekéréséhez
- Legalább tizenöt (15) képből álló sorozat szükséges minden olyan objektumhoz, amelyet a Custom Vision felismerni szeretne. Ha szeretné, akkor használhatja a képeket már biztosított ebben a kurzusban, egy sor csésze).
Előkészületek
- A projekt létrehozásával kapcsolatos problémák elkerülése érdekében javasoljuk, hogy az oktatóanyagban említett projektet gyökér- vagy gyökérmappában hozza létre (a hosszú mappa elérési útjai a buildeléskor problémákat okozhatnak).
- A HoloLens beállítása és tesztelése. Ha segítségre van szüksége ehhez, látogasson el a HoloLens beállítási cikkére.
- Érdemes a kalibrálást és az érzékelő finomhangolását elvégezni egy új HoloLens-alkalmazás fejlesztésének megkezdésekor (néha segíthet az egyes felhasználók számára elvégezni ezeket a feladatokat).
Ha segítségre van szüksége a kalibrációval kapcsolatban, kövesse ezt a hivatkozást a HoloLens-kalibrációs cikkre.
Ha segítségre van szüksége az érzékelőhangoláshoz, kövesse ezt a hivatkozást a HoloLens érzékelőhangolási cikkére.
1. fejezet – A Custom Vision Portál
Az Azure Custom Vision Service használatához konfigurálnia kell annak egy példányát, hogy elérhetővé legyen téve az alkalmazás számára.
Kattintson az Első lépések gombra.

Jelentkezzen be a Custom Vision Portálra.

Ha még nem rendelkezik Azure-fiókkal, létre kell hoznia egyet. Ha ezt az oktatóanyagot osztályterem- vagy laborhelyzetben követi, kérjen segítséget az oktatótól vagy az egyik proktortól az új fiók beállításához.
Ha első alkalommal jelentkezik be, a rendszer a Szolgáltatási feltételek panelen kéri. A feltételek elfogadásához kattintson a jelölőnégyzetre. Ezután kattintson az Elfogadom gombra.

Miután elfogadta a feltételeket, most a Saját projektek szakaszban van. Kattintson az Új projekt elemre.

A jobb oldalon megjelenik egy lap, amely arra kéri, hogy adjon meg néhány mezőt a projekthez.
A projekt nevének beszúrása
A projekt leírásának beszúrása (nem kötelező)
Válasszon egy erőforráscsoportot, vagy hozzon létre egy újat. Az erőforráscsoportok lehetővé teszi az Azure-objektumok gyűjteményének számlázásának monitorozását, vezérlését, kiépítését és kezelését. Javasoljuk, hogy az egyetlen projekthez (például ezekhez a kurzusokhoz) társított összes Azure-szolgáltatást egy közös erőforráscsoport alatt tartsa.

Feljegyzés
Ha többet szeretne megtudni az Azure-erőforráscsoportokról, keresse meg a társított Docsot
Állítsa be a projekttípusokat objektumészlelésként (előzetes verzió).
Ha végzett, kattintson a Projekt létrehozása elemre, és a rendszer átirányítja a Custom Vision Service projektoldalára.
2. fejezet – A Custom Vision-projekt betanítása
A Custom Vision Portálon az elsődleges cél az, hogy betanítsa a projektet a képek adott objektumainak felismerésére.
Legalább tizenöt (15) képre van szüksége minden olyan objektumhoz, amelyet az alkalmazás felismerni szeretne. A tanfolyamhoz mellékelt képeket (csészesorokat) használhatja.
A Custom Vision-projekt betanítása:
Kattintson a + Címkék melletti gombra.

Adjon nevet annak a címkének, amellyel társítani fogja a képeket. Ebben a példában csészék képeit használjuk a felismeréshez, ezért elneveztük a kupa címkéjét. Kattintson a Mentés gombra, ha végzett.

Látni fogja, hogy a címke hozzá lett adva (előfordulhat, hogy újra be kell töltenie a lapot ahhoz, hogy megjelenjen).

Kattintson a Kép hozzáadása elemre a lap közepén.

Kattintson a Helyi fájlok tallózása elemre, és keresse meg azokat a képeket, amellyel egy objektumot szeretne feltölteni, és a minimum tizenöt (15).
Tipp.
Egyszerre több képet is kiválaszthat a feltöltéshez.

Nyomja le a Upload files billentyűt, miután kiválasztotta az összes képet, amellyel be szeretné tanítani a projektet. A fájlok feltöltése megkezdődik. Miután megerősítette a feltöltést, kattintson a Kész gombra.

Ekkor a rendszer feltölti a képeket, de nincs címkézve.

A képek címkézéséhez használja az egeret. Amikor a kép fölé viszi az egérmutatót, a kijelölés kiemelése segít, ha automatikusan kijelöli az objektumot. Ha nem pontos, rajzolhat sajátot. Ehhez tartsa lenyomva a bal gombbal az egérgombot, és húzza a kijelölési régiót, hogy az magában foglalja az objektumot.

Miután kiválasztotta az objektumot a képen, egy kis üzenetben kérni fogja, hogy adja hozzá a régiócímkét. Válassza ki a korábban létrehozott címkét ('Cup', a fenti példában), vagy ha további címkéket ad hozzá, írja be, és kattintson a + (plusz) gombra.

A következő kép címkézéséhez kattintson a panel jobb oldalán található nyílra, vagy zárja be a címke panelt (a panel jobb felső sarkában lévő X ikonra kattintva), majd kattintson a következő képre. Ha elkészült a következő kép, ismételje meg ugyanezt az eljárást. Tegye ezt az összes feltöltött képhez, amíg meg nem címkézték őket.
Feljegyzés
Több objektumot is kijelölhet ugyanabban a képen, például az alábbi képen:

Miután megcímkézte őket, kattintson a címkézett gombra a képernyő bal oldalán a címkézett képek megjelenítéséhez.

Most már készen áll a szolgáltatás betanítása. Kattintson a Betanítás gombra, és megkezdődik az első betanítási iteráció.


Miután elkészült, két gombot láthat, az Alapértelmezett és az Előrejelzési URL-címet. Először kattintson az Alapértelmezett beállítás gombra, majd az Előrejelzés URL-címére.

Feljegyzés
Az ebből biztosított végpont az alapértelmezettként megjelölt iterációra van beállítva. Így ha később létrehoz egy új iterációt , és alapértelmezés szerint frissíti azt, akkor nem kell módosítania a kódot.
Miután az Előrejelzés URL-címre kattintott, nyissa meg a Jegyzettömbet, és másolja és illessze be az URL-címet (más néven a Prediction-Endpointt) és a szolgáltatás előrejelzési kulcsát, hogy később lekérhesse, amikor szüksége van rá a kódban.

3. fejezet – A Unity-projekt beállítása
Az alábbiak a vegyes valósággal való fejlesztésre jellemzően be vannak állítva, és mint ilyen, jó sablon más projektekhez.
Nyissa meg a Unityt, és kattintson az Új gombra.

Most meg kell adnia egy Unity-projektnevet. CustomVisionObjDetection beszúrása. Győződjön meg arról, hogy a projekt típusa 3D, és állítsa a Hely elemet az Ön számára megfelelő helyre (ne feledje, a gyökérkönyvtárakhoz közelebb jobb). Ezután kattintson a Projekt létrehozása elemre.

Ha a Unity nyitva van, érdemes ellenőrizni, hogy az alapértelmezett szkriptszerkesztő a Visual Studióra van-e állítva. Nyissa meg a Beállítások szerkesztése>lehetőséget, majd az új ablakban lépjen a Külső eszközök elemre. Külső szkriptszerkesztő módosítása Visual Studióra. Zárja be a Beállítások ablakot.

Ezután lépjen a Fájlkészítési > beállítások elemre, és váltson a platformra Univerzális Windows-platform, majd kattintson a Platformváltás gombra.

Ugyanabban a buildbeállítások ablakban győződjön meg arról, hogy a következők vannak beállítva:
A céleszköz holoLens értékre van állítva
A build típusa D3D értékre van állítva
Az SDK a Legújabb telepített értékre van állítva
A Visual Studio verziója a Legújabb verzióra van beállítva
A buildelés és a futtatás helyi gépre van állítva
A buildbeállításokban lévő többi beállításnak egyelőre alapértelmezettként kell maradnia.

Ugyanebben a BuildBeállítások ablakban kattintson a Lejátszó beállításai gombra, ezzel megnyitja a kapcsolódó panelt abban a térben, ahol az Ellenőr található.
Ebben a panelen ellenőrizni kell néhány beállítást:
Az Egyéb beállítások lapon:
A szkriptelési futtatókörnyezet verziójának kísérletinek (.NET 4.6 egyenértékűnek) kell lennie, ami elindítja a szerkesztő újraindításának szükségességét.
A szkriptelési háttérrendszernek .NET-nek kell lennie.
Az API kompatibilitási szintjének .NET 4.6-nak kell lennie.

A Közzétételi beállítások lapon, a Képességek területen ellenőrizze a következőt:
InternetClient
Webkamera
SpatialPerception


A panelen lejjebb, az XR beállításaiban (a Közzétételi beállítások alatt található) jelölje be a Virtual Reality támogatott jelölőnégyzetét, majd győződjön meg arról, hogy a Windows Mixed Reality SDK hozzá van adva.

A Buildbeállítások területen a Unity C#-projektek már nem szürkítve jelennek meg: jelölje be a mellette lévő jelölőnégyzetet.
Zárja be a Build Settings ablakot.
A Szerkesztőben kattintson a Projektbeállítások>grafikus szerkesztése elemre>.

A Felügyelő panelen megnyílik a grafikus beállítások. Görgessen lefelé, amíg meg nem jelenik az Always Include Shaders nevű tömb. Adjon hozzá egy pontot a Size változó egyesével való növelésével (ebben a példában 8 volt, ezért 9-et készítettünk). Egy új pont jelenik meg a tömb utolsó helyén, az alábbiak szerint:

A pontban kattintson a pont melletti kis célkörre az árnyékolók listájának megnyitásához. Keresse meg az örökölt árnyékolókat/átlátszó/diffúz árnyékolót, és kattintson rá duplán.

4. fejezet – A CustomVisionObjDetection Unity csomag importálása
Ebben a kurzusban egy Azure-MR-310.unitypackage nevű Unity Asset-csomagot kap.
[TIPP] A Unity által támogatott objektumok, beleértve a teljes jeleneteket is, egy .unitypackage fájlba csomagolhatók, és más projektekbe exportálhatók/ importálhatók. Ez a legbiztonságosabb és leghatékonyabb módszer az eszközök különböző Unity-projektek közötti áthelyezésére.
Itt találja az Azure-MR-310 csomagot, amelyet le kell töltenie.
Az Előtted lévő Unity irányítópulton kattintson a képernyő tetején lévő menüBen az Eszközök elemre, majd az Egyéni csomag >importálása parancsra.

A fájlválasztóval válassza ki az Azure-MR-310.unitypackage csomagot, és kattintson a Megnyitás gombra. Ekkor megjelenik az eszköz összetevőinek listája. Az Importálás gombra kattintva erősítse meg az importálást.

Miután befejezte az importálást, láthatja, hogy a csomagból származó mappák mostantól hozzáadva lettek az Eszközök mappához. Ez a fajta mappastruktúra a Unity-projektekre jellemző.

Az Anyagok mappa a Tekintet kurzor által használt anyagot tartalmazza.
A Beépülő modulok mappa tartalmazza a Szolgáltatás webes válaszának deszerializálásához használt kód által használt Newtonsoft DLL-t. A mappában és az almappában található két (2) különböző verzió szükséges ahhoz, hogy a unity szerkesztő és az UWP-build egyaránt használhassa és elkészíthesse a tárat.
Az Előfabs mappa tartalmazza a jelenetben található előfabsokat. Ezek a következők:
- A GazeCursor, az alkalmazásban használt kurzor. Együttműködik a SpatialMapping előfabával, hogy a fizikai objektumok fölé lehessen helyezni a jelenetet.
- A címke, amely az objektumcímke szükség esetén a jelenetben való megjelenítéséhez használt felhasználói felületi objektum.
- A SpatialMapping objektum, amely lehetővé teszi az alkalmazás számára, hogy virtuális térképet hozzon létre a Microsoft HoloLens térbeli nyomon követésével.
A kurzus előre elkészített jelenetét tartalmazó Jelenetek mappa.
Nyissa meg a Jelenetek mappát a Projekt panelen, és kattintson duplán az ObjDetectionScene elemre a kurzushoz használni kívánt jelenet betöltéséhez.

Feljegyzés
A program nem tartalmaz kódot, a tanfolyamot követve fogja írni a kódot.
5. fejezet – A CustomVisionAnalyser osztály létrehozása.
Ezen a ponton készen áll arra, hogy írjon néhány kódot. A CustomVisionAnalyser osztálysal fog kezdődni.
Feljegyzés
Az alább látható kódban létrehozott Custom Vision Service-hívások a Custom Vision REST API használatával jönnek létre. Ezen keresztül látni fogja, hogyan implementálhatja és használhatja ezt az API-t (hasznos lehet megérteni, hogyan implementálhat önmagában hasonlót). Vegye figyelembe, hogy a Microsoft egy Custom Vision SDK-t kínál, amely a szolgáltatás hívására is használható. További információkért látogasson el a Custom Vision SDK cikkére.
Ez az osztály felelős a következőért:
A legújabb, bájtok tömbjeként rögzített kép betöltése.
A bájttömb elküldése az Azure Custom Vision Service-példányra elemzés céljából.
A válasz fogadása JSON-sztringként.
A válasz deszerializálása és az eredményül kapott előrejelzés továbbítása a SceneOrganiser osztálynak, amely gondoskodik a válasz megjelenítéséről.
Az osztály létrehozása:
Kattintson a jobb gombbal a Projekt panelen található Eszköz mappára, majd kattintson a Mappa létrehozása parancsra>. Hívja meg a szkriptek mappát.

Kattintson duplán az újonnan létrehozott mappára a megnyitásához.
Kattintson a jobb gombbal a mappába, majd kattintson a C#-szkript létrehozása parancsra>. Nevezze el a CustomVisionAnalyser szkriptet .
Kattintson duplán az új CustomVisionAnalyser szkriptre a Visual Studióval való megnyitásához.
Győződjön meg arról, hogy a következő névterekre hivatkozik a fájl tetején:
using Newtonsoft.Json; using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking;A CustomVisionAnalyser osztályban adja hozzá a következő változókat:
/// <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;Feljegyzés
Győződjön meg arról, hogy beszúrja a Szolgáltatás előrejelzési kulcsát a predictionKey változóba és a Prediction-Endpointt a predictionEndpoint változóba. Ezeket a Jegyzettömbbe másolta a 2. fejezet 14. lépésében.
Az Awake() kódját most hozzá kell adni a példányváltozó inicializálásához:
/// <summary> /// Initializes this class /// </summary> private void Awake() { // Allows this instance to behave like a singleton Instance = this; }Adja hozzá a coroutint (az alatta lévő statikus GetImageAsByteArray() metódussal), amely az ImageCapture osztály által rögzített képelemzés eredményeit fogja megkapni.
Feljegyzés
Az AnalyseImageCapture coroutine-ban van egy hívás a SceneOrganiser osztályhoz, amelyet még létre kell hoznia. Ezért ezeket a sorokat egyelőre hagyja megjegyzésben.
/// <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); }Törölje a Start() és Update() metódusokat, mert azok nem lesznek használatban.
Mielőtt visszatér a Unitybe, mentse a módosításokat a Visual Studióban.
Fontos
Mint korábban említettük, ne aggódjon a kód miatt, amely úgy tűnhet, hogy hibát jelez, mivel hamarosan további osztályokat fog biztosítani, amelyek kijavítják ezeket.
6. fejezet – A CustomVisionObjects osztály létrehozása
A most létrehozott osztály a CustomVisionObjects osztály.
Ez a szkript számos objektumot tartalmaz, amelyeket más osztályok használnak a Custom Vision Service-nek indított hívások szerializálására és deszerializálására.
Az osztály létrehozása:
Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. Hívja meg a CustomVisionObjects szkriptet .
Kattintson duplán az új CustomVisionObjects-szkriptre a Visual Studióval való megnyitásához.
Győződjön meg arról, hogy a következő névterekre hivatkozik a fájl tetején:
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking;Törölje a Start() és Update() metódusokat a CustomVisionObjects osztályban, ennek az osztálynak most üresnek kell lennie.
Figyelmeztetés
Fontos, hogy gondosan kövesse a következő utasítást. Ha az új osztálydeklarációkat a CustomVisionObjects osztályba helyezi, fordítási hibákat fog kapni a 10. fejezetben, amely szerint az AnalysisRootObject és a BoundingBox nem található.
Adja hozzá a következő osztályokat a CustomVisionObjects osztályon kívül. Ezeket az objektumokat a Newtonsoft könyvtár használja a válaszadatok szerializálására és deszerializálására:
// 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; } }Mielőtt visszatér a Unitybe, mentse a módosításokat a Visual Studióban.
7. fejezet – A SpatialMapping osztály létrehozása
Ez az osztály beállítja a térbeli leképezési ütközőt a jelenetben, hogy képes legyen észlelni a virtuális objektumok és a valós objektumok közötti ütközéseket.
Az osztály létrehozása:
Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. Hívja meg a SpatialMapping szkriptet .
Kattintson duplán az új SpatialMapping szkriptre a Visual Studióval való megnyitásához.
Győződjön meg arról, hogy az alábbi névterekre hivatkozik a SpatialMapping osztály felett:
using UnityEngine; using UnityEngine.XR.WSA;Ezután adja hozzá a következő változókat a SpatialMapping osztályban a Start() metódus fölé:
/// <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;Adja hozzá az Awake() és a Start() et:
/// <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); }Törölje az Update() metódust.
Mielőtt visszatér a Unitybe, mentse a módosításokat a Visual Studióban.
8. fejezet – A GazeCursor osztály létrehozása
Ez az osztály felelős azért, hogy a kurzort a valós térben a megfelelő helyre állítsa be az előző fejezetben létrehozott SpatialMappingCollider használatával.
Az osztály létrehozása:
Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. A szkript gazeCursor meghívása
Kattintson duplán az új GazeCursor-szkriptre a Visual Studióval való megnyitásához.
Győződjön meg arról, hogy a GazeCursor osztály felett a következő névtérre hivatkozik:
using UnityEngine;Ezután adja hozzá a következő változót a GazeCursor osztályban a Start() metódus fölé.
/// <summary> /// The cursor (this object) mesh renderer /// </summary> private MeshRenderer meshRenderer;Frissítse a Start() metódust a következő kóddal:
/// <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); }Frissítse az Update() metódust a következő kóddal:
/// <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; } }Feljegyzés
Ne aggódjon amiatt, hogy a SceneOrganiser osztály nem található, a következő fejezetben fogja létrehozni.
Mielőtt visszatér a Unitybe, mentse a módosításokat a Visual Studióban.
9. fejezet – A SceneOrganiser osztály létrehozása
Ez az osztály a következő lesz:
Állítsa be a fő kamerát a megfelelő összetevők csatlakoztatásával.
Ha egy objektumot észlel, az lesz a feladata, hogy kiszámítsa a valós világban elfoglalt helyét, és egy címkecímkét helyezzen hozzá a megfelelő címkenévvel.
Az osztály létrehozása:
Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. Nevezze el a SceneOrganiser szkriptet.
Kattintson duplán az új SceneOrganiser szkriptre a Visual Studióval való megnyitásához.
Győződjön meg arról, hogy a SceneOrganiser osztály felett a következő névterekre hivatkozik:
using System.Collections.Generic; using System.Linq; using UnityEngine;Ezután adja hozzá a következő változókat a SceneOrganiser osztályban a Start() metódus fölé:
/// <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;Törölje a Start() és Update() metódusokat.
A változók alatt adja hozzá az Awake() metódust, amely inicializálja az osztályt, és beállítja a jelenetet.
/// <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>(); }Adja hozzá a PlaceAnalysisLabel() metódust, amely példányosítani fogja a címkét a jelenetben (amely ezen a ponton láthatatlan a felhasználó számára). Azt is elhelyezi a quad (szintén láthatatlan), ahol a kép kerül, és átfedésben van a valós világ. Ez azért fontos, mert az elemzés után a szolgáltatásból lekért dobozkoordinátákat visszakövetjük ebbe a quadba, hogy meghatározzuk az objektum hozzávetőleges helyét a valós világban.
/// <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; }Adja hozzá a FinaliseLabel() metódust. Felelős a következőért:
- A Címke szövegének beállítása az előrejelzés címkéjével a legnagyobb megbízhatósággal.
- Hívja meg a határolókeret számítását a quad objektumon, korábban elhelyezve, és helyezze a címkét a jelenetbe.
- A címkemélység beállítása a Határolókeret felé irányuló Raycast használatával, amelynek ütköznie kell a valós világban lévő objektummal.
- Állítsa alaphelyzetbe a rögzítési folyamatot, hogy lehetővé tegye a felhasználó számára egy másik rendszerkép rögzítését.
/// <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(); }Adja hozzá a CalculateBoundingBoxPosition() metódust, amely a Szolgáltatásból lekért Bounding Box koordináták lefordításához és a quadon arányosan történő újbóli létrehozásához szükséges számításokat üzemelteti.
/// <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); }Mielőtt visszatér a Unitybe, mentse a módosításokat a Visual Studióban.
Fontos
A folytatás előtt nyissa meg a CustomVisionAnalyser osztályt, és az AnalyseLastImageCaptured() metóduson belül bontsa ki a következő sorokat:
// 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);
Feljegyzés
Ne aggódjon az ImageCapture osztály "nem található" üzenete miatt, a következő fejezetben fogja létrehozni.
10. fejezet – Az ImageCapture osztály létrehozása
A következő létrehozandó osztály az ImageCapture osztály.
Ez az osztály felelős a következőért:
- Kép rögzítése a HoloLens kamerával, és tárolása az Alkalmazás mappában.
- Koppintás kézmozdulatainak kezelése a felhasználótól.
Az osztály létrehozása:
Nyissa meg a korábban létrehozott Szkriptek mappát.
Kattintson a jobb gombbal a mappába, majd kattintson a C#-szkript létrehozása parancsra>. Nevezze el az ImageCapture szkriptet.
Kattintson duplán az új ImageCapture-szkriptre a Visual Studióval való megnyitásához.
Cserélje le a fájl tetején lévő névtereket a következőre:
using System; using System.IO; using System.Linq; using UnityEngine; using UnityEngine.XR.WSA.Input; using UnityEngine.XR.WSA.WebCam;Ezután adja hozzá a következő változókat az ImageCapture osztályban a Start() metódus fölé:
/// <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;Az Awake() és a Start() metódusok kódját most hozzá kell adni:
/// <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(); }Implementáljon egy kezelőt, amely akkor lesz meghívva, amikor koppintásos kézmozdulat történik:
/// <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); } }Fontos
Ha a kurzor zöld, az azt jelenti, hogy a kamera elérhető a kép készítéséhez. Ha a kurzor piros, az azt jelenti, hogy a kamera foglalt.
Adja hozzá azt a módszert, amelyet az alkalmazás a képrögzítési folyamat elindításához és a kép tárolásához használ:
/// <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); }); }); }Adja hozzá azokat a kezelőket, amelyek akkor lesznek meghívva, amikor a fényképet rögzítették, és hogy mikor lesz készen az elemzésre. Az eredmény ezután a CustomVisionAnalysernek lesz átadva elemzés céljából.
/// <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(); }Mielőtt visszatér a Unitybe, mentse a módosításokat a Visual Studióban.
11. fejezet – A szkriptek beállítása a jelenetben
Most, hogy megírta a projekthez szükséges összes kódot, itt az ideje, hogy beállítsa a szkripteket a jelenetben és az előfabson, hogy helyesen viselkedjenek.
A Unity-szerkesztőben, a Hierarchia panelen válassza ki a fő kamerát.
A Felügyelő panelen a fő kamera kiválasztásával kattintson az Összetevő hozzáadása elemre, majd keresse meg a SceneOrganiser szkriptet, és kattintson duplán a felvételhez.

A Projekt panelen nyissa meg az Előfabs mappát, húzza a Címke előfabétot a Címke üres referencia cél beviteli területére az imént hozzáadott SceneOrganiser szkriptben, ahogyan az alábbi képen látható:

A Hierarchia panelen válassza ki a fő kamera GazeCursor gyermekét.
A Felügyelő panelen a GazeCursor elem kiválasztásával kattintson az Összetevő hozzáadása elemre, majd keresse meg a GazeCursor szkriptet, majd kattintson duplán a hozzáadáshoz.

A Hierarchia panelen válassza ki a fő kamera SpatialMapping gyermekét.
A Felügyelő panelen a SpatialMapping elem kiválasztásával kattintson az Összetevő hozzáadása elemre, majd keresse meg a SpatialMapping szkriptet, majd kattintson rá duplán a hozzáadáshoz.

A fennmaradó, nem beállított szkripteket a SceneOrganiser szkriptben lévő kód fogja hozzáadni futásidőben.
12. fejezet – Építés előtt
Az alkalmazás alapos teszteléséhez közvetlenül a Microsoft HoloLensre kell helyeznie.
Előtte győződjön meg arról, hogy:
A 3. fejezetben említett összes beállítás helyesen van beállítva.
A SceneOrganiser szkript a Fő kamera objektumhoz van csatolva.
A GazeCursor szkript a GazeCursor objektumhoz van csatolva.
A SpatialMapping szkript a SpatialMapping objektumhoz van csatolva.
Az 5. fejezet 6. lépése:
- Győződjön meg arról, hogy beszúrja a szolgáltatás-előrejelzési kulcsot a predictionKey változóba.
- Beszúrta az előrejelzési végpontot a predictionEndpoint osztályba.
13. fejezet – Az UWP-megoldás létrehozása és az alkalmazás oldalbetöltése
Most már készen áll arra, hogy UWP-megoldásként hozza létre az alkalmazást, amelyen üzembe helyezheti a Microsoft HoloLensben. A buildelési folyamat megkezdéséhez:
Nyissa meg a Fájl > összeállítási beállításait.
Jelölje be a Unity C#-projekteket.
Kattintson a Nyitott jelenetek hozzáadása elemre. Ezzel hozzáadja a jelenleg megnyitott jelenetet a buildhez.

Kattintson a Build gombra. A Unity elindít egy Fájlkezelő ablakot, amelyben létre kell hoznia, majd ki kell választania egy mappát az alkalmazás létrehozásához. Hozza létre ezt a mappát, és nevezze el alkalmazásnak. Ezután az alkalmazásmappa kijelölése után kattintson a Mappa kijelölése gombra.
A Unity megkezdi a projekt összeállítását az Alkalmazás mappába.
Ha a Unity befejezte az építkezést (ez eltarthat egy ideig), megnyit egy Fájlkezelő ablakot a build helyén (ellenőrizze a tálcát, mert előfordulhat, hogy nem mindig jelenik meg az ablakok felett, de értesítést küld az új ablak hozzáadásáról).
A Microsoft HoloLensben való üzembe helyezéshez szüksége lesz az eszköz IP-címére (a távoli üzembe helyezéshez), és győződjön meg arról, hogy fejlesztői mód is be van állítva. Megvalósítás:
A HoloLens viselése közben nyissa meg a Beállítások elemet.
Ugrás a Hálózat és internet>wi-Fi>speciális beállításaira
Jegyezze fel az IPv4-címet .
Ezután lépjen vissza a Beállítások, majd az Update &Security for Developers (Frissítés és biztonság>fejlesztőknek) lapra
A fejlesztői mód be van kapcsolva.
Lépjen az új Unity-buildre (az Alkalmazás mappára), és nyissa meg a megoldásfájlt a Visual Studióval.
A Megoldáskonfigurációban válassza a Hibakeresés lehetőséget.
A megoldásplatformon válassza az x86, Távoli gép lehetőséget. A rendszer kérni fogja, hogy szúrja be egy távoli eszköz (ebben az esetben a Microsoft HoloLens) IP-címét .

Nyissa meg a Build menüt, és kattintson a Megoldás üzembe helyezése elemre az alkalmazás HoloLensbe való közvetlen betöltéséhez.
Az alkalmazásnak most már szerepelnie kell a Microsoft HoloLens telepített alkalmazásainak listájában, készen áll az indításra!
Az alkalmazás használata:
- Tekintse meg az Azure Custom Vision Service-ben betanított objektumot, az Objektumészlelést, és használja a Koppintás kézmozdulatot.
- Ha az objektumot sikeresen észleli, egy világűrbeli címkeszöveg jelenik meg a címke nevével.
Fontos
Minden alkalommal, amikor rögzít egy fényképet, és elküldi a Szolgáltatásnak, visszatérhet a Szolgáltatás lapra, és újrataníthatja a szolgáltatást az újonnan rögzített képekkel. Az elején valószínűleg a határolókereteket is ki kell javítania, hogy pontosabb legyen, és újratanítást kapjon a szolgáltatás.
Feljegyzés
Előfordulhat, hogy az elhelyezett feliratszöveg nem jelenik meg az objektum közelében, ha a Microsoft HoloLens érzékelői és/vagy a Unity SpatialTrackingComponent nem tudja elhelyezni a megfelelő ütközőket a valós objektumokhoz képest. Ha ez a helyzet, próbálja meg más felületen használni az alkalmazást.
A Custom Vision objektumészlelési alkalmazása
Gratulálunk, egy vegyes valósági alkalmazást készített, amely az Azure Custom Vision, Object Detection API használatával képes felismerni egy objektumot egy képből, majd hozzávetőleges pozíciót biztosít az objektumnak a 3D térben.

Soron kívüli gyakorlatok
1. gyakorlat
A szövegcímkéhez hozzáadva egy félig átlátszó kockával tördelje be a valódi objektumot egy 3D határolókeretbe.
2. gyakorlat
A Custom Vision Service betanítása további objektumok felismerésére.
3. gyakorlat
Hang lejátszása egy objektum felismerésekor.
4. gyakorlat
Az API-val újra betaníthatja a szolgáltatást ugyanazokkal a képekkel, amelyet az alkalmazás elemez, így pontosabbá teheti a szolgáltatást (egyszerre végezhet előrejelzést és betanítást is).