人臉識別智慧零售場景從零開發
- 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源碼),人臉識別在智慧零售場景下已經得到了廣泛應用,通過人臉識別,店內服務人員可以第一時間獲取進店會員、回頭客的身份資訊,提供個性化服務,提升服務品質和消費體驗,實現精準營銷同送,客流分析等能力。