# 人臉識別
人臉識別,特別是二維的人臉識別現在應該是非常成熟了。
而且。。自此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討論。