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 o 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 os recursos de "Detecção de Objetos" da Visão Personalizada do Azure em um aplicativo de realidade misturada.

Esse 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 e aproximar sua localização no mundo real, conforme fornecido pela captura de câmera do Microsoft HoloLens ou uma câmera conectada a um computador para headsets imersivos (VR).

Resultado do curso

A Visão Personalizada do Azure, 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 agilizar esse processo. Para obter mais informações, visite os seguintes links:

Após a conclusão deste curso, você terá um aplicativo de realidade mista 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á vendo.
  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 do espaço mundial. Isso será feito por meio da utilização do Rastreamento Espacial do Microsoft HoloLens, como uma forma de entender a posição mundial do objeto reconhecido e, em seguida, usando a Tag associada ao que é detectado na imagem, para fornecer o texto do rótulo.

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

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, identificar as previsões feitas pelo Serviço e determinar se elas estavam corretas ou não (marcando qualquer coisa que o Serviço perdeu e ajustando as Caixas Delimitadoras). O Serviço pode então 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 em Unity. Caberá a você aplicar esses conceitos a um aplicativo personalizado que você possa estar criando.

Suporte a dispositivos

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

Pré-requisitos

Observação

Este tutorial foi desenvolvido para desenvolvedores que têm experiência básica com Unity e C#. Esteja ciente também de que os pré-requisitos e as instruções escritas 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 deste curso corresponderão perfeitamente ao que você encontrará em softwares mais recentes do que os listados 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 longos podem causar problemas no momento da compilação).
  2. Configure e teste seu HoloLens. Se você precisar de suporte para isso, visite o artigo de instalaçã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 a calibração, siga este link para o artigo Calibração do HoloLens.

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

Capítulo 1 – O Portal de 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 principal do Serviço de Visão Personalizada.

  2. Clique em Introdução.

    Captura de tela que destaca o botão Introdução.

  3. Entre no Portal de 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 sala de aula ou laboratório, peça ajuda ao seu instrutor ou a um dos supervisores para configurar sua nova conta.

  5. Depois de fazer login 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, você está agora na seção Meus projetos . Clique em Novo Projeto.

    Captura de tela que mostra onde selecionar Novo Projeto.

  7. Uma guia aparecerá no lado direito, solicitando 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).

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

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

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

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

Uma vez no Portal de 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 deseja que seu aplicativo reconheça. Você pode usar as imagens fornecidas com este curso (uma série de xícaras).

Para treinar seu projeto de Visão Personalizada:

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

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

  2. Adicione um nome para a tag que será usada para associar suas imagens. Neste exemplo, estamos usando imagens de xícaras para reconhecimento, então nomeamos a tag para isso, Copa. Clique em Salvar quando terminar.

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

  3. Você notará que sua tag foi adicionada (pode ser necessário recarregar sua página para que ela apareça).

    Captura de tela que mostra onde sua tag foi 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 enviar para um objeto, com o mínimo de quinze (15).

    Dica

    Você pode selecionar várias imagens ao mesmo tempo para fazer upload.

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

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

  8. Para marcar suas imagens, use o mouse. À medida que você passa o mouse sobre sua imagem, um destaque de seleção o ajudará desenhando automaticamente uma seleção ao redor do objeto. Se não for preciso, você pode desenhar o seu próprio. Isso é feito segurando o botão esquerdo do 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 seu objeto na imagem, um pequeno prompt solicitará que você adicione a tag de região. Selecione sua tag criada anteriormente ('Cup', no exemplo acima) ou, se estiver adicionando mais tags, digite-a e clique no botão + (mais).

    Captura de tela que mostra a tag 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 clicar na próxima imagem. Depois de ter a próxima imagem pronta, repita o mesmo procedimento. Faça isso para todas as imagens que você carregou, até que todas sejam marcadas.

    Observação

    Você pode selecionar vários objetos na mesma imagem, como na 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 destaca 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 começará.

    Captura de tela que destaca o botão Treinar.

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

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

    Captura de tela que destaca o botão Tornar padrão.

    Observação

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

  14. Depois de clicar em 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 posteriormente 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 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 destaca o botão Novo.

  2. Agora você precisará fornecer um nome de projeto do Unity. Insira CustomVisionObjDetection. Certifique-se de que o tipo de projeto esteja definido como 3D e defina o Local para 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, na nova janela, navegue até Ferramentas externas. Altere o Editor de Script Externo para Visual Studio. Feche a janela Preferências.

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

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

    Captura de tela que destaca o botão Alternar plataforma.

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

    1. O dispositivo de destino está definido como HoloLens

    2. O Tipo de Construção está definido como D3D

    3. O SDK está definido como Instalado mais recente

    4. A versão do Visual Studio está definida como Mais recente instalado

    5. Build and Run está 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 da Definição de Build.

  6. Na mesma janela Configurações de construçã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 tempo de execução 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, marque:

      1. InternetClient

      2. Webcam

      3. SpatialPerception

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

    3. Mais abaixo no painel, em Configurações XR (encontradas abaixo de Configurações de Publicação), marque 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>gráficos de configurações> do projeto.

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

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

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

  12. No slot, clique no pequeno círculo de destino ao lado do slot para abrir uma lista de shaders. Procure o sombreador Legacy Shaders/Transparent/Diffuse e clique duas vezes nele.

    Captura de tela que destaca o sombreador Legacy Shaders/Transparent/Diffuse.

Capítulo 4 – Importando o pacote CustomVisionObjDetection do Unity

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

[DICA] Todos os objetos suportados pelo 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 o painel do Unity à sua frente, clique em Ativos no menu na parte superior da tela e, em seguida, clique em Importar pacote > personalizado.

    Captura de tela que destaca 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 para este 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 terminar a importação, você notará que as pastas do pacote 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 do Olhar.

    2. A pasta Plugins contém a DLL Newtonsoft usada pelo código para desserializar a resposta da 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 pela compilação UWP.

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

      1. O GazeCursor, o cursor usado no aplicativo. Funcionará em conjunto com o pré-fabricado SpatialMapping para poder ser colocado na cena em cima de 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 rastreamento 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 – Crie 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 da 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). Lembre-se de que a Microsoft oferece um SDK 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 SDK da Visão Personalizada.

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 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 Prediction 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 Ativo, localizada no painel Projeto, e clique em Criar>pasta. Chame a pasta de 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

    Certifique-se de inserir sua Chave de Previsão de Serviço na variável predictionKey e seu 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 estático GetImageAsByteArray() abaixo dela), que obterá os resultados da análise da imagem, capturados pela classe ImageCapture .

    Observação

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

Importante

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

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 para o 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 de 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 as próximas instruções cuidadosamente. Se você colocar as novas declarações de classe dentro da classe CustomVisionObjects , obterá 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. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

Capítulo 7 – Criar a classe SpatialMapping

Esta classe definirá o Spatial Mapping Collider 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 o awake() e o 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. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

Capítulo 8 – Criar a classe GazeCursor

Esta 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 seguinte variável 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 da classe SceneOrganiser não ser encontrada, você a criará no próximo capítulo.

  7. Certifique-se de salvar suas alterações no Visual Studio antes de retornar ao Unity.

Capítulo 9 – Criar a classe SceneOrganiser

Esta classe irá:

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

  • Quando um objeto é detectado, ele será responsável por calcular sua posição no mundo real e colocar um Tag Label próximo a ele com o Tag Name 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 como 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 instanciará o rótulo na cena (que neste momento é 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 quad 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:

    • Definir o texto do rótulo com a marca da previsão com a maior confiança.
    • Chamar o cálculo da Caixa Delimitadora no objeto quad, posicionado anteriormente, e colocar o rótulo na cena.
    • Ajustando a profundidade do rótulo usando um Raycast em direção à caixa delimitadora, que deve colidir com o objeto no mundo real.
    • Redefinindo 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 converter as coordenadas da caixa delimitadora recuperadas do serviço e recriá-las proporcionalmente no quad.

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

    Importante

    Antes de continuar, abra a classe CustomVisionAnalyser e, no método AnalyseLastImageCaptured(), 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' da classe ImageCapture , você a criará no próximo capítulo.

Capítulo 10 – Criar a classe ImageCapture

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

Esta classe é responsável por:

  • Capturar uma imagem usando a câmera do HoloLens e armazená-la na pasta App .
  • 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 os 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 armazene 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 for capturada e quando estiver pronta para ser analisada. O resultado é então 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. Certifique-se de salvar 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 este 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 de Hierarquia, selecione a Câmera Principal.

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

    Captura de tela que mostra o script SceneOrganizer.

  3. No Painel do Projeto, abra a pasta Prefabs, arraste o prefab Label para a área de entrada de destino de referência vazia do Label , 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 Inspetor, com o GazeCursor selecionado, clique em Adicionar componente, procure o 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 Inspetor, com o SpatialMapping selecionado, clique em Adicionar componente, procure o 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 de construir

Para executar um teste completo do seu aplicativo, você precisará carregá-lo no Microsoft HoloLens.

Antes de fazer isso, certifique-se de que:

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

  • O script SceneOrganiser é anexado ao objeto Main Camera .

  • O script GazeCursor é anexado ao objeto GazeCursor .

  • O script SpatialMapping é anexado ao objeto SpatialMapping .

  • No Capítulo 5, Etapa 6:

    • Certifique-se de inserir sua Chave de Previsão de Serviço na variável predictionKey .
    • Você inseriu seu 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 que poderá implantar no Microsoft HoloLens. Para iniciar o processo de compilação:

  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 à compilação.

    Captura de tela que destaca o botão Adicionar cenas abertas.

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

  5. O Unity começará a criar seu projeto na pasta App .

  6. Depois que o Unity terminar de compilar (pode levar algum tempo), ele abrirá uma janela do Explorador de Arquivos no local da compilação (verifique a barra de tarefas, pois nem sempre ela aparece 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 garantir que ele também tenha o Modo de Desenvolvedor definido. Para fazer isso:

    1. Enquanto estiver usando seu HoloLens, abra as Configurações.

    2. Ir para Rede e Internet>Wi-Fi>Opções avançadas

    3. Observe o endereço IPv4 .

    4. Em seguida, navegue de volta para Configurações e, em seguida, para Atualização e segurança>para desenvolvedores

    5. Ative o modo de desenvolvedor.

  8. Navegue até o novo build do Unity (a pasta App ) 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ções, selecione x86, Computador Remoto. Você será solicitado a inserir o endereço IP de um dispositivo remoto (o Microsoft HoloLens, neste 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 o sideload do aplicativo no HoloLens.

  12. Seu aplicativo agora deve aparecer na lista de aplicativos instalados em seu Microsoft HoloLens, pronto para ser iniciado!

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 sucesso, um texto de rótulo de espaço mundial aparecerá com o nome da tag.

Importante

Toda vez que você captura uma foto e a envia para o Serviço, você pode voltar para a página do Serviço e treinar novamente o Serviço com as imagens recém-capturadas. No início, você provavelmente também terá que corrigir as caixas delimitadoras para serem mais precisas e treinar novamente o serviço.

Observação

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

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

Parabéns, você criou um aplicativo de realidade misturada que aproveita a Visão Personalizada do Azure, 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 a Visão Personalizada do Azure, 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 envolver 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 for reconhecido.

Exercício 4

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 a previsão e o treinamento simultaneamente).