HoloLens (1ª geração) e Azure 310: detecção de objetos

Observação

Os tutoriais do Mixed Reality Academy foram projetados com o HoloLens (1ª geração) e os headsets imersivos de realidade misturada em mente. Dessa forma, achamos que é importante continuar disponibilizando esses tutoriais para os desenvolvedores que ainda buscam obter diretrizes para o desenvolvimento visando esses dispositivos. Esses tutoriais não serão atualizados com os conjuntos de ferramentas mais recentes nem com as interações usadas para o HoloLens 2. Eles serão mantidos para continuar funcionando nos dispositivos compatíveis. Haverá uma nova série de tutoriais que serão postados no futuro que demonstrarão como desenvolver para HoloLens 2. Este aviso será atualizado com um link para esses tutoriais quando eles forem postados.


Neste curso, você aprenderá a reconhecer o conteúdo visual personalizado e sua posição espacial em uma imagem fornecida, usando o Azure Visão Personalizada recursos de "Detecção de Objetos" em um aplicativo de realidade misturada.

Esse serviço permitirá que você treine um modelo de machine learning usando imagens de objeto. Em seguida, você usará o modelo treinado para reconhecer objetos semelhantes e aproximar sua localização no mundo real, conforme fornecido pela captura de câmera de Microsoft HoloLens ou uma câmera se conectar a um computador para headsets imersivos (VR).

resultado do curso

Azure Visão Personalizada, a Detecção de Objetos é um Serviço da Microsoft que permite que os desenvolvedores criem classificadores de imagem personalizados. Esses classificadores podem ser usados com novas imagens para detectar objetos dentro dessa nova imagem, fornecendo limites de caixa dentro da própria imagem. O Serviço fornece um portal online simples e fácil de usar para simplificar esse processo. Para obter mais informações, visite os seguintes links:

Após a conclusão deste curso, você terá um aplicativo de realidade misturada que poderá fazer o seguinte:

  1. O usuário poderá olhar para um objeto, que ele treinou usando o Serviço de Visão Personalizada do Azure, Detecção de Objetos.
  2. O usuário usará o gesto tocar para capturar uma imagem do que está olhando.
  3. O aplicativo enviará a imagem para o Serviço de Visão Personalizada do Azure.
  4. Haverá uma resposta do Serviço que exibirá o resultado do reconhecimento como texto de espaço de mundo. Isso será feito utilizando o Acompanhamento Espacial do Microsoft HoloLens, como uma forma de entender a posição mundial do objeto reconhecido e, em seguida, usar a Marca associada ao que é detectado na imagem, para fornecer o texto do rótulo.

O curso também abordará o carregamento manual de imagens, a criação de marcas e o treinamento do Serviço para reconhecer objetos diferentes (no exemplo fornecido, um copo) definindo a Caixa de Limite dentro da imagem enviada.

Importante

Após a criação e o uso do aplicativo, o desenvolvedor deve navegar de volta para o Serviço de Visão Personalizada do Azure e identificar as previsões feitas pelo Serviço e determinar se eles estavam corretos ou não (por meio da marcação de qualquer coisa que o Serviço perdeu e do ajuste das Caixas deLimitadoras). Em seguida, o Serviço pode ser treinado novamente, o que aumentará a probabilidade de reconhecer objetos do mundo real.

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

Suporte a dispositivos

Curso HoloLens Headsets imersivos
MR e Azure 310: detecção de objetos ✔️

Pré-requisitos

Observação

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

Recomendamos o seguinte hardware e software para este curso:

Antes de começar

  1. Para evitar 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 longa podem causar problemas no tempo de build).
  2. Configure e teste o HoloLens. Se você precisar de suporte para isso, visite o artigo de configuração do HoloLens.
  3. É uma boa ideia executar a Calibragem 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 Calibragem, siga este link para o artigo Calibragem do HoloLens.

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

Capítulo 1 – Portal do Visão Personalizada

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

  1. Navegue até a página main do Serviço de Visão Personalizada.

  2. Clique em Introdução.

    Captura de tela que realça o botão Introdução.

  3. Entre no Portal do Visão Personalizada.

    Captura de tela que mostra o botão Entrar.

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

  5. Depois de fazer logon pela primeira vez, você será solicitado com o painel Termos de Serviço . Clique na caixa de seleção para concordar com os termos. Em seguida, clique em Concordo.

    Captura de tela que mostra o painel Termos de Serviço.

  6. Tendo concordado com os termos, agora você está na seção Meus Projetos . Clique em Novo Projeto.

    Captura de tela que mostra onde selecionar Novo Projeto.

  7. Uma guia será exibida no lado direito, o que solicitará que você especifique alguns campos para o projeto.

    1. Inserir um nome para seu projeto

    2. Inserir uma descrição para 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).

      Captura de tela que mostra onde adicionar detalhes para o novo projeto.

    4. Defina os Tipos de Projeto como Detecção de Objetos (versão prévia).

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

Capítulo 2 – Treinar 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 quinze (15) imagens para cada objeto que você gostaria que seu aplicativo reconhecesse. Você pode usar as imagens fornecidas com este curso (uma série de copos).

Para treinar seu projeto de Visão Personalizada:

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

    Captura de tela que mostra o botão + ao lado de Marcas.

  2. Adicione um nome para a marca que será usada para associar suas imagens. Neste exemplo, estamos usando imagens de copos para reconhecimento, portanto, nomeamos a marca para isso, Cup. Clique em Salvar quando terminar.

    Captura de tela que mostra onde adicionar um nome para a marca.

  3. Você observará que sua Marca foi adicionada (talvez seja necessário recarregar sua página para que ela apareça).

    Captura de tela que mostra onde sua marca é adicionada.

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

    Captura de tela que mostra onde adicionar imagens.

  5. Clique em Procurar arquivos locais e navegue até as imagens que deseja carregar para um objeto, com o mínimo sendo quinze (15).

    Dica

    Você pode selecionar várias imagens de cada vez para carregar.

    Captura de tela que mostra as imagens que você pode carregar.

  6. Pressione Carregar arquivos depois de selecionar todas as imagens com as quais deseja treinar o projeto. Os arquivos começarão a ser carregados. Depois de confirmar o upload, clique em Concluído.

    Captura de tela que mostra o progresso das imagens carregadas.

  7. Neste ponto, suas imagens são carregadas, mas não marcadas.

    Captura de tela que mostra uma imagem não registrada.

  8. Para marcar suas imagens, use o mouse. Ao passar o mouse sobre sua imagem, um realce de seleção ajudará você a desenhar automaticamente uma seleção ao redor do objeto. Se não for preciso, você pode desenhar o seu próprio. Isso é feito mantendo o clique à esquerda no mouse e arrastando a região de seleção para abranger seu objeto.

    Captura de tela que mostra como marcar uma imagem.

  9. Após a seleção do objeto dentro da imagem, um pequeno prompt solicitará que você adicione a marca de região. Selecione sua marca criada anteriormente ('Cup', no exemplo acima) ou, se você estiver adicionando mais marcas, digite-a e clique no botão + (mais).

    Captura de tela que mostra a marca que você adicionou à imagem.

  10. Para marcar a próxima imagem, você pode clicar na seta à direita da folha ou fechar a folha de marca (clicando no X no canto superior direito da folha) e, em seguida, clicar na próxima imagem. Depois de preparar a próxima imagem, repita o mesmo procedimento. Faça isso por todas as imagens que você carregou até que todas sejam marcadas.

    Observação

    Você pode selecionar vários objetos na mesma imagem, como a imagem abaixo:

    Captura de tela que mostra vários objetos em uma imagem.

  11. Depois de marcar todos eles, clique no botão marcado , à esquerda da tela, para revelar as imagens marcadas.

    Captura de tela que realça o botão Marcado.

  12. Agora você está pronto para treinar seu serviço. Clique no botão Treinar e a primeira iteração de treinamento será iniciada.

    Captura de tela que realça o botão Treinar.

    Captura de tela que mostra a primeira iteração de treinamento.

  13. Depois que ele for criado, você poderá ver dois botões chamados Tornar padrão e URL de Previsão. Clique em Tornar padrão primeiro e clique em URL de Previsão.

    Captura de tela que realça o botão Tornar padrão.

    Observação

    O ponto de extremidade fornecido a partir disso é definido como qualquer iteração que tenha sido marcada como padrão. Dessa forma, se posteriormente você fizer uma nova Iteração e atualizá-la como padrão, não precisará alterar seu código.

  14. Depois de clicar na URL de Previsão, abra o Bloco de Notas e copie e cole a URL (também chamada de Ponto de Extremidade de Previsão) e a Chave de Previsão de Serviço, para que você possa recuperá-la quando precisar dela mais tarde no código.

    Captura de tela que mostra o ponto de extremidade de previsão e a chave de predição.

Capítulo 3 – Configurar o projeto do Unity

Veja a seguir uma configuração típica para o desenvolvimento com realidade misturada e, como tal, é um bom modelo para outros projetos.

  1. Abra o Unity e clique em Novo.

    Captura de tela que realça o botão Novo.

  2. Agora você precisará fornecer um nome de projeto do Unity. Insira CustomVisionObjDetection. Verifique se o tipo de projeto está definido como 3D e defina o Local como um lugar apropriado para você (lembre-se, mais próximo dos diretórios raiz é melhor). Em seguida, clique em Criar projeto.

    Captura de tela que mostra os detalhes do projeto e onde selecionar Criar 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, em seguida, na nova janela, navegue até Ferramentas Externas. Altere o Editor de Script Externo para o Visual Studio. Feche a janela Preferências.

    Captura de tela que mostra onde alterar o Editor de Script Externo para o Visual Studio.

  4. Em seguida, vá para Configurações de Build de Arquivo > e alterne a Plataforma para Plataforma Universal do Windows e, em seguida, clique no botão Alternar Plataforma.

    Captura de tela que realça o botão Alternar Plataforma.

  5. Na mesma janela Configurações de Build , verifique se o seguinte está definido:

    1. O dispositivo de destino está definido como HoloLens

    2. O Tipo de Build é definido como D3D

    3. O SDK está definido como Mais recente instalado

    4. A versão do Visual Studio está definida como Mais Recente instalada

    5. Compilar e Executar é definido como Computador Local

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

      Captura de tela que mostra as opções de configuração de Configuração de Build.

  6. Na mesma janela Configurações de Build , clique no botão Configurações do Player , isso abrirá o painel relacionado no espaço em que o Inspetor está localizado.

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

    1. Na guia Outras Configurações :

      1. A versão de runtime de script deve ser experimental (equivalente ao .NET 4.6), o que disparará a necessidade de reiniciar o Editor.

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

      3. O Nível de Compatibilidade da API deve ser .NET 4.6.

        Captura de tela que mostra a opção Nível de Compatibilidade da API definida como .NET 4.6.

    2. Na guia Configurações de Publicação, em Recursos, marcar:

      1. InternetClient

      2. Webcam

      3. SpatialPerception

        Captura de tela que mostra a metade superior das opções de configuração funcionalidades.Captura de tela que mostra a metade inferior das opções de configuração funcionalidades.

    3. Mais abaixo no painel, em Configurações de XR (encontradas abaixo de Configurações de Publicação), marque Configurações de Realidade Virtual com Suporte e verifique se o SDK do Windows Mixed Reality foi adicionado.

      Captura de tela que mostra que o SDK do Windows Mixed Reality foi adicionado.

  8. De volta às Configurações de Build, os Projetos C# do Unity não estão mais esmaecidos : marque a caixa de seleção ao lado disso.

  9. Feche a janela Configurações de Build.

  10. No Editor, clique em Editar>Elementos Gráficos deConfigurações> do Projeto.

    Captura de tela que mostra a opção de menu Gráficos selecionada.

  11. No Painel do Inspetor , as Configurações Gráficas estarão abertas. Role para baixo até ver uma matriz chamada Sombreadores Always Include. Adicione um slot aumentando a variável Size em um (neste exemplo, era 8, portanto, fizemos 9). Um novo slot será exibido, na última posição da matriz, conforme mostrado abaixo:

    Captura de tela que realça a matriz Sombreadores Always Included.

  12. No slot, clique no pequeno círculo de destino ao lado do slot para abrir uma lista de sombreadores. Procure o sombreador Sombreador Herdado/Transparente/Difuso e clique duas vezes nele.

    Captura de tela que realça o sombreador Sombreador Herdado/Transparente/Difuso.

Capítulo 4 – Importando o pacote CustomVisionObjDetection Unity

Para este curso, você receberá um Pacote de Ativos do Unity chamado Azure-MR-310.unitypackage.

[DICA] Todos os objetos com suporte do Unity, incluindo cenas inteiras, podem ser empacotados em um arquivo .unitypackage e exportados/importados em outros projetos. É a maneira mais segura e eficiente de mover ativos entre diferentes projetos do Unity.

Você pode encontrar o pacote Azure-MR-310 que precisa baixar aqui.

  1. Com a dashboard do Unity na sua frente, clique em Ativos no menu na parte superior da tela e clique em Importar Pacote > Personalizado.

    Captura de tela que realça a opção de menu Pacote Personalizado.

  2. Use o seletor de arquivos para selecionar o pacote Azure-MR-310.unitypackage e clique em Abrir. Uma lista de componentes desse ativo será exibida para você. Confirme a importação clicando no botão Importar .

    Captura de tela que mostra a lista de componentes de ativos que você deseja importar.

  3. Depois de concluir a importação, você observará que as pastas do pacote agora foram adicionadas à pasta Ativos . Esse tipo de estrutura de pastas é típico para um projeto do Unity.

    Captura de tela que mostra o conteúdo da pasta Ativos.

    1. A pasta Materiais contém o material usado pelo Cursor de Foco.

    2. A pasta Plug-ins contém a DLL Newtonsoft usada pelo código para desserializar a resposta web do serviço. As duas (2) versões diferentes contidas na pasta e na subpasta são necessárias para permitir que a biblioteca seja usada e criada pelo Editor do Unity e pelo build UWP.

    3. A pasta Prefabs contém os pré-fabricados contidos na cena. Eles são:

      1. O GazeCursor, o cursor usado no aplicativo. Trabalhará em conjunto com o pré-fabricado SpatialMapping para poder ser colocado na cena sobre objetos físicos.
      2. O Rótulo, que é o objeto de interface do usuário usado para exibir a marca de objeto na cena quando necessário.
      3. O SpatialMapping, que é o objeto que permite que o aplicativo use a criação de um mapa virtual, usando o acompanhamento espacial do Microsoft HoloLens.
    4. A pasta Cenas que atualmente contém a cena pré-criada para este curso.

  4. Abra a pasta Cenas , no Painel do Projeto, e clique duas vezes em ObjDetectionScene para carregar a cena que você usará para este curso.

    Captura de tela que mostra o ObjDetectionScene na pasta Cenas.

    Observação

    Nenhum código está incluído, você escreverá o código seguindo este curso.

Capítulo 5 – Criar a classe CustomVisionAnalyser.

Neste ponto, você está pronto para escrever algum código. Você começará com a classe CustomVisionAnalyser .

Observação

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

Essa 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 Serviço de Visão Personalizada do Azure para análise.

  • Recebendo a resposta como uma cadeia de caracteres JSON.

  • Desserializando a resposta e passando a Previsão resultante para a classe SceneOrganiser , que cuidará de como a resposta deve ser exibida.

Para criar essa 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.

    Captura de tela que mostra como criar a pasta Scripts.

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

  3. Clique com o botão direito do mouse dentro da pasta e 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. Verifique se você tem os seguintes namespaces referenciados na parte superior do arquivo:

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

    Observação

    Insira a Chave de Previsão de Serviço na variável predictionKey e no ponto de extremidade de previsão na variável predictionEndpoint . Você os copiou para o Bloco de Notas anteriormente, no Capítulo 2, Etapa 14.

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

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Adicione a corrotina (com o método GetImageAsByteArray() estático abaixo dela), que obterá os resultados da análise da imagem, capturada pela classe ImageCapture .

    Observação

    Na corrotina AnalyzeImageCapture , há uma chamada para a classe SceneOrganiser que você ainda está para criar. Portanto, deixe essas linhas comentadas por enquanto.

        /// <summary>
        /// Call the Computer Vision Service to submit the image.
        /// </summary>
        public IEnumerator AnalyseLastImageCaptured(string imagePath)
        {
            Debug.Log("Analyzing...");
    
            WWWForm webForm = new WWWForm();
    
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                Debug.Log("response: " + jsonResponse);
    
                // Create a texture. Texture size does not matter, since
                // LoadImage will replace with the incoming image size.
                //Texture2D tex = new Texture2D(1, 1);
                //tex.LoadImage(imageBytes);
                //SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
                // The response will be in JSON format, therefore it needs to be deserialized
                //AnalysisRootObject analysisRootObject = new AnalysisRootObject();
                //analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
                //SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  9. Exclua os métodos Start() e Update(), pois eles não serão usados.

  10. Salve as alterações no Visual Studio antes de retornar ao Unity.

Importante

Conforme mencionado anteriormente, não se preocupe com o código que pode parecer ter um erro, pois você fornecerá mais classes em breve, o que as corrigirá.

Capítulo 6 – 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 ao Serviço de Visão Personalizada.

Para criar essa 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. Verifique se você tem os seguintes namespaces referenciados na 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.

    Aviso

    É importante que você siga a próxima instrução com cuidado. Se você colocar as novas declarações de classe dentro da classe CustomVisionObjects , receberá erros de compilação no capítulo 10, informando que AnalysisRootObject e BoundingBox não foram encontrados.

  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
    /// Includes Bounding Box
    /// </summary>
    public class AnalysisRootObject
    {
        public string id { get; set; }
        public string project { get; set; }
        public string iteration { get; set; }
        public DateTime created { get; set; }
        public List<Prediction> predictions { get; set; }
    }
    
    public class BoundingBox
    {
        public double left { get; set; }
        public double top { get; set; }
        public double width { get; set; }
        public double height { get; set; }
    }
    
    public class Prediction
    {
        public double probability { get; set; }
        public string tagId { get; set; }
        public string tagName { get; set; }
        public BoundingBox boundingBox { get; set; }
    }
    
  6. Salve as alterações no Visual Studio antes de retornar ao Unity.

Capítulo 7 – Criar a classe SpatialMapping

Essa classe definirá o Colisor de Mapeamento Espacial na cena para poder detectar colisões entre objetos virtuais e objetos reais.

Para criar essa classe:

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

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

  3. Verifique se você tem os seguintes namespaces referenciados acima da classe SpatialMapping :

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. Em seguida, adicione as seguintes variáveis dentro da classe SpatialMapping, acima do método Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static SpatialMapping Instance;
    
        /// <summary>
        /// Used by the GazeCursor as a property with the Raycast call
        /// </summary>
        internal static int PhysicsRaycastMask;
    
        /// <summary>
        /// The layer to use for spatial mapping collisions
        /// </summary>
        internal int physicsLayer = 31;
    
        /// <summary>
        /// Creates environment colliders to work with physics
        /// </summary>
        private SpatialMappingCollider spatialMappingCollider;
    
  5. Adicione Awake() e Start():

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Initialize and configure the collider
            spatialMappingCollider = gameObject.GetComponent<SpatialMappingCollider>();
            spatialMappingCollider.surfaceParent = this.gameObject;
            spatialMappingCollider.freezeUpdates = false;
            spatialMappingCollider.layer = physicsLayer;
    
            // define the mask
            PhysicsRaycastMask = 1 << physicsLayer;
    
            // set the object as active one
            gameObject.SetActive(true);
        }
    
  6. Exclua o método Update( ).

  7. Salve as alterações no Visual Studio antes de retornar ao Unity.

Capítulo 8 – Criar a classe GazeCursor

Essa classe é responsável por configurar o cursor no local correto no espaço real, fazendo uso do SpatialMappingCollider, criado no capítulo anterior.

Para criar essa classe:

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

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

  3. Verifique se você tem o seguinte namespace referenciado acima da classe GazeCursor :

    using UnityEngine;
    
  4. Em seguida, adicione a variável a seguir dentro da classe GazeCursor, acima do método Start().

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Atualize o método Start() com o seguinte código:

        /// <summary>
        /// Runs at initialization right after the Awake method
        /// </summary>
        void Start()
        {
            // Grab the mesh renderer that is on the same object as this script.
            meshRenderer = gameObject.GetComponent<MeshRenderer>();
    
            // Set the cursor reference
            SceneOrganiser.Instance.cursor = gameObject;
            gameObject.GetComponent<Renderer>().material.color = Color.green;
    
            // If you wish to change the size of the cursor you can do so here
            gameObject.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
        }
    
  6. Atualize o método Update() com o seguinte código:

        /// <summary>
        /// Update is called once per frame
        /// </summary>
        void Update()
        {
            // Do a raycast into the world based on the user's head position and orientation.
            Vector3 headPosition = Camera.main.transform.position;
            Vector3 gazeDirection = Camera.main.transform.forward;
    
            RaycastHit gazeHitInfo;
            if (Physics.Raycast(headPosition, gazeDirection, out gazeHitInfo, 30.0f, SpatialMapping.PhysicsRaycastMask))
            {
                // If the raycast hit a hologram, display the cursor mesh.
                meshRenderer.enabled = true;
                // Move the cursor to the point where the raycast hit.
                transform.position = gazeHitInfo.point;
                // Rotate the cursor to hug the surface of the hologram.
                transform.rotation = Quaternion.FromToRotation(Vector3.up, gazeHitInfo.normal);
            }
            else
            {
                // If the raycast did not hit a hologram, hide the cursor mesh.
                meshRenderer.enabled = false;
            }
        }
    

    Observação

    Não se preocupe com o erro para a classe SceneOrganiser não ser encontrada, você o criará no próximo capítulo.

  7. Salve as alterações no Visual Studio antes de retornar ao Unity.

Capítulo 9 – Criar a classe SceneOrganiser

Essa classe vai:

  • Configure a Câmera Principal anexando os componentes apropriados a ela.

  • Quando um objeto for detectado, ele será responsável por calcular sua posição no mundo real e colocar um Rótulo de Marca perto dele com o Nome da Marca apropriado.

Para criar essa classe:

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

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

  3. Verifique se você tem os seguintes namespaces referenciados acima da classe SceneOrganiser :

    using System.Collections.Generic;
    using System.Linq;
    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 Main Camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        public GameObject label;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last Label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.8f;
    
        /// <summary>
        /// The quad object hosting the imposed image captured
        /// </summary>
        private GameObject quad;
    
        /// <summary>
        /// Renderer of the quad object
        /// </summary>
        internal Renderer quadRenderer;
    
  5. Exclua os métodos Start() e Update().

  6. 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 CustomVisionObjects class to this Gameobject
            gameObject.AddComponent<CustomVisionObjects>();
        }
    
  7. Adicione o método PlaceAnalysisLabel(), que criará uma instância do rótulo na cena (que neste ponto é invisível para o usuário). Ele também coloca o quad (também invisível) onde a imagem é colocada e se sobrepõe ao mundo real. Isso é importante porque as coordenadas da caixa recuperadas do Serviço após a análise são rastreadas de volta para esse quadrante para determinar a localização aproximada do objeto no mundo real.

        /// <summary>
        /// Instantiate a Label in the appropriate location relative to the Main Camera.
        /// </summary>
        public void PlaceAnalysisLabel()
        {
            lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
            lastLabelPlacedText.text = "";
            lastLabelPlaced.transform.localScale = new Vector3(0.005f,0.005f,0.005f);
    
            // Create a GameObject to which the texture can be applied
            quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
            quadRenderer = quad.GetComponent<Renderer>() as Renderer;
            Material m = new Material(Shader.Find("Legacy Shaders/Transparent/Diffuse"));
            quadRenderer.material = m;
    
            // Here you can set the transparency of the quad. Useful for debugging
            float transparency = 0f;
            quadRenderer.material.color = new Color(1, 1, 1, transparency);
    
            // Set the position and scale of the quad depending on user position
            quad.transform.parent = transform;
            quad.transform.rotation = transform.rotation;
    
            // The quad is positioned slightly forward in font of the user
            quad.transform.localPosition = new Vector3(0.0f, 0.0f, 3.0f);
    
            // The quad scale as been set with the following value following experimentation,  
            // to allow the image on the quad to be as precisely imposed to the real world as possible
            quad.transform.localScale = new Vector3(3f, 1.65f, 1f);
            quad.transform.parent = null;
        }
    
  8. Adicione o método FinaliseLabel(). Ele é responsável por:

    • Definindo o texto Rótulo com a Marca da Previsão com a maior confiança.
    • Chamando o cálculo da Caixa deLimitadora no objeto quad, posicionado anteriormente e colocando o rótulo na cena.
    • Ajustando a profundidade do rótulo usando um Raycast em direção à Caixa deLimitadora, que deve colidir contra o objeto no mundo real.
    • Redefinir o processo de captura para permitir que o usuário capture outra imagem.
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void FinaliseLabel(AnalysisRootObject analysisObject)
        {
            if (analysisObject.predictions != null)
            {
                lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
                // Sort the predictions to locate the highest one
                List<Prediction> sortedPredictions = new List<Prediction>();
                sortedPredictions = analysisObject.predictions.OrderBy(p => p.probability).ToList();
                Prediction bestPrediction = new Prediction();
                bestPrediction = sortedPredictions[sortedPredictions.Count - 1];
    
                if (bestPrediction.probability > probabilityThreshold)
                {
                    quadRenderer = quad.GetComponent<Renderer>() as Renderer;
                    Bounds quadBounds = quadRenderer.bounds;
    
                    // Position the label as close as possible to the Bounding Box of the prediction 
                    // At this point it will not consider depth
                    lastLabelPlaced.transform.parent = quad.transform;
                    lastLabelPlaced.transform.localPosition = CalculateBoundingBoxPosition(quadBounds, bestPrediction.boundingBox);
    
                    // Set the tag text
                    lastLabelPlacedText.text = bestPrediction.tagName;
    
                    // Cast a ray from the user's head to the currently placed label, it should hit the object detected by the Service.
                    // At that point it will reposition the label where the ray HL sensor collides with the object,
                    // (using the HL spatial tracking)
                    Debug.Log("Repositioning Label");
                    Vector3 headPosition = Camera.main.transform.position;
                    RaycastHit objHitInfo;
                    Vector3 objDirection = lastLabelPlaced.position;
                    if (Physics.Raycast(headPosition, objDirection, out objHitInfo, 30.0f,   SpatialMapping.PhysicsRaycastMask))
                    {
                        lastLabelPlaced.position = objHitInfo.point;
                    }
                }
            }
            // Reset the color of the cursor
            cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the analysis process
            ImageCapture.Instance.ResetImageCapture();        
        }
    
  9. Adicione o método CalculateBoundingBoxPosition(), que hospeda vários cálculos necessários para traduzir as coordenadas da Caixa deLimitadora recuperadas do Serviço e recriá-las proporcionalmente no quadriciclo.

        /// <summary>
        /// This method hosts a series of calculations to determine the position 
        /// of the Bounding Box on the quad created in the real world
        /// by using the Bounding Box received back alongside the Best Prediction
        /// </summary>
        public Vector3 CalculateBoundingBoxPosition(Bounds b, BoundingBox boundingBox)
        {
            Debug.Log($"BB: left {boundingBox.left}, top {boundingBox.top}, width {boundingBox.width}, height {boundingBox.height}");
    
            double centerFromLeft = boundingBox.left + (boundingBox.width / 2);
            double centerFromTop = boundingBox.top + (boundingBox.height / 2);
            Debug.Log($"BB CenterFromLeft {centerFromLeft}, CenterFromTop {centerFromTop}");
    
            double quadWidth = b.size.normalized.x;
            double quadHeight = b.size.normalized.y;
            Debug.Log($"Quad Width {b.size.normalized.x}, Quad Height {b.size.normalized.y}");
    
            double normalisedPos_X = (quadWidth * centerFromLeft) - (quadWidth/2);
            double normalisedPos_Y = (quadHeight * centerFromTop) - (quadHeight/2);
    
            return new Vector3((float)normalisedPos_X, (float)normalisedPos_Y, 0);
        }
    
  10. Salve suas alterações no Visual Studio antes de retornar ao Unity.

    Importante

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

    // Create a texture. Texture size does not matter, since 
    // LoadImage will replace with the incoming image size.
    Texture2D tex = new Texture2D(1, 1);
    tex.LoadImage(imageBytes);
    SceneOrganiser.Instance.quadRenderer.material.SetTexture("_MainTex", tex);
    
    // The response will be in JSON format, therefore it needs to be deserialized
    AnalysisRootObject analysisRootObject = new AnalysisRootObject();
    analysisRootObject = JsonConvert.DeserializeObject<AnalysisRootObject>(jsonResponse);
    
    SceneOrganiser.Instance.FinaliseLabel(analysisRootObject);
    

Observação

Não se preocupe com a mensagem "Não foi possível encontrar a classe ImageCapture ", você a criará no próximo capítulo.

Capítulo 10 – Criar a classe ImageCapture

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

Essa classe é responsável por:

  • Capturando uma imagem usando a câmera do HoloLens e armazenando-a na pasta Aplicativo .
  • Manipulando gestos de toque do usuário.

Para criar essa classe:

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

  2. Clique com o botão direito do mouse dentro da pasta e 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>
        /// 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 métodos Awake() e Start() agora precisa ser adicionado:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the Microsoft HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  7. Implemente um manipulador que será chamado quando ocorrer um gesto de toque:

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            if (!captureIsActive)
            {
                captureIsActive = true;
    
                // Set the cursor color to red
                SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                // Begin the capture loop
                Invoke("ExecuteImageCaptureAndAnalysis", 0);
            }
        }
    

    Importante

    Quando o cursor está verde, significa que a câmera está disponível para tirar a imagem. Quando o cursor está vermelho, significa que a câmera 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()
        {
            // Create a label in world space using the ResultsLabel class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending
                ((res) => res.width * res.height).First();
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(true, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 1.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });
        }
    
  9. Adicione os manipuladores que serão chamados quando a foto tiver sido capturada e para quando ela estiver pronta para ser analisada. Em seguida, o resultado é passado para o CustomVisionAnalyser para análise.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
            try
            {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
            }
            catch (Exception e)
            {
                Debug.LogFormat("Exception capturing photo to disk: {0}", e.Message);
            }
        }
    
        /// <summary>
        /// The camera photo mode has stopped after the capture.
        /// Begin the image analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            Debug.LogFormat("Stopped Photo Mode");
    
            // Dispose from the object in memory and request the image analysis 
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            // Call the image analysis
            StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath)); 
        }
    
        /// <summary>
        /// Stops all capture pending actions
        /// </summary>
        internal void ResetImageCapture()
        {
            captureIsActive = false;
    
            // Set the cursor color to green
            SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Salve suas alterações no Visual Studio antes de retornar ao Unity.

Capítulo 11 – Configurando os scripts na cena

Agora que você escreveu todo o código necessário para esse projeto, é hora de configurar os scripts na cena e, nos pré-fabricados, para que eles se comportem corretamente.

  1. No Editor do Unity, no Painel hierarquia, selecione a Câmera Principal.

  2. No Painel do Inspetor, com a Câmera Principal selecionada, clique em Adicionar Componente e, em seguida, pesquise o script SceneOrganiser e clique duas vezes para adicioná-lo.

    Captura de tela que mostra o script SceneOrganizer.

  3. No Painel de Projeto, abra a pasta Prefabs, arraste o pré-fabricado Rótulo para a área de entrada de destino de referência vazia Rótulo , no script SceneOrganiser que você acabou de adicionar à Câmera Principal, conforme mostrado na imagem abaixo:

    Captura de tela que mostra o script que você adicionou à Câmera Principal.

  4. No Painel de Hierarquia, selecione o filho GazeCursor da Câmera Principal.

  5. No Painel do Inspetor, com o GazeCursor selecionado, clique em Adicionar Componente e, em seguida, pesquise script GazeCursor e clique duas vezes para adicioná-lo.

    Captura de tela que mostra onde você adiciona o script GazeCursor.

  6. Novamente, no Painel de Hierarquia, selecione o filho SpatialMapping da Câmera Principal.

  7. No Painel do Inspetor, com SpatialMapping selecionado, clique em Adicionar Componente e, em seguida, pesquise Script SpatialMapping e clique duas vezes para adicioná-lo.

    Captura de tela que mostra onde você adiciona o script SpatialMapping.

Os scripts restantes que você não definiu serão adicionados pelo código no script SceneOrganiser durante o runtime.

Capítulo 12 – Antes da construção

Para executar um teste completo do aplicativo, você precisará fazer sideload dele no Microsoft HoloLens.

Antes disso, verifique se:

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

  • O script SceneOrganiser é anexado ao objeto Câmera Principal .

  • O script GazeCursor é anexado ao objeto GazeCursor .

  • O script SpatialMapping é anexado ao objeto SpatialMapping .

  • No Capítulo 5, Etapa 6:

    • Insira a Chave de Previsão de Serviço na variável predictionKey .
    • Você inseriu o ponto de extremidade de previsão na classe predictionEndpoint .

Capítulo 13 – Criar a solução UWP e fazer sideload do aplicativo

Agora você está pronto para criar seu aplicativo como uma Solução UWP na qual poderá implantar no Microsoft HoloLens. Para iniciar o processo de build:

  1. Vá para Configurações de Build de Arquivo>.

  2. Marque projetos C# do Unity.

  3. Clique em Adicionar Cenas Abertas. Isso adicionará a cena aberta no momento ao build.

    Captura de tela que realça o botão Adicionar Cenas Abertas.

  4. Clique em Compilar. O Unity iniciará uma janela Explorador de Arquivos, na qual você precisa criar e, em seguida, selecionará uma pasta para criar o aplicativo. Crie essa pasta agora e nomeie-a como Aplicativo. Em seguida, com a pasta Aplicativo selecionada, clique em Selecionar Pasta.

  5. O Unity começará a compilar seu projeto para a pasta Aplicativo .

  6. Depois que o Unity terminar de compilar (pode levar algum tempo), ele abrirá uma janela Explorador de Arquivos no local do build (marcar sua barra de tarefas, pois ela pode nem sempre aparecer acima das janelas, mas notificará você sobre a adição de uma nova janela).

  7. Para implantar no Microsoft HoloLens, você precisará do endereço IP desse dispositivo (para Implantação Remota) e para garantir que ele também tenha o Modo de Desenvolvedor definido. Para fazer isso:

    1. Ao usar o HoloLens, abra as Configurações.

    2. Vá para Rede &Opções Avançadasde 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 Modo de DesenvolvedorAtivado.

  8. Navegue até o novo build do Unity (a pasta Aplicativo ) e abra o arquivo de solução com o Visual Studio.

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

  10. Na Plataforma de Solução, selecione x86, Computador Remoto. Você será solicitado a inserir o endereço IP de um dispositivo remoto (o Microsoft HoloLens, nesse caso, que você anotou).

    Captura de tela que mostra onde inserir o endereço IP.

  11. Vá para o menu Compilar e clique em Implantar Solução para fazer sideload do aplicativo no HoloLens.

  12. Seu aplicativo agora deve aparecer na lista de aplicativos instalados em seu Microsoft HoloLens, prontos para serem iniciados!

Para usar o aplicativo:

  • Examine um objeto, que você treinou com o Serviço de Visão Personalizada do Azure, a Detecção de Objetos e use o gesto Tocar.
  • Se o objeto for detectado com êxito, um texto de rótulo de espaço mundial aparecerá com o nome da marca.

Importante

Sempre que você captura uma foto e a envia para o Serviço, você pode voltar para a página Serviço e treinar novamente o Serviço com as imagens recém-capturadas. No início, você provavelmente também precisará corrigir as Caixas deLimitadoras para ser mais preciso e treinar novamente o Serviço.

Observação

O Texto do Rótulo colocado pode não aparecer perto do objeto quando o Microsoft HoloLens sensores e/ou SpatialTrackingComponent no Unity não consegue colocar os colisores apropriados, em relação aos objetos do mundo real. Tente usar o aplicativo em uma superfície diferente se esse for o caso.

Seu Visão Personalizada, aplicativo de Detecção de Objetos

Parabéns, você criou um aplicativo de realidade misturada que aproveita o Visão Personalizada do Azure, a API de Detecção de Objetos, que pode reconhecer um objeto de uma imagem e, em seguida, fornecer uma posição aproximada para esse objeto no espaço 3D.

Captura de tela que mostra um aplicativo de realidade misturada que aproveita o Visão Personalizada do Azure, a API de Detecção de Objetos.

Exercícios de bônus

Exercício 1

Adicionando ao Rótulo de Texto, use um cubo semitransparente para encapsular o objeto real em uma Caixa deLimitadora 3D.

Exercício 2

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

Exercício 3

Reproduza um som quando um objeto é reconhecido.

Exercício 4

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