Поделиться через


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 Иммерсивные гарнитуры
MR и Azure 302b: пользовательское визуальное представление ✔️ ✔️

Примечание.

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

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

Примечание.

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

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

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

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

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

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

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

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

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

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

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

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

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

    Примечание.

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

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

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

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

    Создать новый проект

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

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

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

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

    4. Задайте для типов проектов классификацию

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

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

      Если вы хотите узнать больше о группах ресурсов Azure, посетите статью группы ресурсов.

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

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

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

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

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

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

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

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

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

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

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

    Добавить изображения

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

    Примечание.

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

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

    Выбор тегов

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

    Отправить файлы

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

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

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

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

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

    Примечание.

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

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

    Копирование и вставка URL-адреса и ключа прогнозирования

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    1. Целевое устройство имеет значение HoloLens

      Для иммерсивных гарнитур задайте для целевого устройства значение Any Device.

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

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

    4. Версия Visual Studio установлена в качестве последней версии

    5. Для сборки и запуска задано значение Local Machine

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

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

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

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

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

      3. Откройте только что созданную папку "Сцены" , а затем в поле "Файл": текстовое поле, введите CustomVisionScene и нажмите кнопку "Сохранить".

        Имя нового файла сцены

        Помните, что сцены Unity необходимо сохранить в папке Assets , так как они должны быть связаны с проектом Unity. Создание папки сцен (и других аналогичных папок) — это типичный способ структурирования проекта 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 (приведенных ниже параметров публикации), установите флажок "Поддерживаемая виртуальная реальность", убедитесь, что пакет SDK для Windows Смешанная реальность добавлен.

    Настройка параметров 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
    • Проверка процесса Dont

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

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

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

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

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

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

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

    4. Задайте для параметра Clear Flags значение Solid Color (игнорируйте этот параметр для иммерсивной гарнитуры).

    5. Задайте цвет фона компонента камеры black, Alpha 0 (шестнадцатеричный код: #000000000) (игнорируйте это для иммерсивной гарнитуры).

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

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

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

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

Примечание.

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

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

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

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

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

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

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

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

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

  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. Теперь необходимо добавить код для пробуждения(), чтобы инициализировать переменную экземпляра:

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

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

    Примечание.

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

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

Создаваемый класс — это класс CustomVisionObjects .

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

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

Важно отметить конечную точку, которую предоставляет служба Пользовательское визуальное распознавание, так как приведенная ниже структура JSON была настроена для работы с Пользовательское визуальное распознавание прогнозированием версии 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. Не забудьте сохранить изменения в Visual Studio , прежде чем вернуться в Unity.

Примечание.

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

Глава 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), настроена для работы с Пользовательское визуальное распознавание Обучение версии 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(). Этот метод установит ранее созданную и обученную итерацию как Default. После завершения этот метод должен удалить предыдущую итерацию, существующую в службе. На момент написания этого курса в службе допускается ограничение в 10 итерации не более десяти (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. Не забудьте сохранить изменения в Visual Studio , прежде чем вернуться в Unity.

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

Этот класс:

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

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

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

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

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

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

  1. Щелкните правой кнопкой мыши в папке "Скрипты", а затем щелкните "Создать>скрипт C#". Назовите скрипт SceneOrganiser.

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

  3. Вам потребуется только одно пространство имен, удалите остальные из класса 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(), который создает и размещает курсор основной камеры и метод 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. Не забудьте сохранить изменения в Visual Studio , прежде чем вернуться в Unity.

Внимание

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

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

Глава 12. Перед созданием

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

Перед выполнением убедитесь, что:

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

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

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

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

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

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

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

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

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

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

  2. Тик Unity C# Projects.

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

  4. Unity начнет создание проекта в папку приложения .

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

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

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

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

    2. Переход к сети и расширенным параметрам Wi-Fi>в Интернете>

    3. Обратите внимание на IPv4-адрес .

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

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

  2. Перейдите к новой сборке Unity (папке приложения) и откройте файл решения с помощью 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;

or

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

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

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

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

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

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

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

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

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

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

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

    Вкладка

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

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

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

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

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

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

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

    Запуск модели службы обученияВыбор параметра по умолчанию

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

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

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

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

Упражнение 1

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

Упражнение 2

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

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

Упражнение 3

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