HoloLens(第一代)和 Azure 304:人脸识别


注意

混合现实学院教程在制作时考虑到了 HoloLens(第一代)和混合现实沉浸式头戴显示设备。 因此,对于仍在寻求这些设备的开发指导的开发人员而言,我们觉得很有必要保留这些教程。 我们不会在这些教程中更新 HoloLens 2 所用的最新工具集或集成相关的内容。 我们将维护这些教程,使之持续适用于支持的设备。 将来会发布一系列演示如何针对 HoloLens 2 进行开发的新教程。 此通知将在教程发布时通过指向这些教程的链接进行更新。


完成此课程的结果

本课程介绍如何使用 Azure 认知服务和 Microsoft 人脸 API 将人脸识别功能添加到混合现实应用程序。

Azure 人脸 API 是一项 Microsoft 服务,它为开发人员提供最先进的人脸算法,所有这些算法都位于云中。 人脸 API 有两个主要功能:通过属性进行人脸检测并进行人脸识别。 这样,开发人员只需为人脸设置一组组,然后稍后将查询图像发送到服务,以确定人脸所属的人员。 有关详细信息,请访问 Azure 人脸识别页

完成本课程后,你将获得一个可执行以下操作的混合现实 HoloLens 应用程序:

  1. 使用板载 HoloLens 相机,使用“点击手势”启动图像捕获。
  2. 将捕获的图像发送到 Azure 人脸 API 服务。
  3. 接收人脸 API 算法的结果。
  4. 使用简单的用户界面,以显示匹配人员的姓名。

这将指导你如何将人脸 API 服务的结果获取到基于 Unity 的混合现实应用程序中。

在应用程序中,由你决定结果与设计的集成方式。 本课程旨在教授如何将 Azure 服务与 Unity 项目集成。 你的任务是运用从本课程中学到的知识来增强混合现实应用程序。

设备支持

课程 HoloLens 沉浸式头戴显示设备
MR 和 Azure 304:人脸识别 ✔️ ✔️

注意

尽管本课程重点介绍 HoloLens,但你也可以将本课程中学到的知识运用到 Windows Mixed Reality 沉浸式 (VR) 头戴显示设备。 由于沉浸式 (VR) 头戴显示设备没有可用的摄像头,因此你需要将外部摄像头连接到电脑。 随着课程的进行,你将看到有关支持沉浸式 (VR) 头戴显示设备可能需要进行的任何更改的说明。

先决条件

注意

本教程专为具有 Unity 和 C# 基本经验的开发人员设计。 另请注意,本文档中的先决条件和书面说明在编写时(2018 年 5 月)已经过测试和验证。 可随意使用最新软件(如安装工具一文所列的软件),但不应假设本课程中的信息会与你在比下列版本更高的软件中找到的内容完全一致。

建议在本课程中使用以下硬件和软件:

开始之前

  1. 为了避免在生成此项目时遇到问题,强烈建议在根文件夹或接近根的文件夹中创建本教程中提到的项目(长文件夹路径会在生成时导致问题)。
  2. 设置并测试 HoloLens。 如需有关设置 HoloLens 的支持,请确保参阅“HoloLens 设置”一文
  3. 在开始开发新的 HoloLens 应用时,建议执行校准和传感器优化(有时 HoloLens 应用可以帮助为每个用户执行这些任务)。

有关校准的帮助信息,请单击此链接访问“HoloLens 校准”一文

有关传感器优化的帮助信息,请单击此链接访问“HoloLens 传感器优化”一文

第 1 章 - Azure 门户

若要在 Azure 中使用 人脸 API 服务,需要配置该服务的实例以供应用程序使用。

  1. 首先,登录到 Azure 门户

    注意

    如果你没有 Azure 帐户,需要创建一个。 如果你在课堂或实验室场景中跟着本教程学习,请让讲师或监督人员帮助设置你的新帐户。

  2. 登录后,单击左上角的“新建”,搜索“人脸 API”,并按“Enter”键

    搜索人脸 api

    注意

    在更新的门户中,“新建”一词可能已替换为“创建资源”

  3. 新页面将提供人脸 API 的说明。 在该提示的左下角,选择“创建”按钮,以创建与此服务的关联

    人脸 api 信息

  4. 单击“创建”后

    1. 插入此服务实例的所需名称。

    2. 选择一个订阅。

    3. 选择合适的“定价层”,如果这是你首次创建人脸 API 服务,则会向你提供免费层(名为 F0)

    4. 选择一个资源组或创建一个新资源组。 通过资源组,可监视和预配 Azure 资产集合、控制其访问权限并管理其计费。 建议保留与单个项目(例如通用资源组下的这些实验室)相关联的所有 Azure 服务。

      若要详细了解 Azure 资源组,请访问资源组一文。

    5. 稍后使用的 UWP 应用 “Person Maker” 需要使用“美国西部”作为位置。

    6. 还需要确认了解应用于此服务的条款和条件。

    7. 选择“创建”。*

      创建人脸 api 服务

  5. 单击创建后,*必须等待服务创建完成,这可能需要一分钟时间。

  6. 创建服务实例后,门户中将显示一条通知。

    服务创建通知

  7. 单击通知以浏览新的服务实例。

    转到资源通知

  8. 准备好后,单击通知中的“转到资源”按钮,浏览新的服务实例

    访问人脸 api 密钥

  9. 在本教程中,应用程序将需要调用你的服务,这是使用服务的订阅“密钥”来完成的。 在人脸 API 服务快速入门页中,第一个点是数字 1,用于获取密钥。

  10. 服务页上选择蓝色“密钥”超链接(如果在“快速启动”页上),或者选择服务导航菜单左侧中的“密钥”链接(由‘密钥’图标表示)以显示密钥。

    注意

    请记下其中一个密钥并保护好,因为稍后需要用到它。

第 2 章 - 使用 “Person Maker” UWP 应用程序

请确保下载名为 Person Maker 的预创建 UWP 应用程序。 此应用不是本课程的最终产品,只是一个可帮助你创建 Azure 条目的工具,下一个项目将依赖它。

“Person Maker” 允许创建与人员以及人员组关联的 Azure 条目。 应用程序会以一种格式放置所有所需信息,该格式随后可供 Face API 用来识别已添加的人脸。

[重要] “Person Maker” 使用一些基本限制,以帮助确保没有超过“免费订阅层”每分钟的服务调用数。 当发生限制时,顶部的绿色文本将更改为红色,并更新为“活动状态”。如果是这种情况,只需等待应用程序(它会等到下次可以继续访问人脸服务,当可以再次使用时,将更新为“非活动状态”)。

此应用程序使用 Microsoft.ProjectOxford.Face 库,可让你充分利用人脸 API。 此库作为 NuGet 包免费提供。 有关此情况的详细信息以及类似的 API,请确保访问 API 参考文章

注意

这些只是所需的步骤,有关如何执行这些操作的说明,请查看后文。 “Person Maker” 应用允许进行以下操作:

  • 创建一个人员组,该组由要与之关联的多个人员组成。 利用 Azure 帐户,可以托管多个人员组。

  • 创建作为人员组成员的人员。 每个人都有多个与之关联的人脸图像。

  • 人脸图像分配给某个人员,使 Azure 人脸 API 服务能够按相应的人脸识别该人员

  • 训练 Azure 人脸 API 服务

请注意,为训练此应用识别人员,需要将每个人员的十 (10) 张特写照片添加到人员组。 Windows 10 Cam 应用可帮助你获取这些信息。 必须确保每张照片都很清楚(避免模糊、遮蔽或离使用者太远),照片为 jpg 或 png 文件格式,图像文件大小不超过 “4 MB”,且不小于 “1 KB”

注意

如果你在学习本教程,请不要使用自己的人脸进行训练,因为当你戴上 HoloLens 时,不能看着自己。 使用同事或同学的人脸。

运行 “Person Maker”

  1. 打开 “PersonMaker” 文件夹,然后双击 PersonMaker 解决方案,使用 Visual Studio 将其打开。

  2. PersonMaker 解决方案打开后,请确保:

    1. 解决方案配置设置为“调试”

    2. 解决方案平台设置为 “x86”

    3. 目标平台为“本地计算机”

    4. 你还可能需要还原 NuGet 包(右键单击解决方案并选择“还原 NuGet 包”)。

  3. 单击本地计算机,应用程序将启动。 请注意,在较小的屏幕上,所有内容可能都不可见,不过您可以向下滚动查看。

    person maker 用户界面

  4. 插入 “Azure 身份验证密钥”,在 Azure 的人脸 API 服务中可以找到。

  5. 插入:

    1. 要分配给人员组ID。 ID 必须是小写,且不能包含空格。 记下此 ID,因为稍后会在 Unity 项目中用到它。
    2. 要分配给人员组名称可以有空格。
  6. 按“创建人员组”按钮。 此按钮下应显示一条确认消息。

注意

如果出现“拒绝访问”错误,请检查为 Azure 服务设置的位置。 如上所述,此应用是为“美国西部”设计的。

重要

你会注意到,你也可以单击“提取已知组”按钮:这适用于你已创建人员组并希望使用它,而不是创建新的人员组。 请注意,如果在具有已知组的情况下单击创建人员组,仍然会提取组。

  1. 插入要创建的人员姓名

    1. 单击“创建人员”按钮。

    2. 此按钮下应显示一条确认消息。

    3. 如果要删除先前创建的人员,可以在文本框中输入姓名并按“删除人员”

  2. 请确保知道想要添加到组中的人员的十 (10) 张照片的位置。

  3. 按“创建并打开文件夹”,打开 Windows 资源管理器中与该人员关联的文件夹。 在文件夹中添加十 (10) 张图像。 它们必须是 JPGPNG 文件格式。

  4. 单击“提交到 Azure”。 计数器将显示提交状态,完成后会显示一条消息。

  5. 计数器完成并显示一条确认消息后,单击“训练”以训练服务。

完成此过程后,就可以开始来到 Unity。

第 3 章 - 设置 Unity 项目

下面是用于使用混合现实进行开发的典型设置,因此,这对其他项目来说是一个不错的模板。

  1. 打开 Unity,单击“新建”

    启动新的 Unity 项目。

  2. 现在需要提供 Unity 项目名称。 插入 “MR_FaceRecognition”。 确保将项目类型设置为“3D”。 将“位置”设置为适合你的位置(请记住,越接近根目录越好)。 然后,单击“创建项目”

    提供新的 Unity 项目的详细信息。

  3. 当 Unity 处于打开状态时,有必要检查默认“脚本编辑器”是否设置为“Visual Studio”。 转到“编辑”>“首选项”,然后在新窗口中导航到“外部工具”。 将外部脚本编辑器更改为 Visual Studio 2017。 关闭“首选项”窗口。

    更新脚本编辑器首选项。

  4. 接下来,转到“文件”>“生成设置”,然后单击“切换平台”按钮将平台切换到“通用 Windows 平台”

    “生成设置”窗口,将平台切换为 UWP。

  5. 转到“文件”>“生成设置”,并确保

    1. 将“目标设备”设置为“HoloLens”

      对于沉浸式头戴显示设备,将“目标设备”设置为“任何设备”

    2. 将“生成类型”设置为“D3D”

    3. 将“SDK”设置为“最新安装的版本”

    4. 将“Visual Studio 版本”设置为“最新安装的版本”

    5. 将“生成并运行”设置为“本地计算机”

    6. 保存场景并将其添加到生成。

      1. 通过选择“添加开放场景”来执行此操作。 将出现一个保存窗口。

        单击“添加开放场景”按钮

      2. 选择“新建文件夹”按钮以创建新文件夹,将其命名为“场景”

        创建新的 Scripts 文件夹

      3. 打开新创建的“场景”文件夹,然后在“文件名:”文本字段中,键入 FaceRecScene,然后按“保存”

        为新场景提供名称。

    7. 在“生成设置”中,其余设置目前应保留为默认值

  6. 在“生成设置”窗口中,单击“播放器设置”按钮,这会在检查器所在的空间中打开相关面板

    打开播放器设置。

  7. 在此面板中,需要验证一些设置:

    1. 在“其他设置”选项卡中

      1. 脚本运行时版本应为实验性版本(.NET 4.6 等效版本)。 更改此将触发需要重新启动编辑器。

      2. “脚本后端”应为 “.NET”

      3. “API 兼容性级别”应为“.NET 4.6”

        更新其他设置。

    2. 在“发布设置”选项卡的“功能”下,检查以下内容

      • InternetClient

      • 网络摄像头

        更新发布设置。

    3. 在面板再靠下部分,在“发布设置”下的“XR 设置”中,勾选“支持虚拟现实”,确保已添加“Windows Mixed Reality SDK”

      更新 XR 设置。

  8. 返回“生成设置”,此时 “Unity C#” 项目不再灰显;勾选此内容旁边的复选框

  9. 关闭“生成设置”窗口 。

  10. 保存场景和项目(“文件”>“保存场景/文件”>“保存项目”)

第 4 章 – 主相机设置

重要

如果要跳过本课程的“Unity 设置”部分并继续直接编写代码,请随意下载此 .unitypackage,并将其作为自定义包导入项目中。 请注意,此包还包括第 5 章中介绍的导入 Newtonsoft DLL。 导入后,可以从第 6 章继续。

  1. 在“层次结构”面板中选择“主摄像头”

  2. 选择后,你将能够在“检查器面板”中查看“主摄像头”的所有组件

    1. Camera 对象必须命名为“主相机”(注意拼写!)

    2. “主摄像头”标记必须设置为“MainCamera”(注意拼写!)

    3. 请确保将“转换位置”设置为“0, 0, 0”

    4. 将“清除标志”设置为“纯色”

    5. 将相机组件的背景色设置为“黑色,Alpha 0 (十六进制代码: #00000000)”

      设置相机组件

第 5 章 - 导入 Newtonsoft.Json 库

重要

如果在上一章中导入了“.unitypackage”,可以跳过本章。

为了帮助你反序列化和序列化接收并发送到机器人服务的对象,需要下载 Newtonsoft.Json 库。 可在此 Unity 包文件中找到已使用正确的 Unity 文件夹结构组织的兼容版本。

若要导入库,请执行以下操作:

  1. 下载 Unity 加载项。

  2. 单击资产导入包自定义包

    导入 Newtonsoft.Json

  3. 查找已下载的 Unity 包,然后单击“打开”。

  4. 确保已勾选包的所有组件,然后单击“导入”

    导入 Newtonsoft.Json 资产

第 6 章 - 创建 FaceAnalysis 类

FaceAnalysis 类的用途是托管与 Azure 人脸识别服务通信所需的方法。

  • 向服务发送捕获图像后,它会分析该图像并识别其中人脸,并确定其中是否有人脸属于已知人员。
  • 如果找到已知人员,此类将在场景中以 UI 文本显示其名称。

若要创建 FaceAnalysis 类,请执行以下操作:

  1. 右键单击位于“项目面板”中的“资产”文件夹,然后单击“创建”“文件夹”>。 将该文件夹命名为“脚本”

    创建 FaceAnalysis 类。

  2. 双击刚刚创建的文件夹,打开它。

  3. 在文件夹中单击邮件,然后单击“创建”>“C# 脚本”。 将该脚本命名为 FaceAnalysis

  4. 双击新的 FaceAnalysis 脚本,在 Visual Studio 2017 中将其打开。

  5. FaceAnalysis 类上方输入以下命名空间:

        using Newtonsoft.Json;
        using System.Collections;
        using System.Collections.Generic;
        using System.IO;
        using System.Text;
        using UnityEngine;
        using UnityEngine.Networking;
    
  6. 现在需要添加用于反序列化的所有对象。 这些对象需要添加到 FaceAnalysis 脚本的“外部”(在底部波形括号下方)。

        /// <summary>
        /// The Person Group object
        /// </summary>
        public class Group_RootObject
        {
            public string personGroupId { get; set; }
            public string name { get; set; }
            public object userData { get; set; }
        }
    
        /// <summary>
        /// The Person Face object
        /// </summary>
        public class Face_RootObject
        {
            public string faceId { get; set; }
        }
    
        /// <summary>
        /// Collection of faces that needs to be identified
        /// </summary>
        public class FacesToIdentify_RootObject
        {
            public string personGroupId { get; set; }
            public List<string> faceIds { get; set; }
            public int maxNumOfCandidatesReturned { get; set; }
            public double confidenceThreshold { get; set; }
        }
    
        /// <summary>
        /// Collection of Candidates for the face
        /// </summary>
        public class Candidate_RootObject
        {
            public string faceId { get; set; }
            public List<Candidate> candidates { get; set; }
        }
    
        public class Candidate
        {
            public string personId { get; set; }
            public double confidence { get; set; }
        }
    
        /// <summary>
        /// Name and Id of the identified Person
        /// </summary>
        public class IdentifiedPerson_RootObject
        {
            public string personId { get; set; }
            public string name { get; set; }
        }
    
  7. 现在删除 Start()Update() 方法,因为不会用到它们。

  8. FaceAnalysis 类中,添加以下变量:

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static FaceAnalysis Instance;
    
        /// <summary>
        /// The analysis result text
        /// </summary>
        private TextMesh labelText;
    
        /// <summary>
        /// Bytes of the image captured with camera
        /// </summary>
        internal byte[] imageBytes;
    
        /// <summary>
        /// Path of the image captured with camera
        /// </summary>
        internal string imagePath;
    
        /// <summary>
        /// Base endpoint of Face Recognition Service
        /// </summary>
        const string baseEndpoint = "https://westus.api.cognitive.microsoft.com/face/v1.0/";
    
        /// <summary>
        /// Auth key of Face Recognition Service
        /// </summary>
        private const string key = "- Insert your key here -";
    
        /// <summary>
        /// Id (name) of the created person group 
        /// </summary>
        private const string personGroupId = "- Insert your group Id here -";
    

    注意

    将“密钥”和“personGroupId” 替换为服务密钥以及之前创建的组的 ID。

  9. 添加 用于初始化类的 Awake() 方法,将 ImageCapture 类添加到主相机并调用 Label 创建方法:

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            // Allows this instance to behave like a singleton
            Instance = this;
    
            // Add the ImageCapture Class to this Game Object
            gameObject.AddComponent<ImageCapture>();
    
            // Create the text label in the scene
            CreateLabel();
        }
    
  10. 添加 CreateLabel() 方法,该方法创建 Label 对象以显示分析结果:

        /// <summary>
        /// Spawns cursor for the Main Camera
        /// </summary>
        private void CreateLabel()
        {
            // Create a sphere as new cursor
            GameObject newLabel = new GameObject();
    
            // Attach the label to the Main Camera
            newLabel.transform.parent = gameObject.transform;
    
            // Resize and position the new cursor
            newLabel.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f);
            newLabel.transform.position = new Vector3(0f, 3f, 60f);
    
            // Creating the text of the Label
            labelText = newLabel.AddComponent<TextMesh>();
            labelText.anchor = TextAnchor.MiddleCenter;
            labelText.alignment = TextAlignment.Center;
            labelText.tabSize = 4;
            labelText.fontSize = 50;
            labelText.text = ".";       
        }
    
  11. 添加 DetectFacesFromImage()GetImageAsByteArray() 方法。 前者将请求人脸识别服务检测所提交图像中任何可能的人脸,而后者是将捕获的图像转换为字节数组所必需的:

        /// <summary>
        /// Detect faces from a submitted image
        /// </summary>
        internal IEnumerator DetectFacesFromImage()
        {
            WWWForm webForm = new WWWForm();
            string detectFacesEndpoint = $"{baseEndpoint}detect";
    
            // Change the image into a bytes array
            imageBytes = GetImageAsByteArray(imagePath);
    
            using (UnityWebRequest www = 
                UnityWebRequest.Post(detectFacesEndpoint, webForm))
            {
                www.SetRequestHeader("Ocp-Apim-Subscription-Key", key);
                www.SetRequestHeader("Content-Type", "application/octet-stream");
                www.uploadHandler.contentType = "application/octet-stream";
                www.uploadHandler = new UploadHandlerRaw(imageBytes);
                www.downloadHandler = new DownloadHandlerBuffer();
    
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
                Face_RootObject[] face_RootObject = 
                    JsonConvert.DeserializeObject<Face_RootObject[]>(jsonResponse);
    
                List<string> facesIdList = new List<string>();
                // Create a list with the face Ids of faces detected in image
                foreach (Face_RootObject faceRO in face_RootObject)
                {
                    facesIdList.Add(faceRO.faceId);
                    Debug.Log($"Detected face - Id: {faceRO.faceId}");
                }
    
                StartCoroutine(IdentifyFaces(facesIdList));
            }
        }
    
        /// <summary>
        /// Returns the contents of the specified 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);
        }
    
  12. 添加 IdentifyFaces() 方法,该方法请求人脸识别服务识别以前在提交的图像中检测到的任何已知人脸。 请求将返回已标识的人员的 id,而不是姓名:

        /// <summary>
        /// Identify the faces found in the image within the person group
        /// </summary>
        internal IEnumerator IdentifyFaces(List<string> listOfFacesIdToIdentify)
        {
            // Create the object hosting the faces to identify
            FacesToIdentify_RootObject facesToIdentify = new FacesToIdentify_RootObject();
            facesToIdentify.faceIds = new List<string>();
            facesToIdentify.personGroupId = personGroupId;
            foreach (string facesId in listOfFacesIdToIdentify)
            {
                facesToIdentify.faceIds.Add(facesId);
            }
            facesToIdentify.maxNumOfCandidatesReturned = 1;
            facesToIdentify.confidenceThreshold = 0.5;
    
            // Serialize to Json format
            string facesToIdentifyJson = JsonConvert.SerializeObject(facesToIdentify);
            // Change the object into a bytes array
            byte[] facesData = Encoding.UTF8.GetBytes(facesToIdentifyJson);
    
            WWWForm webForm = new WWWForm();
            string detectFacesEndpoint = $"{baseEndpoint}identify";
    
            using (UnityWebRequest www = UnityWebRequest.Post(detectFacesEndpoint, webForm))
            {
                www.SetRequestHeader("Ocp-Apim-Subscription-Key", key);
                www.SetRequestHeader("Content-Type", "application/json");
                www.uploadHandler.contentType = "application/json";
                www.uploadHandler = new UploadHandlerRaw(facesData);
                www.downloadHandler = new DownloadHandlerBuffer();
    
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
                Debug.Log($"Get Person - jsonResponse: {jsonResponse}");
                Candidate_RootObject [] candidate_RootObject = JsonConvert.DeserializeObject<Candidate_RootObject[]>(jsonResponse);
    
                // For each face to identify that ahs been submitted, display its candidate
                foreach (Candidate_RootObject candidateRO in candidate_RootObject)
                {
                    StartCoroutine(GetPerson(candidateRO.candidates[0].personId));
    
                    // Delay the next "GetPerson" call, so all faces candidate are displayed properly
                    yield return new WaitForSeconds(3);
                }           
            }
        }
    
  13. 添加 GetPerson() 方法。 通过提供人员 id,此方法随后请求人脸识别服务返回已标识人员的姓名:

        /// <summary>
        /// Provided a personId, retrieve the person name associated with it
        /// </summary>
        internal IEnumerator GetPerson(string personId)
        {
            string getGroupEndpoint = $"{baseEndpoint}persongroups/{personGroupId}/persons/{personId}?";
            WWWForm webForm = new WWWForm();
    
            using (UnityWebRequest www = UnityWebRequest.Get(getGroupEndpoint))
            {
                www.SetRequestHeader("Ocp-Apim-Subscription-Key", key);
                www.downloadHandler = new DownloadHandlerBuffer();
                yield return www.SendWebRequest();
                string jsonResponse = www.downloadHandler.text;
    
                Debug.Log($"Get Person - jsonResponse: {jsonResponse}");
                IdentifiedPerson_RootObject identifiedPerson_RootObject = JsonConvert.DeserializeObject<IdentifiedPerson_RootObject>(jsonResponse);
    
                // Display the name of the person in the UI
                labelText.text = identifiedPerson_RootObject.name;
            }
        }
    
  14. 请记得在返回到 Unity 编辑器之前“保存”更改。

  15. 在 Unity 编辑器中,将 FaceAnalysis 脚本从“项目”面板中的“脚本”文件夹拖动到“层次结构”面板中的“主相机”对象中。 新的脚本组件将添加到“主相机”。

将 FaceAnalysis 放到“主相机”上

第 7 章 – 创建 ImageCapture 类

ImageCapture 类的用途是托管与 Azure人脸识别服务通信所需的方法,以分析要捕获的图像,识别图像中的人脸并确定该图像是否属于已知人员。 如果找到已知人员,此类将在场景中以 UI 文本显示其名称。

若要创建 ImageCapture 类,请执行以下操作:

  1. 在之前创建的“脚本”文件夹中单击右键,然后单击“创建”,“C# 脚本”。 将该脚本命名为“ImageCapture”

  2. 双击新的“ImageCapture”脚本,在 Visual Studio 2017 中将其打开

  3. 在 ImageCapture 类上方输入以下命名空间:

        using System.IO;
        using System.Linq;
        using UnityEngine;
        using UnityEngine.XR.WSA.Input;
        using UnityEngine.XR.WSA.WebCam;
    
  4. ImageCapture 类中,添加以下变量:

        /// <summary>
        /// Allows this class to behave like a singleton
        /// </summary>
        public static ImageCapture instance;
    
        /// <summary>
        /// Keeps track of tapCounts to name the captured images 
        /// </summary>
        private int tapsCount;
    
        /// <summary>
        /// PhotoCapture object used to capture images on HoloLens 
        /// </summary>
        private PhotoCapture photoCaptureObject = null;
    
        /// <summary>
        /// HoloLens class to capture user gestures
        /// </summary>
        private GestureRecognizer recognizer;
    
  5. 添加初始化类所需的 Awake()Start() 方法,并允许 HoloLens 捕获用户的手势:

        /// <summary>
        /// Initialises this class
        /// </summary>
        private void Awake()
        {
            instance = this;
        }
    
        /// <summary>
        /// Called right after Awake
        /// </summary>
        void Start()
        {
            // Initialises user gestures capture 
            recognizer = new GestureRecognizer();
            recognizer.SetRecognizableGestures(GestureSettings.Tap);
            recognizer.Tapped += TapHandler;
            recognizer.StartCapturingGestures();
        }
    
  6. 添加用户执行点击手势时调用的 TapHandler()

        /// <summary>
        /// Respond to Tap Input.
        /// </summary>
        private void TapHandler(TappedEventArgs obj)
        {
            tapsCount++;
            ExecuteImageCaptureAndAnalysis();
        }
    
  7. 添加 ExecuteImageCaptureAndAnalysis() 方法,该方法将开始图像捕获过程:

        /// <summary>
        /// Begin process of Image Capturing and send To Azure Computer Vision service.
        /// </summary>
        private void ExecuteImageCaptureAndAnalysis()
        {
            Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending
                ((res) => res.width * res.height).First();
            Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
    
            PhotoCapture.CreateAsync(false, delegate (PhotoCapture captureObject)
            {
                photoCaptureObject = captureObject;
    
                CameraParameters c = new CameraParameters();
                c.hologramOpacity = 0.0f;
                c.cameraResolutionWidth = targetTexture.width;
                c.cameraResolutionHeight = targetTexture.height;
                c.pixelFormat = CapturePixelFormat.BGRA32;
    
                captureObject.StartPhotoModeAsync(c, delegate (PhotoCapture.PhotoCaptureResult result)
                {
                    string filename = string.Format(@"CapturedImage{0}.jpg", tapsCount);
                    string filePath = Path.Combine(Application.persistentDataPath, filename);
    
                    // Set the image path on the FaceAnalysis class
                    FaceAnalysis.Instance.imagePath = filePath;
    
                    photoCaptureObject.TakePhotoAsync
                    (filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
                });
            });
        }
    
  8. 添加在照片捕获过程完成时调用的处理程序:

        /// <summary>
        /// Called right after the photo capture process has concluded
        /// </summary>
        void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
        {
            photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
        }
    
        /// <summary>
        /// Register the full execution of the Photo Capture. If successful, it will begin the Image Analysis process.
        /// </summary>
        void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
        {
            photoCaptureObject.Dispose();
            photoCaptureObject = null;
    
            // Request image caputer analysis
            StartCoroutine(FaceAnalysis.Instance.DetectFacesFromImage());
        }
    
  9. 请记得在返回到 Unity 编辑器之前“保存”更改。

第 8 章 - 生成解决方案

若要对应用程序执行全面测试,需要将应用程序旁加载到 HoloLens。

执行此操作之前,请确保:

  • 第 3 章中提到的所有设置均正确设置。
  • 将脚本 FaceAnalysis 附加到“主相机”对象。
  • 已在 FaceAnalysis 脚本中设置“身份验证密钥”和“组 ID”

此时,你已准备好生成解决方案。 生成解决方案后,即可部署应用程序。

若要开始生成过程,请执行以下操作:

  1. 单击“文件”,“保存”以保存当前场景。

  2. 转到“文件”,“生成设置”,单击“添加打开的场景”。

  3. 请确保勾选“Unity C# 项目”。

    部署 Visual Studio 解决方案

  4. 按“生成”。 执行此操作后,Unity 将启动“文件资源管理器”窗口,你需要在其中创建并选择一个文件夹来生成应用。 现在,在 Unity 项目中创建该文件夹,并将其命名为“应用”。 选择“应用”文件夹,然后按“选择文件夹”。

  5. Unity 将开始将项目生成到“应用”文件夹。

  6. Unity 完成生成后(可能需要一段时间),它将在生成位置打开“文件资源管理器”窗口。

    从 Visual Studio 部署解决方案

  7. 打开“应用”文件夹,然后打开新的“项目解决方案”(如上所示,MR_FaceRecognition.sln)。

第 9 章 - 部署应用程序

在 HoloLens 上部署:

  1. 将需要 HoloLens 的 IP 地址(用于远程部署),并确保 HoloLens 处于“开发人员模式”。 要执行此操作:

    1. 佩戴 HoloLens 时,打开“设置”
    2. 转到“网络和 Internet”>“Wi-Fi”>“高级选项”
    3. 记下 “IPv4” 地址
    4. 接下来,导航回“设置”,然后转到“更新和安全”>“面向开发人员”
    5. 将“开发人员模式”设置为“打开”。
  2. 导航到新的 Unity 生成(“应用”文件夹)并使用 Visual Studio 打开解决方案文件

  3. 在“解决方案配置”中,选择“调试”

  4. 在“解决方案平台”中,选择“x86,远程计算机”

    更改解决方案配置

  5. 转到“生成”菜单,并单击“部署解决方案”,将应用程序旁加载到 HoloLens

  6. 你的应用现在应显示在 HoloLens 上的已安装应用列表中,随时可以启动!

注意

若要部署到沉浸式头戴显示设备,请将“解决方案平台”设置为“本地计算机”,并将“配置”设置为“调试”,将“平台”设置为“x86”。 然后,使用“生成”菜单,选择“部署解决方案”,将其部署到本地计算机

第 10 章 - 使用应用程序

  1. 戴上 HoloLens,启动应用。

  2. 查看在 人脸 API 注册的人员。 请确保:

    • 人脸不太遥远并且清晰可见
    • 环境照明不太暗
  3. 使用点击手势来捕获人员的照片。

  4. 等待应用发送分析请求并接收响应。

  5. 如果人员已成功识别,则该人员的名称将显示为 UI 文本。

  6. 可以每隔几秒钟使用点击手势重复捕获过程。

你已完成 Azure 人脸 API 应用程序

恭喜,你构建了一个混合现实应用,它利用 Azure 人脸识别服务检测图像中的人脸,并识别所有已知的人脸。

完成此课程的结果

额外练习

练习 1

“Azure 人脸 API” 的强大功能足以在单个图像中检测到 64 个人脸。 扩展应用程序,使其能够在许多其他人中识别两个或三个人脸。

练习 2

“Azure 人脸 API” 还可以返回各种类型的属性信息。 将此集成到应用程序中。 与情感 API 结合时,这会更加有趣。