HoloLens (1. generációs) és Azure 310: Objektumészlelés

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.

tanfolyam eredménye

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:

  1. A felhasználó megtekinthet egy objektumot, amelyet az Azure Custom Vision Service objektumészlelésével betanított.
  2. A felhasználó a Koppintás kézmozdulat segítségével rögzít egy képet arról, hogy mit néznek.
  3. Az alkalmazás elküldi a rendszerképet az Azure Custom Vision Service-nek.
  4. 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:

Előkészületek

  1. 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).
  2. 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.
  3. É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.

  1. Lépjen a Custom Vision Service főoldalára.

  2. Kattintson az Első lépések gombra.

    Az Első lépések gombot kiemelő képernyőkép.

  3. Jelentkezzen be a Custom Vision Portálra.

    Képernyőkép a Bejelentkezés gombról.

  4. 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.

  5. 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.

    Képernyőkép a Szolgáltatási feltételek panelről.

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

    Képernyőkép az Új projekt lehetőség kiválasztásáról.

  7. A jobb oldalon megjelenik egy lap, amely arra kéri, hogy adjon meg néhány mezőt a projekthez.

    1. A projekt nevének beszúrása

    2. A projekt leírásának beszúrása (nem kötelező)

    3. 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.

      Képernyőkép az új projekt részleteinek hozzáadásáról.

      Feljegyzés

      Ha többet szeretne megtudni az Azure-erőforráscsoportokról, keresse meg a társított Docsot

    4. Állítsa be a projekttípusokat objektumészlelésként (előzetes verzió).

  8. 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:

  1. Kattintson a + Címkék melletti gombra.

    A Címkék melletti + gombot ábrázoló képernyőkép.

  2. 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.

    Képernyőkép a címke nevének hozzáadásáról.

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

    A címke hozzáadásának helyét bemutató képernyőkép.

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

    Képernyőkép a képek hozzáadásának helyét ábrázoló képernyőképről.

  5. 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.

    Képernyőkép a feltölthető képekről.

  6. 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.

    Képernyőkép a feltöltött képek állapotáról.

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

    Egy nem megjelölt képet ábrázoló képernyőkép.

  8. 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.

    Kép címkézését bemutató képernyőkép.

  9. 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.

    Képernyőkép a képhez hozzáadott címkéről.

  10. 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:

    Képernyőkép egy kép több objektumát ábrázoló képről.

  11. 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.

    A Címkézett gombot kiemelő képernyőkép.

  12. 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ó.

    Képernyőkép a Betanítása gombról.

    Képernyőkép az első betanítási iterációról.

  13. 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.

    Az Alapértelmezett létrehozása gombot kiemelő képernyőkép.

    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.

  14. 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.

    Képernyőkép az előrejelzési végpontról és az előfeltételkulcsról.

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.

  1. Nyissa meg a Unityt, és kattintson az Új gombra.

    Az Új gombot kiemelő képernyőkép.

  2. 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.

    Képernyőkép a projekt részleteiről és a Projekt létrehozása lehetőség kiválasztásáról.

  3. 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.

    Képernyőkép a Külső szkriptszerkesztő Visual Studióra való módosításáról.

  4. 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.

    A Platformváltás gombot kiemelő képernyőkép.

  5. Ugyanabban a buildbeállítások ablakban győződjön meg arról, hogy a következők vannak beállítva:

    1. A céleszköz holoLens értékre van állítva

    2. A build típusa D3D értékre van állítva

    3. Az SDK a Legújabb telepített értékre van állítva

    4. A Visual Studio verziója a Legújabb verzióra van beállítva

    5. A buildelés és a futtatás helyi gépre van állítva

    6. A buildbeállításokban lévő többi beállításnak egyelőre alapértelmezettként kell maradnia.

      Képernyőkép a Buildbeállítás konfigurációs beállításairól.

  6. 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ó.

  7. Ebben a panelen ellenőrizni kell néhány beállítást:

    1. Az Egyéb beállítások lapon:

      1. 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.

      2. A szkriptelési háttérrendszernek .NET-nek kell lennie.

      3. Az API kompatibilitási szintjének .NET 4.6-nak kell lennie.

        Képernyőkép a .NET 4.6-ra beállított API-kompatibilitási szint beállításról.

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

      1. InternetClient

      2. Webkamera

      3. SpatialPerception

        Képernyőkép a Képességek konfigurációs beállításainak felső feléről.Képernyőkép a Capabilities konfigurációs beállításainak alsó feléről.

    3. 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.

      Képernyőkép a Windows Mixed Reality SDK hozzáadásáról.

  8. 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.

  9. Zárja be a Build Settings ablakot.

  10. A Szerkesztőben kattintson a Projektbeállítások>grafikus szerkesztése elemre>.

    Képernyőkép a Kijelölt Grafikus menü beállításról.

  11. 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:

    Képernyőkép az Always Included Shaders tömbről.

  12. 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.

    Az örökölt árnyékolókat/átlátszó/diffúz árnyékolót kiemelő képernyőkép.

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.

  1. 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.

    Az Egyéni csomag menüt kiemelő képernyőkép.

  2. 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.

    Képernyőkép az importálni kívánt eszközösszetevők listájáról.

  3. 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ő.

    Képernyőkép az Eszközök mappa tartalmáról.

    1. Az Anyagok mappa a Tekintet kurzor által használt anyagot tartalmazza.

    2. 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.

    3. Az Előfabs mappa tartalmazza a jelenetben található előfabsokat. Ezek a következők:

      1. 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.
      2. 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.
      3. 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.
    4. A kurzus előre elkészített jelenetét tartalmazó Jelenetek mappa.

  4. 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.

    Képernyőkép a Jelenetek mappában található ObjDetectionScene fájlról.

    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:

  1. 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.

    Képernyőkép a Szkriptek mappa létrehozásáról.

  2. Kattintson duplán az újonnan létrehozott mappára a megnyitásához.

  3. Kattintson a jobb gombbal a mappába, majd kattintson a C#-szkript létrehozása parancsra>. Nevezze el a CustomVisionAnalyser szkriptet .

  4. Kattintson duplán az új CustomVisionAnalyser szkriptre a Visual Studióval való megnyitásához.

  5. 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;
    
  6. 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.

  7. 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;
        }
    
  8. 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);
        }
    
  9. Törölje a Start() és Update() metódusokat, mert azok nem lesznek használatban.

  10. 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:

  1. Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. Hívja meg a CustomVisionObjects szkriptet .

  2. Kattintson duplán az új CustomVisionObjects-szkriptre a Visual Studióval való megnyitásához.

  3. 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;
    
  4. 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ó.

  5. 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; }
    }
    
  6. 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:

  1. Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. Hívja meg a SpatialMapping szkriptet .

  2. Kattintson duplán az új SpatialMapping szkriptre a Visual Studióval való megnyitásához.

  3. 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;
    
  4. 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;
    
  5. 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);
        }
    
  6. Törölje az Update() metódust.

  7. 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:

  1. Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. A szkript gazeCursor meghívása

  2. Kattintson duplán az új GazeCursor-szkriptre a Visual Studióval való megnyitásához.

  3. Győződjön meg arról, hogy a GazeCursor osztály felett a következő névtérre hivatkozik:

    using UnityEngine;
    
  4. 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;
    
  5. 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);
        }
    
  6. 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.

  7. 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:

  1. Kattintson a jobb gombbal a Szkriptek mappába, majd kattintson a C#-szkript létrehozása parancsra>. Nevezze el a SceneOrganiser szkriptet.

  2. Kattintson duplán az új SceneOrganiser szkriptre a Visual Studióval való megnyitásához.

  3. 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;
    
  4. 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;
    
  5. Törölje a Start() és Update() metódusokat.

  6. 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>();
        }
    
  7. 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;
        }
    
  8. 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();        
        }
    
  9. 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);
        }
    
  10. 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:

  1. Nyissa meg a korábban létrehozott Szkriptek mappát.

  2. Kattintson a jobb gombbal a mappába, majd kattintson a C#-szkript létrehozása parancsra>. Nevezze el az ImageCapture szkriptet.

  3. Kattintson duplán az új ImageCapture-szkriptre a Visual Studióval való megnyitásához.

  4. 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;
    
  5. 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;
    
  6. 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();
        }
    
  7. 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.

  8. 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);              
                });
            });
        }
    
  9. 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();
        }
    
  10. 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.

  1. A Unity-szerkesztőben, a Hierarchia panelen válassza ki a fő kamerát.

  2. 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.

    Képernyőkép a SceneOrganizer szkriptről.

  3. 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ó:

    Képernyőkép a fő kamerához hozzáadott szkriptről.

  4. A Hierarchia panelen válassza ki a fő kamera GazeCursor gyermekét.

  5. 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 GazeCursor szkript hozzáadásának helyét bemutató képernyőkép.

  6. A Hierarchia panelen válassza ki a fő kamera SpatialMapping gyermekét.

  7. 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.

    Képernyőkép a SpatialMapping szkript hozzáadásának helyével.

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:

  1. Nyissa meg a Fájl > összeállítási beállításait.

  2. Jelölje be a Unity C#-projekteket.

  3. Kattintson a Nyitott jelenetek hozzáadása elemre. Ezzel hozzáadja a jelenleg megnyitott jelenetet a buildhez.

    Képernyőkép a Nyitott jelenetek hozzáadása gombról.

  4. 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.

  5. A Unity megkezdi a projekt összeállítását az Alkalmazás mappába.

  6. 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).

  7. 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:

    1. A HoloLens viselése közben nyissa meg a Beállítások elemet.

    2. Ugrás a Hálózat és internet>wi-Fi>speciális beállításaira

    3. Jegyezze fel az IPv4-címet .

    4. 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

    5. A fejlesztői mód be van kapcsolva.

  8. Lépjen az új Unity-buildre (az Alkalmazás mappára), és nyissa meg a megoldásfájlt a Visual Studióval.

  9. A Megoldáskonfigurációban válassza a Hibakeresés lehetőséget.

  10. 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 .

    Képernyőkép az IP-cím beszúrásának helyével.

  11. 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.

  12. 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.

Az Azure Custom Vision, Object Detection API-t használó vegyes valóság alkalmazás képernyőképe.

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).