HoloLens (1.ª generación) y Azure 310: Detección de objetos

Nota

Los tutoriales de Mixed Reality Academy se han diseñado teniendo en cuenta HoloLens (1.ª generación) y los cascos envolventes de realidad mixta. Por lo tanto, creemos que es importante conservar estos tutoriales para los desarrolladores que sigan buscando instrucciones sobre el desarrollo para esos dispositivos. Estos tutoriales no se actualizarán con los conjuntos de herramientas o las interacciones más recientes que se usan para HoloLens 2. Se mantendrán para que sigan funcionando en los dispositivos compatibles. Habrá una nueva serie de tutoriales que se publicarán en el futuro que demostrarán cómo desarrollar para HoloLens 2. Este aviso se actualizará con un vínculo a esos tutoriales cuando se publiquen.


En este curso, aprenderá a reconocer contenido visual personalizado y su posición espacial dentro de una imagen proporcionada, mediante Azure Custom Vision funcionalidades de "Detección de objetos" en una aplicación de realidad mixta.

Este servicio le permitirá entrenar un modelo de Machine Learning mediante imágenes de objetos. A continuación, usará el modelo entrenado para reconocer objetos similares y aproximarse a su ubicación en el mundo real, según lo proporcionado por la captura de cámara de Microsoft HoloLens o una cámara se conecte a un EQUIPO para cascos envolventes (VR).

resultado del curso

Azure Custom Vision, detección de objetos es un servicio de Microsoft que permite a los desarrolladores crear clasificadores de imágenes personalizados. A continuación, estos clasificadores se pueden usar con nuevas imágenes para detectar objetos dentro de esa nueva imagen, proporcionando límites de box dentro de la propia imagen. El servicio proporciona un portal en línea sencillo y fácil de usar para simplificar este proceso. Para más información, visite los vínculos siguientes:

Al finalizar este curso, tendrá una aplicación de realidad mixta que podrá hacer lo siguiente:

  1. El usuario podrá mirar un objeto, que ha entrenado mediante Azure Custom Vision Service, Detección de objetos.
  2. El usuario usará el gesto Pulsar para capturar una imagen de lo que está mirando.
  3. La aplicación enviará la imagen al servicio azure Custom Vision.
  4. Habrá una respuesta del Servicio que mostrará el resultado del reconocimiento como texto de espacio mundial. Esto se logrará mediante el uso del seguimiento espacial de la Microsoft HoloLens, como una manera de comprender la posición mundial del objeto reconocido y, a continuación, usar la etiqueta asociada a lo que se detecta en la imagen, para proporcionar el texto de la etiqueta.

El curso también tratará la carga manual de imágenes, la creación de etiquetas y el entrenamiento del servicio para reconocer objetos diferentes (en el ejemplo proporcionado, una taza) estableciendo el cuadro de límites dentro de la imagen que envíe.

Importante

Después de la creación y el uso de la aplicación, el desarrollador debe volver al servicio de Azure Custom Vision e identificar las predicciones realizadas por el servicio y determinar si eran correctos o no (mediante el etiquetado de todo lo que falta el servicio y el ajuste de los cuadros de límite). Después, se puede volver a entrenar el servicio, lo que aumentará la probabilidad de que reconozca objetos del mundo real.

Este curso le enseñará a obtener los resultados de Azure Custom Vision Service, Detección de objetos, en una aplicación de ejemplo basada en Unity. Dependerá de usted aplicar estos conceptos a una aplicación personalizada que podría compilar.

Compatibilidad con dispositivos

Curso HoloLens Cascos envolventes
Realidad mixta y Azure (310): Detección de objetos ✔️

Prerrequisitos

Nota:

Este tutorial está diseñado para desarrolladores que tienen experiencia básica con Unity y C#. Tenga en cuenta también que los requisitos previos y las instrucciones escritas dentro de este documento representan lo que se ha probado y comprobado en el momento de la escritura (julio de 2018). Puede usar el software más reciente, como se muestra en el artículo sobre la instalación de las herramientas , aunque no debe asumirse que la información de este curso coincidirá perfectamente con lo que encontrará en el software más reciente que lo que se muestra a continuación.

Se recomienda el siguiente hardware y software para este curso:

Antes de empezar

  1. Para evitar encontrar problemas al compilar este proyecto, se recomienda encarecidamente crear el proyecto mencionado en este tutorial en una carpeta raíz o casi raíz (las rutas de acceso de carpetas largas pueden causar problemas en tiempo de compilación).
  2. Configure y pruebe holoLens. Si necesita soporte técnico para esto, visite el artículo de configuración de HoloLens.
  3. Es una buena idea realizar la calibración y la optimización del sensor al empezar a desarrollar una nueva aplicación holoLens (a veces puede ayudar a realizar esas tareas para cada usuario).

Para obtener ayuda sobre calibración, siga este vínculo al artículo Calibración de HoloLens.

Para obtener ayuda sobre la optimización de sensores, siga este vínculo al artículo Optimización de sensores de HoloLens.

Capítulo 1: El portal de Custom Vision

Para usar Azure Custom Vision Service, deberá configurar una instancia de ella para que esté disponible para la aplicación.

  1. Vaya a la página principal del servicio Custom Vision.

  2. Haga clic en Introducción.

    Captura de pantalla que resalta el botón Introducción.

  3. Inicie sesión en el portal de Custom Vision.

    Captura de pantalla que muestra el botón Iniciar sesión.

  4. Si aún no tiene una cuenta de Azure, deberá crear una. Si sigue este tutorial en una situación de clase o laboratorio, pida a su instructor o a uno de los proctores que le ayuden a configurar la nueva cuenta.

  5. Una vez que haya iniciado sesión por primera vez, se le pedirá el panel Términos de servicio . Haga clic en la casilla para aceptar los términos. A continuación, haga clic en Acepto.

    Captura de pantalla que muestra el panel Términos de servicio.

  6. Después de haber aceptado los términos, ahora se encuentra en la sección Mis proyectos . Haga clic en Nuevo proyecto.

    Captura de pantalla que muestra dónde seleccionar Nuevo proyecto.

  7. Aparecerá una pestaña en el lado derecho, que le pedirá que especifique algunos campos para el proyecto.

    1. Insertar un nombre para el proyecto

    2. Insertar una descripción para el proyecto (opcional)

    3. Elija un grupo de recursos o cree uno nuevo. Un grupo de recursos proporciona una manera de supervisar, controlar el acceso, aprovisionar y administrar la facturación de una colección de recursos de Azure. Se recomienda mantener todos los servicios de Azure asociados a un único proyecto (por ejemplo, estos cursos) en un grupo de recursos común).

      Captura de pantalla que muestra dónde agregar detalles para el nuevo proyecto.

    4. Establezca Los tipos de proyecto como detección de objetos (versión preliminar).

  8. Una vez que haya terminado, haga clic en Crear proyecto y se le redirigirá a la página del proyecto de servicio de Custom Vision.

Capítulo 2: Entrenamiento del proyecto de Custom Vision

Una vez en el portal de Custom Vision, el objetivo principal es entrenar el proyecto para reconocer objetos específicos en imágenes.

Necesita al menos quince (15) imágenes para cada objeto que quiera que reconozca la aplicación. Puede usar las imágenes proporcionadas con este curso (una serie de tazas).

Para entrenar el proyecto de Custom Vision:

  1. Haga clic en el + botón situado junto a Etiquetas.

    Captura de pantalla que muestra el botón + situado junto a Etiquetas.

  2. Agregue un nombre para la etiqueta con la que se usará para asociar las imágenes. En este ejemplo se usan imágenes de tazas para el reconocimiento, por lo que se ha llamado la etiqueta para este, Cup. Haga clic en Guardar una vez finalizada.

    Captura de pantalla que muestra dónde agregar un nombre para la etiqueta.

  3. Observará que se ha agregado la etiqueta (es posible que tenga que volver a cargar la página para que aparezca).

    Captura de pantalla que muestra dónde se agrega la etiqueta.

  4. Haga clic en Agregar imágenes en el centro de la página.

    Captura de pantalla que muestra dónde agregar imágenes.

  5. Haga clic en Examinar archivos locales y vaya a las imágenes que desea cargar para un objeto, con el mínimo de quince (15).

    Sugerencia

    Puede seleccionar varias imágenes a la vez para cargar.

    Captura de pantalla que muestra las imágenes que puede cargar.

  6. Presione Cargar archivos una vez que haya seleccionado todas las imágenes con las que desea entrenar el proyecto. Los archivos comenzarán a cargarse. Una vez que haya confirmado la carga, haga clic en Listo.

    Captura de pantalla que muestra el progreso de las imágenes cargadas.

  7. En este momento, las imágenes se cargan, pero no se etiquetan.

    Captura de pantalla que muestra una imagen sin etiquetar.

  8. Para etiquetar las imágenes, use el mouse. Al mantener el puntero sobre la imagen, un resaltado de selección le ayudará a dibujar automáticamente una selección alrededor del objeto. Si no es preciso, puede dibujar su propio. Esto se logra manteniendo presionado el clic izquierdo en el mouse y arrastrando la región de selección para abarcar el objeto.

    Captura de pantalla que muestra cómo etiquetar una imagen.

  9. Después de la selección del objeto dentro de la imagen, un pequeño mensaje le pedirá que agregue la etiqueta de región. Seleccione la etiqueta creada anteriormente ("Cup", en el ejemplo anterior), o si va a agregar más etiquetas, escriba en y haga clic en el botón + (más).

    Captura de pantalla que muestra la etiqueta que agregó a la imagen.

  10. Para etiquetar la imagen siguiente, puede hacer clic en la flecha situada a la derecha de la hoja o cerrar la hoja de etiqueta (haciendo clic en la X en la esquina superior derecha de la hoja) y, a continuación, hacer clic en la imagen siguiente. Una vez que la imagen siguiente esté lista, repita el mismo procedimiento. Haga esto para todas las imágenes que haya cargado, hasta que se etiqueten.

    Nota

    Puede seleccionar varios objetos en la misma imagen, como la imagen siguiente:

    Captura de pantalla que muestra varios objetos en una imagen.

  11. Una vez que los haya etiquetado todo, haga clic en el botón etiquetado , a la izquierda de la pantalla, para mostrar las imágenes etiquetadas.

    Captura de pantalla que resalta el botón Etiquetado.

  12. Ya está listo para entrenar el servicio. Haga clic en el botón Entrenar y comenzará la primera iteración de entrenamiento.

    Captura de pantalla que resalta el botón Entrenar.

    Captura de pantalla que muestra la primera iteración de entrenamiento.

  13. Una vez compilado, podrá ver dos botones denominados Make default (Establecer dirección URL predeterminada ) y Prediction URL (Dirección URL de predicción). Haga clic en Make default first (Crear valor predeterminado ) y, a continuación, haga clic en Prediction URL (Dirección URL de predicción).

    Captura de pantalla que resalta el botón Make default (Crear valor predeterminado).

    Nota

    El punto de conexión que se proporciona a partir de este, se establece en cualquier iteración que se haya marcado como predeterminada. Por lo tanto, si más adelante realiza una nueva iteración y la actualiza como predeterminada, no tendrá que cambiar el código.

  14. Una vez que haya hecho clic en Url de predicción, abra el Bloc de notas y copie y pegue la dirección URL (también denominada Prediction-Endpoint) y la clave de predicción del servicio, para que pueda recuperarla cuando la necesite más adelante en el código.

    Captura de pantalla que muestra el punto de conexión de predicción y la clave de predición.

Capítulo 3: Configuración del proyecto de Unity

A continuación se muestra una configuración típica para desarrollar con realidad mixta y, como tal, es una buena plantilla para otros proyectos.

  1. Abra Unity y haga clic en Nuevo.

    Captura de pantalla que resalta el botón Nuevo.

  2. Ahora deberá proporcionar un nombre de proyecto de Unity. Inserte CustomVisionObjDetection. Asegúrese de que el tipo de proyecto esté establecido en 3D y establezca la ubicación en algún lugar adecuado para usted (recuerde que más cerca de los directorios raíz es mejor). A continuación, haga clic en Crear proyecto.

    Captura de pantalla que muestra los detalles del proyecto y dónde seleccionar Crear proyecto.

  3. Con Unity abierto, vale la pena comprobar que el Editor de scripts predeterminado está establecido en Visual Studio. Vaya a Editar>preferencias y, a continuación, en la nueva ventana, vaya a Herramientas externas. Cambie el Editor de scripts externos a Visual Studio. Cierre la ventana Preferencias.

    Captura de pantalla que muestra dónde cambiar el Editor de scripts externos a Visual Studio.

  4. A continuación, vaya a Configuración de compilación de archivos > y cambie la plataforma a Plataforma universal de Windows y, a continuación, haga clic en el botón Cambiar plataforma.

    Captura de pantalla que resalta el botón Switch Platform (Cambiar plataforma).

  5. En la misma ventana Configuración de compilación, asegúrese de que se establecen las siguientes opciones:

    1. El dispositivo de destino está establecido en HoloLens.

    2. El tipo de compilación se establece en D3D.

    3. El SDK se establece en Latest installed (Versión más reciente instalada)

    4. La versión de Visual Studio se establece en Latest installed (Versión más reciente instalada)

    5. Compilar y ejecutar está establecido en Equipo local

    6. La configuración restante, en Configuración de compilación, debe dejarse como predeterminada por ahora.

      Captura de pantalla que muestra las opciones de configuración de Configuración de compilación.

  6. En la misma ventana Configuración de compilación, haga clic en el botón Configuración del reproductor ; se abrirá el panel relacionado en el espacio donde se encuentra el Inspector .

  7. En este panel, es necesario comprobar algunas opciones de configuración:

    1. En la pestaña Otros valores:

      1. La versión del entorno de ejecución de scripting debe ser Experimental (equivalente a.NET 4.6), lo que desencadenará una necesidad de reiniciar el editor.

      2. El back-end de scripting debe ser .NET.

      3. El nivel de compatibilidad de API debe ser .NET 4.6.

        Captura de pantalla que muestra la opción Nivel de compatibilidad de API establecida en .NET 4.6.

    2. En la pestaña Configuración de publicación , en Funcionalidades, active:

      1. InternetClient

      2. Cámara web

      3. SpatialPerception

        Captura de pantalla que muestra la mitad superior de las opciones de configuración De funcionalidades.Captura de pantalla que muestra la mitad inferior de las opciones de configuración De funcionalidades.

    3. Más abajo del panel, en Configuración de XR (que se encuentra a continuación de Configuración de publicación), marque Virtual Reality Supported y, a continuación, asegúrese de que se agrega el SDK de Windows Mixed Reality.

      Captura de pantalla que muestra que se agrega el SDK de Windows Mixed Reality.

  8. De nuevo en Configuración de compilación, los proyectos de C# de Unity ya no están atenuados: marque la casilla situada junto a esta.

  9. Cierre la ventana Build Settings (Configuración de compilación).

  10. En el Editor, haga clic en Editar>gráficosde configuración> del proyecto.

    Captura de pantalla que muestra la opción de menú Gráficos seleccionada.

  11. En el Panel inspector , se abrirá la configuración de gráficos . Desplácese hacia abajo hasta que vea una matriz denominada Sombreadores de inclusión siempre. Agregue una ranura aumentando la variable Size en uno (en este ejemplo, era 8, por lo que lo hicimos 9). Aparecerá una nueva ranura, en la última posición de la matriz, como se muestra a continuación:

    Captura de pantalla que resalta la matriz Sombreadores always included.

  12. En la ranura, haga clic en el círculo de destino pequeño junto a la ranura para abrir una lista de sombreadores. Busque los sombreadores heredados/ Sombreador transparente/difuso y haga doble clic en él.

    Captura de pantalla en la que se resaltan los sombreadores heredados, el sombreador transparente o difuso.

Capítulo 4: Importación del paquete de Unity CustomVisionObjDetection

Para este curso, se le proporciona un paquete de recursos de Unity denominado Azure-MR-310.unitypackage.

[SUGERENCIA] Los objetos admitidos por Unity, incluidas las escenas completas, se pueden empaquetar en un archivo .unitypackage y exportarse o importarse en otros proyectos. Es la forma más segura y eficaz de mover recursos entre diferentes proyectos de Unity.

Puede encontrar el paquete Azure-MR-310 que necesita descargar aquí.

  1. Con el panel de Unity delante de usted, haga clic en Activos en el menú de la parte superior de la pantalla y, a continuación, haga clic en Importar paquete > personalizado.

    Captura de pantalla que resalta la opción de menú Paquete personalizado.

  2. Use el selector de archivos para seleccionar el paquete Azure-MR-310.unitypackage y haga clic en Abrir. Se mostrará una lista de componentes para este recurso. Para confirmar la importación, haga clic en el botón Importar .

    Captura de pantalla que muestra la lista de componentes de recursos que desea importar.

  3. Una vez que haya terminado de importarse, observará que las carpetas del paquete se han agregado ahora a la carpeta Assets . Este tipo de estructura de carpetas es típico para un proyecto de Unity.

    Captura de pantalla que muestra el contenido de la carpeta Assets.

    1. La carpeta Materiales contiene el material utilizado por el cursor de mirada.

    2. La carpeta Plugins contiene el archivo DLL newtonsoft usado por el código para deserializar la respuesta web del servicio. Las dos (2) versiones diferentes contenidas en la carpeta y la subcarpeta son necesarias para permitir que la biblioteca se use y compile mediante el Editor de Unity y la compilación de UWP.

    3. La carpeta Prefabs contiene los objetos prefabricados contenidos en la escena. Estos son:

      1. GazeCursor, el cursor usado en la aplicación. Funcionará junto con el objeto prefabricado SpatialMapping para poder colocarse en la escena sobre objetos físicos.
      2. Etiqueta, que es el objeto de interfaz de usuario que se usa para mostrar la etiqueta de objeto en la escena cuando sea necesario.
      3. SpatialMapping, que es el objeto que permite a la aplicación usar la creación de un mapa virtual, mediante el seguimiento espacial de la Microsoft HoloLens.
    4. La carpeta Escenas que contiene actualmente la escena pregenerada para este curso.

  4. Abra la carpeta Escenas , en el Panel del proyecto y haga doble clic en ObjDetectionScene, para cargar la escena que usará para este curso.

    Captura de pantalla que muestra ObjDetectionScene en la carpeta Escenas.

    Nota

    No se incluye ningún código, escribirá el código siguiendo este curso.

Capítulo 5: Creación de la clase CustomVisionAnalyser.

En este momento, está listo para escribir código. Comenzará con la clase CustomVisionAnalyser .

Nota

Las llamadas al servicio de Custom Vision, realizadas en el código que se muestra a continuación, se realizan mediante la API rest de Custom Vision. Mediante este uso, verá cómo implementar y usar esta API (útil para comprender cómo implementar algo similar por su cuenta). Tenga en cuenta que Microsoft ofrece un SDK de Custom Vision que también se puede usar para realizar llamadas al servicio. Para obtener más información, visite el artículo Custom Vision SDK.

Esta clase es responsable de:

  • Cargando la imagen más reciente capturada como una matriz de bytes.

  • Envío de la matriz de bytes a la instancia de Azure Custom Vision Service para su análisis.

  • Recepción de la respuesta como una cadena JSON.

  • Deserializar la respuesta y pasar la predicción resultante a la clase SceneOrganiser , que se encargará de cómo se debe mostrar la respuesta.

Para crear esta clase:

  1. Haga clic con el botón derecho en la carpeta de recursos, ubicada en el Panel de proyectos y, a continuación, haga clic en Crear>carpeta. Llame a la carpeta Scripts.

    Captura de pantalla que muestra cómo crear la carpeta Scripts.

  2. Haga doble clic en la carpeta recién creada para abrirla.

  3. Haga clic con el botón derecho dentro de la carpeta y, a continuación, haga clic en Crear>script de C#. Asigne al script el nombre CustomVisionAnalyser.

  4. Haga doble clic en el nuevo script CustomVisionAnalyser para abrirlo con Visual Studio.

  5. Asegúrese de que tiene los siguientes espacios de nombres a los que se hace referencia en la parte superior del archivo:

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  6. En la clase CustomVisionAnalyser , agregue las siguientes variables:

        /// <summary>
        /// Unique instance of this class
        /// </summary>
        public static CustomVisionAnalyser Instance;
    
        /// <summary>
        /// Insert your prediction key here
        /// </summary>
        private string predictionKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your prediction endpoint here
        /// </summary>
        private string predictionEndpoint = "Insert your prediction endpoint here";
    
        /// <summary>
        /// Bite array of the image to submit for analysis
        /// </summary>
        [HideInInspector] public byte[] imageBytes;
    

    Nota

    Asegúrese de insertar la clave de predicción del servicio en la variable predictionKey y prediction-Endpoint en la variable predictionEndpoint . Los copió en el Bloc de notas anteriormente, en el capítulo 2, paso 14.

  7. El código de Awake() ahora debe agregarse para inicializar la variable Instance:

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Agregue la corrutina (con el método estático GetImageAsByteArray() debajo), que obtendrá los resultados del análisis de la imagen, capturados por la clase ImageCapture .

    Nota

    En la corrutina AnalyseImageCapture , hay una llamada a la clase SceneOrganiser que aún tiene que crear. Por lo tanto, deje esas líneas comentadas por ahora.

        /// <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. Elimine los métodos Start() y Update(), ya que no se usarán.

  10. Asegúrese de guardar los cambios en Visual Studio antes de volver a Unity.

Importante

Como se mencionó anteriormente, no se preocupe por el código que puede parecer que tiene un error, ya que proporcionará más clases pronto, lo que corregirá estos.

Capítulo 6: Creación de la clase CustomVisionObjects

La clase que creará ahora es la clase CustomVisionObjects .

Este script contiene varios objetos usados por otras clases para serializar y deserializar las llamadas realizadas al servicio Custom Vision.

Para crear esta clase:

  1. Haga clic con el botón derecho en la carpeta Scripts y, a continuación, haga clic en Crear>script de C#. Llame al script CustomVisionObjects.

  2. Haga doble clic en el nuevo script CustomVisionObjects para abrirlo con Visual Studio.

  3. Asegúrese de que tiene los siguientes espacios de nombres a los que se hace referencia en la parte superior del archivo:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Elimine los métodos Start() y Update() dentro de la clase CustomVisionObjects ; esta clase debería estar vacía.

    Advertencia

    Es importante que siga cuidadosamente las instrucciones siguientes. Si coloca las nuevas declaraciones de clase dentro de la clase CustomVisionObjects , obtendrá errores de compilación en el capítulo 10, indicando que no se encuentran AnalysisRootObject y BoundingBox .

  5. Agregue las siguientes clases fuera de la clase CustomVisionObjects . La biblioteca Newtonsoft usa estos objetos para serializar y deserializar los datos de respuesta:

    // 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. Asegúrese de guardar los cambios en Visual Studio antes de volver a Unity.

Capítulo 7: Creación de la clase SpatialMapping

Esta clase establecerá el colisionador de asignación espacial en la escena para poder detectar colisiones entre objetos virtuales y objetos reales.

Para crear esta clase:

  1. Haga clic con el botón derecho en la carpeta Scripts y, a continuación, haga clic en Crear>script de C#. Llame al script SpatialMapping.

  2. Haga doble clic en el nuevo script SpatialMapping para abrirlo con Visual Studio.

  3. Asegúrese de que tiene los siguientes espacios de nombres a los que se hace referencia sobre la clase SpatialMapping :

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. A continuación, agregue las siguientes variables dentro de la clase SpatialMapping, encima del 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. Agregue el Valor de Awake() y 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. Elimine el método Update().

  7. Asegúrese de guardar los cambios en Visual Studio antes de volver a Unity.

Capítulo 8: Creación de la clase GazeCursor

Esta clase es responsable de configurar el cursor en la ubicación correcta en el espacio real, haciendo uso de SpatialMappingCollider, creado en el capítulo anterior.

Para crear esta clase:

  1. Haga clic con el botón derecho en la carpeta Scripts y, a continuación, haga clic en Crear>script de C#. Llamada al script GazeCursor

  2. Haga doble clic en el nuevo script GazeCursor para abrirlo con Visual Studio.

  3. Asegúrese de que tiene el siguiente espacio de nombres al que se hace referencia encima de la clase GazeCursor :

    using UnityEngine;
    
  4. A continuación, agregue la siguiente variable dentro de la clase GazeCursor, encima del método Start().

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Actualice el método Start() con el código siguiente:

        /// <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. Actualice el método Update() con el código siguiente:

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

    Nota

    No se preocupe por el error de la clase SceneOrganiser que no se encuentra, lo creará en el siguiente capítulo.

  7. Asegúrese de guardar los cambios en Visual Studio antes de volver a Unity.

Capítulo 9: Creación de la clase SceneOrganiser

Esta clase hará lo siguiente:

  • Configure la cámara principal mediante la conexión de los componentes adecuados.

  • Cuando se detecta un objeto, será responsable de calcular su posición en el mundo real y colocar una etiqueta etiqueta cerca de él con el nombre de etiqueta adecuado.

Para crear esta clase:

  1. Haga clic con el botón derecho en la carpeta Scripts y, a continuación, haga clic en Crear>script de C#. Asigne al script el nombre SceneOrganiser.

  2. Haga doble clic en el nuevo script SceneOrganiser para abrirlo con Visual Studio.

  3. Asegúrese de que tiene los siguientes espacios de nombres a los que se hace referencia encima de la clase SceneOrganiser :

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
  4. A continuación, agregue las siguientes variables dentro de la clase SceneOrganiser, encima del 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. Elimine los métodos Start() y Update().

  6. Debajo de las variables, agregue el método Awake(), que inicializará la clase y configurará la escena.

        /// <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. Agregue el método PlaceAnalysisLabel(), que creará una instancia de la etiqueta en la escena (que en este punto es invisible para el usuario). También coloca el quad (también invisible) donde se coloca la imagen y se superpone con el mundo real. Esto es importante porque las coordenadas del cuadro recuperadas del servicio después de realizar el análisis se realizan un seguimiento en este cuadrátero para determinar la ubicación aproximada del objeto en el 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. Agregue el método FinaliseLabel(). Es el responsable de:

    • Establecer el texto etiqueta con la etiqueta de la predicción con la máxima confianza.
    • Llamar al cálculo del rectángulo delimitador en el objeto quad, colocado anteriormente y colocando la etiqueta en la escena.
    • Ajuste de la profundidad de la etiqueta mediante un Raycast hacia el rectángulo de selección, que debería colisionar contra el objeto en el mundo real.
    • Restablecer el proceso de captura para permitir al usuario capturar otra imagen.
        /// <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. Agregue el método CalculateBoundingBoxPosition(), que hospeda varios cálculos necesarios para traducir las coordenadas del rectángulo de selección recuperados del servicio y volver a crearlas proporcionalmente en el cuadrátero.

        /// <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. Asegúrese de guardar los cambios en Visual Studio antes de volver a Unity.

    Importante

    Antes de continuar, abra la clase CustomVisionAnalyser y, en el método AnalysisLastImageCaptured(), quite la marca de comentario de las líneas siguientes:

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

Nota

No se preocupe por el mensaje "no se pudo encontrar" de la clase ImageCapture , lo creará en el siguiente capítulo.

Capítulo 10: Creación de la clase ImageCapture

La siguiente clase que va a crear es la clase ImageCapture .

Esta clase es responsable de:

  • Capturar una imagen mediante la cámara HoloLens y almacenarla en la carpeta Aplicación .
  • Controlar gestos de pulsación del usuario.

Para crear esta clase:

  1. Vaya a la carpeta Scripts que creó anteriormente.

  2. Haga clic con el botón derecho en la carpeta y, a continuación, haga clic en Crear>script de C#. Asigne al script el nombre ImageCapture.

  3. Haga doble clic en el nuevo script ImageCapture para abrirlo con Visual Studio.

  4. Reemplace los espacios de nombres en la parte superior del archivo por lo siguiente:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. A continuación, agregue las siguientes variables dentro de la clase ImageCapture, encima del 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. Ahora es necesario agregar código para los métodos Awake() y Start():

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

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

    Cuando el cursor es verde, significa que la cámara está disponible para tomar la imagen. Cuando el cursor es rojo, significa que la cámara está ocupada.

  8. Agregue el método que usa la aplicación para iniciar el proceso de captura de imágenes y almacenar la imagen:

        /// <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. Agregue los controladores a los que se llamará cuando se haya capturado la foto y para cuando esté listo para analizarse. A continuación, el resultado se pasa al CustomVisionAnalyser para su análisis.

        /// <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. Asegúrese de guardar los cambios en Visual Studio antes de volver a Unity.

Capítulo 11: Configuración de los scripts en la escena

Ahora que ha escrito todo el código necesario para este proyecto, es el momento de configurar los scripts en la escena y, en los objetos prefabricados, para que se comporten correctamente.

  1. En el Editor de Unity, en el Panel de jerarquía, seleccione la Cámara principal.

  2. En el Panel inspector, con la cámara principal seleccionada, haga clic en Agregar componente y busque Script SceneOrganiser y haga doble clic para agregarlo.

    Captura de pantalla que muestra el script SceneOrganizer.

  3. En el Panel de proyectos, abra la carpeta Prefabs, arrastre el objeto prefabricado Etiqueta al área de entrada de destino de referencia vacía Etiqueta, en el script SceneOrganiser que acaba de agregar a la cámara principal, como se muestra en la imagen siguiente:

    Captura de pantalla que muestra el script que agregó a la cámara principal.

  4. En el Panel de jerarquía, seleccione el elemento secundario GazeCursor de la cámara principal.

  5. En el Panel inspector, con gazeCursor seleccionado, haga clic en Agregar componente y busque script GazeCursor y haga doble clic para agregarlo.

    Captura de pantalla que muestra dónde se agrega el script GazeCursor.

  6. De nuevo, en el Panel de jerarquía, seleccione el elemento secundario SpatialMapping de la cámara principal.

  7. En el Panel inspector, con spatialMapping seleccionado, haga clic en Agregar componente y busque script SpatialMapping y haga doble clic para agregarlo.

    Captura de pantalla que muestra dónde se agrega el script SpatialMapping.

El código del script SceneOrganiser agregará los scripts restantes que no haya establecido durante el tiempo de ejecución.

Capítulo 12: Antes de la construcción

Para realizar una prueba exhaustiva de la aplicación, deberá transferirla localmente a la Microsoft HoloLens.

Antes de hacerlo, asegúrese de que:

  • Todas las configuraciones mencionadas en el capítulo 3 se establecen correctamente.

  • El script SceneOrganiser se adjunta al objeto Cámara principal .

  • El script GazeCursor se adjunta al objeto GazeCursor .

  • El script SpatialMapping está asociado al objeto SpatialMapping .

  • En el capítulo 5, paso 6:

    • Asegúrese de insertar la clave de predicción del servicio en la variable predictionKey .
    • Ha insertado el punto de conexión de predicción en la clase predictionEndpoint .

Capítulo 13: Compilar la solución para UWP y transferir localmente la aplicación

Ya está listo para compilar la aplicación como solución para UWP en la que podrá realizar la implementación en la Microsoft HoloLens. Para comenzar el proceso de compilación:

  1. Vaya a Configuración de compilación de archivos>.

  2. Marque proyectos de C# de Unity.

  3. Haga clic en Agregar escenas abiertas. Esto agregará la escena abierta actualmente a la compilación.

    Captura de pantalla que resalta el botón Agregar escenas abiertas.

  4. Haga clic en Generar. Unity iniciará una ventana de Explorador de archivos, donde debe crear y, a continuación, seleccionará una carpeta en la que compilará la aplicación. Cree esa carpeta ahora y asígnela el nombre App. A continuación, con la carpeta Aplicación seleccionada, haga clic en Seleccionar carpeta.

  5. Unity comenzará a compilar el proyecto en la carpeta Aplicación .

  6. Una vez que Unity haya terminado de compilar (es posible que tarde algún tiempo), abrirá una ventana de Explorador de archivos en la ubicación de la compilación (compruebe la barra de tareas, ya que puede que no siempre aparezca encima de las ventanas, pero le notificará la adición de una nueva ventana).

  7. Para realizar la implementación en Microsoft HoloLens, necesitará la dirección IP de ese dispositivo (para implementación remota) y para asegurarse de que también tiene el modo de desarrollador establecido. Para ello, siga estos pasos:

    1. Mientras llevas tu HoloLens, abre la configuración.

    2. Vaya a Red &Opciones avanzadasde Wi-Fi para> Internet >

    3. Anote la dirección IPv4 .

    4. A continuación, vuelva a Configuración y, a continuación, a Actualizar & Seguridad>para desarrolladores.

    5. Establezca el modo de desarrolladoractivado.

  8. Vaya a la nueva compilación de Unity (la carpeta Aplicación ) y abra el archivo de solución con Visual Studio.

  9. En Configuración de la solución, seleccione Depurar.

  10. En la Plataforma de soluciones, seleccione x86, Máquina remota. Se le pedirá que inserte la dirección IP de un dispositivo remoto (el Microsoft HoloLens, en este caso, que anotó).

    Captura de pantalla que muestra dónde insertar la dirección IP.

  11. Vaya al menú Compilar y haga clic en Implementar solución para transferir localmente la aplicación a HoloLens.

  12. La aplicación debería aparecer ahora en la lista de aplicaciones instaladas en la Microsoft HoloLens, lista para iniciarse.

Para usar la aplicación:

  • Examine un objeto que ha entrenado con azure Custom Vision Service, Detección de objetos y use el gesto Pulsar.
  • Si el objeto se detecta correctamente, aparecerá un texto de etiqueta de espacio mundial con el nombre de etiqueta.

Importante

Cada vez que capture una foto y la envíe al servicio, puede volver a la página Servicio y volver a entrenar el servicio con las imágenes recién capturadas. Al principio, probablemente también tendrá que corregir los cuadros de límite para que sean más precisos y volver a entrenar el servicio.

Nota

Es posible que el texto de etiqueta colocado no aparezca cerca del objeto cuando los sensores de Microsoft HoloLens o SpatialTrackingComponent en Unity no pueden colocar los colisionadores adecuados, en relación con los objetos del mundo real. Intente usar la aplicación en una superficie diferente si es así.

La aplicación Custom Vision detección de objetos

Enhorabuena, ha creado una aplicación de realidad mixta que aprovecha azure Custom Vision, Object Detection API, que puede reconocer un objeto de una imagen y, a continuación, proporcionar una posición aproximada para ese objeto en el espacio 3D.

Captura de pantalla que muestra una aplicación de realidad mixta que aprovecha Azure Custom Vision, Object Detection API.

Ejercicios extra

Ejercicio 1

Agregar a la etiqueta de texto, use un cubo semitransparente para encapsular el objeto real en un rectángulo delimitador 3D.

Ejercicio 2

Entrene el servicio de Custom Vision para reconocer más objetos.

Ejercicio 3

Reproducir un sonido cuando se reconoce un objeto.

Ejercicio 4

Use la API para volver a entrenar el servicio con las mismas imágenes que analiza la aplicación, por lo que para que el servicio sea más preciso (realice la predicción y el entrenamiento simultáneamente).