Condividi tramite


HoloLens (prima generazione) e Azure 302b: Visione personalizzata


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 all'interno di un'immagine fornita, usando le funzionalità di Azure Visione personalizzata 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, come fornito dall'acquisizione della fotocamera di Microsoft HoloLens o da una fotocamera connessa al PC per visori VR immersive.

risultato del corso

Azure Visione personalizzata è un servizio cognitivo Microsoft che consente agli sviluppatori di creare classificatori di immagini personalizzati. Questi classificatori possono quindi essere usati con nuove immagini per riconoscere o classificare oggetti all'interno di tale nuova immagine. Il servizio offre un portale online semplice e semplice da usare per semplificare il processo. Per altre informazioni, visitare la pagina del servizio azure Visione personalizzata.

Al termine di questo corso, si avrà un'applicazione di realtà mista che sarà in grado di funzionare in due modalità:

  • Modalità di analisi: configurazione manuale del servizio Visione personalizzata caricando immagini, creando tag ed eseguendo il training del servizio per riconoscere oggetti diversi (in questo caso mouse e tastiera). Si creerà quindi un'app HoloLens che acquisirà immagini usando la fotocamera e tenterà di riconoscere tali oggetti nel mondo reale.

  • Modalità di training: implementerai il codice che abiliterà una "Modalità training" nell'app. La modalità di training consentirà di acquisire immagini usando la fotocamera di HoloLens, caricare le immagini acquisite nel servizio ed eseguire il training del modello di visione personalizzata.

Questo corso illustra come ottenere i risultati dal servizio Visione personalizzata 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 302b: Visione personalizzata ✔️ ✔️

Nota

Anche se questo corso è incentrato principalmente su HoloLens, puoi anche applicare ciò che impari in questo corso ai visori VR immersive di Windows Realtà mista. Poiché i visori VR immersive non hanno fotocamere accessibili, è necessaria una fotocamera esterna connessa al PC. Mentre segui il corso, vedrai le note su eventuali modifiche che potresti dover usare per supportare visori VR immersive.

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 configurare HoloLens, assicurati di visitare 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 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 dei servizi di Visione personalizzata

Per usare il servizio Visione personalizzata in Azure, è necessario configurare un'istanza del servizio da rendere disponibile per l'applicazione.

  1. Passare prima alla pagina principale del servizio Visione personalizzata.

  2. Fare clic sul pulsante Inizia .

    Introduzione al servizio Visione personalizzata

  3. Accedere al portale del servizio di Visione personalizzata.

    Accedi al portale

    Nota

    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.

  4. 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 clicca su Accetto.

    Condizioni d'uso

  5. Dopo aver accettato le Condizioni, si passerà alla sezione Progetti del portale. Fare clic su Nuovo progetto.

    Creare un nuovo progetto

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

    4. Impostare i tipi di progetto su Classificazione

    5. Impostare i domini su Generale.

      Impostare i domini

      Per altre informazioni sui gruppi di risorse di Azure, vedere l'articolo relativo al gruppo di risorse.

  7. Al termine, fare clic su Crea progetto, si verrà reindirizzati alla pagina del progetto Visione personalizzata Servizio.

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 cinque (5) immagini, anche se si preferisce dieci (10), per ogni oggetto che si vuole riconoscere l'applicazione. È possibile usare le immagini fornite con questo corso (un mouse del computer e una tastiera).

Per eseguire il training del progetto di servizio Visione personalizzata:

  1. Fare clic sul + pulsante accanto a Tag.

    Aggiungere un nuovo tag

  2. Aggiungere il nome dell'oggetto da riconoscere. Fare clic su Salva.

    Aggiungere il nome dell'oggetto e salvare

  3. Si noterà che il tag è stato aggiunto (potrebbe essere necessario ricaricare la pagina per visualizzarla). Fare clic sulla casella di controllo insieme al nuovo tag, se non è già selezionata.

    Abilitare un nuovo tag

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

    Aggiunta di immagini

  5. Fare clic su Sfoglia file locali e cercare, quindi selezionare le immagini da caricare, con il minimo cinque (5). Tenere presente che tutte queste immagini devono contenere l'oggetto di cui si esegue il training.

    Nota

    È possibile selezionare più immagini alla volta per caricare.

  6. Dopo aver visualizzato le immagini nella scheda, selezionare il tag appropriato nella casella Tag personali.

    Selezionare i tag

  7. Fare clic su Carica file. I file inizieranno a caricare. Dopo aver confermato il caricamento, fare clic su Fine.

    Caricare file

  8. Ripetere lo stesso processo per creare un nuovo tag denominato tastiera e caricare le foto appropriate. Assicurarsi di deselezionare Mouse dopo aver creato i nuovi tag, in modo da visualizzare la finestra Aggiungi immagini.

  9. Dopo aver configurato entrambi i tag, fare clic su Esegui training e la prima iterazione di training inizierà a compilare.

    Abilitare l'iterazione di training

  10. Una volta compilata, 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).

    Impostare l'URL predefinito e di stima

    Nota

    L'URL dell'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.

  11. Dopo aver fatto clic su Url previsione, aprire Blocco note e copiare e incollare l'URL e la chiave di stima, in modo da poterlo recuperare quando necessario più avanti nel codice.

    Copiare e incollare l'URL e la chiave di stima

  12. Fare clic sull'ingranaggio in alto a destra nella schermata.

    Fare clic sull'icona a forma di ingranaggio per aprire le impostazioni

  13. Copiare il tasto di training e incollarlo in un Blocco note, per usarlo in un secondo momento.

    Copiare la chiave di training

  14. Copiare anche l'ID progetto e incollarlo nel file del Blocco note per usarlo in un secondo momento.

    Copiare l'ID progetto

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.

    Creare un nuovo progetto Unity

  2. Sarà ora necessario specificare un nome di progetto Unity. Inserire AzureCustomVision. Assicurarsi che il modello di progetto sia impostato su 3D. Impostare La posizione su un punto appropriato per l'utente (tenere presente che più vicino alle directory radice è preferibile). Fare quindi clic su Crea progetto.

    Configurare le impostazioni del 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 2017. Chiudere la finestra Preferenze .

    Configurare gli strumenti esterni

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

    Configurare le impostazioni di compilazione

  5. Mentre ancora in Impostazioni di compilazione file > e assicurarsi che:

    1. Il dispositivo di destinazione è impostato su HoloLens

      Per i visori VR immersive, impostare Dispositivo di destinazione su Qualsiasi dispositivo.

    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. Salvare la scena e aggiungerla alla compilazione.

      1. A tale scopo, selezionare Aggiungi scene aperte. Verrà visualizzata una finestra di salvataggio.

        Aggiungere una scena aperta all'elenco di compilazioni

      2. Creare una nuova cartella per questa cartella e qualsiasi scena futura, quindi selezionare il pulsante Nuova cartella per creare una nuova cartella, denominarla Scene.

        Creare una nuova cartella della scena

      3. Aprire la cartella Scenes appena creata e quindi nel campo Nome file: testo digitare CustomVisionScene, quindi fare clic su Salva.

        Assegnare un nome al nuovo file della scena

        Tenere presente che è necessario salvare le scene di Unity all'interno della cartella Assets , perché devono essere associate al progetto Unity. La creazione della cartella scene (e altre cartelle simili) è un modo tipico di strutturare un progetto Unity.

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

      Impostazioni di compilazione predefinite

  6. Nella finestra Impostazioni di compilazione fare clic sul pulsante 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

      Impostare la compatibilità dell'API

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

      1. InternetClient

      2. Webcam

      3. Microfono

      Configurare le impostazioni di pubblicazione

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

    Configurare le impostazioni XR

  8. Tornare in Build Settings Unity C# Projects (Impostazioni di compilazione) I progetti C# non sono più disattivati. Selezionare la casella di controllo accanto a questa opzione.

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

  10. Salvare la scena e il progetto (FILE > SAVE SCENE/FILE > SAVE PROJECT).

Capitolo 4 - Importazione della DLL Newtonsoft in Unity

Importante

Se si vuole ignorare il componente Configurazione unity di questo corso e continuare direttamente nel codice, è possibile scaricare questo pacchetto Azure-MR-302b.unitypackage, importarlo nel progetto come pacchetto personalizzato e quindi continuare dal capitolo 6.

Questo corso richiede l'uso della libreria Newtonsoft , che è possibile aggiungere come DLL agli asset. Il pacchetto contenente questa libreria può essere scaricato da questo collegamento. Per importare la libreria Newtonsoft nel progetto, usare il pacchetto Unity fornito con questo corso.

  1. Aggiungere .unitypackage a Unity usando l'opzione di menu Asset>Importapacchetto>personalizzato pacchetto.

  2. Nella casella Importa pacchetto Unity visualizzata verificare che tutti gli elementi in (e inclusi) Plug-in siano selezionati.

    Importare tutti gli elementi del pacchetto

  3. Fare clic sul pulsante Importa per aggiungere gli elementi al progetto.

  4. Passare alla cartella Newtonsoft in Plug-in nella visualizzazione del progetto e selezionare il plug-in Newtonsoft.Json.

    Selezionare il plug-in Newtonsoft

  5. Con il plug-in Newtonsoft.Json selezionato, assicurarsi che Qualsiasi piattaforma sia deselezionata, quindi assicurarsi che anche WSAPlayer sia deselezionato, quindi fare clic su Applica. Questo è solo per confermare che i file sono configurati correttamente.

    Configurare il plug-in Newtonsoft

    Nota

    Contrassegnando questi plug-in, questi plug-in vengono configurati in modo che vengano usati solo nell'editor di Unity. Nella cartella WSA è presente un set diverso che verrà usato dopo l'esportazione del progetto da Unity.

  6. Successivamente, è necessario aprire la cartella WSA , all'interno della cartella Newtonsoft . Verrà visualizzata una copia dello stesso file appena configurato. Selezionare il file e quindi nel controllo verificare che

    • Qualsiasi piattaforma è deselezionata
    • viene controllato solo WSAPlayer
    • Il processo dont viene controllato

    Configurare le impostazioni della piattaforma del plug-in Newtonsoft

Capitolo 5 - Configurazione della fotocamera

  1. Nel pannello Gerarchia selezionare la fotocamera principale.

  2. Dopo aver selezionato, sarà possibile visualizzare tutti i componenti della fotocamera principale nel pannello di controllo.

    1. L'oggetto fotocamera deve essere denominato Main Camera (si noti l'ortografia!)

    2. Il tag della fotocamera principale deve essere impostato su MainCamera (si noti l'ortografia!)

    3. Assicurarsi che la posizione di trasformazione sia impostata su 0, 0, 0

    4. Impostare Clear Flags (Cancella flag) su Solid Color (ignorare questo valore per il visore VR immersivo).

    5. Impostare il colore di sfondo del componente fotocamera su Nero, Alfa 0 (Codice esadecimale: #00000000) (ignorarlo per il visore VR immersivo).

    Configurare le proprietà del componente Fotocamera

Capitolo 6: 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 del servizio Visione personalizzata che può essere usato anche per effettuare chiamate al servizio. Per altre informazioni, vedere l'articolo Visione personalizzata Service 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 nel pannello del progetto, quindi scegliere Crea > cartella. Chiamare la cartella Scripts.

    Creare la cartella degli script

  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. Aggiornare gli spazi dei nomi nella parte superiore del file in modo che corrispondano al seguente:

    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    using Newtonsoft.Json;
    
  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>
        /// Byte array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Nota

    Assicurarsi di inserire la chiave di stima nella variabile predictionKey e nell'endpoint di previsione nella variabile predictionEndpoint. Queste sono state copiate in Blocco note in precedenza nel corso.

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

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Eliminare i metodi Start() e Update().

  9. Aggiungere quindi 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)
        {
            WWWForm webForm = new WWWForm();
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                // The response will be in JSON format, therefore it needs to be deserialized    
    
                // The following lines refers to a class that you will build in later Chapters
                // Wait until then to uncomment these lines
    
                //AnalysisObject analysisObject = new AnalysisObject();
                //analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
                //SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  10. Assicurarsi di salvare le modifiche in Visual Studio prima di tornare a Unity.

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

Avviso

È importante prendere nota dell'endpoint fornito dal servizio Visione personalizzata, perché la struttura JSON seguente è stata configurata per funzionare con Visione personalizzata Prediction v2.0. Se si dispone di una versione diversa, potrebbe essere necessario aggiornare la struttura seguente.

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. Aggiungere gli spazi dei nomi seguenti all'inizio 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.

  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
    /// </summary> 
    [Serializable]
    public class AnalysisObject
    {
        public List<Prediction> Predictions { get; set; }
    }
    
    [Serializable]
    public class Prediction
    {
        public string TagName { get; set; }
        public double Probability { get; set; }
    }
    

Capitolo 8 - Creare la classe VoiceRecognizer

Questa classe riconoscerà l'input vocale dell'utente.

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

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

  3. Aggiungere gli spazi dei nomi seguenti sopra la classe VoiceRecognizer :

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

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static VoiceRecognizer Instance;
    
        /// <summary>
        /// Recognizer class for voice recognition
        /// </summary>
        internal KeywordRecognizer keywordRecognizer;
    
        /// <summary>
        /// List of Keywords registered
        /// </summary>
        private Dictionary<string, Action> _keywords = new Dictionary<string, Action>();
    
  5. Aggiungere i metodi Awake() e Start(), quest'ultimo dei quali configurerà le parole chiave utente da riconoscere quando si associa un tag a un'immagine:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start ()
        {
    
            Array tagsArray = Enum.GetValues(typeof(CustomVisionTrainer.Tags));
    
            foreach (object tagWord in tagsArray)
            {
                _keywords.Add(tagWord.ToString(), () =>
                {
                    // When a word is recognized, the following line will be called
                    CustomVisionTrainer.Instance.VerifyTag(tagWord.ToString());
                });
            }
    
            _keywords.Add("Discard", () =>
            {
                // When a word is recognized, the following line will be called
                // The user does not want to submit the image
                // therefore ignore and discard the process
                ImageCapture.Instance.ResetImageCapture();
                keywordRecognizer.Stop();
            });
    
            //Create the keyword recognizer 
            keywordRecognizer = new KeywordRecognizer(_keywords.Keys.ToArray());
    
            // Register for the OnPhraseRecognized event 
            keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
        }
    
  6. Eliminare il metodo Update().

  7. Aggiungere il gestore seguente, che viene chiamato ogni volta che viene riconosciuto l'input vocale:

        /// <summary>
        /// Handler called when a word is recognized
        /// </summary>
        private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
        {
            Action keywordAction;
            // if the keyword recognized is in our dictionary, call that Action.
            if (_keywords.TryGetValue(args.text, out keywordAction))
            {
                keywordAction.Invoke();
            }
        }
    
  8. Assicurarsi di salvare le modifiche in Visual Studio prima di tornare a Unity.

Nota

Non preoccuparsi del codice che potrebbe sembrare di avere un errore, perché si fornirà presto altre classi, che risolveranno questi problemi.

Capitolo 9 - Creare la classe CustomVisionTrainer

Questa classe concatena una serie di chiamate Web per eseguire il training del servizio Visione personalizzata. Ogni chiamata verrà illustrata in dettaglio proprio sopra il codice.

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

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

  3. Aggiungere gli spazi dei nomi seguenti sopra la classe CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Aggiungere quindi le variabili seguenti all'interno della classe CustomVisionTrainer, sopra il metodo Start().

    Nota

    L'URL di training usato qui è disponibile all'interno della documentazione di Visione personalizzata Training 1.2 e ha una struttura di:https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Per altre informazioni, vedere l'API di riferimento Visione personalizzata Training v1.2.

    Avviso

    È importante prendere nota dell'endpoint fornito dal servizio Visione personalizzata per la modalità di training, perché la struttura JSON usata (all'interno della classe CustomVisionObjects) è stata configurata per funzionare con Visione personalizzata Training v1.2. Se si dispone di una versione diversa, potrebbe essere necessario aggiornare la struttura Objects .

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static CustomVisionTrainer Instance;
    
        /// <summary>
        /// Custom Vision Service URL root
        /// </summary>
        private string url = "https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/";
    
        /// <summary>
        /// Insert your prediction key here
        /// </summary>
        private string trainingKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your Project Id here
        /// </summary>
        private string projectId = "- Insert your Project Id here -";
    
        /// <summary>
        /// Byte array of the image to submit for analysis
        /// </summary>
        internal byte[] imageBytes;
    
        /// <summary>
        /// The Tags accepted
        /// </summary>
        internal enum Tags {Mouse, Keyboard}
    
        /// <summary>
        /// The UI displaying the training Chapters
        /// </summary>
        private TextMesh trainingUI_TextMesh;
    

    Importante

    Assicurarsi di aggiungere il valore chiave di servizio (chiave di training) e il valore id progetto annotato in precedenza. Questi sono i valori raccolti dal portale in precedenza nel corso (capitolo 2, passaggio 10 e versioni successive).

  5. Aggiungere i metodi Start() e Awake() seguenti. Questi metodi vengono chiamati all'inizializzazione e contengono la chiamata per configurare l'interfaccia utente:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        private void Start()
        { 
            trainingUI_TextMesh = SceneOrganiser.Instance.CreateTrainingUI("TrainingUI", 0.04f, 0, 4, false);
        }
    
  6. Eliminare il metodo Update(). Questa classe non sarà necessaria.

  7. Aggiungere il metodo RequestTagSelection(). Questo metodo è il primo a essere chiamato quando un'immagine è stata acquisita e archiviata nel dispositivo ed è ora pronta per essere inviata al servizio Visione personalizzata, per eseguirne il training. Questo metodo visualizza, nell'interfaccia utente di training, un set di parole chiave che l'utente può usare per contrassegnare l'immagine acquisita. Avvisa anche la classe VoiceRecognizer per iniziare ad ascoltare l'utente per l'input vocale.

        internal void RequestTagSelection()
        {
            trainingUI_TextMesh.gameObject.SetActive(true);
            trainingUI_TextMesh.text = $" \nUse voice command \nto choose between the following tags: \nMouse\nKeyboard \nor say Discard";
    
            VoiceRecognizer.Instance.keywordRecognizer.Start();
        }
    
  8. Aggiungere il metodo VerifyTag(). Questo metodo riceverà l'input vocale riconosciuto dalla classe VoiceRecognizer e ne verificherà la validità e quindi inizierà il processo di training.

        /// <summary>
        /// Verify voice input against stored tags.
        /// If positive, it will begin the Service training process.
        /// </summary>
        internal void VerifyTag(string spokenTag)
        {
            if (spokenTag == Tags.Mouse.ToString() || spokenTag == Tags.Keyboard.ToString())
            {
                trainingUI_TextMesh.text = $"Tag chosen: {spokenTag}";
                VoiceRecognizer.Instance.keywordRecognizer.Stop();
                StartCoroutine(SubmitImageForTraining(ImageCapture.Instance.filePath, spokenTag));
            }
        }
    
  9. Aggiungere il metodo SubmitImageForTraining(). Questo metodo inizierà il processo di training del servizio Visione personalizzata. Il primo passaggio consiste nel recuperare l'ID tag dal servizio associato all'input vocale convalidato dall'utente. L'ID tag verrà quindi caricato insieme all'immagine.

        /// <summary>
        /// Call the Custom Vision Service to submit the image.
        /// </summary>
        public IEnumerator SubmitImageForTraining(string imagePath, string tag)
        {
            yield return new WaitForSeconds(2);
            trainingUI_TextMesh.text = $"Submitting Image \nwith tag: {tag} \nto Custom Vision Service";
            string imageId = string.Empty;
            string tagId = string.Empty;
    
            // Retrieving the Tag Id relative to the voice input
            string getTagIdEndpoint = string.Format("{0}{1}/tags", url, projectId);
            using (UnityWebRequest www = UnityWebRequest.Get(getTagIdEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
    
                Tags_RootObject tagRootObject = JsonConvert.DeserializeObject<Tags_RootObject>(jsonResponse);
    
                foreach (TagOfProject tOP in tagRootObject.Tags)
                {
                    if (tOP.Name == tag)
                    {
                        tagId = tOP.Id;
                    }             
                }
            }
    
            // Creating the image object to send for training
            List<IMultipartFormSection> multipartList = new List<IMultipartFormSection>();
            MultipartObject multipartObject = new MultipartObject();
            multipartObject.contentType = "application/octet-stream";
            multipartObject.fileName = "";
            multipartObject.sectionData = GetImageAsByteArray(imagePath);
            multipartList.Add(multipartObject);
    
            string createImageFromDataEndpoint = string.Format("{0}{1}/images?tagIds={2}", url, projectId, tagId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(createImageFromDataEndpoint, multipartList))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);           
    
                //unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                www.SetRequestHeader("Training-Key", trainingKey);
    
                // The upload handler will help uploading the byte array with the request
                www.uploadHandler = new UploadHandlerRaw(imageBytes);
    
                // The download handler will help receiving the analysis from Azure
                www.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                ImageRootObject m = JsonConvert.DeserializeObject<ImageRootObject>(jsonResponse);
                imageId = m.Images[0].Image.Id;
            }
            trainingUI_TextMesh.text = "Image uploaded";
            StartCoroutine(TrainCustomVisionProject());
        }
    
  10. Aggiungere il metodo TrainCustomVisionProject(). Dopo che l'immagine è stata inviata e contrassegnata, questo metodo verrà chiamato. Verrà creata una nuova iterazione che verrà sottoposta a training con tutte le immagini precedenti inviate al servizio più l'immagine appena caricata. Al termine del training, questo metodo chiamerà un metodo per impostare l'iterazione appena creata come Default, in modo che l'endpoint usato per l'analisi sia l'iterazione con training più recente.

        /// <summary>
        /// Call the Custom Vision Service to train the Service.
        /// It will generate a new Iteration in the Service
        /// </summary>
        public IEnumerator TrainCustomVisionProject()
        {
            yield return new WaitForSeconds(2);
    
            trainingUI_TextMesh.text = "Training Custom Vision Service";
    
            WWWForm webForm = new WWWForm();
    
            string trainProjectEndpoint = string.Format("{0}{1}/train", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(trainProjectEndpoint, webForm))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
                Debug.Log($"Training - JSON Response: {jsonResponse}");
    
                // A new iteration that has just been created and trained
                Iteration iteration = new Iteration();
                iteration = JsonConvert.DeserializeObject<Iteration>(jsonResponse);
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Custom Vision Trained";
    
                    // Since the Service has a limited number of iterations available,
                    // we need to set the last trained iteration as default
                    // and delete all the iterations you dont need anymore
                    StartCoroutine(SetDefaultIteration(iteration)); 
                }
            }
        }
    
  11. Aggiungere il metodo SetDefaultIteration(). Questo metodo imposta l'iterazione creata e sottoposta a training in precedenza come Default. Al termine, questo metodo dovrà eliminare l'iterazione precedente esistente nel servizio. Al momento della stesura di questo corso, esiste un limite massimo di dieci (10) iterazioni consentite contemporaneamente nel servizio.

        /// <summary>
        /// Set the newly created iteration as Default
        /// </summary>
        private IEnumerator SetDefaultIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
            trainingUI_TextMesh.text = "Setting default iteration";
    
            // Set the last trained iteration to default
            iteration.IsDefault = true;
    
            // Convert the iteration object as JSON
            string iterationAsJson = JsonConvert.SerializeObject(iteration);
            byte[] bytes = Encoding.UTF8.GetBytes(iterationAsJson);
    
            string setDefaultIterationEndpoint = string.Format("{0}{1}/iterations/{2}", 
                                                            url, projectId, iteration.Id);
    
            using (UnityWebRequest www = UnityWebRequest.Put(setDefaultIterationEndpoint, bytes))
            {
                www.method = "PATCH";
                www.SetRequestHeader("Training-Key", trainingKey);
                www.SetRequestHeader("Content-Type", "application/json");
                www.downloadHandler = new DownloadHandlerBuffer();
    
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Default iteration is set \nDeleting Unused Iteration";
                    StartCoroutine(DeletePreviousIteration(iteration));
                }
            }
        }
    
  12. Aggiungere il metodo DeletePreviousIteration(). Questo metodo troverà ed eliminerà l'iterazione non predefinita precedente:

        /// <summary>
        /// Delete the previous non-default iteration.
        /// </summary>
        public IEnumerator DeletePreviousIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
    
            trainingUI_TextMesh.text = "Deleting Unused \nIteration";
    
            string iterationToDeleteId = string.Empty;
    
            string findAllIterationsEndpoint = string.Format("{0}{1}/iterations", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Get(findAllIterationsEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                // The iteration that has just been trained
                List<Iteration> iterationsList = new List<Iteration>();
                iterationsList = JsonConvert.DeserializeObject<List<Iteration>>(jsonResponse);
    
                foreach (Iteration i in iterationsList)
                {
                    if (i.IsDefault != true)
                    {
                        Debug.Log($"Cleaning - Deleting iteration: {i.Name}, {i.Id}");
                        iterationToDeleteId = i.Id;
                        break;
                    }
                }
            }
    
            string deleteEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iterationToDeleteId);
    
            using (UnityWebRequest www2 = UnityWebRequest.Delete(deleteEndpoint))
            {
                www2.SetRequestHeader("Training-Key", trainingKey);
                www2.downloadHandler = new DownloadHandlerBuffer();
                yield return www2.SendWebRequest();
                string jsonResponse = www2.downloadHandler.text;
    
                trainingUI_TextMesh.text = "Iteration Deleted";
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "Ready for next \ncapture";
    
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "";
                ImageCapture.Instance.ResetImageCapture();
            }
        }
    
  13. L'ultimo metodo da aggiungere in questa classe è il metodo GetImageAsByteArray(), usato nelle chiamate Web per convertire l'immagine acquisita in una matrice di byte.

        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
            BinaryReader binaryReader = new BinaryReader(fileStream);
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  14. Assicurarsi di salvare le modifiche in Visual Studio prima di tornare a Unity.

Capitolo 10 - Creare la classe SceneOrganiser

Questa classe:

  • Creare un oggetto Cursor da collegare alla fotocamera principale.

  • Creare un oggetto Label che verrà visualizzato quando il servizio riconosce gli oggetti reali.

  • Configurare la fotocamera principale collegando i componenti appropriati.

  • Quando si usa la modalità di analisi, generare le etichette in fase di esecuzione, nello spazio globale appropriato rispetto alla posizione della fotocamera principale e visualizzare i dati ricevuti dal servizio Visione personalizzata.

  • Quando si usa la modalità training, generare l'interfaccia utente che visualizzerà le diverse fasi del processo di training.

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. Sarà necessario un solo spazio dei nomi, rimuovere gli altri dall'alto della classe SceneOrganiser :

    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 camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        internal GameObject label;
    
        /// <summary>
        /// Object providing the current status of the camera.
        /// </summary>
        internal TextMesh cameraStatusIndicator;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.5f;
    
  5. 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 CustomVisionTrainer class to this GameObject
            gameObject.AddComponent<CustomVisionTrainer>();
    
            // Add the VoiceRecogniser class to this GameObject
            gameObject.AddComponent<VoiceRecognizer>();
    
            // Add the CustomVisionObjects class to this GameObject
            gameObject.AddComponent<CustomVisionObjects>();
    
            // Create the camera Cursor
            cursor = CreateCameraCursor();
    
            // Load the label prefab as reference
            label = CreateLabel();
    
            // Create the camera status indicator label, and place it above where predictions
            // and training UI will appear.
            cameraStatusIndicator = CreateTrainingUI("Status Indicator", 0.02f, 0.2f, 3, true);
    
            // Set camera status indicator to loading.
            SetCameraStatus("Loading");
        }
    
  7. Aggiungere ora il metodo CreateCameraCursor() che crea e posiziona il cursore Main Camera e il metodo CreateLabel(), che crea l'oggetto Analysis Label .

        /// <summary>
        /// Spawns cursor for the Main Camera
        /// </summary>
        private GameObject CreateCameraCursor()
        {
            // Create a sphere as new cursor
            GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    
            // Attach it to the camera
            newCursor.transform.parent = gameObject.transform;
    
            // Resize the new cursor
            newCursor.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f);
    
            // Move it to the correct position
            newCursor.transform.localPosition = new Vector3(0, 0, 4);
    
            // Set the cursor color to red
            newCursor.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse"));
            newCursor.GetComponent<Renderer>().material.color = Color.green;
    
            return newCursor;
        }
    
        /// <summary>
        /// Create the analysis label object
        /// </summary>
        private GameObject CreateLabel()
        {
            // Create a sphere as new cursor
            GameObject newLabel = new GameObject();
    
            // Resize the new cursor
            newLabel.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
    
            // Creating the text of the label
            TextMesh t = newLabel.AddComponent<TextMesh>();
            t.anchor = TextAnchor.MiddleCenter;
            t.alignment = TextAlignment.Center;
            t.fontSize = 50;
            t.text = "";
    
            return newLabel;
        }
    
  8. Aggiungere il metodo SetCameraStatus(), che gestirà i messaggi destinati alla mesh di testo che fornisce lo stato della fotocamera.

        /// <summary>
        /// Set the camera status to a provided string. Will be coloured if it matches a keyword.
        /// </summary>
        /// <param name="statusText">Input string</param>
        public void SetCameraStatus(string statusText)
        {
            if (string.IsNullOrEmpty(statusText) == false)
            {
                string message = "white";
    
                switch (statusText.ToLower())
                {
                    case "loading":
                        message = "yellow";
                        break;
    
                    case "ready":
                        message = "green";
                        break;
    
                    case "uploading image":
                        message = "red";
                        break;
    
                    case "looping capture":
                        message = "yellow";
                        break;
    
                    case "analysis":
                        message = "red";
                        break;
                }
    
                cameraStatusIndicator.GetComponent<TextMesh>().text = $"Camera Status:\n<color={message}>{statusText}..</color>";
            }
        }
    
  9. Aggiungere i metodi PlaceAnalysisLabel() e SetTagsToLastLabel(), che generano e visualizzano i dati del servizio Visione personalizzata nella scena.

        /// <summary>
        /// Instantiate a label in the appropriate location relative to the Main Camera.
        /// </summary>
        public void PlaceAnalysisLabel()
        {
            lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
        }
    
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void SetTagsToLastLabel(AnalysisObject analysisObject)
        {
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
    
            if (analysisObject.Predictions != null)
            {
                foreach (Prediction p in analysisObject.Predictions)
                {
                    if (p.Probability > 0.02)
                    {
                        lastLabelPlacedText.text += $"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}";
                        Debug.Log($"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}");
                    }
                }
            }
        }
    
  10. Infine, aggiungere il metodo CreateTrainingUI() che genererà l'interfaccia utente visualizzando le più fasi del processo di training quando l'applicazione è in modalità training. Questo metodo verrà inoltre sfruttato per creare l'oggetto stato della fotocamera.

        /// <summary>
        /// Create a 3D Text Mesh in scene, with various parameters.
        /// </summary>
        /// <param name="name">name of object</param>
        /// <param name="scale">scale of object (i.e. 0.04f)</param>
        /// <param name="yPos">height above the cursor (i.e. 0.3f</param>
        /// <param name="zPos">distance from the camera</param>
        /// <param name="setActive">whether the text mesh should be visible when it has been created</param>
        /// <returns>Returns a 3D text mesh within the scene</returns>
        internal TextMesh CreateTrainingUI(string name, float scale, float yPos, float zPos, bool setActive)
        {
            GameObject display = new GameObject(name, typeof(TextMesh));
            display.transform.parent = Camera.main.transform;
            display.transform.localPosition = new Vector3(0, yPos, zPos);
            display.SetActive(setActive);
            display.transform.localScale = new Vector3(scale, scale, scale);
            display.transform.rotation = new Quaternion();
            TextMesh textMesh = display.GetComponent<TextMesh>();
            textMesh.anchor = TextAnchor.MiddleCenter;
            textMesh.alignment = TextAlignment.Center;
            return textMesh;
        }
    
  11. 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:

  AnalysisObject analysisObject = new AnalysisObject();
  analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
  SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);

Capitolo 11 - 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 dell'app.

  • Gestione dei movimenti di tocco dell'utente.

  • Gestione del valore Enum che determina se l'applicazione verrà eseguita in modalità di analisi o in modalità training .

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>
        /// Loop timer
        /// </summary>
        private float secondsBetweenCaptures = 10f;
    
        /// <summary>
        /// Application main functionalities switch
        /// </summary>
        internal enum AppModes {Analysis, Training }
    
        /// <summary>
        /// Local variable for current AppMode
        /// </summary>
        internal AppModes AppMode { get; private set; }
    
        /// <summary>
        /// Flagging if the capture loop is running
        /// </summary>
        internal bool captureIsActive;
    
        /// <summary>
        /// File path of current analysed photo
        /// </summary>
        internal string filePath = string.Empty;
    
  6. È ora necessario aggiungere il codice per i metodi Awake() e Start():

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

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            switch (AppMode)
            {
                case AppModes.Analysis:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to looping capture.
                        SceneOrganiser.Instance.SetCameraStatus("Looping Capture");
    
                        // Begin the capture loop
                        InvokeRepeating("ExecuteImageCaptureAndAnalysis", 0, secondsBetweenCaptures);
                    }
                    else
                    {
                        // The user tapped while the app was analyzing 
                        // therefore stop the analysis process
                        ResetImageCapture();
                    }
                    break;
    
                case AppModes.Training:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Call the image capture
                        ExecuteImageCaptureAndAnalysis();
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to uploading image.
                        SceneOrganiser.Instance.SetCameraStatus("Uploading Image");
                    }              
                    break;
            }     
        }
    

    Nota

    In modalità analisi , il metodo TapHandler funge da opzione per avviare o arrestare il ciclo di acquisizione foto.

    In modalità Training acquisisce un'immagine dalla fotocamera.

    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()
        {
            // Update camera status to analysis.
            SceneOrganiser.Instance.SetCameraStatus("Analysis");
    
            // Create a label in world space using the SceneOrganiser class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
    
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 0.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });   
        }
    
  9. 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 o CustomVisionTrainer a seconda della modalità in cui è impostato il codice.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
        }
    
    
        /// <summary>
        /// The camera photo mode has stopped after the capture.
        /// Begin the Image Analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            Debug.LogFormat("Stopped Photo Mode");
    
            // Dispose from the object in memory and request the image analysis 
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            switch (AppMode)
            {
                case AppModes.Analysis:
                    // Call the image analysis
                    StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath));
                    break;
    
                case AppModes.Training:
                    // Call training using captured image
                    CustomVisionTrainer.Instance.RequestTagSelection();
                    break;
            }
        }
    
        /// <summary>
        /// Stops all capture pending actions
        /// </summary>
        internal void ResetImageCapture()
        {
            captureIsActive = false;
    
            // Set the cursor color to green
            SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Update camera status to ready.
            SceneOrganiser.Instance.SetCameraStatus("Ready");
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Assicurarsi di salvare le modifiche in Visual Studio prima di tornare a Unity.

  11. Ora che tutti gli script sono stati completati, tornare nell'editor di Unity, quindi fare clic e trascinare la classe SceneOrganiser dalla cartella Scripts all'oggetto Camera principale nel pannello Gerarchia.

Capitolo 12 - Prima dell'edificio

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

Prima di procedere, assicurarsi che:

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

  • Tutti i campi della fotocamera principale, pannello di controllo, vengono assegnati correttamente.

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

  • Assicurarsi di inserire la chiave di stima nella variabile predictionKey .

  • L'endpoint di previsione è stato inserito nella variabile predictionEndpoint.

  • La chiave di training è stata inserita nella variabile trainingKey della classe CustomVisionTrainer.

  • L'ID progetto è stato inserito nella variabile projectId della classe CustomVisionTrainer.

Capitolo 13 - Compilare e trasferire localmente l'applicazione

Per avviare il processo di compilazione :

  1. Passare a Impostazioni di compilazione file>.

  2. Selezionare Progetti C# Unity.

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

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

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

Per eseguire la distribuzione in HoloLens:

  1. È necessario l'indirizzo IP di HoloLens (per la distribuzione remota) e assicurarsi che HoloLens sia in 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.

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

  3. Nella configurazione della soluzione selezionare Debug.

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

    Indirizzo IP impostato

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

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

Nota

Per eseguire la distribuzione in visore VR immersive, impostare Piattaforma soluzione su Computer locale e impostare Configurazione su Debug, con x86 come piattaforma. Eseguire quindi la distribuzione nel computer locale, usando la voce di menu Compila , selezionando Distribuisci soluzione.

Per usare l'applicazione:

Per cambiare la funzionalità dell'app tra la modalità training e la modalità stima , è necessario aggiornare la variabile AppMode , che si trova nel metodo Awake() che si trova all'interno della classe ImageCapture .

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Training;

or

        // Change this flag to switch between Analysis mode and Training mode 
        AppMode = AppModes.Analysis;

In modalità Training :

  • Guarda mouse o tastiera e usa il movimento Tap.

  • Verrà quindi visualizzato il testo che chiede di specificare un tag.

  • Pronunciare mouse o tastiera.

In modalità stima :

  • Esaminare un oggetto e usare il movimento Tap.

  • Il testo verrà visualizzato fornendo l'oggetto rilevato, con la probabilità più alta (viene normalizzata).

Capitolo 14 - Valutare e migliorare il modello di Visione personalizzata

Per rendere il servizio più accurato, è necessario continuare a eseguire il training del modello usato per la stima. Questa operazione viene eseguita usando la nuova applicazione, sia con le modalità di training che di stima , con quest'ultimo che richiede di visitare il portale, che è illustrato in questo capitolo. Prepararsi a rivedere il portale più volte per migliorare continuamente il modello.

  1. Passare di nuovo al portale di Azure Visione personalizzata e, una volta che si è nel progetto, selezionare la scheda Stime (dal centro superiore della pagina):

    Scheda Seleziona stime

  2. Verranno visualizzate tutte le immagini inviate al servizio durante l'esecuzione dell'applicazione. Se si passa il puntatore del mouse sulle immagini, verranno fornite le stime effettuate per l'immagine:

    Elenco di immagini di stima

  3. Selezionare una delle immagini per aprirla. Una volta aperta, verranno visualizzate le stime effettuate per l'immagine a destra. Se le stime sono corrette e si vuole aggiungere questa immagine al modello di training del servizio, fare clic sulla casella di input My Tags (Tag personali) e selezionare il tag da associare. Al termine, fare clic sul pulsante Salva e chiudi in basso a destra e continuare con l'immagine successiva.

    Selezionare l'immagine da aprire

  4. Quando si torna alla griglia delle immagini, si noterà che le immagini a cui sono stati aggiunti tag (e salvati) verranno rimosse. Se trovi immagini che pensi di non avere il tuo elemento con tag, puoi eliminarle facendo clic sul segno di graduazione su tale immagine (questa operazione può essere eseguita per diverse immagini) e quindi facendo clic su Elimina nell'angolo superiore destro della pagina della griglia. Nel popup che segue, è possibile fare clic su Sì, eliminare o No, per confermare l'eliminazione o annullarla, rispettivamente.

    Eliminare le immagini

  5. Quando si è pronti per procedere, fare clic sul pulsante treno verde in alto a destra. Il modello di servizio verrà sottoposto a training con tutte le immagini ora fornite (che lo renderanno più accurato). Al termine del training, assicurarsi di fare clic sul pulsante Rendi predefinito ancora una volta, in modo che l'URL di stima continui a usare l'iterazione più aggiornata del servizio.

    Avviare il modello di servizio di trainingSelezionare l'opzione make default (Imposta impostazione predefinita)

Applicazione API Visione personalizzata completata

È stata creata un'app di realtà mista che sfrutta l'API di Azure Visione personalizzata per riconoscere gli oggetti reali, eseguire il training del modello di servizio e visualizzare l'attendibilità di ciò che è stato visto.

Esempio di progetto completato

Esercizi aggiuntivi

Esercizio 1

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

Esercizio 2

Per espandere le nozioni apprese, completare gli esercizi seguenti:

Riprodurre un suono quando viene riconosciuto un oggetto.

Esercizio 3

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