人脸识别智慧零售场景从零开发
- 2019 年 12 月 23 日
- 笔记
近年来,人脸识别技术在智慧零售、人脸门禁和安防系统等领域得到了越来越多的应用。人脸识别在我们的日常生活中已随处可见,下面通过一个小故事讲述如何在智慧零售场景中使用腾讯云人脸识别接口进行开发。
源码地址:https://github.com/rodson/smart-shop
背景
小明是计算机专业的应届毕业生,家里开超市连锁,毕业后回家继承了家业。他有着远大的抱负,在大学期间学习了很多互联网AI相关的知识,希望利用技术把家族产业做大做强。
问题
在家里的超市观察了一段时间后,小明发现了不少问题,其中最主要的问题是缺少客户的身份信息,导致很多优化工作无从开展,如针对客户的信息提供个性化服务,根据客流优化运营等等。于是小明开始思考对客户身份进行自动识别方案。
方案
这个问题没有困扰小明很久,小明在大学期间对人脸识别技术颇有研究,他想到了通过人脸识别技术解决他的这个痛点。通过抓取客户人脸照,进行人脸识别,特征提取,与构建好的人脸库进行1vN比较,从而识别出客户的身份。

小明开始构思系统框架,为了实现他的智能客户识别系统,他需要构建一个VIP人脸库,一个普通用户人脸库,通过在超市门口安装摄像头获取客户的人脸照片,然后将照片上传到云端进行人脸识别和特征比较,从而识别出客户的信息。

流程
系统主要包含VIP注册和身份识别两个功能,VIP注册的流程很简单,只需将客户人脸信息添加到VIP库即可。身份识别功能稍微复杂些,小明决定画出流程图让自己的思路更清晰。

腾讯云人脸识别
完成了框架和流程设计后,小明开始考虑技术实现,他在网上搜索人脸识别的接口能力,发现腾讯云人脸识别的能力可以满足需求,腾讯云人脸识别接口支持构造一个“AppId->人员库->人员->人脸”的拓扑结构,通过创建人员库、创建人员和人脸搜索接口可以完成系统云端的身份识别功能。

腾讯云人脸识别接入流程
小明做大学毕设的时候曾经调用过腾讯云的接口,他很快找到了之前写的接入流程文档,主要有下面几个步骤:
服务开通
进入人脸识别控制台,点击开通服务

获取SecretId/SecretKey
进入API密钥管理,创建SecretId/SecretKey

接口调用
开通服务并获取了SecretId/SecretKey之后就可以进行接口调用了,在api explorer上提供了在线调用的能力

云API的签名生成对于初次使用的人来说会有一定学习成本,小明当初也费了一些时间才掌握,不过云API提供了各种语言的SDK方便开发者进行接口调用。小明使用自己比较熟悉的NodeJS SDK快速撸了一串示例代码。
// 引入腾讯云SDK const tencentcloud = require('tencentcloud-sdk-nodejs'); // 人脸识别Client类,封装了人脸识别接口的调用 const IaiClient = tencentcloud.iai.v20180301.Client; // 人脸识别Model,封装了各接口的请求结构 const models = tencentcloud.iai.v20180301.Models; // 身份凭证类,存储secretId和secretKey const Credential = tencentcloud.common.Credential; // 用自己的secretId,secretKey替换 const secretId = 'your secretId'; const secretKey = 'your secretKey'; // 实例化调用client let cred = new Credential(secretId, secretKey); let client = new IaiClient(cred); // 构造DetectFace请求参数 const req = new models.DetectFaceRequest(); req.Url = 'http://img1.gtimg.com/sports/pics/hv1/197/212/2052/133485557.jpg'; // 调用DetectFace接口 client.DetectFace(req, function(errMsg, response) { if (errMsg) { // 返回错误 console.log(errMsg); return; } // 返回正常结果 console.log(response.to_json_string()); });
人脸识别云API接口封装
根据小明的设计,要实现这个智能客户识别系统,需要建立两个人脸库(VIP库和普通人脸库),并往库中添加对应的人脸,然后通过人脸搜索完成客户身份的自动识别。小明很快就在腾讯云人脸识别API文档上找到了需要的接口,并将接口封装成返回promise的方式,以便更好的写异步调用。
CreateGroup(创建人员库)
用于创建一个空的人员库,通过FaceModelVerson入参可选择算法版本,目前支持“2.0”和“3.0”,并建议使用“3.0”。

function CreateGroup({ groupId, groupName }) { return new Promise((resolve) => { const req = new models.CreateGroupRequest(); req.GroupId = groupId; req.GroupName = groupName req.FaceModelVersion = '3.0'; // 使用3.0算法版本 client.CreateGroup(req, (errMsg, response) => { if (errMsg) { resolve(errMsg); return; } resolve(response); }); }); }
CreatePerson(创建人员)
创建人员,添加人脸、姓名、性别及其他相关信息。创建人员时需要指定GroupId,图片支持Url或base64的方式传入。

function CreatePerson({ image, personId, personName, groupId }) { return new Promise((resolve) => { const req = new models.CreatePersonRequest(); req.PersonId = personId; req.PersonName = personName; req.GroupId = groupId; req.Image = image; client.CreatePerson(req, (errMsg, response) => { if (errMsg) { resolve(errMsg); return; } resolve(response); }); }); }
SearchFaces(人脸搜索)
用于对一张待识别的人脸图片,在一个或多个人员库中识别出最相似的 TopN 人员,识别结果按照相似度从大到小排序。

人脸搜索的接口稍微比较复杂,从库中搜索人脸MaxFaceNum用于指定输入图片最多识别的人脸数,对应Response的Results的条数,MaxPersonNum用于指定搜索人员库中返回的Person数,对应Candidates的条数。
// 人脸搜索结果示例 { "Response": { "Results": [ { "Candidates": [ { "PersonId": "1001", "FaceId": "2875093635484912302", "PersonName": "Jerry", "Gender": 0, "Score": 100, "PersonGroupInfos": [ { "GroupId": "abc", "PersonExDescriptions": [ "description1", "description2" ] } ] } ], "FaceRect": { "X": 370, "Y": 46, "Width": 75, "Height": 75 } } ], "FaceNum": 1, "RequestId": "57b42b73-b978-45b9-8095-8f50e9642d35" } }
function SearchFaces({ groupIds, image }) { return new Promise((resolve) => { const req = new models.SearchFacesRequest(); req.GroupIds = groupIds; req.Image = image; req.MaxFaceNum = 1; // 只检测输入图的最大脸 req.MaxPersonNum = 1; // 只返回最相似的Person req.NeedPersonInfo = 1; // 返回Person信息 client.SearchFaces(req, (errMsg, response) => { if (errMsg) { resolve(errMsg); return; } resolve(response) }); }); }
系统技术架构设计
小明画好了系统的技术架构,准备实现服务端的DEMO。

代码实现
因为小明比较熟悉NodeJS,所以选择Koa实现来后台服务。
入口文件
const Koa = require('koa'); const Router = require('koa-router'); const bodyParser = require('koa-bodyparser'); const createGroup = require('./apis/createGroup'); const registerUser = require('./apis/registerUser'); const getUserInfo = require('./apis/getUserInfo'); const app = new Koa(); app.use(bodyParser()); const router = new Router(); router.post('/createGroup', createGroup); router.post('/registerUser', registerUser); router.post('/getUserInfo', getUserInfo); app.use(router.routes()); app.listen(3000);
API接口
createGroup
人脸库创建,用于创建VIP库和普通人员库。
const iaiClient = require('../iaiClient'); module.exports = async (ctx) => { const { groupId, groupName } = ctx.request.body; try { const response = await iaiClient.CreateGroup({ groupId, groupName }); ctx.body = response; } catch (e) { ctx.body = e; } }
registerUser
上传人脸和用户名,进行VIP注册。
const iaiClient = require('../iaiClient'); const uuid = require('uuid/v4'); module.exports = async (ctx) => { const { image, personName } = ctx.request.body; try { const response = await iaiClient.CreatePerson({ groupId: 'group-vip', personId: uuid(), personName, image }); ctx.body = response; } catch (e) { ctx.body = e; } }
getUserInfo
上传客户人脸照,判断客户身份。
const iaiClient = require('../iaiClient'); const uuid = require('uuid/v4'); module.exports = async (ctx) => { const { image } = ctx.request.body; try { // 搜索vip库 const resVIP = await iaiClient.SearchFaces({ groupIds: ['group-vip'], image }); // 客户是VIP if (resVIP.Results && resVIP.Results[0].Candidates[0]) { ctx.body = { RequestId: resVIP.RequestId, UserType: 'VIP', UserName: resVIP.Results[0].Candidates[0].PersonName } return; } // 搜索普通客户库 const resNormal = await iaiClient.SearchFaces({ groupIds: ['group-normal'], image }); // 客户是回头客 if (resNormal.Results && resNormal.Results[0].Candidates[0]) { ctx.body = { RequestId: resNormal.RequestId, UserType: 'Normal', UserName: resNormal.Results[0].Candidates[0].PersonName } return; } // 新客户,添加到普通客户库中 const personId = uuid(); const resNewUser = await iaiClient.CreatePerson({ groupId: 'group-normal', personId: personId, personName: personId, image }); ctx.body = { RequestId: resNewUser.RequestId, UserType: 'New', userName: personId }; } catch (e) { ctx.body = e; } }
测试用例
为了验证接口的功能,小明用mocha写了测试用例,模拟几类客户识别的场景,并顺利通过了所有的用例。
it('客户1注册VIP,返回成功', (done) => { request .post('/registerUser') .send({ image: personVIPImg, personName: 'curry' }) .end((err, res) => { assert.ok(res.body.FaceId !== undefined, `requestId: ${res.body.RequestId}`); done(); }) }); it('客户1进店,判断客户类型,返回客户1名称,类型VIP', (done) => { request .post('/getUserInfo') .send({ image: personVIPImg }) .end((err, res) => { assert.equal(res.body.UserType, 'VIP', `requestId: ${res.body.RequestId}`); assert.equal(res.body.UserName, 'curry', `requestId: ${res.body.RequestId}`); done(); }); }); it('新客户2进店,判断客户类型,返回类型为新客户', (done) => { request .post('/getUserInfo') .send({ image: personNormalImg }) .end((err, res) => { assert.equal(res.body.UserType, 'New', `requestId: ${res.body.RequestId}`); done(); }); }); it('新客户2再次进店,判断客户类型,返回类型为回头客', (done) => { request .post('/getUserInfo') .send({ image: personNormalImg }) .end((err, res) => { assert.equal(res.body.UserType, 'Normal', `requestId: ${res.body.RequestId}`); done(); }); })

总结
本文通过一个小故事介绍了如何基于腾讯云人脸识别接口实现客户身份自动识别服务(demo源码),人脸识别在智慧零售场景下已经得到了广泛应用,通过人脸识别,店内服务人员可以第一时间获取进店会员、回头客的身份信息,提供个性化服务,提升服务质量和消费体验,实现精准营销同送,客流分析等能力。