HoloLens (1-го поколения) и Azure 310: обнаружение объектов

Примечание

Руководства Mixed Reality Academy были разработаны для иммерсивных гарнитур HoloLens (1-го поколения) и иммерсивных гарнитур Mixed Reality. Поэтому мы считаем, что важно оставить эти руководства для разработчиков, которые ищут рекомендации по разработке для этих устройств. Данные руководства не будут обновляться с учетом последних наборов инструментов или возможностей взаимодействия для HoloLens 2. Они будут сохранены для работы на поддерживаемых устройствах. В будущем будет опубликована новая серия учебников, демонстрирующих разработку для HoloLens 2. В этом уведомлении будет добавлена ссылка на эти руководства при их публикации.


В этом курсе вы узнаете, как распознавать пользовательское визуальное содержимое и его пространственное положение в предоставленном изображении с помощью azure Пользовательское визуальное распознавание возможностей обнаружения объектов в приложении смешанной реальности.

Эта служба позволит обучить модель машинного обучения с помощью образов объектов. Затем вы будете использовать обученную модель для распознавания похожих объектов и приближения их расположения в реальном мире, как это предусмотрено записью камеры Microsoft HoloLens или подключением камеры к компьютеру для иммерсивных гарнитур (VR).

результат курса

Azure Пользовательское визуальное распознавание обнаружение объектов — это служба Майкрософт, которая позволяет разработчикам создавать пользовательские классификаторы изображений. Затем эти классификаторы можно использовать с новыми изображениями для обнаружения объектов в этом новом изображении, предоставляя границы поля в самом изображении. Служба предоставляет простой и удобный в использовании интернет-портал для упрощения этого процесса. Дополнительные сведения см. по следующим ссылкам:

По завершении этого курса у вас будет приложение смешанной реальности, которое сможет выполнять следующие действия:

  1. Пользователь сможет взглянуть на объект, который он обучил с помощью службы Пользовательское визуальное распознавание Azure, обнаружение объектов.
  2. Пользователь будет использовать жест касания для захвата изображения того, что он смотрит.
  3. Приложение отправит образ в службу Пользовательское визуальное распознавание Azure.
  4. Будет получен ответ от Службы, в котором будет отображаться результат распознавания в виде текста мирового пространства. Это будет достигнуто путем использования пространственного отслеживания Microsoft HoloLens в качестве способа понимания положения в мире распознанного объекта, а затем с помощью тега, связанного с обнаруженным на изображении, для предоставления текста метки.

Курс также будет охватывать загрузку изображений вручную, создание тегов и обучение службы распознаванию различных объектов (в приведенном примере чашка) путем установки поля границы в отправляемом изображении.

Важно!

После создания и использования приложения разработчик должен вернуться к Службе Пользовательское визуальное распознавание Azure и определить прогнозы, сделанные службой, и определить, были ли они правильными (путем добавления тегов, пропущенных Службой, и настройки ограничивающих прямоугольниц). Затем Служба может быть переобучена, что повысит вероятность того, что она распознает объекты реального мира.

В этом курсе вы узнаете, как получить результаты из службы Пользовательское визуальное распознавание Azure , обнаружение объектов, в пример приложения на основе Unity. Вы сможете применить эти понятия к пользовательскому приложению, которое вы можете создать.

Поддержка устройств

Курс HoloLens Иммерсивные гарнитуры
310. Смешанная реальность и Azure: обнаружение объектов ✔️

Предварительные требования

Примечание

Это руководство предназначено для разработчиков, имеющих базовый опыт работы с Unity и C#. Кроме того, имейте в виду, что предварительные требования и письменные инструкции в этом документе представляют то, что было протестировано и проверено на момент написания статьи (июль 2018 г.). Вы можете использовать последнюю версию программного обеспечения, как указано в статье установка инструментов , хотя не следует предполагать, что информация в этом курсе будет идеально соответствовать тому, что вы найдете в более новом программном обеспечении, чем указано ниже.

Для этого курса мы рекомендуем использовать следующее оборудование и программное обеспечение:

Перед началом работы

  1. Чтобы избежать проблем со сборкой этого проекта, настоятельно рекомендуется создать проект, упомянутый в этом руководстве, в корневой или почти корневой папке (длинные пути к папкам могут вызвать проблемы во время сборки).
  2. Настройте и протестируйте HoloLens. Если вам нужна поддержка для этого, ознакомьтесь со статьей о настройке HoloLens.
  3. Рекомендуется выполнять калибровку и настройку датчиков при разработке нового приложения HoloLens (иногда это может помочь выполнить эти задачи для каждого пользователя).

Для получения справки по калибровке перейдите по этой ссылке на статью Калибровка HoloLens.

Чтобы получить справку по настройке датчика, перейдите по этой ссылке на статью Настройка датчика HoloLens.

Глава 1. Портал Пользовательское визуальное распознавание

Чтобы использовать службу Пользовательское визуальное распознавание Azure, необходимо настроить ее экземпляр для предоставления доступа к приложению.

  1. Перейдите на страницу main службы Пользовательское визуальное распознавание.

  2. Щелкните начало работы.

    Снимок экрана, на котором выделена кнопка начало работы.

  3. Войдите на портал Пользовательское визуальное распознавание.

    Снимок экрана: кнопка

  4. Если у вас еще нет учетной записи Azure, ее необходимо создать. Если вы выполняете это руководство в учебной или лабораторной ситуации, обратитесь к преподавателю или одному из прокторов за помощью в настройке новой учетной записи.

  5. Когда вы войдете в систему в первый раз, появится запрос на панели Условий обслуживания . Установите флажок, чтобы принять условия. Затем нажмите кнопку Я принимаю.

    Снимок экрана: панель

  6. Согласившись с условиями, вы находитесь в разделе Мои проекты . Щелкните Создать проект.

    Снимок экрана, на котором показано, где выбрать новый проект.

  7. В правой части появится вкладка, на которой будет предложено указать некоторые поля для проекта.

    1. Вставка имени проекта

    2. Вставка описания проекта (необязательно)

    3. Выберите группу ресурсов или создайте новую. Группа ресурсов предоставляет способ мониторинга, контроля доступа, подготовки и управления выставлением счетов для коллекции ресурсов Azure. Рекомендуется сохранить все службы Azure, связанные с одним проектом (например, эти курсы), в общей группе ресурсов.

      Снимок экрана, на котором показано, где можно добавить сведения о новом проекте.

    4. Задайте для типа проектазначение Обнаружение объектов (предварительная версия).

  8. По завершении щелкните Создать проект, и вы будете перенаправлены на страницу проекта службы Пользовательское визуальное распознавание.

Глава 2. Обучение проекта Пользовательское визуальное распознавание

На портале Пользовательское визуальное распознавание основной целью является обучение проекта распознаванию определенных объектов на изображениях.

Вам потребуется по крайней мере 15 изображений для каждого объекта, который вы хотите распознать приложением. Вы можете использовать изображения, предоставленные в этом курсе (серия кубков).

Чтобы обучить проект Пользовательское визуальное распознавание, выполните приведенные далее действия.

  1. Нажмите кнопку + рядом с элементом Теги.

    Снимок экрана: кнопка

  2. Добавьте имя для тега, который будет использоваться для связывания изображений с. В этом примере мы используем изображения кубков для распознавания, поэтому назвали тег для этого, Кубок. После завершения нажмите кнопку Сохранить .

    Снимок экрана: место добавления имени для тега.

  3. Вы заметите, что добавлен тег (возможно, потребуется перезагрузить страницу, чтобы она появилась).

    Снимок экрана: место добавления тега.

  4. Щелкните Добавить изображения в центре страницы.

    Снимок экрана: место для добавления изображений.

  5. Щелкните Обзор локальных файлов и перейдите к изображениям, которые вы хотите отправить для одного объекта, не менее пятнадцати (15).

    Совет

    Вы можете выбрать несколько изображений одновременно для отправки.

    Снимок экрана: изображения, которые можно отправить.

  6. После выбора всех изображений, с которыми вы хотите обучить проект, нажмите кнопку Отправить файлы . Начнется отправка файлов. После подтверждения отправки нажмите кнопку Готово.

    Снимок экрана: ход выполнения отправленных изображений.

  7. На этом этапе изображения отправляются, но не помечены тегами.

    Снимок экрана: изображение без потемов.

  8. Чтобы пометить изображения, используйте мышь. Когда вы наведите указатель мыши на изображение, выделение выделения поможет вам автоматически нарисовать выделение вокруг объекта. Если он не является точным, вы можете нарисовать свой собственный. Это достигается путем щелчка левой кнопкой мыши и перетаскивания области выделения для охвата объекта.

    Снимок экрана: добавление тегов к изображению.

  9. После выбора объекта на изображении появится небольшой запрос на добавление тега региона. Выберите ранее созданный тег ("Кубок", в приведенном выше примере) или, если вы добавляете дополнительные теги, введите его в и нажмите кнопку + (плюс).

    Снимок экрана: тег, добавленный в изображение.

  10. Чтобы пометить следующее изображение, щелкните стрелку справа от колонки или закройте колонку тега (щелкнув X в правом верхнем углу колонки), а затем щелкните следующее изображение. После подготовки следующего образа повторите ту же процедуру. Сделайте это для всех отправленных изображений, пока они не будут помечены тегами.

    Примечание

    На одном изображении можно выбрать несколько объектов, как показано ниже:

    Снимок экрана: несколько объектов на изображении.

  11. Помечая их все, нажмите кнопку с тегами в левой части экрана, чтобы отобразить изображения с тегами.

    Снимок экрана, на котором выделена кнопка с тегами.

  12. Теперь вы готовы обучить службу. Нажмите кнопку Обучение , и начнется первая итерация обучения.

    Снимок экрана, на котором выделена кнопка

    Снимок экрана: первая итерация обучения.

  13. После сборки вы увидите две кнопки с именем Сделать по умолчанию и URL-адрес прогнозирования. Сначала щелкните Сделать по умолчанию , а затем — URL-адрес прогнозирования.

    Снимок экрана, на котором выделена кнопка

    Примечание

    Конечная точка, которая предоставляется из этого параметра, имеет значение , которое помечено как итерация по умолчанию. Таким образом, если позже вы сделаете новую итерацию и обновите ее по умолчанию, вам не нужно будет изменять код.

  14. Щелкнув URL-адрес прогнозирования, откройте Блокнот, скопируйте и вставьте URL-адрес (также называемый конечной точкой прогнозирования) и ключ прогнозирования службы, чтобы получить его при необходимости позже в коде.

    Снимок экрана: конечная точка прогнозирования и ключ предустановки.

Глава 3. Настройка проекта Unity

Ниже приведена типичная настройка для разработки со смешанной реальностью и, следовательно, хороший шаблон для других проектов.

  1. Откройте Unity и нажмите кнопку Создать.

    Снимок экрана: кнопка

  2. Теперь необходимо указать имя проекта Unity. Вставьте CustomVisionObjDetection. Убедитесь, что для типа проекта задано значение 3D, а для параметра Расположение задайте подходящее для вас место (помните, что лучше ближе к корневым каталогам). Затем щелкните Создать проект.

    Снимок экрана, на котором показаны сведения о проекте и место для выбора пункта Создать проект.

  3. При открытии Unity стоит проверить, что для редактора скриптов по умолчанию задано значение Visual Studio. Перейдите в раздел Изменение>параметров , а затем в новом окне перейдите к разделу Внешние инструменты. Измените внешний редактор скриптов на Visual Studio. Закройте окно Параметры .

    Снимок экрана, на котором показано, где изменить внешний редактор скриптов на Visual Studio.

  4. Затем перейдите в раздел Параметры сборки файлов > и переключите платформу на универсальная платформа Windows, а затем нажмите кнопку Переключить платформу.

    Снимок экрана, на котором выделена кнопка

  5. Убедитесь, что в том же окне Параметры сборки заданы следующие параметры:

    1. Для целевого устройствазадано значение HoloLens

    2. Для параметра Тип сборки задано значение D3D.

    3. Для пакета SDK задано значение Последняя установленная версия.

    4. Версия Visual Studio имеет значение Последняя установленная

    5. Для сборки и запуска задано значение Локальный компьютер.

    6. Остальные параметры в разделе Параметры сборки пока следует оставить по умолчанию.

      Снимок экрана: параметры конфигурации параметров сборки.

  6. В том же окне Параметры сборки нажмите кнопку Параметры проигрывателя . Откроется соответствующая панель в пространстве, где находится инспектор .

  7. На этой панели необходимо проверить несколько параметров:

    1. На вкладке Другие параметры :

      1. Версия среды выполнения сценариев должна быть экспериментальной (эквивалент .NET 4.6), что вызовет необходимость перезапуска редактора.

      2. Серверная часть сценариев должна быть .NET.

      3. Уровень совместимости API должен быть .NET 4.6.

        Снимок экрана, на котором показан параметр

    2. На вкладке Параметры публикации в разделе Возможности проверка:

      1. InternetClient;

      2. Веб-камера

      3. SpatialPerception;

        Снимок экрана: верхняя половина параметров конфигурации Возможностей.Снимок экрана: нижняя половина параметров конфигурации возможностей.

    3. Далее вниз по панели в разделе Параметры XR (в разделе Параметры публикации) установите флажок Виртуальная реальность Поддерживается, а затем убедитесь, что пакет SDK для Windows Mixed Reality добавлен.

      Снимок экрана: добавлен пакет SDK для Windows Mixed Reality.

  8. Вернитесь в параметры сборки, проекты Unity C# больше не выделены серым цветом: установите флажок рядом с этим.

  9. Закройте окно Build Settings (Параметры сборки).

  10. В редакторе щелкните Изменить>параметры проекта Графика>.

    Снимок экрана: выбранный пункт меню

  11. На панели Инспектор будут открыты параметры графики . Прокрутите вниз, пока не увидите массив с именем Всегда включать шейдеры. Добавьте слот, увеличив переменную Size на один (в этом примере она была 8, поэтому мы сделали ее 9). В последней позиции массива появится новый слот, как показано ниже:

    Снимок экрана: массив всегда включенных шейдеров.

  12. В слоте щелкните небольшой целевой круг рядом с слотом, чтобы открыть список шейдеров. Найдите шейдеры прежних версий,прозрачный/диффузный шейдер и дважды щелкните его.

    Снимок экрана: шейдеры прежних версий,прозрачные/диффузные шейдеры.

Глава 4. Импорт пакета Unity CustomVisionObjDetection

Для этого курса вам будет предоставлен пакет ресурсов Unity с именем Azure-MR-310.unitypackage.

[СОВЕТ] Все объекты, поддерживаемые Unity, включая целые сцены, можно упаковываться в UNITYPACKAGE-файл и экспортировать или импортировать в другие проекты. Это самый безопасный и эффективный способ перемещения ресурсов между разными проектами Unity.

Пакет Azure-MR-310, который необходимо скачать, можно найти здесь.

  1. На панели мониторинга Unity щелкните Активы в меню в верхней части экрана, а затем выберите Импорт > пользовательского пакета.

    Снимок экрана, на котором выделен пункт меню

  2. С помощью средства выбора файлов выберите пакет Azure-MR-310.unitypackage и нажмите кнопку Открыть. Отобразится список компонентов для этого ресурса. Подтвердите импорт, нажав кнопку Импорт .

    Снимок экрана: список компонентов ресурсов, которые требуется импортировать.

  3. После завершения импорта вы увидите, что папки из пакета теперь добавлены в папку Assets . Такой тип структуры папок является типичным для проекта Unity.

    Снимок экрана: содержимое папки Assets.

    1. Папка Materials содержит материал, используемый курсором gaze.

    2. Папка Plugins содержит библиотеку DLL Newtonsoft, используемую кодом для десериализации веб-ответа службы. Две (2) разные версии, содержащиеся в папке и вложенной папке, необходимы, чтобы библиотека использовалась и создавалась как редактором Unity, так и сборкой UWP.

    3. Папка Prefabs содержит префабы, содержащиеся в сцене. Это следующие.

      1. GazeCursor, курсор, используемый в приложении. Будет работать вместе с заготовкой SpatialMapping, чтобы иметь возможность размещаться в сцене поверх физических объектов.
      2. Label— объект пользовательского интерфейса, используемый для отображения тега объекта в сцене при необходимости.
      3. SpatialMapping, который является объектом, который позволяет приложению использовать создание виртуальной карты с помощью пространственного отслеживания Microsoft HoloLens.
    4. Папка Scenes , которая в настоящее время содержит предварительно созданную сцену для этого курса.

  4. Откройте папку Scenes на панели проекта и дважды щелкните objDetectionScene, чтобы загрузить сцену, которую вы будете использовать для этого курса.

    Снимок экрана: ObjDetectionScene в папке Scenes.

    Примечание

    Код не включен. Вы напишете код, следуя этому курсу.

Глава 5. Создание класса CustomVisionAnalyser.

На этом этапе вы готовы написать код. Вы начнете с класса CustomVisionAnalyser .

Примечание

Вызовы службы Пользовательское визуальное распознавание, выполненные в приведенном ниже коде, выполняются с помощью Пользовательское визуальное распознавание REST API. С помощью этого вы узнаете, как реализовать и использовать этот API (полезно для понимания того, как реализовать что-то подобное самостоятельно). Имейте в виду, что корпорация Майкрософт предлагает пакет SDK для Пользовательское визуальное распознавание, который также можно использовать для вызовов службы. Дополнительные сведения см. в статье о пакете SDK для Пользовательское визуальное распознавание.

Этот класс отвечает за:

  • Загрузка последнего изображения, записанного в виде массива байтов.

  • Отправка массива байтов в экземпляр Службы Пользовательское визуальное распознавание Azure для анализа.

  • Получение ответа в виде строки JSON.

  • Десериализация ответа и передача полученного прогноза в класс SceneOrganiser , который будет заботиться о том, как должен отображаться ответ.

Чтобы создать этот класс, выполните указанные ниже действия.

  1. Щелкните правой кнопкой мыши папку ресурса, расположенную на панели проектов, и выберите команду Создать>папку. Вызовите папку Scripts.

    Снимок экрана: создание папки

  2. Дважды щелкните созданную папку, чтобы открыть ее.

  3. Щелкните правой кнопкой мыши папку и выберите команду Создать>скрипт C#. Назовите скрипт CustomVisionAnalyser.

  4. Дважды щелкните новый скрипт CustomVisionAnalyser , чтобы открыть его с помощью Visual Studio.

  5. Убедитесь, что в верхней части файла имеются ссылки на следующие пространства имен:

    using Newtonsoft.Json;
    using System.Collections;
    using System.IO;
    using UnityEngine;
    using UnityEngine.Networking;
    
  6. В классе CustomVisionAnalyser добавьте следующие переменные:

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

    Примечание

    Убедитесь, что вы вставляете ключ прогнозирования службы в переменную predictionKey и prediction-Endpoint в переменную predictionEndpoint . Вы скопировали их в Блокнот ранее в главе 2, шаг 14.

  7. Теперь необходимо добавить код для Awake() для инициализации переменной Instance:

        /// <summary>
        /// Initializes this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Добавьте сопрограмму (со статическим методом GetImageAsByteArray(), которая получит результаты анализа изображения, записанного классом ImageCapture .

    Примечание

    В сопрограмме AnalyseImageCapture есть вызов класса SceneOrganiser , который еще предстоит создать. Поэтому пока оставьте эти строки прокомментированы.

        /// <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. Удалите методы Start() и Update(), так как они не будут использоваться.

  10. Не забудьте сохранить изменения в Visual Studio, прежде чем возвращаться в Unity.

Важно!

Как упоминалось ранее, не беспокойтесь о коде, который может показаться ошибкой, так как в ближайшее время вы предоставите дополнительные классы, которые исправят эти ошибки.

Глава 6. Создание класса CustomVisionObjects

Класс, который вы создадите сейчас, является классом CustomVisionObjects .

Этот скрипт содержит ряд объектов, используемых другими классами для сериализации и десериализации вызовов службы Пользовательское визуальное распознавание.

Чтобы создать этот класс, выполните указанные ниже действия.

  1. Щелкните правой кнопкой мыши папку Скрипты и выберите команду Создать>скрипт C#. Вызовите скрипт CustomVisionObjects.

  2. Дважды щелкните новый скрипт CustomVisionObjects , чтобы открыть его в Visual Studio.

  3. Убедитесь, что в верхней части файла имеются ссылки на следующие пространства имен:

    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Удалите методы Start() и Update() в классе CustomVisionObjects . Теперь этот класс должен быть пустым.

    Предупреждение

    Важно внимательно следовать следующей инструкции. Если вы поместите объявления нового класса в класс CustomVisionObjects , в главе 10 вы получите ошибки компиляции, указывающие, что AnalysisRootObject и BoundingBox не найдены.

  5. Добавьте следующие классы вне класса CustomVisionObjects . Эти объекты используются библиотекой Newtonsoft для сериализации и десериализации данных ответа:

    // 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. Не забудьте сохранить изменения в Visual Studio, прежде чем возвращаться в Unity.

Глава 7. Создание класса SpatialMapping

Этот класс задаст коллайдер пространственного сопоставления в сцене, чтобы иметь возможность обнаруживать столкновения между виртуальными объектами и реальными объектами.

Чтобы создать этот класс, выполните указанные ниже действия.

  1. Щелкните правой кнопкой мыши папку Скрипты и выберите команду Создать>скрипт C#. Вызовите скрипт SpatialMapping.

  2. Дважды щелкните новый скрипт SpatialMapping , чтобы открыть его в Visual Studio.

  3. Убедитесь, что над классом SpatialMapping имеются ссылки на следующие пространства имен:

    using UnityEngine;
    using UnityEngine.XR.WSA;
    
  4. Затем добавьте следующие переменные в класс SpatialMapping над методом 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. Добавьте Awake() и 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. Удалите метод Update().

  7. Не забудьте сохранить изменения в Visual Studio, прежде чем возвращаться в Unity.

Глава 8. Создание класса GazeCursor

Этот класс отвечает за настройку курсора в правильном расположении в реальном пространстве, используя SpatialMappingCollider, созданный в предыдущей главе.

Чтобы создать этот класс, выполните указанные ниже действия.

  1. Щелкните правой кнопкой мыши папку Скрипты и выберите команду Создать>скрипт C#. Вызов скрипта GazeCursor

  2. Дважды щелкните новый скрипт GazeCursor, чтобы открыть его с помощью Visual Studio.

  3. Убедитесь, что над классом GazeCursor указано следующее пространство имен:

    using UnityEngine;
    
  4. Затем добавьте следующую переменную в класс GazeCursor над методом Start().

        /// <summary>
        /// The cursor (this object) mesh renderer
        /// </summary>
        private MeshRenderer meshRenderer;
    
  5. Обновите метод Start() следующим кодом:

        /// <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. Обновите метод Update() следующим кодом:

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

    Примечание

    Не беспокойтесь о том, что ошибка для класса SceneOrganiser не найдена, вы создадите его в следующей главе.

  7. Не забудьте сохранить изменения в Visual Studio, прежде чем возвращаться в Unity.

Глава 9. Создание класса SceneOrganiser

Этот класс будет:

  • Настройте основную камеру , подключив к ней соответствующие компоненты.

  • При обнаружении объекта он будет отвечать за вычисление его положения в реальном мире и размещение рядом с ним метки с соответствующим именем тега.

Чтобы создать этот класс, выполните указанные ниже действия.

  1. Щелкните правой кнопкой мыши папку Скрипты и выберите команду Создать>скрипт C#. Присвойте скрипту имя SceneOrganiser.

  2. Дважды щелкните новый скрипт SceneOrganiser , чтобы открыть его с помощью Visual Studio.

  3. Убедитесь, что над классом SceneOrganiser имеются ссылки на следующие пространства имен:

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
  4. Затем добавьте следующие переменные в класс SceneOrganiser над методом 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. Удалите методы Start() и Update().

  6. Под переменными добавьте метод Awake(), который инициализирует класс и настраивает сцену.

        /// <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. Добавьте метод PlaceAnalysisLabel(), который создаст экземпляр метки в сцене (которая на данный момент невидима для пользователя). Он также помещает квадроцикл (также невидимый), где размещается изображение, и перекрывается с реальным миром. Это важно, так как координаты поля, полученные из службы после анализа, отслеживаются обратно в этот четырехугольник, чтобы определить приблизительное расположение объекта в реальном мире.

        /// <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. Добавьте метод FinaliseLabel(). Он отвечает за следующее:

    • Задав текст Метка прогноза с наивысшей достоверностью.
    • Вызов вычисления ограничивающего прямоугольника для четырехугольника, расположенного ранее, и размещение метки в сцене.
    • Настройка глубины метки с помощью raycast к ограничивающей рамке, которая должна столкнуться с объектом в реальном мире.
    • Сброс процесса записи, чтобы позволить пользователю записать другое изображение.
        /// <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. Добавьте метод CalculateBoundingBoxPosition(), который размещает ряд вычислений, необходимых для преобразования координат ограничивающего прямоугольника , полученных из службы, и воссоздания их пропорционально на четырехугольнике.

        /// <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. Не забудьте сохранить изменения в Visual Studio, прежде чем возвращаться в Unity.

    Важно!

    Прежде чем продолжить, откройте класс CustomVisionAnalyser и в методе AnalyseLastImageCaptured()раскомментируйте следующие строки:

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

Примечание

Не беспокойтесь о сообщении класса ImageCapture "не удалось найти", вы создадите его в следующей главе.

Глава 10. Создание класса ImageCapture

Следующий класс, который вы собираетесь создать, — это класс ImageCapture .

Этот класс отвечает за:

  • Захват изображения с помощью камеры HoloLens и сохранение его в папке Приложения .
  • Обработка жестов касания от пользователя.

Чтобы создать этот класс, выполните указанные ниже действия.

  1. Перейдите в папку Скрипты, созданную ранее.

  2. Щелкните правой кнопкой мыши папку и выберите команду Создать>скрипт C#. Назовите скрипт ImageCapture.

  3. Дважды щелкните новый скрипт ImageCapture , чтобы открыть его в Visual Studio.

  4. Замените пространства имен в верхней части файла следующим кодом:

    using System;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.XR.WSA.Input;
    using UnityEngine.XR.WSA.WebCam;
    
  5. Затем добавьте следующие переменные в класс ImageCapture над методом 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. Теперь необходимо добавить код для методов Awake() и 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. Реализуйте обработчик, который будет вызываться при выполнении жеста Касания:

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

    Важно!

    Если курсор зеленый, это означает, что камера доступна для получения изображения. Если курсор красный, это означает, что камера занята.

  8. Добавьте метод, который приложение использует для запуска процесса записи образа и сохранения изображения:

        /// <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. Добавьте обработчики, которые будут вызываться, когда фотография будет захвачена и когда она будет готова к анализу. Затем результат передается в CustomVisionAnalyser для анализа.

        /// <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. Не забудьте сохранить изменения в Visual Studio, прежде чем возвращаться в Unity.

Глава 11. Настройка скриптов в сцене

Теперь, когда вы написали весь код, необходимый для этого проекта, настало время настроить скрипты в сцене и на заготовках, чтобы они правильно функционировали.

  1. В редакторе Unity на панели иерархии выберите основную камеру.

  2. На панели Инспектор, выбрав основную камеру , щелкните Добавить компонент, а затем найдите сценарий SceneOrganiser и дважды щелкните его, чтобы добавить его.

    Снимок экрана: скрипт SceneOrganizer.

  3. На панели проекта откройте папку Prefabs и перетащите заготовку Метка в область ввода Метка пустой целевой целевой ссылки в скрипте SceneOrganiser , который вы только что добавили в основную камеру, как показано на рисунке ниже:

    Снимок экрана: скрипт, добавленный в основную камеру.

  4. На панели иерархии выберите дочерний элемент GazeCursorосновной камеры.

  5. На панели Инспектор, выбрав GazeCursor , щелкните Добавить компонент, а затем найдите скрипт GazeCursor и дважды щелкните его, чтобы добавить его.

    Снимок экрана: место добавления скрипта GazeCursor.

  6. Снова на панели иерархии выберите дочерний элемент SpatialMappingосновной камеры.

  7. На панели Инспектор, выбрав SpatialMapping , щелкните Добавить компонент, а затем найдите скрипт SpatialMapping и дважды щелкните его, чтобы добавить его.

    Снимок экрана: место добавления скрипта SpatialMapping.

Остальные скрипты, которые вы не настроили, будут добавлены кодом в скрипт SceneOrganiser во время выполнения.

Глава 12. Перед сборкой

Чтобы выполнить тщательную проверку приложения, необходимо загрузить его на Microsoft HoloLens.

Прежде чем сделать, убедитесь, что:

  • Все параметры, упомянутые в главе 3 , заданы правильно.

  • Скрипт SceneOrganiser присоединяется к объекту Main Camera .

  • Скрипт GazeCursor присоединяется к объекту GazeCursor .

  • Скрипт SpatialMapping присоединяется к объекту SpatialMapping .

  • В главе 5, шаг 6:

    • Вставьте ключ прогнозирования службы в переменную predictionKey .
    • Вы вставляете конечную точку прогнозирования в класс predictionEndpoint .

Глава 13. Создание решения UWP и загрузка неопубликованного приложения

Теперь вы готовы создать приложение в качестве решения UWP, которое можно будет развернуть на Microsoft HoloLens. Чтобы начать процесс сборки, выполните следующие действия.

  1. Перейдите в раздел Параметры сборки файлов>.

  2. Установите флажок Проекты C# в Unity.

  3. Щелкните Добавить открытые сцены. При этом в сборку будет добавлена открытая в данный момент сцена.

    Снимок экрана, на котором выделена кнопка

  4. Щелкните Построить. Unity запустит окно проводник, в котором необходимо создать, а затем выбрать папку для сборки приложения. Создайте папку и назовите ее App. Затем, выбрав папку приложения , щелкните Выбрать папку.

  5. Unity начнет сборку проекта в папке App .

  6. После завершения сборки Unity (это может занять некоторое время), откроется окно проводник в расположении сборки (проверка панели задач, так как она может не всегда отображаться над окнами, но будет уведомлять вас о добавлении нового окна).

  7. Чтобы выполнить развертывание на Microsoft HoloLens, вам потребуется IP-адрес этого устройства (для удаленного развертывания) и убедиться, что на нем также установлен режим разработчика. Для этого выполните следующие действия.

    1. При ношении HoloLens откройте раздел Параметры.

    2. Перейдите в раздел Сетевые & ДополнительныепараметрыWi-Fi> в Интернете >

    3. Запишите IPv4-адрес .

    4. Затем вернитесь к разделу Параметры, а затем выберите Обновление безопасности &>для разработчиков.

    5. Установите режим разработчикавключено.

  8. Перейдите к новой сборке Unity (папке App ) и откройте файл решения в Visual Studio.

  9. В разделе Конфигурация решения выберите Отладка.

  10. В разделе Платформа решения выберите x86, Удаленный компьютер. Вам будет предложено вставить IP-адрес удаленного устройства (в данном случае Microsoft HoloLens, которое вы записали).

    Снимок экрана, на котором показано, куда вставить IP-адрес.

  11. Перейдите в меню Сборка и щелкните Развернуть решение , чтобы загрузить неопубликованное приложение в HoloLens.

  12. Теперь ваше приложение должно появиться в списке установленных приложений на Microsoft HoloLens, готовых к запуску!

Чтобы использовать приложение, выполните следующие действия.

  • Просмотрите объект, который вы обучили с помощью службы azure Пользовательское визуальное распознавание, обнаружения объектов, и используйте жест Касание.
  • Если объект успешно обнаружен, появится текст метки в пространстве с именем тега.

Важно!

Каждый раз, когда вы захватываете фотографию и отправляете ее в Службу, вы можете вернуться на страницу Службы и повторно обучить Службу с новыми изображениями. В начале, вам, вероятно, также придется исправить ограничивающие прямоугольник , чтобы быть более точным и переобучение Службы.

Примечание

Текст метки может не отображаться рядом с объектом, если Microsoft HoloLens датчикам и (или) Объекту SpatialTrackingComponent в Unity не удается разместить соответствующие коллайдеры относительно объектов реального мира. Попробуйте использовать приложение на другой поверхности, если это так.

Приложение обнаружения объектов Пользовательское визуальное распознавание

Поздравляем! Вы создали приложение смешанной реальности, которое использует Пользовательское визуальное распознавание Azure, API обнаружения объектов, который может распознавать объект на изображении, а затем предоставлять приблизительное положение этого объекта в трехмерном пространстве.

Снимок экрана: приложение смешанной реальности, использующее Пользовательское визуальное распознавание Azure, API обнаружения объектов.

Дополнительные упражнения

Упражнение 1.

Добавляя к текстовой метке, используйте полупрозрачный куб для заключения реального объекта в трехмерный ограничивающий прямоугольник.

Упражнение 2

Обучите службу Пользовательское визуальное распознавание распознавать больше объектов.

Упражнение 3

Воспроизведение звука при распознавании объекта.

Упражнение 4

Используйте API для повторного обучения службы с теми же изображениями, которые анализирует приложение, чтобы сделать службу более точной (выполняйте прогнозирование и обучение одновременно).