人脸识别

人脸识别

人脸识别,特别是二维的人脸识别现在应该是非常成熟了。

而且。。自此 IPhone X 之后,各个安卓手机也都开始增加人脸识别。虽然安卓常见的二维识别安全的远低于 IPhone X 三维的识别,但并不影响我们玩玩。

这里,我将根据 Azure Cognitive Service 中的 Face API 来实现一个很简单的人脸识别 Demo。

当然不是自己写的识别服务 ˋ( ° ▽、° )

技术选用

  1. 客户端:微信小程序

    选用的理由非常简单,和电脑相比,手机的摄像头明显好不止一个档次。

    同时,微信小程序非常接近 Web 开发的模式,还提供了 Camera 组件方便我们调用。

  2. 服务端:ASP.NET Core

    顺手 + Azure 有提供对应的.net Core 版 SDK。

准备工作

首先,我们需要有一个 Azure 订阅用来获取 Face API 的 key。

国内世纪互联版的可以通过身份证进行 1 元试用注册。

国际版需要 VISA 或者 MasterCard 信用卡才能注册,且注意所在地区不能选中国

这里我用的是国际版 Azure。

然后,我们转到Face API Reference。在这里我们可以看到 Face API 的所有功能介绍与调用方式。

根据阅读文档,调用的大致流程如下:

  1. 创建 PersonGroup

    PUT /persongroups/{personGroupId}

  2. 创建 Person

    POST /persongroups/{personGroupId}/persons

    {
     "name": "person1",
     "usuerData": "UserData(optional)"
    }
  3. 添加人脸数据(Add Face)

    POST /persongroups/{personGroupId}/persons/{personId}/persistedFaces

    参数有些多这里就不具体写了
    值得关注的是,有两种上传方式。

    1. 通过application/octet-stream直接上传图片文件。
    2. 通过application/json上传对应图片文件的 url。
  4. 重复步骤 2, 3 添加所有的数据

    每个 Person 最多可以有 248 张人脸数据。

    Each person entry can hold up to 248 faces.

  5. 训练人脸识别模型

    POST /persongroups/{personGroupId}/train

    在训练期间可以调用/persongroups/{personGroupId}/training来获取训练状态。

至此,人脸识别的模型就训练好了,我们之后所要做的就是调用这个模型。

我根据 SDK,简单的写了一个控制台应用(FaceAPIDemo.Console),可以用来简化实现步骤 2~5。当然直接用 POSTMAN 调用也行。

调用流程大概如下:

  1. 调用 Detect API 来识别人脸并获取对应的 Face Id。
  2. 将 Face Id 传递给 Identity API,来获取识别结果。

具体的内容,我会在服务端代码中细说。

构建服务器端

首先还是建立一个 Empty Web 应用,我们通过 NuGet 安装 Face API 的 SDK:Microsoft.ProjectOxford.Face.DotNetCore,以及用于图像压缩的库:Magick.NET-Q8-AnyCPU

然后,在 Startup 中向服务容器添加 FaceServiceClient 以及 MVC 服务。

public void ConfigureServices(IServiceCollection services)
{
    // 添加FaceServiceClient用来调用Azure Face API
    // 第二个参数为Face API的终结点
    services.AddTransient(_ => new FaceServiceClient("Your Cognitive Service Key", "https://eastasia.api.cognitive.microsoft.com/face/v1.0/"));
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseMvc();
}

随后,便是添加熟悉的 Controller。

这里我们定义两个 API。

  1. api/Face/Upload 上传图片返回 FaceId 和人脸位置信息
  2. api/Face/Identify 根据 FaceId 识别对应实体

最后的代码如下:

[Produces("application/json")]
[Route("api/[Controller]")]
public class FaceController : Controller
{
    private readonly FaceServiceClient _faceClient;
    private const string groupId = "Your Group Id";

    public FaceController(FaceServiceClient faceClient)
    {
        _faceClient = faceClient;
    }

    /// <summary>
    /// 上次图片获取Face Id
    /// </summary>
    /// <param name="image">待识别的图片</param>
    /// <returns></returns>
    [Route("[Action]")]
    [HttpPost]
    public async Task<JsonResult> Upload(IFormFile image)
    {
        // 启用图片压缩,提高传输速度
        var magickImage = new MagickImage(image.OpenReadStream())
        {
            Quality = 50
        };
        var faces = await _faceClient.DetectAsync(new MemoryStream(magickImage.ToByteArray()));
        // 返回Face Id以及人脸位置信息
        return Json(faces.Select(face => new
        {
            Id = face.FaceId,
            Rect = face.FaceRectangle
        }));
    }

    /// <summary>
    /// 根据Face Id识别人脸
    /// </summary>
    /// <param name="model"></param>
    /// <returns></returns>
    [Route("[Action]")]
    [HttpPost]
    public async Task<JsonResult> Identify([FromBody]IdentifyModel model)
    {
        // 识别人脸
        var identifyResults = await _faceClient.IdentifyAsync(groupId, model.Faces, 0.6f);
        List<IdentifyResult> result = new List<IdentifyResult>();
        foreach (var item in identifyResults)
        {
            // 跳过无识别结果的人脸
            if (item.Candidates.Length == 0)
            {
                continue;
            }
            // 获取第一个识别结果的对应实体
            var person = await _faceClient.GetPersonAsync(groupId, item.Candidates.First().PersonId);
            result.Add(new IdentifyResult
            {
                Name = person.Name,
                StudentId = person.UserData
            });
        }
        return Json(result);
    }
}

因为只是简单的演示 Demo,我这边把 Key 和 GroupId 都写死了,实际应用中可以对应的修改。

构建微信小程序

其实 Demo 介绍到这里,也没太多要做的了。

我们的微信小程序需要做的只是两件事:

  1. 通过 Camera 组件捕捉包含人脸的图片。
  2. 调用上一步写好的 API。

这里我就不写小程序创建的步骤了,以后有空的会单独开一个章节来介绍。

这里我们创建了 Index 界面,相关代码如下:

index.wxml

<!--pages/index/index.wxml-->
<!-- 调用Camera组件来捕捉图像 -->
<camera device-position="front" flash="off" binderror="error" style="width: 750rpx; height: 750rpx;">
  <!-- 由于Camera是原生组件,只能通过cover-view才能覆盖在其上方 -->
  <block wx:for="{{faces}}" wx:key="id">
    <!-- 通过CSS实现矩形框 -->
    <cover-view class="box" style="left:{{item.rect.left}}px;top:{{item.rect.top}}px;width:{{item.rect.width}}px;height:{{item.rect.height}}px;">
    </cover-view>
  </block>
</camera>

<button type="primary" bindtap="takePhoto">识别</button>
<!-- 显示人脸数据 -->
<view wx:for="{{faces}}" wx:key="id">
  <view>Face {{index}}:</view>
  <view>{{item.id}}</view>
</view>
<view wx:for="{{results}}" wx:key="studentId">
  <view>Name: {{item.name}}</view>
  <view>StudentId: {{item.studentId}}</view>
</view>

index.wxss

/* pages/index/index.wxss */
/*
* 用于标出人脸位置
*/
.box {
  border: 5rpx solid green;
  position: relative;
}

index.js

// pages/index/index.js
// 定义API终结点
const baseUrl = "http://localhost:5000";

Page({
  // 页面的初始数据
  data: {
    // 保存FaceId和人脸位置信息
    faces: [],
    // 保存识别到的实体信息
    results: []
  },
  // 步骤图像,并上传到UploadAPI
  takePhoto() {
    let that = this;
    // 获取Camera上下文
    const ctx = wx.createCameraContext();
    // 捕捉图像
    ctx.takePhoto({
      quality: "low",
      success: res => {
        // 在捕捉成功后将图片直接上传到Upload API
        wx.uploadFile({
          url: baseUrl + "/api/Face/Upload",
          filePath: res.tempImagePath,
          name: "Image",
          success: function(res) {
            let obj = JSON.parse(res.data);
            // 保存检测到的人脸数据
            that.setData({
              faces: obj,
              results: []
            });
            // 若检测到人脸就就进一步调用识别API
            if (obj.length > 0) {
              that.identifyFace(obj.map(face => face.id));
            }
          }
        });
      }
    });
  },
  // 输出错误信息
  error(e) {
    console.log(e.detail);
  },
  // 调用Identify API
  identifyFace(faceIds) {
    let that = this;
    wx.request({
      url: baseUrl + "/api/Face/Identify",
      method: "POST",
      data: {
        faces: faceIds
      },
      dataType: "json",
      success: function(res) {
        that.setData({
          results: res.data
        });
      }
    });
  }
});

需要讲的部分都已经注释在代码当中了,如果有问题的欢迎开 Issue 讨论。

最后放一张效果图。。。算了还是不放了。。大家有兴趣单独联系吧。

总结

这样一个流程走下来,其实并没有什么特别困难的地方。

对应类似于人脸识别这样商业化成熟的技术,我们所需做的其实也就是调用相应服务的 API 就完工了。

最后,所有的代码已经上传 Github: https://github.com/yiluomyt/FaceAPIDemo,觉得有帮助的可以给个 Star⭐,欢迎提 Issue 讨论。

支付宝收款码
Copyright © yiluomyt 2020