Partilhar via


HoloLens (1.ª geração) e Azure 302b: Visão personalizada


Nota

Os tutoriais da Academia de Realidade Mista foram projetados com HoloLens (1ª geração) e Headsets Imersivos de Realidade Mista em mente. Como tal, sentimos que é importante deixar estes tutoriais no lugar para desenvolvedores que ainda estão procurando orientação no desenvolvimento para esses dispositivos. Esses tutoriais não serão atualizados com os conjuntos de ferramentas ou interações mais recentes que estão sendo usados para o HoloLens 2. Eles serão mantidos para continuar trabalhando nos dispositivos suportados. Haverá uma nova série de tutoriais que serão publicados no futuro que demonstrarão como desenvolver para o HoloLens 2. Este aviso será atualizado com um link para esses tutoriais quando eles forem publicados.


Neste curso, você aprenderá a reconhecer conteúdo visual personalizado em uma imagem fornecida, usando os recursos da Visão Personalizada do Azure em um aplicativo de realidade mista.

Este serviço permitirá que você treine um modelo de aprendizado de máquina usando imagens de objeto. Em seguida, você usará o modelo treinado para reconhecer objetos semelhantes, conforme fornecido pela captura de câmera do Microsoft HoloLens ou uma câmera conectada ao seu PC para fones de ouvido imersivos (VR).

resultado do curso

A Visão Personalizada do Azure é um Serviço Cognitivo da Microsoft que permite aos programadores criar classificadores de imagem personalizados. Esses classificadores podem ser usados com novas imagens para reconhecer ou classificar objetos dentro dessa nova imagem. O Serviço disponibiliza um portal online simples e fácil de utilizar para agilizar o processo. Para obter mais informações, visite a página do Serviço de Visão Personalizada do Azure.

Após a conclusão deste curso, você terá um aplicativo de realidade mista que será capaz de trabalhar em dois modos:

  • Modo de Análise: configurar o Serviço de Visão Personalizada manualmente carregando imagens, criando tags e treinando o Serviço para reconhecer diferentes objetos (neste caso, mouse e teclado). Em seguida, você criará um aplicativo HoloLens que capturará imagens usando a câmera e tentará reconhecer esses objetos no mundo real.

  • Modo de treinamento: você implementará um código que habilitará um "Modo de treinamento" em seu aplicativo. O modo de treinamento permitirá que você capture imagens usando a câmera do HoloLens, carregue as imagens capturadas para o Serviço e treine o modelo de visão personalizado.

Este curso ensinará como obter os resultados do Serviço de Visão Personalizada em um aplicativo de exemplo baseado em Unity. Caberá a você aplicar esses conceitos a um aplicativo personalizado que você pode estar criando.

Suporte de dispositivos

Curso HoloLens Auriculares imersivos
MR e Azure 302b: Visão personalizada ✔️ ✔️

Nota

Embora este curso se concentre principalmente no HoloLens, você também pode aplicar o que aprendeu neste curso aos fones de ouvido imersivos (VR) do Windows Mixed Reality. Como os fones de ouvido imersivos (VR) não têm câmeras acessíveis, você precisará de uma câmera externa conectada ao seu PC. Ao acompanhar o curso, você verá anotações sobre quaisquer alterações que talvez precise empregar para suportar fones de ouvido imersivos (VR).

Pré-requisitos

Nota

Este tutorial foi projetado para desenvolvedores que têm experiência básica com Unity e C#. Tenha também em atenção que os pré-requisitos e as instruções escritas contidas neste documento representam o que foi testado e verificado no momento da redação (julho de 2018). Você é livre para usar o software mais recente, conforme listado no artigo instalar as ferramentas , embora não se deva presumir que as informações neste curso corresponderão perfeitamente ao que você encontrará em software mais recente do que o listado abaixo.

Recomendamos o seguinte hardware e software para este curso:

Antes de começar

  1. Para evitar encontrar problemas ao criar este projeto, é altamente recomendável que você crie o projeto mencionado neste tutorial em uma pasta raiz ou quase raiz (caminhos de pasta longos podem causar problemas em tempo de compilação).
  2. Configure e teste o seu HoloLens. Se você precisar de suporte para configurar seu HoloLens, visite o artigo de configuração do HoloLens.
  3. É uma boa ideia executar a calibração e o ajuste do sensor ao começar a desenvolver um novo aplicativo HoloLens (às vezes, pode ajudar a executar essas tarefas para cada usuário).

Para obter ajuda sobre calibração, siga este link para o artigo Calibração HoloLens.

Para obter ajuda sobre o ajuste do sensor, siga este link para o artigo HoloLens Sensor Tuning.

Capítulo 1 - O Portal de Serviços de Visão Personalizada

Para usar o Serviço de Visão Personalizada no Azure, você precisará configurar uma instância do Serviço a ser disponibilizada para seu aplicativo.

  1. Primeiro, navegue até a página principal do Serviço de Visão Personalizada.

  2. Clique no botão Começar .

    Introdução ao Serviço de Visão Personalizada

  3. Entre no Portal de Serviço de Visão Personalizada.

    Inicie a sessão no portal

    Nota

    Se ainda não tiver uma conta do Azure, terá de criar uma. Se você estiver seguindo este tutorial em uma situação de sala de aula ou laboratório, peça ajuda ao seu instrutor ou a um dos proctors para configurar sua nova conta.

  4. Depois de iniciar sessão pela primeira vez, ser-lhe-á apresentado o painel Termos de Serviço . Clique na caixa de seleção para concordar com os termos. Em seguida, clique em Concordo.

    Terms of service

  5. Tendo concordado com os Termos, você será navegado para a seção Projetos do Portal. Clique em Novo Projeto.

    Criar novo projeto

  6. Uma guia aparecerá no lado direito, que solicitará que você especifique alguns campos para o projeto.

    1. Insira um Nome para o seu projeto.

    2. Insira uma Descrição para o seu projeto (opcional).

    3. Escolha um Grupo de Recursos ou crie um novo. Um grupo de recursos fornece uma maneira de monitorar, controlar o acesso, provisionar e gerenciar a cobrança de uma coleção de ativos do Azure. É recomendável manter todos os Serviços do Azure associados a um único projeto (por exemplo, como esses cursos) em um grupo de recursos comum).

    4. Definir os tipos de projeto para classificação

    5. Defina os domínios como Geral.

      Definir os domínios

      Se desejar ler mais sobre os Grupos de Recursos do Azure, visite o artigo do grupo de recursos.

  7. Quando terminar, clique em Criar projeto, você será redirecionado para o Serviço de Visão Personalizada, página do projeto.

Capítulo 2 - Treinando seu projeto de Visão Personalizada

Uma vez no portal Visão Personalizada, seu objetivo principal é treinar seu projeto para reconhecer objetos específicos em imagens. Você precisa de pelo menos cinco (5) imagens, embora dez (10) seja preferível, para cada objeto que você gostaria que seu aplicativo reconhecesse. Você pode usar as imagens fornecidas com este curso (um mouse de computador e um teclado).

Para treinar seu projeto de Serviço de Visão Personalizada:

  1. Clique no + botão ao lado de Tags.

    Adicionar etiqueta nova

  2. Adicione o nome do objeto que você gostaria de reconhecer. Clique em Guardar.

    Adicionar nome do objeto e salvar

  3. Você notará que sua tag foi adicionada (talvez seja necessário recarregar sua página para que ela apareça). Clique na caixa de seleção ao lado da nova tag, se ela ainda não estiver marcada.

    Ativar nova tag

  4. Clique em Adicionar imagens no centro da página.

    Adicionar imagens

  5. Clique em Procurar ficheiros locais e pesquise, em seguida, selecione as imagens que gostaria de carregar, sendo o mínimo de cinco (5). Lembre-se de que todas essas imagens devem conter o objeto que você está treinando.

    Nota

    Você pode selecionar várias imagens ao mesmo tempo, para carregar.

  6. Depois de ver as imagens na guia, selecione a tag apropriada na caixa Minhas tags .

    Selecionar tags

  7. Clique em Carregar ficheiros. Os ficheiros começarão a ser carregados. Assim que tiver a confirmação do carregamento, clique em Concluído.

    Carregar ficheiros

  8. Repita o mesmo processo para criar uma nova tag chamada Teclado e carregue as fotos apropriadas para ela. Certifique-se de desmarcar Mouse depois de criar as novas tags, para mostrar a janela Adicionar imagens.

  9. Depois de configurar ambas as tags, clique em Treinar e a primeira iteração de treinamento começará a ser construída.

    Habilitar iteração de treinamento

  10. Depois de criado, você poderá ver dois botões chamados Tornar padrão e URL de previsão. Clique em Tornar padrão primeiro e, em seguida, clique em URL de previsão.

    Tornar URL padrão e de previsão

    Nota

    O URL do ponto de extremidade fornecido a partir disso é definido como qualquer Iteração que tenha sido marcada como padrão. Como tal, se mais tarde fizer uma nova iteração e atualizá-la como padrão, não precisará alterar seu código.

  11. Depois de clicar no URL de previsão, abra o bloco de notas e copie e cole o URL e a chave de previsão, para que você possa recuperá-lo quando precisar dele mais tarde no código.

    Copiar e colar URL e Chave de Previsão

  12. Clique na engrenagem no canto superior direito da tela.

    Clique no ícone de engrenagem para abrir as configurações

  13. Copie a Chave de Treinamento e cole-a em um Bloco de Notas, para uso posterior.

    Copiar chave de treinamento

  14. Copie também a ID do projeto e cole-a no arquivo do Bloco de Notas, para uso posterior.

    Copiar ID do projeto

Capítulo 3 - Configurar o projeto Unity

O seguinte é uma configuração típica para desenvolver com realidade mista e, como tal, é um bom modelo para outros projetos.

  1. Abra o Unity e clique em Novo.

    Criar novo projeto Unity

  2. Agora você precisará fornecer um nome de projeto Unity. Insira AzureCustomVision. Verifique se o modelo de projeto está definido como 3D. Defina o Local para algum lugar apropriado para você (lembre-se, mais perto de diretórios raiz é melhor). Em seguida, clique em Criar projeto.

    Definir configurações do projeto

  3. Com o Unity aberto, vale a pena verificar se o Editor de Scripts padrão está definido como Visual Studio. Vá para Editar>Preferências e, na nova janela, navegue até Ferramentas Externas. Altere o Editor de Scripts Externo para Visual Studio 2017. Feche a janela Preferências .

    Configurar ferramentas externas

  4. Em seguida, vá para Configurações de compilação de arquivo > e selecione Plataforma Universal do Windows e, em seguida, clique no botão Alternar plataforma para aplicar sua seleção.

    Definir configurações de compilação

  5. Enquanto ainda estiver em Configurações de compilação de arquivo > e certifique-se de que:

    1. O dispositivo alvo está definido como HoloLens

      Para os auriculares imersivos, defina Target Device como Any Device.

    2. O tipo de compilação está definido como D3D

    3. O SDK está definido como Instalado mais recente

    4. Versão do Visual Studio está definida como A versão mais recente instalada

    5. Build and Run está definido como Máquina Local

    6. Salve a cena e adicione-a à compilação.

      1. Faça isso selecionando Adicionar cenas abertas. Será exibida uma janela de salvamento.

        Adicionar cena aberta à lista de construção

      2. Crie uma nova pasta para esta e qualquer cena futura e, em seguida, selecione o botão Nova pasta , para criar uma nova pasta, nomeie-a Cenas.

        Criar nova pasta de cena

      3. Abra a pasta Cenas recém-criada e, no campo de texto Nome do arquivo:, digite CustomVisionScene e clique em Salvar.

        Nomeie o novo arquivo de cena

        Esteja ciente, você deve salvar suas cenas Unity dentro da pasta Ativos , pois elas devem estar associadas ao projeto Unity. Criar a pasta scenes (e outras pastas semelhantes) é uma maneira típica de estruturar um projeto Unity.

    7. As configurações restantes, em Configurações de compilação, devem ser deixadas como padrão por enquanto.

      Configurações de compilação padrão

  6. Na janela Configurações de compilação, clique no botão Configurações do player, isso abrirá o painel relacionado no espaço onde o inspetor está localizado.

  7. Neste painel, algumas configurações precisam ser verificadas:

    1. Na guia Outras configurações:

      1. A versão do Scripting Runtime deve ser experimental (equivalente ao .NET 4.6), o que acionará a necessidade de reiniciar o Editor.

      2. O back-end de scripts deve ser .NET

      3. O nível de compatibilidade da API deve ser .NET 4.6

      Definir compatibilidade de API

    2. Na guia Configurações de publicação , em Recursos, verifique:

      1. InternetClient

      2. Webcam

      3. Microfone

      Definir definições de publicação

    3. Mais abaixo no painel, em Configurações XR (encontradas abaixo de Configurações de publicação), marque Realidade Virtual suportada, verifique se o SDK de realidade mista do Windows foi adicionado.

    Definir configurações de XR

  8. De volta às configurações de compilação, o Unity C# Projects não está mais acinzentado, marque a caixa de seleção ao lado disso.

  9. Feche a janela Configurações de compilação.

  10. Salve sua cena e projeto (FILE > SAVE SCENE / FILE > SAVE PROJECT).

Capítulo 4 - Importando a DLL Newtonsoft no Unity

Importante

Se você deseja pular o componente Unity set deste curso e continuar direto no código, sinta-se à vontade para baixar este Azure-MR-302b.unitypackage, importá-lo para seu projeto como um pacote personalizado e continuar a partir do Capítulo 6.

Este curso requer o uso da biblioteca Newtonsoft , que você pode adicionar como uma DLL aos seus ativos. O pacote que contém esta biblioteca pode ser descarregado a partir deste link. Para importar a biblioteca Newtonsoft para o seu projeto, use o Pacote Unity que veio com este curso.

  1. Adicione o .unitypackage ao Unity usando a opção de menu Pacote personalizado do pacote> de importação de ativos.>

  2. Na caixa Importar pacote Unity que aparece, verifique se tudo em (e incluindo) Plug-ins está selecionado.

    Importar todos os itens do pacote

  3. Clique no botão Importar para adicionar os itens ao seu projeto.

  4. Vá para a pasta Newtonsoft em Plugins na visualização do projeto e selecione o plug-in Newtonsoft.Json.

    Selecione Newtonsoft plugin

  5. Com o plug-in Newtonsoft.Json selecionado, verifique se Any Platform está desmarcado, certifique-se de que o WSAPlayer também esteja desmarcado e clique em Aplicar. Isso é apenas para confirmar que os arquivos estão configurados corretamente.

    Configurar o plug-in Newtonsoft

    Nota

    Marcar esses plug-ins os configura para serem usados apenas no Editor Unity. Há um conjunto diferente deles na pasta WSA que será usado depois que o projeto for exportado do Unity.

  6. Em seguida, você precisa abrir a pasta WSA , dentro da pasta Newtonsoft . Você verá uma cópia do mesmo arquivo que acabou de configurar. Selecione o arquivo e, em seguida, no inspetor, verifique se

    • Qualquer plataforma está desmarcada
    • apenas o WSAPlayer está verificado
    • O processo não é verificado

    Definir as configurações da plataforma de plug-in Newtonsoft

Capítulo 5 - Configuração da câmara

  1. No Painel Hierarquia, selecione a Câmara Principal.

  2. Uma vez selecionado, você será capaz de ver todos os componentes da câmera principal no painel do inspetor.

    1. O objeto da câmera deve ser chamado de câmera principal (observe a ortografia!)

    2. A tag da câmera principal deve ser definida como MainCamera (observe a ortografia!)

    3. Verifique se a Posição de transformação está definida como 0, 0, 0

    4. Defina Clear Flags como Solid Color (ignore isso para fones de ouvido imersivos).

    5. Defina a cor de fundo do componente da câmera para preto, Alpha 0 (código hexadecimal: #00000000) (ignore isso para fone de ouvido imersivo).

    Configurar propriedades do componente Câmara

Capítulo 6 - Crie a classe CustomVisionAnalyser.

Neste ponto, você está pronto para escrever algum código.

Você começará com a classe CustomVisionAnalyser .

Nota

As chamadas para o Serviço de Visão Personalizada feitas no código mostrado abaixo são feitas usando a API REST de Visão Personalizada. Ao usar isso, você verá como implementar e fazer uso dessa API (útil para entender como implementar algo semelhante por conta própria). Esteja ciente de que a Microsoft oferece um SDK do Serviço de Visão Personalizada que também pode ser usado para fazer chamadas para o Serviço. Para obter mais informações, visite o artigo Custom Vision Service SDK .

Esta classe é responsável por:

  • Carregando a imagem mais recente capturada como uma matriz de bytes.

  • Enviar a matriz de bytes para sua instância do Azure Custom Vision Service para análise.

  • Recebendo a resposta como uma cadeia de caracteres JSON.

  • Desserializar a resposta e passar a previsão resultante para a classe SceneOrganiser, que cuidará de como a resposta deve ser exibida.

Para criar esta classe:

  1. Clique com o botão direito do mouse na Pasta de ativos localizada no Painel do projeto e clique em Criar > pasta. Chame a pasta Scripts.

    Criar pasta de scripts

  2. Clique duas vezes na pasta recém-criada, para abri-la.

  3. Clique com o botão direito do rato dentro da pasta e, em seguida, clique em Criar>Script C#. Nomeie o script CustomVisionAnalyser.

  4. Clique duas vezes no novo script CustomVisionAnalyser para abri-lo com o Visual Studio.

  5. Atualize os namespaces na parte superior do arquivo para corresponder ao seguinte:

    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    using Newtonsoft.Json;
    
  6. Na classe CustomVisionAnalyser, adicione as seguintes variáveis:

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

    Certifique-se de inserir sua Chave de Previsão na variável predictionKey e seu Prediction Endpoint na variável predictionEndpoint. Você os copiou para o Bloco de Notas no início do curso.

  7. O código para Awake() agora precisa ser adicionado para inicializar a variável Instance:

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Exclua os métodos Start() e Update().

  9. Em seguida, adicione a corotina (com o método estático GetImageAsByteArray() abaixo dele), que obterá os resultados da análise da imagem capturada pela classe ImageCapture .

    Nota

    Na co-rotina AnalyseImageCapture, há uma chamada para a classe SceneOrganiser que você ainda não criou. Portanto, deixe essas linhas comentadas por enquanto.

        /// <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. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

Capítulo 7 - Criar a classe CustomVisionObjects

A classe que você criará agora é a classe CustomVisionObjects .

Esse script contém vários objetos usados por outras classes para serializar e desserializar as chamadas feitas para o Serviço de Visão Personalizada.

Aviso

É importante que você tome nota do ponto de extremidade que o Serviço de Visão Personalizada lhe fornece, pois a estrutura JSON abaixo foi configurada para funcionar com o Custom Vision Prediction v2.0. Se você tiver uma versão diferente, talvez seja necessário atualizar a estrutura abaixo.

Para criar esta classe:

  1. Clique com o botão direito do mouse dentro da pasta Scripts e clique em Criar>Script C#. Chame o script CustomVisionObjects.

  2. Clique duas vezes no novo script CustomVisionObjects para abri-lo com o Visual Studio.

  3. Adicione os seguintes namespaces à parte superior do arquivo:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Exclua os métodos Start() e Update() dentro da classe CustomVisionObjects , essa classe agora deve estar vazia.

  5. Adicione as seguintes classes fora da classe CustomVisionObjects . Esses objetos são usados pela biblioteca Newtonsoft para serializar e desserializar os dados de resposta:

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

Capítulo 8 - Criar a classe VoiceRecognizer

Essa classe reconhecerá a entrada de voz do usuário.

Para criar esta classe:

  1. Clique com o botão direito do mouse dentro da pasta Scripts e clique em Criar>Script C#. Chame o script de VoiceRecognizer.

  2. Clique duas vezes no novo script VoiceRecognizer para abri-lo com o Visual Studio.

  3. Adicione os seguintes namespaces acima da classe VoiceRecognizer :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Windows.Speech;
    
  4. Em seguida, adicione as seguintes variáveis dentro da classe VoiceRecognizer, acima do método 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. Adicione os métodos Awake() e Start(), o último dos quais configurará as palavras-chave do usuário a serem reconhecidas ao associar uma tag a uma imagem:

        /// <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. Exclua o método Update( ).

  7. Adicione o seguinte manipulador, que é chamado sempre que a entrada de voz é reconhecida:

        /// <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. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

Nota

Não se preocupe com o código que pode parecer ter um erro, pois você fornecerá mais classes em breve, o que irá corrigi-los.

Capítulo 9 - Criar a classe CustomVisionTrainer

Esta classe encadeará uma série de chamadas na Web para treinar o Serviço de Visão Personalizada. Cada chamada será explicada em detalhes logo acima do código.

Para criar esta classe:

  1. Clique com o botão direito do mouse dentro da pasta Scripts e clique em Criar>Script C#. Chame o script CustomVisionTrainer.

  2. Clique duas vezes no novo script CustomVisionTrainer para abri-lo com o Visual Studio.

  3. Adicione os seguintes namespaces acima da classe CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Em seguida, adicione as seguintes variáveis dentro da classe CustomVisionTrainer , acima do método Start( ).

    Nota

    A URL de treinamento usada aqui é fornecida na documentação do Custom Vision Training 1.2 e tem uma estrutura de: https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Para obter mais informações, visite a API de referência do Custom Vision Training v1.2.

    Aviso

    É importante que você tome nota do ponto de extremidade que o Serviço de Visão Personalizada fornece para o modo de treinamento, pois a estrutura JSON usada (dentro da classe CustomVisionObjects) foi configurada para trabalhar com o Treinamento de Visão Personalizada v1.2. Se você tiver uma versão diferente, talvez seja necessário atualizar a estrutura Objetos .

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

    Certifique-se de adicionar o valor da Chave de Serviço (Chave de Treinamento) e o valor da ID do Projeto, que você anotou anteriormente, esses são os valores que você coletou do portal no início do curso (Capítulo 2, etapa 10 em diante).

  5. Adicione os seguintes métodos Start() e Awake(). Esses métodos são chamados na inicialização e contêm a chamada para configurar a interface do usuário:

        /// <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. Exclua o método Update( ). Esta classe não vai precisar disso.

  7. Adicione o método RequestTagSelection(). Este método é o primeiro a ser chamado quando uma imagem foi capturada e armazenada no dispositivo e agora está pronta para ser submetida ao Serviço de Visão Personalizada, para treiná-la. Esse método exibe, na interface do usuário de treinamento, um conjunto de palavras-chave que o usuário pode usar para marcar a imagem que foi capturada. Ele também alerta a classe VoiceRecognizer para começar a ouvir o usuário para entrada de voz.

        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. Adicione o método VerifyTag(). Este método receberá a entrada de voz reconhecida pela classe VoiceRecognizer e verificará a sua validade e, em seguida, iniciará o processo de formação.

        /// <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. Adicione o método SubmitImageForTraining(). Esse método iniciará o processo de treinamento do Serviço de Visão Personalizada. A primeira etapa é recuperar o ID da marca do serviço que está associado à entrada de fala validada do usuário. O ID da tag será então carregado junto com a imagem.

        /// <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. Adicione o método TrainCustomVisionProject(). Uma vez que a imagem tenha sido enviada e marcada, este método será chamado. Ele criará uma nova iteração que será treinada com todas as imagens anteriores enviadas ao Serviço mais a imagem recém-carregada. Depois que o treinamento for concluído, esse método chamará um método para definir a Iteração recém-criada como Padrão, para que o ponto de extremidade que você está usando para análise seja a iteração treinada mais 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. Adicione o método SetDefaultIteration(). Esse método definirá a iteração criada e treinada anteriormente como Padrão. Uma vez concluído, este método terá que excluir a iteração anterior existente no Serviço. No momento da redação deste curso, há um limite máximo de dez (10) iterações permitidas para existir ao mesmo tempo no Serviço.

        /// <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. Adicione o método DeletePreviousIteration(). Este método localizará e excluirá a iteração não padrão anterior:

        /// <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. O último método a ser adicionado nessa classe é o método GetImageAsByteArray(), usado nas chamadas da Web para converter a imagem capturada em uma matriz de bytes.

        /// <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. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

Capítulo 10 - Criar a classe SceneOrganiser

Esta classe irá:

  • Crie um objeto Cursor para anexar à câmera principal.

  • Crie um objeto Label que aparecerá quando o Serviço reconhecer os objetos do mundo real.

  • Configure a câmera principal conectando os componentes apropriados a ela.

  • Quando estiver no Modo de Análise, gere as Etiquetas em tempo de execução, no espaço mundial apropriado em relação à posição da Câmara Principal, e exiba os dados recebidos do Serviço de Visão Personalizada.

  • Quando estiver no Modo de Treinamento, gere a interface do usuário que exibirá as diferentes etapas do processo de treinamento.

Para criar esta classe:

  1. Clique com o botão direito do mouse dentro da pasta Scripts e clique em Criar>Script C#. Nomeie o script como SceneOrganiser.

  2. Clique duas vezes no novo script SceneOrganiser para abri-lo com o Visual Studio.

  3. Você só precisará de um namespace, remova os outros acima da classe SceneOrganiser :

    using UnityEngine;
    
  4. Em seguida, adicione as seguintes variáveis dentro da classe SceneOrganiser, acima do método 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. Exclua os métodos Start() e Update().

  6. Logo abaixo das variáveis, adicione o método Awake(), que inicializará a classe e configurará a cena.

        /// <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. Agora, adicione o método CreateCameraCursor() que cria e posiciona o cursor da câmera principal e o método CreateLabel(), que cria o objeto 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. Adicione o método SetCameraStatus(), que manipulará mensagens destinadas à malha de texto fornecendo o status da câmera.

        /// <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. Adicione os métodos PlaceAnalysisLabel() e SetTagsToLastLabel(), que gerarão e exibirão os dados do Serviço de Visão Personalizada na cena.

        /// <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. Por fim, adicione o método CreateTrainingUI(), que gerará a interface do usuário exibindo os vários estágios do processo de treinamento quando o aplicativo estiver no Modo de treinamento. Esse método também será aproveitado para criar o objeto de status da câmera.

        /// <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. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

Importante

Antes de continuar, abra a classe CustomVisionAnalyser e, dentro do método AnalyseLastImageCaptured(), remova o comentário das seguintes linhas:

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

Capítulo 11 - Criar a classe ImageCapture

A próxima classe que você vai criar é a classe ImageCapture .

Esta classe é responsável por:

  • Capturar uma imagem usando a câmera HoloLens e armazená-la na Pasta do aplicativo .

  • Manuseamento Toque em gestos do utilizador.

  • Manter o valor Enum que determina se o aplicativo será executado no modo de Análise ou no Modo de Treinamento.

Para criar esta classe:

  1. Vá para a pasta Scripts que você criou anteriormente.

  2. Clique com o botão direito do rato dentro da pasta e, em seguida, clique em Criar > Script C#. Nomeie o script ImageCapture.

  3. Clique duas vezes no novo script ImageCapture para abri-lo com o Visual Studio.

  4. Substitua os namespaces na parte superior do arquivo pelo seguinte:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Em seguida, adicione as seguintes variáveis dentro da classe ImageCapture, acima do método 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. O código para os métodos Awake() e Start() agora precisa ser adicionado:

        /// <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. Implemente um manipulador que será chamado quando ocorrer um gesto de toque.

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

    No modo de análise , o método TapHandler atua como uma opção para iniciar ou parar o loop de captura de fotos.

    No modo de treinamento , ele irá capturar uma imagem da câmera.

    Quando o cursor está verde, significa que a câmara está disponível para tirar a imagem.

    Quando o cursor está vermelho, significa que a câmara está ocupada.

  8. Adicione o método que o aplicativo usa para iniciar o processo de captura de imagem e armazenar a imagem.

        /// <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. Adicione os manipuladores que serão chamados quando a foto tiver sido capturada e para quando estiver pronta para ser analisada. O resultado é então passado para o CustomVisionAnalyser ou o CustomVisionTrainer , dependendo do modo em que o código está definido.

        /// <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. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

  11. Agora que todos os scripts foram concluídos, volte no Editor Unity e, em seguida, clique e arraste a classe SceneOrganiser da pasta Scripts para o objeto Main Camera no Painel de Hierarquia.

Capítulo 12 - Antes de construir

Para realizar um teste completo do seu aplicativo, você precisará fazer o sideload dele em seu HoloLens.

Antes de o fazer, certifique-se de que:

  • Todas as configurações mencionadas no Capítulo 2 estão definidas corretamente.

  • Todos os campos na Câmara Principal, Painel do Inspetor, são atribuídos corretamente.

  • O script SceneOrganiser é anexado ao objeto Main Camera .

  • Certifique-se de inserir sua chave de previsão na variável predictionKey.

  • Você inseriu seu Prediction Endpoint na variável predictionEndpoint .

  • Você inseriu sua Chave de Treinamento na variável trainingKey , da classe CustomVisionTrainer .

  • Você inseriu sua ID de projeto na variável projectId, da classe CustomVisionTrainer.

Capítulo 13 - Criar e fazer sideload do seu aplicativo

Para iniciar o processo de compilação :

  1. Vá para Configurações de compilação de arquivo>.

  2. Marque Unity C# Projects.

  3. Clique em Compilar. Unity irá iniciar uma janela do Explorador de Arquivos, onde você precisa criar e, em seguida, selecionar uma pasta para construir o aplicativo. Crie essa pasta agora e nomeie-a como App. Em seguida, com a pasta App selecionada, clique em Select Folder.

  4. Unity começará a construir seu projeto para a pasta App .

  5. Assim que o Unity terminar de construir (pode levar algum tempo), ele abrirá uma janela do Explorador de Arquivos no local da sua compilação (verifique sua barra de tarefas, pois nem sempre ela aparecerá acima de suas janelas, mas notificará você sobre a adição de uma nova janela).

Para implantar no HoloLens:

  1. Você precisará do endereço IP do seu HoloLens (para implantação remota) e para garantir que seu HoloLens esteja no modo de desenvolvedor. Para tal:

    1. Enquanto estiver a usar o HoloLens, abra as Definições.

    2. Ir para Rede & Opções Avançadas de Wi-Fi>da Internet>

    3. Observe o endereço IPv4 .

    4. Em seguida, navegue de volta para Configurações e, em seguida, para Atualizar & Segurança>para desenvolvedores

    5. Defina o modo de desenvolvedor ativado.

  2. Navegue até sua nova compilação Unity (a pasta App) e abra o arquivo de solução com o Visual Studio.

  3. Na Configuração da Solução, selecione Depurar.

  4. Na Plataforma de Solução, selecione x86, Máquina Remota. Você será solicitado a inserir o endereço IP de um dispositivo remoto (o HoloLens, neste caso, que você observou).

    Definir endereço IP

  5. Vá para o menu Build e clique em Deploy Solution para fazer sideload do aplicativo para o seu HoloLens.

  6. Seu aplicativo agora deve aparecer na lista de aplicativos instalados no seu HoloLens, pronto para ser iniciado!

Nota

Para implantar no fone de ouvido imersivo, defina a Plataforma de Solução como Máquina Local e defina a Configuração como Depurar, com x86 como Plataforma. Em seguida, implante na máquina local, usando o item de menu Compilar , selecionando Implantar Solução.

Para usar o aplicativo:

Para alternar a funcionalidade do aplicativo entre o modo Training e o modo Prediction, você precisa atualizar a variável AppMode, localizada no método Awake() localizado na classe ImageCapture.

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

ou

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

No modo de Treinamento :

  • Olhe para Mouse ou Teclado e use o gesto Toque.

  • Em seguida, aparecerá um texto solicitando que você forneça uma tag.

  • Diga mouse ou teclado.

No modo de previsão :

  • Olhe para um objeto e use o gesto Toque.

  • O texto aparecerá fornecendo o objeto detetado, com a maior probabilidade (isso é normalizado).

Capítulo 14 - Avaliar e melhorar o seu modelo de Visão Personalizada

Para tornar seu Serviço mais preciso, você precisará continuar a treinar o modelo usado para previsão. Isto é conseguido através da utilização da sua nova aplicação, com os modos de treino e previsão , sendo que este último exige que visite o portal, que é o que é abordado neste Capítulo. Esteja preparado para revisitar o seu portal muitas vezes, para melhorar continuamente o seu modelo.

  1. Vá para o Portal de Visão Personalizada do Azure novamente e, quando estiver em seu projeto, selecione a guia Previsões (no centro superior da página):

    Guia Selecionar previsões

  2. Você verá todas as imagens que foram enviadas para o seu Serviço enquanto seu aplicativo estava em execução. Se você passar o mouse sobre as imagens, elas fornecerão as previsões que foram feitas para essa imagem:

    Lista de imagens de previsão

  3. Selecione uma das suas imagens para abri-la. Uma vez aberto, você verá as previsões feitas para essa imagem à direita. Se as previsões estiverem corretas e desejar adicionar essa imagem ao modelo de treinamento do seu Serviço, clique na caixa de entrada Minhas Tags e selecione a tag que deseja associar. Quando terminar, clique no botão Salvar e fechar no canto inferior direito e continue para a próxima imagem.

    Selecione a imagem para abrir

  4. Quando você estiver de volta à grade de imagens, você notará que as imagens às quais você adicionou tags (e salvas), serão removidas. Se você encontrar imagens que você acha que não têm seu item marcado dentro delas, você pode excluí-las, clicando no tick sobre essa imagem (pode fazer isso para várias imagens) e, em seguida, clicando em Excluir no canto superior direito da página de grade. No pop-up a seguir, você pode clicar em Sim, excluir ou Não, para confirmar a exclusão ou cancelá-la, respectivamente.

    Eliminar imagens

  5. Quando estiver pronto para continuar, clique no botão verde Trem no canto superior direito. Seu modelo de serviço será treinado com todas as imagens que você forneceu agora (o que o tornará mais preciso). Quando o treinamento estiver concluído, certifique-se de clicar no botão Tornar padrão mais uma vez, para que sua URL de previsão continue a usar a iteração mais atualizada do seu Serviço.

    Iniciar o modelo de serviço de treinamentoSelecione a opção tornar padrão

Seu aplicativo Custom Vision API concluído

Parabéns, você criou um aplicativo de realidade mista que aproveita a API de Visão Personalizada do Azure para reconhecer objetos do mundo real, treinar o modelo de Serviço e exibir confiança do que foi visto.

Exemplo de projeto concluído

Exercícios de bónus

Exercício 1

Treine seu Serviço de Visão Personalizada para reconhecer mais objetos.

Exercício 2

Como forma de expandir o que aprendeu, complete os seguintes exercícios:

Reproduzir um som quando um objeto é reconhecido.

Exercício 3

Use a API para treinar novamente seu Serviço com as mesmas imagens que seu aplicativo está analisando, para tornar o Serviço mais preciso (faça previsão e treinamento simultaneamente).