# 人脸识别
人脸识别,特别是二维的人脸识别现在应该是非常成熟了。
而且。。自此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](https://westus.dev.cognitive.microsoft.com/docs/services/563879b61984550e40cbbe8d/operations/563879b61984550f30395236)。在这里我们可以看到Face API的所有功能介绍与调用方式。
根据阅读文档,调用的大致流程如下:
1. 创建PersonGroup
PUT /persongroups/{personGroupId}
2. 创建Person
POST /persongroups/{personGroupId}/persons
```json
{
"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服务。
```cs
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识别对应实体
最后的代码如下:
```cs
[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
```html
<!--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
```css
/* pages/index/index.wxss */
/*
* 用于标出人脸位置
*/
.box {
border: 5rpx solid green;
position: relative;
}
```
index.js
```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](https://github.com/yiluomyt/FaceAPIDemo),觉得有帮助的可以给个Star⭐,欢迎提Issue讨论。