HoloLens (prima generazione) e Azure 310: rilevamento degli oggetti

Nota

Le esercitazioni di Mixed Reality Academy sono state progettate in base a HoloLens (prima generazione) e ai visori VR immersive di realtà mista. Pertanto, riteniamo importante lasciarle a disposizione degli sviluppatori a cui serve ancora materiale sussidiario per lo sviluppo di questi dispositivi. Queste esercitazioni non verranno aggiornate con i set di strumenti o le interazioni più recenti usati per HoloLens 2. Rimarranno invariate per consentire di continuare a lavorare sui dispositivi supportati. Ci sarà una nuova serie di esercitazioni che verranno pubblicate in futuro che dimostreranno come sviluppare per HoloLens 2. Questo avviso verrà aggiornato con un collegamento a tali esercitazioni quando vengono pubblicate.


In questo corso si apprenderà come riconoscere contenuto visivo personalizzato e la relativa posizione spaziale all'interno di un'immagine fornita, usando Azure Visione personalizzata funzionalità "Rilevamento oggetti" in un'applicazione di realtà mista.

Questo servizio consente di eseguire il training di un modello di Machine Learning usando immagini oggetto. Si userà quindi il modello sottoposto a training per riconoscere oggetti simili e approssimarne la posizione nel mondo reale, come fornito dall'acquisizione della fotocamera di Microsoft HoloLens o una fotocamera si connettono a un PC per visori VR immersive.

risultato del corso

Azure Visione personalizzata, Rilevamento oggetti è un servizio Microsoft che consente agli sviluppatori di creare classificatori di immagini personalizzati. Questi classificatori possono quindi essere usati con nuove immagini per rilevare gli oggetti all'interno di tale nuova immagine, fornendo limiti box all'interno dell'immagine stessa. Il servizio offre un portale online semplice e semplice da usare per semplificare questo processo. Per altre informazioni, visitare i collegamenti seguenti:

Al termine di questo corso, si avrà un'applicazione di realtà mista che sarà in grado di eseguire le operazioni seguenti:

  1. L'utente sarà in grado di esaminare un oggetto, sottoposto a training usando il servizio azure Visione personalizzata, rilevamento oggetti.
  2. L'utente userà il movimento Tap per acquisire un'immagine di ciò che stanno guardando.
  3. L'app invierà l'immagine al servizio azure Visione personalizzata.
  4. Verrà visualizzata una risposta dal servizio che visualizzerà il risultato del riconoscimento come testo dello spazio globale. Questa operazione verrà eseguita usando il rilevamento spaziale di Microsoft HoloLens, come modo per comprendere la posizione globale dell'oggetto riconosciuto e quindi usando il tag associato a ciò che viene rilevato nell'immagine, per fornire il testo dell'etichetta.

Il corso illustra anche il caricamento manuale di immagini, la creazione di tag e il training del servizio per riconoscere oggetti diversi (nell'esempio fornito, una tazza) impostando la casella limite all'interno dell'immagine che si invia.

Importante

Dopo la creazione e l'uso dell'app, lo sviluppatore deve tornare al servizio di Visione personalizzata di Azure e identificare le stime effettuate dal servizio e determinare se sono state corrette o meno (tramite l'assegnazione di tag a tutto il servizio perso e la modifica dei rettangoli di selezione). È quindi possibile ripetere il training del servizio, aumentando così la probabilità di riconoscere oggetti reali.

Questo corso illustra come ottenere i risultati dal servizio azure Visione personalizzata, rilevamento oggetti, in un'applicazione di esempio basata su Unity. Sarà necessario applicare questi concetti a un'applicazione personalizzata che si sta creando.

Supporto di dispositivi

Corso HoloLens Visori VR immersive
MR e Azure 310: rilevamento oggetti ✔️

Prerequisiti

Nota

Questa esercitazione è progettata per gli sviluppatori che hanno esperienza di base con Unity e C#. Tenere presente anche che i prerequisiti e le istruzioni scritte all'interno di questo documento rappresentano ciò che è stato testato e verificato al momento della scrittura (luglio 2018). Sei libero di usare il software più recente, come elencato all'interno dell'articolo installare gli strumenti , anche se non si deve presumere che le informazioni in questo corso corrisponderanno perfettamente a ciò che troverai nel software più recente rispetto a quello elencato di seguito.

Per questo corso è consigliabile usare l'hardware e il software seguenti:

Prima di iniziare

  1. Per evitare di riscontrare problemi durante la compilazione di questo progetto, è consigliabile creare il progetto menzionato in questa esercitazione in una cartella radice o quasi radice (i percorsi delle cartelle lunghe possono causare problemi in fase di compilazione).
  2. Configurare e testare HoloLens. Se hai bisogno di supporto per questo, visita l'articolo configurazione di HoloLens.
  3. È consigliabile eseguire la calibrazione e l'ottimizzazione dei sensori quando si inizia a sviluppare una nuova app HoloLens (a volte può essere utile per eseguire tali attività per ogni utente).

Per informazioni su Calibrazione, seguire questo collegamento all'articolo Calibrazione di HoloLens.

Per informazioni sull'ottimizzazione dei sensori, seguire questo collegamento all'articolo Ottimizzazione del sensore HoloLens.

Capitolo 1 - Portale di Visione personalizzata

Per usare il servizio azure Visione personalizzata, è necessario configurare un'istanza di tale servizio per renderla disponibile per l'applicazione.

  1. Passare alla pagina principale del servizio Visione personalizzata.

  2. Fare clic su Introduzione.

    Screenshot che evidenzia il pulsante Attività iniziali.

  3. Accedere al portale di Visione personalizzata.

    Screenshot che mostra il pulsante Accedi.

  4. Se non si ha già un account Azure, è necessario crearne uno. Se si segue questa esercitazione in una classe o in una situazione di laboratorio, chiedere all'insegnante o a uno dei prottori di assistenza per configurare il nuovo account.

  5. Una volta effettuato l'accesso per la prima volta, verrà visualizzato il pannello Condizioni per l'utilizzo del servizio . Fare clic sulla casella di controllo per accettare le condizioni. Quindi fare clic su Accetto.

    Screenshot che mostra il pannello Condizioni per il servizio.

  6. Dopo aver accettato le condizioni, si è ora nella sezione Progetti personali . Fare clic su Nuovo progetto.

    Screenshot che mostra dove selezionare Nuovo progetto.

  7. Verrà visualizzata una scheda sul lato destro, che richiederà di specificare alcuni campi per il progetto.

    1. Inserire un nome per il progetto

    2. Inserire una descrizione per il progetto (facoltativo)

    3. Scegliere un gruppo di risorse o crearne uno nuovo. Un gruppo di risorse consente di monitorare, controllare l'accesso, effettuare il provisioning e gestire la fatturazione per una raccolta di asset di Azure. È consigliabile mantenere tutti i servizi di Azure associati a un singolo progetto (ad esempio, questi corsi) in un gruppo di risorse comune.

      Screenshot che mostra dove aggiungere dettagli per il nuovo progetto.

    4. Impostare i tipi di progetto come Rilevamento oggetti (anteprima).Set the Project Types as Object Detection (preview).

  8. Al termine, fare clic su Crea progetto e si verrà reindirizzati alla pagina del progetto del servizio Visione personalizzata.

Capitolo 2 - Formazione del progetto Visione personalizzata

Una volta nel portale di Visione personalizzata, l'obiettivo principale consiste nel eseguire il training del progetto per riconoscere oggetti specifici nelle immagini.

Sono necessarie almeno quindici immagini (15) per ogni oggetto che si vuole riconoscere dall'applicazione. È possibile usare le immagini fornite con questo corso (una serie di tazze).

Per eseguire il training del progetto Visione personalizzata:

  1. Fare clic sul + pulsante accanto a Tag.

    Screenshot che mostra il pulsante + accanto a Tag.

  2. Aggiungere un nome per il tag che verrà usato per associare le immagini. In questo esempio si usano immagini di tazze per il riconoscimento, quindi è stato denominato il tag per questo, Cup. Al termine, fare clic su Salva .

    Screenshot che mostra dove aggiungere un nome per il tag.

  3. Si noterà che il tag è stato aggiunto (potrebbe essere necessario ricaricare la pagina per visualizzarla).

    Screenshot che mostra dove viene aggiunto il tag.

  4. Fare clic su Aggiungi immagini al centro della pagina.

    Screenshot che mostra dove aggiungere immagini.

  5. Fare clic su Sfoglia i file locali e passare alle immagini da caricare per un oggetto, con il minimo di quindici (15).

    Suggerimento

    È possibile selezionare più immagini alla volta per caricare.

    Screenshot che mostra le immagini che è possibile caricare.

  6. Premere Carica file dopo aver selezionato tutte le immagini con cui si vuole eseguire il training del progetto. I file inizieranno a caricare. Dopo aver confermato il caricamento, fare clic su Fine.

    Screenshot che mostra lo stato di avanzamento delle immagini caricate.

  7. A questo punto le immagini vengono caricate, ma non contrassegnate.

    Screenshot che mostra un'immagine senza tag.

  8. Per contrassegnare le immagini, usa il mouse. Quando si passa il puntatore del mouse sull'immagine, un'evidenziazione della selezione consente di disegnare automaticamente una selezione intorno all'oggetto. Se non è accurato, è possibile disegnare il proprio. Questa operazione viene eseguita tenendo premuto il clic sinistro del mouse e trascinando l'area di selezione per includere l'oggetto.

    Screenshot che mostra come contrassegnare un'immagine.

  9. Dopo la selezione dell'oggetto all'interno dell'immagine, verrà richiesto di specificare Aggiungi tag area. Selezionare il tag creato in precedenza ('Cup', nell'esempio precedente) oppure se si aggiungono altri tag, digitare e fare clic sul pulsante + (più).

    Screenshot che mostra il tag aggiunto all'immagine.

  10. Per contrassegnare l'immagine successiva, è possibile fare clic sulla freccia a destra del pannello oppure chiudere il pannello del tag (facendo clic sulla X nell'angolo superiore destro del pannello) e quindi fare clic sull'immagine successiva. Dopo aver pronto l'immagine successiva, ripetere la stessa procedura. Eseguire questa operazione per tutte le immagini caricate, fino a quando non vengono contrassegnate tutte.

    Nota

    È possibile selezionare diversi oggetti nella stessa immagine, ad esempio l'immagine seguente:

    Screenshot che mostra più oggetti in un'immagine.

  11. Dopo averle contrassegnate tutte, fare clic sul pulsante con tag , a sinistra della schermata, per visualizzare le immagini contrassegnate.

    Screenshot che evidenzia il pulsante Con tag.

  12. È ora possibile eseguire il training del servizio. Fare clic sul pulsante Train (Esegui training) e verrà avviata la prima iterazione di training.

    Screenshot che evidenzia il pulsante Esegui training.

    Screenshot che mostra la prima iterazione di training.

  13. Dopo la compilazione, sarà possibile visualizzare due pulsanti denominati Make default (Rendi predefinito ) e Prediction URL (URL previsione). Fare clic su Make default first (Imposta impostazione predefinita ) e quindi su Prediction URL (URL previsione).

    Screenshot che evidenzia il pulsante Rendi predefinito.

    Nota

    L'endpoint fornito da questo oggetto è impostato su qualsiasi iterazione sia stata contrassegnata come predefinita. Di conseguenza, se in un secondo momento si crea una nuova iterazione e la si aggiorna come impostazione predefinita, non sarà necessario modificare il codice.

  14. Dopo aver fatto clic sull'URL di stima, aprire Il Blocco note e copiare e incollare l'URL (detto anche prediction-endpoint) e la chiave di stima del servizio, in modo da poterlo recuperare quando necessario in un secondo momento nel codice.

    Screenshot che mostra l'endpoint di stima e la chiave di predizione.

Capitolo 3 - Configurare il progetto Unity

Di seguito è riportata una configurazione tipica per lo sviluppo con la realtà mista e, di conseguenza, è un modello valido per altri progetti.

  1. Aprire Unity e fare clic su Nuovo.

    Screenshot che evidenzia il pulsante Nuovo.

  2. Sarà ora necessario specificare un nome di progetto Unity. Inserisci CustomVisionObjDetection. Assicurarsi che il tipo di progetto sia impostato su 3D e impostare La posizione su una posizione appropriata (tenere presente che più vicino alle directory radice è preferibile). Fare quindi clic su Crea progetto.

    Screenshot che mostra i dettagli del progetto e la posizione in cui selezionare Crea progetto.

  3. Con Unity aperto, vale la pena verificare che l'editor di script predefinito sia impostato su Visual Studio. Passare a Modifica>preferenze e quindi dalla nuova finestra passare a Strumenti esterni. Modificare l'editor di script esterni in Visual Studio. Chiudere la finestra Preferenze .

    Screenshot che mostra dove modificare l'editor di script esterni in Visual Studio.

  4. Passare quindi a Impostazioni compilazione file > e passare alla piattaforma per piattaforma UWP (Universal Windows Platform), quindi fare clic sul pulsante Cambia piattaforma.

    Screenshot che evidenzia il pulsante Cambia piattaforma.

  5. Nella stessa finestra Delle impostazioni di compilazione verificare che siano impostate le impostazioni seguenti:

    1. Il dispositivo di destinazione è impostato su HoloLens

    2. Il tipo di compilazione è impostato su D3D

    3. L'SDK è impostato su Latest installed (Versione più recente installata)

    4. La versione di Visual Studio è impostata su Versione più recente installata

    5. Compilazione ed esecuzione è impostata su Computer locale

    6. Le impostazioni rimanenti, in Impostazioni di compilazione, devono essere lasciate come predefinite per il momento.

      Screenshot che mostra le opzioni di configurazione dell'impostazione di compilazione.

  6. Nella stessa finestra Build Settings (Impostazioni compilazione) fare clic sul pulsante Player Settings (Impostazioni lettore) per aprire il pannello correlato nello spazio in cui si trova il controllo .

  7. In questo pannello è necessario verificare alcune impostazioni:

    1. Nella scheda Altre impostazioni :

      1. La versione del runtime di scripting deve essere sperimentale (equivalente a .NET 4.6), che attiverà la necessità di riavviare l'editor.

      2. Il back-end di scripting deve essere .NET.

      3. Il livello di compatibilità api deve essere .NET 4.6.

        Screenshot che mostra l'opzione Livello di compatibilità API impostata su .NET 4.6.

    2. Nella scheda Impostazioni di pubblicazione, in Funzionalità selezionare:

      1. InternetClient

      2. Webcam

      3. SpatialPerception

        Screenshot che mostra la metà superiore delle opzioni di configurazione delle funzionalità.Screenshot che mostra la metà inferiore delle opzioni di configurazione delle funzionalità.

    3. Più avanti nel pannello, in Impostazioni XR (disponibili sotto Impostazioni di pubblicazione), selezionare Virtual Reality Supported (Realtà virtuale supportata), quindi assicurarsi che Windows Realtà mista SDK sia stato aggiunto.

      Screenshot che mostra che è stato aggiunto Windows Realtà mista SDK.

  8. Tornare in Impostazioni di compilazione, i progetti C# di Unity non sono più disattivati: selezionare la casella di controllo accanto a questa.

  9. Chiudi la finestra Build Settings (Impostazioni di compilazione).

  10. Nell'editor fare clic su Modifica>grafica impostazioni>progetto.

    Screenshot che mostra l'opzione di menu Grafica selezionata.

  11. Nel Pannello di controllo verranno aperte le impostazioni grafiche. Scorrere verso il basso fino a visualizzare una matrice denominata Includi sempre shader. Aggiungere uno slot aumentando la variabile Size di uno (in questo esempio era 8 in modo da renderlo 9). Verrà visualizzato un nuovo slot, nell'ultima posizione della matrice, come illustrato di seguito:

    Screenshot che evidenzia la matrice Always Included Shader.

  12. Nello slot fare clic sul piccolo cerchio di destinazione accanto allo slot per aprire un elenco di shader. Cercare lo shader legacy/Transparent/Diffuse shader e fare doppio clic su di esso.

    Screenshot che evidenzia gli shader legacy/Transparent/Diffuse shader.

Capitolo 4 - Importazione del pacchetto Unity CustomVisionObjDetection

Per questo corso viene fornito un pacchetto di asset unity denominato Azure-MR-310.unitypackage.

[SUGGERIMENTO] Tutti gli oggetti supportati da Unity, incluse le scene intere, possono essere inseriti in un file con estensione unitypackage e esportati/importati in altri progetti. È il modo più sicuro ed efficiente per spostare gli asset tra progetti Unity diversi.

È possibile trovare il pacchetto Azure-MR-310 che è necessario scaricare qui.

  1. Con il dashboard unity di fronte all'utente, fare clic su Asset nel menu nella parte superiore della schermata, quindi fare clic su Importa pacchetto> personalizzato.

    Screenshot che evidenzia l'opzione di menu Pacchetto personalizzato.

  2. Usare la selezione file per selezionare il pacchetto Azure-MR-310.unitypackage e fare clic su Apri. Verrà visualizzato un elenco di componenti per questo asset. Confermare l'importazione facendo clic sul pulsante Importa .

    Screenshot che mostra l'elenco dei componenti di asset da importare.

  3. Al termine dell'importazione, si noterà che le cartelle del pacchetto sono state aggiunte alla cartella Assets . Questo tipo di struttura di cartelle è tipico per un progetto Unity.

    Screenshot che mostra il contenuto della cartella Assets.

    1. La cartella Materials contiene il materiale utilizzato dal cursore dello sguardo fisso.

    2. La cartella Plugins contiene la DLL Newtonsoft usata dal codice per deserializzare la risposta Web del servizio. Le due versioni diverse (2) contenute nella cartella e nella sottocartella sono necessarie per consentire l'uso e la compilazione della libreria da parte dell'editor unity e della compilazione UWP.

    3. La cartella Prefab contiene i prefab contenuti nella scena. Essi sono:

      1. GazeCursor, cursore usato nell'applicazione. Collaborerà con il prefab SpatialMapping per poter essere posizionato nella scena sopra gli oggetti fisici.
      2. Label, ovvero l'oggetto dell'interfaccia utente usato per visualizzare il tag dell'oggetto nella scena quando necessario.
      3. SpatialMapping, ovvero l'oggetto che consente all'applicazione di creare una mappa virtuale, usando il rilevamento spaziale di Microsoft HoloLens.
    4. Cartella Scenes che contiene attualmente la scena predefinita per questo corso.

  4. Aprire la cartella Scene nel pannello del progetto e fare doppio clic su ObjDetectionScene per caricare la scena che verrà usata per questo corso.

    Screenshot che mostra ObjDetectionScene nella cartella Scenes.

    Nota

    Non è incluso alcun codice, si scriverà il codice seguendo questo corso.

Capitolo 5 - Creare la classe CustomVisionAnalyser.

A questo punto si è pronti per scrivere codice. Si inizierà con la classe CustomVisionAnalyser .

Nota

Le chiamate al servizio Visione personalizzata, effettuate nel codice riportato di seguito, vengono effettuate usando l'API REST Visione personalizzata. Tramite questa operazione, si vedrà come implementare e usare questa API (utile per comprendere come implementare qualcosa di simile in modo autonomo). Tenere presente che Microsoft offre un SDK Visione personalizzata che può essere usato anche per effettuare chiamate al servizio. Per altre informazioni, vedere l'articolo Visione personalizzata SDK.

Questa classe è responsabile di:

  • Caricamento dell'immagine più recente acquisita come matrice di byte.

  • Invio della matrice di byte all'istanza del servizio Visione personalizzata di Azure per l'analisi.

  • Ricezione della risposta come stringa JSON.

  • Deserializzazione della risposta e passaggio della stima risultante alla classe SceneOrganiser, che si occuperà della modalità di visualizzazione della risposta.

Per creare questa classe:

  1. Fare clic con il pulsante destro del mouse nella cartella asset, disponibile nel pannello del progetto, quindi scegliere Crea>cartella. Chiamare la cartella Scripts.

    Screenshot che mostra come creare la cartella Scripts.

  2. Fare doppio clic sulla cartella appena creata per aprirla.

  3. Fare clic con il pulsante destro del mouse all'interno della cartella, quindi scegliere Crea>script C#. Assegnare allo script il nome CustomVisionAnalyser.

  4. Fare doppio clic sul nuovo script CustomVisionAnalyser per aprirlo con Visual Studio.

  5. Assicurarsi di disporre degli spazi dei nomi seguenti a cui si fa riferimento nella parte superiore del file:

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  6. Nella classe CustomVisionAnalyser aggiungere le variabili seguenti:

        /// <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;
    

    Nota

    Assicurarsi di inserire la chiave di stima del servizio nella variabile predictionKey e nell'endpoint di previsione nella variabile predictionEndpoint . Questi elementi sono stati copiati in precedenza nel Blocco note, nel capitolo 2, passaggio 14.

  7. Il codice per Awake() deve ora essere aggiunto per inizializzare la variabile instance:

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Aggiungere la coroutine (con il metodo statico GetImageAsByteArray() sottostante, che otterrà i risultati dell'analisi dell'immagine, acquisita dalla classe ImageCapture.

    Nota

    Nella coroutine AnalyzeImageCapture è presente una chiamata alla classe SceneOrganiser ancora da creare. Pertanto, lasciare queste righe commentate per il momento.

        /// <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. Eliminare i metodi Start() e Update(), perché non verranno usati.

  10. Assicurarsi di salvare le modifiche in Visual Studio, prima di tornare a Unity.

Importante

Come accennato in precedenza, non preoccuparsi del codice che potrebbe sembrare di avere un errore, perché verranno fornite altre classi a breve, che correggeranno tali errori.

Capitolo 6 - Creare la classe CustomVisionObjects

La classe che verrà creata ora è la classe CustomVisionObjects .

Questo script contiene un numero di oggetti utilizzati da altre classi per serializzare e deserializzare le chiamate effettuate al servizio Visione personalizzata.

Per creare questa classe:

  1. Fare clic con il pulsante destro del mouse all'interno della cartella Script, quindi scegliere Crea>script C#. Chiamare lo script CustomVisionObjects.

  2. Fare doppio clic sul nuovo script CustomVisionObjects per aprirlo con Visual Studio.

  3. Assicurarsi di disporre degli spazi dei nomi seguenti a cui si fa riferimento nella parte superiore del file:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Eliminare i metodi Start() e Update() all'interno della classe CustomVisionObjects , questa classe dovrebbe ora essere vuota.

    Avviso

    È importante seguire attentamente le istruzioni successive. Se si inseriscono le nuove dichiarazioni di classe all'interno della classe CustomVisionObjects , si otterranno errori di compilazione nel capitolo 10, indicando che AnalysisRootObject e BoundingBox non vengono trovati.

  5. Aggiungere le classi seguenti all'esterno della classe CustomVisionObjects . Questi oggetti vengono usati dalla libreria Newtonsoft per serializzare e deserializzare i dati della risposta:

    // 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. Assicurarsi di salvare le modifiche in Visual Studio, prima di tornare a Unity.

Capitolo 7 - Creare la classe SpatialMapping

Questa classe imposta il collisore mapping spaziale nella scena in modo da poter rilevare conflitti tra oggetti virtuali e oggetti reali.

Per creare questa classe:

  1. Fare clic con il pulsante destro del mouse all'interno della cartella Script, quindi scegliere Crea>script C#. Chiamare lo script SpatialMapping.

  2. Fare doppio clic sul nuovo script SpatialMapping per aprirlo con Visual Studio.

  3. Assicurarsi di disporre degli spazi dei nomi seguenti a cui si fa riferimento sopra la classe SpatialMapping :

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. Aggiungere quindi le variabili seguenti all'interno della classe SpatialMapping, sopra il metodo Start():

        /// <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. Aggiungere Il metodo Awake() e Start():Add the Awake() and Start():

        /// <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. Eliminare il metodo Update().

  7. Assicurarsi di salvare le modifiche in Visual Studio, prima di tornare a Unity.

Capitolo 8 - Creare la classe GazeCursor

Questa classe è responsabile della configurazione del cursore nella posizione corretta nello spazio reale, usando SpatialMappingCollider, creato nel capitolo precedente.

Per creare questa classe:

  1. Fare clic con il pulsante destro del mouse all'interno della cartella Script, quindi scegliere Crea>script C#. Chiamare lo script GazeCursor

  2. Fare doppio clic sul nuovo script GazeCursor per aprirlo con Visual Studio.

  3. Assicurarsi di disporre dello spazio dei nomi seguente a cui si fa riferimento sopra la classe GazeCursor :

    using UnityEngine;
    
  4. Aggiungere quindi la variabile seguente all'interno della classe GazeCursor, sopra il metodo Start().

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Aggiornare il metodo Start() con il codice seguente:

        /// <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. Aggiornare il metodo Update() con il codice seguente:

        /// <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;
            }
        }
    

    Nota

    Non preoccuparti dell'errore per la classe SceneOrganiser non trovata, lo creerai nel capitolo successivo.

  7. Assicurarsi di salvare le modifiche in Visual Studio, prima di tornare a Unity.

Capitolo 9 - Creare la classe SceneOrganiser

Questa classe:

  • Configurare la fotocamera principale collegando i componenti appropriati.

  • Quando viene rilevato un oggetto, sarà responsabile del calcolo della posizione nel mondo reale e dell'inserimento di un'etichetta tag accanto al nome del tag appropriato.

Per creare questa classe:

  1. Fare clic con il pulsante destro del mouse all'interno della cartella Script, quindi scegliere Crea>script C#. Assegnare allo script il nome SceneOrganiser.

  2. Fare doppio clic sul nuovo script SceneOrganiser per aprirlo con Visual Studio.

  3. Assicurarsi di disporre degli spazi dei nomi seguenti a cui si fa riferimento sopra la classe SceneOrganiser :

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
  4. Aggiungere quindi le variabili seguenti all'interno della classe SceneOrganiser, sopra il metodo Start():

        /// <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. Eliminare i metodi Start() e Update().

  6. Sotto le variabili aggiungere il metodo Awake(), che inizializzerà la classe e configurerà la scena.

        /// <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. Aggiungere il metodo PlaceAnalysisLabel(), che crea un'istanza dell'etichetta nella scena ( che a questo punto è invisibile all'utente). Posiziona anche il quad (anche invisibile) in cui viene posizionata l'immagine e si sovrappone al mondo reale. Questo è importante perché le coordinate della casella recuperate dal servizio dopo l'analisi vengono tracciate in questo quad per determinare la posizione approssimativa dell'oggetto nel mondo reale.

        /// <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. Aggiungere il metodo FinaliseLabel(). È responsabile delle operazioni seguenti:

    • Impostazione del testo etichetta con il tag della stima con la massima attendibilità.
    • Chiamata al calcolo del rettangolo delimitatore sull'oggetto quad, posizionato in precedenza e inserendo l'etichetta nella scena.
    • Regolando la profondità dell'etichetta usando un Raycast verso il rettangolo delimitatore, che dovrebbe scontrarsi con l'oggetto nel mondo reale.
    • Reimpostazione del processo di acquisizione per consentire all'utente di acquisire un'altra immagine.
        /// <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. Aggiungere il metodo CalculateBoundingBoxPosition(), che ospita una serie di calcoli necessari per convertire le coordinate del rettangolo delimitatore recuperate dal servizio e ricrearle proporzionalmente sul quad.

        /// <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. Assicurarsi di salvare le modifiche in Visual Studio, prima di tornare a Unity.

    Importante

    Prima di continuare, aprire la classe CustomVisionAnalyser e all'interno del metodo AnalyzeLastImageCaptured() rimuovere il commento dalle righe seguenti:

    // 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);
    

Nota

Non preoccuparti del messaggio 'Impossibile trovare' la classe ImageCapture , la creerai nel capitolo successivo.

Capitolo 10 - Creare la classe ImageCapture

La classe successiva che si intende creare è la classe ImageCapture .

Questa classe è responsabile di:

  • Acquisizione di un'immagine usando la fotocamera HoloLens e archiviarla nella cartella App .
  • Gestione dei movimenti di tocco dell'utente.

Per creare questa classe:

  1. Passare alla cartella Scripts creata in precedenza.

  2. Fare clic con il pulsante destro del mouse all'interno della cartella, quindi scegliere Crea>script C#. Assegnare allo script il nome ImageCapture.

  3. Fare doppio clic sul nuovo script ImageCapture per aprirlo con Visual Studio.

  4. Sostituire gli spazi dei nomi nella parte superiore del file con quanto segue:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Aggiungere quindi le variabili seguenti all'interno della classe ImageCapture, sopra il metodo Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static ImageCapture Instance;
    
        /// <summary>
        /// Keep counts of the taps for image renaming
        /// </summary>
        private int captureCount = 0;
    
        /// <summary>
        /// Photo Capture object
        /// </summary>
        private PhotoCapture photoCaptureObject = null;
    
        /// <summary>
        /// Allows gestures recognition in HoloLens
        /// </summary>
        private GestureRecognizer recognizer;
    
        /// <summary>
        /// Flagging if the capture loop is running
        /// </summary>
        internal bool captureIsActive;
    
        /// <summary>
        /// File path of current analysed photo
        /// </summary>
        internal string filePath = string.Empty;
    
  6. È ora necessario aggiungere il codice per i metodi Awake() e Start():

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the Microsoft HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  7. Implementare un gestore che verrà chiamato quando si verifica un movimento tap:

        /// <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);
            }
        }
    

    Importante

    Quando il cursore è verde, significa che la fotocamera è disponibile per acquisire l'immagine. Quando il cursore è rosso, significa che la fotocamera è occupata.

  8. Aggiungere il metodo usato dall'applicazione per avviare il processo di acquisizione delle immagini e archiviare l'immagine:

        /// <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. Aggiungere i gestori che verranno chiamati quando la foto è stata acquisita e per quando è pronta per l'analisi. Il risultato viene quindi passato a CustomVisionAnalyser per l'analisi.

        /// <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. Assicurarsi di salvare le modifiche in Visual Studio, prima di tornare a Unity.

Capitolo 11 - Configurazione degli script nella scena

Ora che è stato scritto tutto il codice necessario per questo progetto, è il momento di configurare gli script nella scena e sui prefab, affinché si comportino correttamente.

  1. Nel pannello Gerarchia dell'editor di Unity selezionare la fotocamera principale.

  2. Nel Pannello di controllo, con la fotocamera principale selezionata, fare clic su Aggiungi componente, quindi cercare script SceneOrganiser e fare doppio clic su per aggiungerlo.

    Screenshot che mostra lo script SceneOrganizer.

  3. Nel pannello del progetto aprire la cartella Prefabs, trascinare il prefab Label nell'area di input della destinazione di riferimento vuota Label, nello script SceneOrganiser appena aggiunto alla fotocamera principale, come illustrato nell'immagine seguente:

    Screenshot che mostra lo script aggiunto alla fotocamera principale.

  4. Nel pannello Gerarchia selezionare l'elemento figlio GazeCursor della fotocamera principale.

  5. Nel pannello di controllo, con gazeCursor selezionato, fare clic su Aggiungi componente, quindi cercare lo script GazeCursor e fare doppio clic per aggiungerlo.

    Screenshot che mostra dove si aggiunge lo script GazeCursor.

  6. Anche in questo caso, nel pannello Gerarchia selezionare l'elemento figlio SpatialMapping della fotocamera principale.

  7. Nel Pannello di controllo, con spatialMapping selezionato, fare clic su Aggiungi componente, quindi cercare script SpatialMapping e fare doppio clic su per aggiungerlo.

    Screenshot che mostra dove aggiungere lo script SpatialMapping.

Gli script rimanenti non impostati verranno aggiunti dal codice nello script SceneOrganiser durante il runtime.

Capitolo 12 - Prima dell'edificio

Per eseguire un test approfondito dell'applicazione, è necessario trasferire localmente l'applicazione in Microsoft HoloLens.

Prima di procedere, assicurarsi che:

  • Tutte le impostazioni indicate nel capitolo 3 vengono impostate correttamente.

  • Lo script SceneOrganiser è collegato all'oggetto Main Camera .

  • Lo script GazeCursor è associato all'oggetto GazeCursor .

  • Lo script SpatialMapping è associato all'oggetto SpatialMapping .

  • Nel capitolo 5, passaggio 6:

    • Assicurarsi di inserire la chiave di stima del servizio nella variabile predictionKey .
    • L'endpoint di previsione è stato inserito nella classe predictionEndpoint.

Capitolo 13: Creare la soluzione UWP e trasferire localmente l'applicazione

A questo punto si è pronti per compilare l'applicazione come soluzione UWP in cui sarà possibile eseguire la distribuzione in Microsoft HoloLens. Per avviare il processo di compilazione:

  1. Passare a Impostazioni di compilazione file>.

  2. Selezionare Progetti C# Unity.

  3. Fare clic su Aggiungi scene aperte. Verrà aggiunta la scena attualmente aperta alla compilazione.

    Screenshot che evidenzia il pulsante Aggiungi scene aperte.

  4. Fare clic su Compila. Unity avvierà una finestra Esplora file, in cui è necessario creare e quindi selezionare una cartella in cui compilare l'app. Creare ora la cartella e denominarla App. Quindi, con la cartella App selezionata, fare clic su Seleziona cartella.

  5. Unity inizierà a compilare il progetto nella cartella App .

  6. Una volta completata la compilazione di Unity (potrebbe essere necessario del tempo), verrà aperta una finestra Esplora file nella posizione della compilazione (controllare la barra delle applicazioni, perché potrebbe non essere sempre visualizzata sopra le finestre, ma invierà una notifica dell'aggiunta di una nuova finestra).

  7. Per eseguire la distribuzione in Microsoft HoloLens, è necessario l'indirizzo IP del dispositivo (per distribuzione remota) e per assicurarsi che sia impostato anche la modalità sviluppatore. A questo scopo, è necessario:

    1. Mentre indossa HoloLens, apri le impostazioni.

    2. Vai a Rete e Internet>Wi-Fi>Opzioni avanzate

    3. Prendere nota dell'indirizzo IPv4 .

    4. Tornare quindi a Impostazioni e quindi a Aggiorna e sicurezza>per sviluppatori

    5. Impostare la modalità sviluppatore attivata.

  8. Passare alla nuova compilazione unity ( cartella App ) e aprire il file della soluzione con Visual Studio.

  9. Nella configurazione della soluzione selezionare Debug.

  10. Nella piattaforma della soluzione selezionare x86, Computer remoto. Verrà richiesto di inserire l'indirizzo IP di un dispositivo remoto (microsoft HoloLens, in questo caso, annotato).

    Screenshot che mostra dove inserire l'indirizzo IP.

  11. Passare al menu Compila e fare clic su Distribuisci soluzione per trasferire localmente l'applicazione in HoloLens.

  12. L'app dovrebbe ora essere visualizzata nell'elenco delle app installate in Microsoft HoloLens, pronte per l'avvio.

Per usare l'applicazione:

  • Esaminare un oggetto, sottoposto a training con il servizio Visione personalizzata di Azure, rilevamento oggetti e usare il movimento Tap.
  • Se l'oggetto viene rilevato correttamente, verrà visualizzato un testo etichetta dello spazio globale con il nome del tag.

Importante

Ogni volta che si acquisisce una foto e la si invia al servizio, è possibile tornare alla pagina Servizio e ripetere il training del servizio con le immagini appena acquisite. All'inizio, probabilmente sarà necessario correggere anche i rettangoli di selezione per essere più accurati e ripetere il training del servizio.

Nota

Il testo etichetta posizionato potrebbe non apparire vicino all'oggetto quando i sensori di Microsoft HoloLens e/o SpatialTrackingComponent in Unity non riescono a posizionare i collider appropriati, rispetto agli oggetti reali. Provare a usare l'applicazione su una superficie diversa, se questo è il caso.

Applicazione di rilevamento oggetti Visione personalizzata

È stata creata un'app di realtà mista che usa l'API Rilevamento oggetti di Azure Visione personalizzata, che può riconoscere un oggetto da un'immagine e quindi fornire una posizione approssimativa per tale oggetto nello spazio 3D.

Screenshot che mostra un'app di realtà mista che sfrutta l'API Rilevamento oggetti di Azure Visione personalizzata.

Esercizi aggiuntivi

Esercizio 1

L'aggiunta all'etichetta di testo usa un cubo semitrasparente per eseguire il wrapping dell'oggetto reale in un rettangolo delimitatore 3D.

Esercizio 2

Eseguire il training del servizio Visione personalizzata per riconoscere più oggetti.

Esercizio 3

Riprodurre un suono quando viene riconosciuto un oggetto.

Esercizio 4

Usare l'API per eseguire nuovamente il training del servizio con le stesse immagini che l'app sta analizzando, in modo da rendere il servizio più accurato (eseguire contemporaneamente stime e training).