HoloLens (1-го поколения) и Azure 302b: Пользовательское визуальное распознавание


Примечание

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


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

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

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

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

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

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

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

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

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

Курс HoloLens Иммерсивные гарнитуры
302b. Смешанная реальность и Azure: пользовательское визуальное распознавание ✔️ ✔️

Примечание

Хотя в этом курсе основное внимание уделяется HoloLens, вы также можете применить полученные в этом курсе сведения для Windows Mixed Reality иммерсивных гарнитур (VR). Так как иммерсивные гарнитуры (VR) не имеют доступных камер, вам потребуется внешняя камера, подключенная к компьютеру. По мере прохождения курса вы увидите заметки о любых изменениях, которые могут потребоваться для поддержки иммерсивных гарнитур (VR).

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

Примечание

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

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

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

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

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

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

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

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

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

  2. Нажмите кнопку Начало работы .

    Начало работы со службой Пользовательское визуальное распознавание

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

    Вход на портал

    Примечание

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

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

    Условия предоставления услуг

  5. Согласившись с Условиями, вы перейдете в раздел Проекты на портале. Щелкните Создать проект.

    Создание нового проекта

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

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

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

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

    4. Задайте для типы проектовзначение Классификация

    5. Задайте для свойства Доменызначение Общие.

      Настройка доменов

      Дополнительные сведения о группах ресурсов Azure см. в этой статье.

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

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

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

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

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

    Добавление нового тега

  2. Добавьте имя объекта, который вы хотите распознать. Щелкните Save(Сохранить).

    Добавление имени объекта и сохранение

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

    Включение нового тега

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

    Добавление изображений.

  5. Щелкните Обзор локальных файлов и выполните поиск, а затем выберите изображения, которые вы хотите отправить, не менее пяти (5). Помните, что все эти изображения должны содержать объект, который вы обучаете.

    Примечание

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

  6. Когда вы увидите изображения на вкладке, выберите соответствующий тег в поле Мои теги .

    Выбор тегов

  7. Щелкните Отправить файлы. Начнется отправка файлов. После подтверждения отправки нажмите кнопку Готово.

    Отправка файлов

  8. Повторите эту же процедуру, чтобы создать новый тегс именем Keyboard и отправить соответствующие фотографии для него. После создания новых тегов снимите флажокМышь , чтобы отобразить окно Добавление изображений .

  9. После настройки обоих тегов щелкните Обучить, и первая итерация обучения начнет сборку.

    Включение итерации обучения

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

    Создание URL-адреса по умолчанию и прогнозирования

    Примечание

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

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

    Копирование и вставка URL-адреса и prediction-key

  12. Щелкните шестеренку в правом верхнем углу экрана.

    Щелкните значок шестеренки, чтобы открыть параметры

  13. Скопируйте ключ обучения и вставьте его в Блокнот для последующего использования.

    Копирование ключа обучения

  14. Кроме того, скопируйте идентификатор проекта и вставьте его в файл Блокнота для последующего использования.

    Копирование идентификатора проекта

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

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

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

    Создание проекта Unity

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

    Настройка параметров проекта

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

    Настройка внешних инструментов

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

    Настройка параметров сборки

  5. Оставаясь в параметрах сборки файла > и убедитесь, что:

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

      Для иммерсивных гарнитур установите для параметра Целевое устройствозначение Любое устройство.

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

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

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

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

    6. Сохраните сцену и добавьте ее в сборку.

      1. Для этого выберите Добавить открытые сцены. Появится окно сохранения.

        Добавление открытой сцены в список сборки

      2. Создайте новую папку для этой и любой будущей сцены, а затем нажмите кнопку Создать папку , чтобы создать новую папку, назовите ее Scenes.

        Создание папки сцены

      3. Откройте только что созданную папку Scenes , а затем в текстовом поле Имя файла введитеCustomVisionScene, а затем нажмите кнопку Сохранить.

        Присвойте имя новому файлу сцены

        Имейте в виду, что сцены Unity необходимо сохранить в папке Assets , так как они должны быть связаны с проектом Unity. Создание папки scenes (и других аналогичных папок) — типичный способ структурирования проекта Unity.

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

      Параметры сборки по умолчанию

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

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

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

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

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

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

      Настройка compantiblity API

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

      1. InternetClient;

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

      3. Микрофон

      Настройка параметров публикации

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

    Настройка параметров XR

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

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

  10. Сохраните сцену и проект (FILE > SAVE SCENE/FILE > SAVE PROJECT).

Глава 4. Импорт библиотеки DLL Newtonsoft в Unity

Важно!

Если вы хотите пропустить компонент настройки Unity этого курса и перейти непосредственно к коду, скачайте этот пакет Azure-MR-302b.unitypackage, импортируйте его в проект в качестве пользовательского пакета, а затем перейдите к главе 6.

Этот курс требует использования библиотеки Newtonsoft , которую можно добавить в качестве библиотеки DLL в ресурсы. Пакет, содержащий эту библиотеку, можно скачать по этой ссылке. Чтобы импортировать библиотеку Newtonsoft в проект, используйте пакет Unity, который входит в этот курс.

  1. Добавьте .unitypackage в Unity с помощью параметра меню Пользовательскийпакетимпортапакета>ресурсов>.

  2. В появившемся окне Импорт пакета Unity убедитесь, что выбрано все содержимое в разделе (и в том числе) Подключаемые модули .

    Импорт всех элементов пакета

  3. Нажмите кнопку Импорт , чтобы добавить элементы в проект.

  4. Перейдите в папку Newtonsoft в разделе Подключаемые модули в представлении проекта и выберите подключаемый модуль Newtonsoft.Json.

    Выбор подключаемого модуля Newtonsoft

  5. Выбрав подключаемый модуль Newtonsoft.Json, снимите флажокЛюбая платформа, а затем снимите флажокWSAPlayer, а затем нажмите кнопку Применить. Это просто для подтверждения правильности настройки файлов.

    Настройка подключаемого модуля Newtonsoft

    Примечание

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

  6. Затем необходимо открыть папку WSA в папке Newtonsoft . Вы увидите копию того же файла, который вы только что настроили. Выберите файл, а затем в инспекторе убедитесь, что

    • Флажок "Любая платформа " снят
    • Установлен флажоктолькоWSAPlayer
    • Проверка не выполняется

    Настройка параметров платформы подключаемого модуля Newtonsoft

Глава 5. Настройка камеры

  1. На панели Иерархия выберите основную камеру.

  2. После выбора вы увидите все компоненты основной камеры на панели инспектора.

    1. Объект камеры должен называться Main Camera (обратите внимание на орфографию!)

    2. Для тега основной камеры должно быть задано значение MainCamera (обратите внимание на орфографию!)

    3. Убедитесь, что для позиции преобразования задано значение 0, 0, 0.

    4. Установите для параметра Очистить флаги значение Сплошной цвет (игнорируйте это для иммерсивной гарнитуры).

    5. Задайте для параметра Цвет фона компонента камеры значение Черный, Альфа 0 (шестнадцатеричный код: #00000000) (игнорируйте это для иммерсивной гарнитуры).

    Настройка свойств компонента камеры

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

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

Вы начнете с класса CustomVisionAnalyser .

Примечание

Вызовы службы Пользовательское визуальное распознавание, выполненные в приведенном ниже коде, выполняются с помощью ПОЛЬЗОВАТЕЛЬСКОЕ ВИЗУАЛЬНОЕ РАСПОЗНАВАНИЕ REST API. С помощью этого вы узнаете, как реализовать и использовать этот API (полезно для понимания того, как реализовать что-то подобное самостоятельно). Имейте в виду, что корпорация Майкрософт предлагает пакет SDK службы Пользовательское визуальное распознавание, который также можно использовать для вызовов службы. Дополнительные сведения см. в статье пакет SDK службы Пользовательское визуальное распознавание.

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

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

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

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

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

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

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

    Папка

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

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

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

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

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

    Примечание

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

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

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
        }
    
  8. Удалите методы Start() и Update().

  9. Затем добавьте сопрограмму (со статическим методом GetImageAsByteArray(), которая получит результаты анализа изображения, записанного классом ImageCapture .

    Примечание

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

        /// <summary>
        /// Call the Computer Vision Service to submit the image.
        /// </summary>
        public IEnumerator AnalyseLastImageCaptured(string imagePath)
        {
            WWWForm webForm = new WWWForm();
            using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(predictionEndpoint, webForm))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);
    
                unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                unityWebRequest.SetRequestHeader("Prediction-Key", predictionKey);
    
                // The upload handler will help uploading the byte array with the request
                unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
                unityWebRequest.uploadHandler.contentType = "application/octet-stream";
    
                // The download handler will help receiving the analysis from Azure
                unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return unityWebRequest.SendWebRequest();
    
                string jsonResponse = unityWebRequest.downloadHandler.text;
    
                // The response will be in JSON format, therefore it needs to be deserialized    
    
                // The following lines refers to a class that you will build in later Chapters
                // Wait until then to uncomment these lines
    
                //AnalysisObject analysisObject = new AnalysisObject();
                //analysisObject = JsonConvert.DeserializeObject<AnalysisObject>(jsonResponse);
                //SceneOrganiser.Instance.SetTagsToLastLabel(analysisObject);
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
    
            BinaryReader binaryReader = new BinaryReader(fileStream);
    
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  10. Перед возвращением в Unity обязательно сохраните изменения в Visual Studio.

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

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

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

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

Важно заметить конечную точку, которую предоставляет служба Пользовательское визуальное распознавание, так как приведенная ниже структура JSON настроена для работы с Пользовательское визуальное распознавание Prediction версии 2.0. Если у вас другая версия, может потребоваться обновить приведенную ниже структуру.

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

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

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

  3. Добавьте следующие пространства имен в начало файла:

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

  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
    /// </summary> 
    [Serializable]
    public class AnalysisObject
    {
        public List<Prediction> Predictions { get; set; }
    }
    
    [Serializable]
    public class Prediction
    {
        public string TagName { get; set; }
        public double Probability { get; set; }
    }
    

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

Этот класс распознает голосовые данные пользователя.

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

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

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

  3. Добавьте следующие пространства имен над классом VoiceRecognizer :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Windows.Speech;
    
  4. Затем добавьте следующие переменные в класс VoiceRecognizer над методом Start():

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static VoiceRecognizer Instance;
    
        /// <summary>
        /// Recognizer class for voice recognition
        /// </summary>
        internal KeywordRecognizer keywordRecognizer;
    
        /// <summary>
        /// List of Keywords registered
        /// </summary>
        private Dictionary<string, Action> _keywords = new Dictionary<string, Action>();
    
  5. Добавьте методы Awake() и Start(), последний из которых настроит ключевые слова пользователя для распознавания при связывании тега с изображением:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start ()
        {
    
            Array tagsArray = Enum.GetValues(typeof(CustomVisionTrainer.Tags));
    
            foreach (object tagWord in tagsArray)
            {
                _keywords.Add(tagWord.ToString(), () =>
                {
                    // When a word is recognized, the following line will be called
                    CustomVisionTrainer.Instance.VerifyTag(tagWord.ToString());
                });
            }
    
            _keywords.Add("Discard", () =>
            {
                // When a word is recognized, the following line will be called
                // The user does not want to submit the image
                // therefore ignore and discard the process
                ImageCapture.Instance.ResetImageCapture();
                keywordRecognizer.Stop();
            });
    
            //Create the keyword recognizer 
            keywordRecognizer = new KeywordRecognizer(_keywords.Keys.ToArray());
    
            // Register for the OnPhraseRecognized event 
            keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
        }
    
  6. Удалите метод Update().

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

        /// <summary>
        /// Handler called when a word is recognized
        /// </summary>
        private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
        {
            Action keywordAction;
            // if the keyword recognized is in our dictionary, call that Action.
            if (_keywords.TryGetValue(args.text, out keywordAction))
            {
                keywordAction.Invoke();
            }
        }
    
  8. Перед возвращением в Unity обязательно сохраните изменения в Visual Studio.

Примечание

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

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

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

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

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

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

  3. Добавьте следующие пространства имен над классом CustomVisionTrainer :

    using Newtonsoft.Json;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    
  4. Затем добавьте следующие переменные в класс CustomVisionTrainer над методом Start().

    Примечание

    URL-адрес обучения, используемый здесь, предоставляется в документации по Пользовательское визуальное распознавание Training 1.2 и имеет структуру:https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/{projectId}/
    Дополнительные сведения см. в справочнике по API обучения Пользовательское визуальное распознавание версии 1.2.

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

    Важно отметить конечную точку, которую служба Пользовательское визуальное распознавание предоставляет для режима обучения, так как структура JSON, используемая (в классе CustomVisionObjects), настроена для работы с Пользовательское визуальное распознавание Training версии 1.2. Если у вас другая версия, может потребоваться обновить структуру Объектов .

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static CustomVisionTrainer Instance;
    
        /// <summary>
        /// Custom Vision Service URL root
        /// </summary>
        private string url = "https://southcentralus.api.cognitive.microsoft.com/customvision/v1.2/Training/projects/";
    
        /// <summary>
        /// Insert your prediction key here
        /// </summary>
        private string trainingKey = "- Insert your key here -";
    
        /// <summary>
        /// Insert your Project Id here
        /// </summary>
        private string projectId = "- Insert your Project Id here -";
    
        /// <summary>
        /// Byte array of the image to submit for analysis
        /// </summary>
        internal byte[] imageBytes;
    
        /// <summary>
        /// The Tags accepted
        /// </summary>
        internal enum Tags {Mouse, Keyboard}
    
        /// <summary>
        /// The UI displaying the training Chapters
        /// </summary>
        private TextMesh trainingUI_TextMesh;
    

    Важно!

    Убедитесь, что вы добавили значение ключа службы (обучающий ключ) и значение идентификатора проекта, которое вы записали ранее. это значения, собранные на портале ранее в курсе (глава 2, шаг 10 и более поздние).

  5. Добавьте следующие методы Start() и Awake(). Эти методы вызываются при инициализации и содержат вызов для настройки пользовательского интерфейса:

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        private void Start()
        { 
            trainingUI_TextMesh = SceneOrganiser.Instance.CreateTrainingUI("TrainingUI", 0.04f, 0, 4, false);
        }
    
  6. Удалите метод Update(). Этот класс не потребуется.

  7. Добавьте метод RequestTagSelection(). Этот метод первым вызывается, когда образ был записан и сохранен на устройстве и теперь готов к отправке в службу Пользовательское визуальное распознавание для его обучения. Этот метод отображает в пользовательском интерфейсе обучения набор ключевых слов, которые пользователь может использовать для добавления тегов к изображению, которое было записано. Он также оповещает класс VoiceRecognizer , чтобы начать прослушивание пользователя для голосового ввода.

        internal void RequestTagSelection()
        {
            trainingUI_TextMesh.gameObject.SetActive(true);
            trainingUI_TextMesh.text = $" \nUse voice command \nto choose between the following tags: \nMouse\nKeyboard \nor say Discard";
    
            VoiceRecognizer.Instance.keywordRecognizer.Start();
        }
    
  8. Добавьте метод VerifyTag(). Этот метод получит голосовые данные, распознанные классом VoiceRecognizer , и проверит его допустимость, а затем начнет процесс обучения.

        /// <summary>
        /// Verify voice input against stored tags.
        /// If positive, it will begin the Service training process.
        /// </summary>
        internal void VerifyTag(string spokenTag)
        {
            if (spokenTag == Tags.Mouse.ToString() || spokenTag == Tags.Keyboard.ToString())
            {
                trainingUI_TextMesh.text = $"Tag chosen: {spokenTag}";
                VoiceRecognizer.Instance.keywordRecognizer.Stop();
                StartCoroutine(SubmitImageForTraining(ImageCapture.Instance.filePath, spokenTag));
            }
        }
    
  9. Добавьте метод SubmitImageForTraining(). Этот метод начнет процесс обучения службы Пользовательское визуальное распознавание. Первым шагом является получение идентификатора тега из службы, связанной с проверенным речевым вводом от пользователя. Затем идентификатор тега будет отправлен вместе с изображением.

        /// <summary>
        /// Call the Custom Vision Service to submit the image.
        /// </summary>
        public IEnumerator SubmitImageForTraining(string imagePath, string tag)
        {
            yield return new WaitForSeconds(2);
            trainingUI_TextMesh.text = $"Submitting Image \nwith tag: {tag} \nto Custom Vision Service";
            string imageId = string.Empty;
            string tagId = string.Empty;
    
            // Retrieving the Tag Id relative to the voice input
            string getTagIdEndpoint = string.Format("{0}{1}/tags", url, projectId);
            using (UnityWebRequest www = UnityWebRequest.Get(getTagIdEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
    
                Tags_RootObject tagRootObject = JsonConvert.DeserializeObject<Tags_RootObject>(jsonResponse);
    
                foreach (TagOfProject tOP in tagRootObject.Tags)
                {
                    if (tOP.Name == tag)
                    {
                        tagId = tOP.Id;
                    }             
                }
            }
    
            // Creating the image object to send for training
            List<IMultipartFormSection> multipartList = new List<IMultipartFormSection>();
            MultipartObject multipartObject = new MultipartObject();
            multipartObject.contentType = "application/octet-stream";
            multipartObject.fileName = "";
            multipartObject.sectionData = GetImageAsByteArray(imagePath);
            multipartList.Add(multipartObject);
    
            string createImageFromDataEndpoint = string.Format("{0}{1}/images?tagIds={2}", url, projectId, tagId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(createImageFromDataEndpoint, multipartList))
            {
                // Gets a byte array out of the saved image
                imageBytes = GetImageAsByteArray(imagePath);           
    
                //unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
                www.SetRequestHeader("Training-Key", trainingKey);
    
                // The upload handler will help uploading the byte array with the request
                www.uploadHandler = new UploadHandlerRaw(imageBytes);
    
                // The download handler will help receiving the analysis from Azure
                www.downloadHandler = new DownloadHandlerBuffer();
    
                // Send the request
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                ImageRootObject m = JsonConvert.DeserializeObject<ImageRootObject>(jsonResponse);
                imageId = m.Images[0].Image.Id;
            }
            trainingUI_TextMesh.text = "Image uploaded";
            StartCoroutine(TrainCustomVisionProject());
        }
    
  10. Добавьте метод TrainCustomVisionProject(). После отправки изображения и добавления тегов будет вызван этот метод. Он создаст новую итерацию, которая будет обучена со всеми предыдущими образами, отправленными в службу, а также только что отправленным образом. После завершения обучения этот метод вызывает метод , чтобы задать для созданной итерации значение по умолчанию, чтобы конечная точка, используемая для анализа, была последней обученной итерацией.

        /// <summary>
        /// Call the Custom Vision Service to train the Service.
        /// It will generate a new Iteration in the Service
        /// </summary>
        public IEnumerator TrainCustomVisionProject()
        {
            yield return new WaitForSeconds(2);
    
            trainingUI_TextMesh.text = "Training Custom Vision Service";
    
            WWWForm webForm = new WWWForm();
    
            string trainProjectEndpoint = string.Format("{0}{1}/train", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Post(trainProjectEndpoint, webForm))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
                Debug.Log($"Training - JSON Response: {jsonResponse}");
    
                // A new iteration that has just been created and trained
                Iteration iteration = new Iteration();
                iteration = JsonConvert.DeserializeObject<Iteration>(jsonResponse);
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Custom Vision Trained";
    
                    // Since the Service has a limited number of iterations available,
                    // we need to set the last trained iteration as default
                    // and delete all the iterations you dont need anymore
                    StartCoroutine(SetDefaultIteration(iteration)); 
                }
            }
        }
    
  11. Добавьте метод SetDefaultIteration(). Этот метод установит ранее созданную и обученную итерацию в качестве значения По умолчанию. После завершения этого метода потребуется удалить предыдущую итерацию, имеющуюся в службе. На момент написания этого курса существует ограничение в десять (10) итераций, которые могут существовать одновременно в Службе.

        /// <summary>
        /// Set the newly created iteration as Default
        /// </summary>
        private IEnumerator SetDefaultIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
            trainingUI_TextMesh.text = "Setting default iteration";
    
            // Set the last trained iteration to default
            iteration.IsDefault = true;
    
            // Convert the iteration object as JSON
            string iterationAsJson = JsonConvert.SerializeObject(iteration);
            byte[] bytes = Encoding.UTF8.GetBytes(iterationAsJson);
    
            string setDefaultIterationEndpoint = string.Format("{0}{1}/iterations/{2}", 
                                                            url, projectId, iteration.Id);
    
            using (UnityWebRequest www = UnityWebRequest.Put(setDefaultIterationEndpoint, bytes))
            {
                www.method = "PATCH";
                www.SetRequestHeader("Training-Key", trainingKey);
                www.SetRequestHeader("Content-Type", "application/json");
                www.downloadHandler = new DownloadHandlerBuffer();
    
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                if (www.isDone)
                {
                    trainingUI_TextMesh.text = "Default iteration is set \nDeleting Unused Iteration";
                    StartCoroutine(DeletePreviousIteration(iteration));
                }
            }
        }
    
  12. Добавьте метод DeletePreviousIteration(). Этот метод найдет и удалит предыдущую итерацию, не являющейся стандартной:

        /// <summary>
        /// Delete the previous non-default iteration.
        /// </summary>
        public IEnumerator DeletePreviousIteration(Iteration iteration)
        {
            yield return new WaitForSeconds(5);
    
            trainingUI_TextMesh.text = "Deleting Unused \nIteration";
    
            string iterationToDeleteId = string.Empty;
    
            string findAllIterationsEndpoint = string.Format("{0}{1}/iterations", url, projectId);
    
            using (UnityWebRequest www = UnityWebRequest.Get(findAllIterationsEndpoint))
            {
                www.SetRequestHeader("Training-Key", trainingKey);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
    
                string jsonResponse = www.downloadHandler.text;
    
                // The iteration that has just been trained
                List<Iteration> iterationsList = new List<Iteration>();
                iterationsList = JsonConvert.DeserializeObject<List<Iteration>>(jsonResponse);
    
                foreach (Iteration i in iterationsList)
                {
                    if (i.IsDefault != true)
                    {
                        Debug.Log($"Cleaning - Deleting iteration: {i.Name}, {i.Id}");
                        iterationToDeleteId = i.Id;
                        break;
                    }
                }
            }
    
            string deleteEndpoint = string.Format("{0}{1}/iterations/{2}", url, projectId, iterationToDeleteId);
    
            using (UnityWebRequest www2 = UnityWebRequest.Delete(deleteEndpoint))
            {
                www2.SetRequestHeader("Training-Key", trainingKey);
                www2.downloadHandler = new DownloadHandlerBuffer();
                yield return www2.SendWebRequest();
                string jsonResponse = www2.downloadHandler.text;
    
                trainingUI_TextMesh.text = "Iteration Deleted";
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "Ready for next \ncapture";
    
                yield return new WaitForSeconds(2);
                trainingUI_TextMesh.text = "";
                ImageCapture.Instance.ResetImageCapture();
            }
        }
    
  13. Последний метод, добавляемый в этот класс, — это метод GetImageAsByteArray(), используемый в веб-вызовах для преобразования изображения, записанного в массив байтов.

        /// <summary>
        /// Returns the contents of the specified image file as a byte array.
        /// </summary>
        static byte[] GetImageAsByteArray(string imageFilePath)
        {
            FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
            BinaryReader binaryReader = new BinaryReader(fileStream);
            return binaryReader.ReadBytes((int)fileStream.Length);
        }
    
  14. Перед возвращением в Unity обязательно сохраните изменения в Visual Studio.

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

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

  • Создайте объект Cursor для подключения к основной камере.

  • Создайте объект Label , который будет отображаться, когда служба распознает реальные объекты.

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

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

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

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

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

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

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

    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 camera
        /// </summary>
        internal GameObject cursor;
    
        /// <summary>
        /// The label used to display the analysis on the objects in the real world
        /// </summary>
        internal GameObject label;
    
        /// <summary>
        /// Object providing the current status of the camera.
        /// </summary>
        internal TextMesh cameraStatusIndicator;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal Transform lastLabelPlaced;
    
        /// <summary>
        /// Reference to the last label positioned
        /// </summary>
        internal TextMesh lastLabelPlacedText;
    
        /// <summary>
        /// Current threshold accepted for displaying the label
        /// Reduce this value to display the recognition more often
        /// </summary>
        internal float probabilityThreshold = 0.5f;
    
  5. Удалите методы 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 CustomVisionTrainer class to this GameObject
            gameObject.AddComponent<CustomVisionTrainer>();
    
            // Add the VoiceRecogniser class to this GameObject
            gameObject.AddComponent<VoiceRecognizer>();
    
            // Add the CustomVisionObjects class to this GameObject
            gameObject.AddComponent<CustomVisionObjects>();
    
            // Create the camera Cursor
            cursor = CreateCameraCursor();
    
            // Load the label prefab as reference
            label = CreateLabel();
    
            // Create the camera status indicator label, and place it above where predictions
            // and training UI will appear.
            cameraStatusIndicator = CreateTrainingUI("Status Indicator", 0.02f, 0.2f, 3, true);
    
            // Set camera status indicator to loading.
            SetCameraStatus("Loading");
        }
    
  7. Теперь добавьте метод CreateCameraCursor(), который создает и размещает курсор Main Camera, и метод CreateLabel(), который создает объект Analysis Label .

        /// <summary>
        /// Spawns cursor for the Main Camera
        /// </summary>
        private GameObject CreateCameraCursor()
        {
            // Create a sphere as new cursor
            GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    
            // Attach it to the camera
            newCursor.transform.parent = gameObject.transform;
    
            // Resize the new cursor
            newCursor.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f);
    
            // Move it to the correct position
            newCursor.transform.localPosition = new Vector3(0, 0, 4);
    
            // Set the cursor color to red
            newCursor.GetComponent<Renderer>().material = new Material(Shader.Find("Diffuse"));
            newCursor.GetComponent<Renderer>().material.color = Color.green;
    
            return newCursor;
        }
    
        /// <summary>
        /// Create the analysis label object
        /// </summary>
        private GameObject CreateLabel()
        {
            // Create a sphere as new cursor
            GameObject newLabel = new GameObject();
    
            // Resize the new cursor
            newLabel.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);
    
            // Creating the text of the label
            TextMesh t = newLabel.AddComponent<TextMesh>();
            t.anchor = TextAnchor.MiddleCenter;
            t.alignment = TextAlignment.Center;
            t.fontSize = 50;
            t.text = "";
    
            return newLabel;
        }
    
  8. Добавьте метод SetCameraStatus(), который будет обрабатывать сообщения, предназначенные для сетки текста, предоставляя состояние камеры.

        /// <summary>
        /// Set the camera status to a provided string. Will be coloured if it matches a keyword.
        /// </summary>
        /// <param name="statusText">Input string</param>
        public void SetCameraStatus(string statusText)
        {
            if (string.IsNullOrEmpty(statusText) == false)
            {
                string message = "white";
    
                switch (statusText.ToLower())
                {
                    case "loading":
                        message = "yellow";
                        break;
    
                    case "ready":
                        message = "green";
                        break;
    
                    case "uploading image":
                        message = "red";
                        break;
    
                    case "looping capture":
                        message = "yellow";
                        break;
    
                    case "analysis":
                        message = "red";
                        break;
                }
    
                cameraStatusIndicator.GetComponent<TextMesh>().text = $"Camera Status:\n<color={message}>{statusText}..</color>";
            }
        }
    
  9. Добавьте методы PlaceAnalysisLabel() и SetTagsToLastLabel(), которые будут порождать и отображать данные из службы Пользовательское визуальное распознавание в сцене.

        /// <summary>
        /// Instantiate a label in the appropriate location relative to the Main Camera.
        /// </summary>
        public void PlaceAnalysisLabel()
        {
            lastLabelPlaced = Instantiate(label.transform, cursor.transform.position, transform.rotation);
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
        }
    
        /// <summary>
        /// Set the Tags as Text of the last label created. 
        /// </summary>
        public void SetTagsToLastLabel(AnalysisObject analysisObject)
        {
            lastLabelPlacedText = lastLabelPlaced.GetComponent<TextMesh>();
    
            if (analysisObject.Predictions != null)
            {
                foreach (Prediction p in analysisObject.Predictions)
                {
                    if (p.Probability > 0.02)
                    {
                        lastLabelPlacedText.text += $"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}";
                        Debug.Log($"Detected: {p.TagName} {p.Probability.ToString("0.00 \n")}");
                    }
                }
            }
        }
    
  10. Наконец, добавьте метод CreateTrainingUI(), который будет порождать пользовательский интерфейс, отображающий несколько этапов процесса обучения, когда приложение находится в режиме обучения. Этот метод также будет использоваться для создания объекта состояния камеры.

        /// <summary>
        /// Create a 3D Text Mesh in scene, with various parameters.
        /// </summary>
        /// <param name="name">name of object</param>
        /// <param name="scale">scale of object (i.e. 0.04f)</param>
        /// <param name="yPos">height above the cursor (i.e. 0.3f</param>
        /// <param name="zPos">distance from the camera</param>
        /// <param name="setActive">whether the text mesh should be visible when it has been created</param>
        /// <returns>Returns a 3D text mesh within the scene</returns>
        internal TextMesh CreateTrainingUI(string name, float scale, float yPos, float zPos, bool setActive)
        {
            GameObject display = new GameObject(name, typeof(TextMesh));
            display.transform.parent = Camera.main.transform;
            display.transform.localPosition = new Vector3(0, yPos, zPos);
            display.SetActive(setActive);
            display.transform.localScale = new Vector3(scale, scale, scale);
            display.transform.rotation = new Quaternion();
            TextMesh textMesh = display.GetComponent<TextMesh>();
            textMesh.anchor = TextAnchor.MiddleCenter;
            textMesh.alignment = TextAlignment.Center;
            return textMesh;
        }
    
  11. Перед возвращением в Unity обязательно сохраните изменения в Visual Studio.

Важно!

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

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

Глава 11. Создание класса 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>
        /// Loop timer
        /// </summary>
        private float secondsBetweenCaptures = 10f;
    
        /// <summary>
        /// Application main functionalities switch
        /// </summary>
        internal enum AppModes {Analysis, Training }
    
        /// <summary>
        /// Local variable for current AppMode
        /// </summary>
        internal AppModes AppMode { get; private set; }
    
        /// <summary>
        /// Flagging if the capture loop is running
        /// </summary>
        internal bool captureIsActive;
    
        /// <summary>
        /// File path of current analysed photo
        /// </summary>
        internal string filePath = string.Empty;
    
  6. Теперь необходимо добавить код для методов Awake() и Start().

        /// <summary>
        /// Called on initialization
        /// </summary>
        private void Awake()
        {
            Instance = this;
    
            // Change this flag to switch between Analysis Mode and Training Mode 
            AppMode = AppModes.Training;
        }
    
        /// <summary>
        /// Runs at initialization right after Awake method
        /// </summary>
        void Start()
        {
            // Clean up the LocalState folder of this application from all photos stored
            DirectoryInfo info = new DirectoryInfo(Application.persistentDataPath);
            var fileInfo = info.GetFiles();
            foreach (var file in fileInfo)
            {
                try
                {
                    file.Delete();
                }
                catch (Exception)
                {
                    Debug.LogFormat("Cannot delete file: ", file.Name);
                }
            } 
    
            // Subscribing to the HoloLens API gesture recognizer to track user gestures
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
    
            SceneOrganiser.Instance.SetCameraStatus("Ready");
        }
    
  7. Реализуйте обработчик, который будет вызываться при выполнении жеста касания.

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            switch (AppMode)
            {
                case AppModes.Analysis:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to looping capture.
                        SceneOrganiser.Instance.SetCameraStatus("Looping Capture");
    
                        // Begin the capture loop
                        InvokeRepeating("ExecuteImageCaptureAndAnalysis", 0, secondsBetweenCaptures);
                    }
                    else
                    {
                        // The user tapped while the app was analyzing 
                        // therefore stop the analysis process
                        ResetImageCapture();
                    }
                    break;
    
                case AppModes.Training:
                    if (!captureIsActive)
                    {
                        captureIsActive = true;
    
                        // Call the image capture
                        ExecuteImageCaptureAndAnalysis();
    
                        // Set the cursor color to red
                        SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.red;
    
                        // Update camera status to uploading image.
                        SceneOrganiser.Instance.SetCameraStatus("Uploading Image");
                    }              
                    break;
            }     
        }
    

    Примечание

    В режиме анализа метод TapHandler выступает в качестве переключателя для запуска или остановки цикла фотофиксации.

    В режиме обучения он будет захватывать изображение с камеры.

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

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

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

        /// <summary>
        /// Begin process of Image Capturing and send To Azure Custom Vision Service.
        /// </summary>
        private void ExecuteImageCaptureAndAnalysis()
        {
            // Update camera status to analysis.
            SceneOrganiser.Instance.SetCameraStatus("Analysis");
    
            // Create a label in world space using the SceneOrganiser class 
            // Invisible at this point but correctly positioned where the image was taken
            SceneOrganiser.Instance.PlaceAnalysisLabel();
    
            // Set the camera resolution to be the highest possible
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
    
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            // Begin capture process, set the image format
            PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters camParameters = new CameraParameters
                {
                    hologramOpacity = 0.0f,
                    cameraResolutionWidth = targetTexture.width,
                    cameraResolutionHeight = targetTexture.height,
                    pixelFormat = CapturePixelFormat.BGRA32
                };
    
                // Capture the image from the camera and save it in the App internal folder
                captureObject.StartPhotoModeAsync(camParameters, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", captureCount);
                    filePath = Path.Combine(Application.persistentDataPath, filename);          
                    captureCount++;              
                    photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);              
                });
            });   
        }
    
  9. Добавьте обработчики, которые будут вызываться, когда фотография будет захвачена, а также когда она будет готова к анализу. Затем результат передается в CustomVisionAnalyser или CustomVisionTrainer в зависимости от того, в каком режиме задан код.

        /// <summary>
        /// Register the full execution of the Photo Capture. 
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
                // Call StopPhotoMode once the image has successfully captured
                photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
        }
    
    
        /// <summary>
        /// The camera photo mode has stopped after the capture.
        /// Begin the Image Analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            Debug.LogFormat("Stopped Photo Mode");
    
            // Dispose from the object in memory and request the image analysis 
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            switch (AppMode)
            {
                case AppModes.Analysis:
                    // Call the image analysis
                    StartCoroutine(CustomVisionAnalyser.Instance.AnalyseLastImageCaptured(filePath));
                    break;
    
                case AppModes.Training:
                    // Call training using captured image
                    CustomVisionTrainer.Instance.RequestTagSelection();
                    break;
            }
        }
    
        /// <summary>
        /// Stops all capture pending actions
        /// </summary>
        internal void ResetImageCapture()
        {
            captureIsActive = false;
    
            // Set the cursor color to green
            SceneOrganiser.Instance.cursor.GetComponent<Renderer>().material.color = Color.green;
    
            // Update camera status to ready.
            SceneOrganiser.Instance.SetCameraStatus("Ready");
    
            // Stop the capture loop if active
            CancelInvoke();
        }
    
  10. Перед возвращением в Unity обязательно сохраните изменения в Visual Studio.

  11. Теперь, когда все скрипты завершены, вернитесь в редактор Unity, а затем щелкните и перетащите класс SceneOrganiser из папки Scripts в объект Main Camera на панели иерархии.

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

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

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

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

  • Все поля на панели инспектора главной камеры назначаются правильно.

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

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

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

  • Вы вставили ключ обучения в переменную trainingKey класса CustomVisionTrainer .

  • Вы вставляете идентификатор проекта в переменную projectId класса CustomVisionTrainer .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Задание IP-адреса

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

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

Примечание

Чтобы выполнить развертывание на иммерсивной гарнитуре, задайте для параметра Платформа решениязначение Локальный компьютер, а для параметра Конфигурациязначение Отладка с x86 в качестве платформы. Затем выполните развертывание на локальном компьютере с помощью пункта меню Сборка и выберите Развернуть решение.

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

Чтобы переключить функциональные возможности приложения между режимами обучения и прогнозирования , необходимо обновить переменную AppMode , расположенную в методе Awake(), расположенном в классе ImageCapture .

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

или

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

В режиме обучения :

  • Найдите мышь или клавиатуру и используйте жест касания.

  • Затем появится текст с запросом на ввод тега.

  • Скажите мышь или клавиатуру.

В режиме прогнозирования :

  • Посмотрите на объект и используйте жест Касание.

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

Глава 14. Оценка и улучшение модели Пользовательское визуальное распознавание

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

  1. Снова перейдите на портал Azure Пользовательское визуальное распознавание и, оказавшись в проекте, перейдите на вкладку Прогнозы (в верхней части страницы).

    Вкладка

  2. Вы увидите все образы, отправленные в службу во время работы приложения. Если наведите указатель мыши на изображения, они будут предоставлять вам прогнозы, которые были сделаны для этого изображения:

    Список изображений прогнозирования

  3. Выберите одно из изображений, чтобы открыть его. После открытия вы увидите прогнозы, сделанные для этого изображения справа. Если прогнозы были верными и вы хотите добавить это изображение в модель обучения службы, щелкните поле Входные данные Мои теги и выберите тег, который вы хотите связать. По завершении нажмите кнопку Сохранить и закрыть в правом нижнем углу и перейдите к следующему изображению.

    Выберите изображение, чтобы открыть

  4. Вернувшись к сетке изображений, вы заметите, что изображения, к которым вы добавили теги (и сохранили), будут удалены. Если вы нашли изображения, которые, по вашему мнению, не имеют помеченного элемента, вы можете удалить их, щелкнув галочку на этом изображении (можно сделать это для нескольких изображений), а затем нажав кнопку Удалить в правом верхнем углу страницы сетки. В следующем всплывающем окне можно нажать кнопку Да, удалить или Нет, чтобы подтвердить удаление или отменить его соответственно.

    Удаление изображений

  5. Когда вы будете готовы продолжить, нажмите зеленую кнопку Обучение в правом верхнем углу. Модель службы будет обучена на всех предоставленных вами образах (что сделает ее более точной). После завершения обучения еще раз нажмите кнопку Сделать по умолчанию , чтобы URL-адрес прогнозирования продолжал использовать самую актуальную итерацию службы.

    Начать обучение модели службыВыбор параметра сделать по умолчанию

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

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

Пример готового проекта

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

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

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

Упражнение 2

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

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

Упражнение 3

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