小程序人脸动态识别摇头点头采坑建议
前言
应需求,在小程序内部弄一个人脸动态活体扫描,检测一下摇摇头,点点头之类的。但是在网上的相关信息却没有,偶尔有几个思路,所以就在这里记录下自己的探究历程
说明
首先说明的是这篇文章采用的是百度云的人脸识别,与腾讯的人脸识别不同的是,百度人脸识别的api还提供三维旋转的角度检测,这样对于实现检测人脸识别摇头和点头是非常简单的。
探究
实现思路
创造一个定时器,在定时器里面使用拍照之后使用人脸识别接口,这样就变成动态检测了。
定时器
setTimeout和setInterval区别及选择
setTimeout和setInterval都可以用作定时器,但是我们得先大致区分一下两者的部分区别:
setTimeout会保证在指定好的延时时间后执行,但是setInterval则不会这样。 如果function中的代码有耗时载操作,那么使用setTimeout方法递归,则可能会增加总递归的时间。
而使用setInterval方法,如果程序中耗时比延时间隔长,则会立刻回调函数。( 更多关于setInterval计时不准确可以点击这里了解。)
因为setInterval计时并不准确,同时我们定时器同时存在耗时操作(调用百度云接口),所以我们这里就采用setTimeout +递归方式来实现定时。
定时器代码
every_camera_upload: function(acstoken) { //定时拍摄照片
let that = this;
timer = setTimeout(function() {//设置定时器,并赋给全局变量timer
//that.camera().then(resx => {//循环拍照
// that.uploadBaiDuPicture(acstoken).then(rx =>{//上传百度云接口
// that.judge_face(rx);//获取返回的json数据并分析
// });
that.every_camera_upload(that.data.getAccessToken); //方法中调用定时器实现循环
// });
}, 1500);
}
//clearTimeout(timer); //此方法不能放在定时器方法内部,
//用于清除新一轮循环中函数还未运行时清除定时器,
//也不能放在every_camera_upload()方法内部,应为要重复调用,应放于其他方法内部。
解释一下代码,在方法内部,设置一个setTimeout赋给全局变量,在定时器内部调用camer方法,之后则重复调用every_camera_upload()方法实现递归。clearTimeout()的作用用于清除循环,必须在其他方法中使用。
实现功能过程
获取accessToken
阅读百度云人脸识别接口得知,先获取access_token进行身份验证,但我们不能将密钥密匙放在客户端上防止别人抓包逆工程获取到,此时可以将其放在服务器或者使用云开发的云函数上(博主使用的云函数)
云函数代码
// 云函数入口文件
const cloud = require('wx-server-sdk');
cloud.init();
// 云函数入口函数
var getAccessToken = function () { //人脸识别API
var https = require('https');
var qs = require('querystring');
const param = qs.stringify({
'grant_type': 'client_credentials',
'client_id': '你的 Api Key',
'client_secret': '你的 Secret Key'
});
var access_token;
return new Promise((resolve,reject) => {
let body =[];
https.get(
{
hostname: 'aip.baidubce.com',
path: '/oauth/2.0/token?' + param,
agent: false
},
function (res) {
res.on('data',(chunk) => {
body.push(chunk);
});
res.on('end', () =>{
let data = Buffer.concat(body).toString();
let getData = JSON.parse(data);
access_token = getData.access_token;
console.log(access_token);
resolve(access_token);
});
}
);
}).then(res =>{
return access_token;
});
}
exports.main = (event, context) => {
let datas = getAccessToken();
return datas;
}
小程序端代码
getAccessToken: function() { //获取accesstoken
var x = new Promise((resolve, reject) => {
wx.cloud.callFunction({
name: 'getBaiDuAccessToken',
data: {},
success: resy => {
console.log(resy);
console.log(resy.result);
resolve(resy.result);
},
fail: resy => {
wx.showToast({
title: 'accesstoken报错',
icon: 'none',
duration: 2000
});
}
});
});
return x;
}
开始定时循环拍摄
获取到access_token后,就可以在小程序循环拍摄上传了,但注意的是access_token最好放在服务器上让小程序将照片发送到后端让后端发送请求接口,但考虑到诸多性能问题,博主就将其放在小程序端上。
循环拍照
every_camera_upload: function(acstoken) { //定时拍摄照片
let that = this;
timer = setTimeout(function() {
that.camera().then(resx => {
that.uploadBaiDuPicture(acstoken).then(rx =>{
that.judge_face(rx);
});
that.every_camera_upload(that.data.getAccessToken); //方法中调用定时器实现循环
});
}, 1500);
发送请求
发送请求最好由服务器来做,服务器解析之后返回给小程序端,博主将其放在小程序端。
uploadBaiDuPicture: function(restoken) { //上传百度云照片并解析
var base64 = wx.getFileSystemManager().readFileSync(this.data.tempImagePath, 'base64');
let data = [{ //百度云的锅,在json格式外需要外加一个[]
image: base64,
image_type: 'BASE64'
}];
return new Promise((resolve, reject) => {
wx.request({
url: 'https://aip.baidubce.com/rest/2.0/face/v3/faceverify?access_token=' + restoken,
data: data,
// dataType: "json",
method: 'POST',
header: {
'Content-Type': 'application/json'
},
success(res) {
resolve(res);
}
})
});
}
由于拍摄的照片是在本地上,发送请求就必须将本地照片进行base64编码后放入data请求参数中,特别重要的一点,如果你碰上这种错误返回:
那八成是由于请求参数外没有加 [ ],如上代码显示,json请求格式外要 [] ,这个错误网上信息很少,解释为百度人脸识别v3版本的api表单是个list,接口开发文档也没有相关注明,所以请注意这个坑,引用:活体检测error_code":222200"
摇头点头顺序设置
judge_face: function(res) { //判断用户点头摇头动作
let that = this;
if (res.data.error_code == 0) {
if (this.data.prove_face_front == false){
that.judge_prove_face_front(res);
}else if (that.data.judge_left_change_right_head_count < 2) {
that.judge_left_change_right_head(res); //判断用户摇头
} else if(that.data.judge_down_to_up_head_count < 2){
that.judge_down_to_up_head(res); //判断用户点头
}else {
wx.showLoading({
title: '上传中',
});
innerAudioConext.src = "/music/upload.mp3"
innerAudioConext.play();
this.uploadUserPictureProve();
clearTimeout(timer);
}
} else if (res.data.error_code == 222202) {
wx.showToast({
title: '未识别到脸部',
icon: 'none',
duration: 500
});
} else {
wx.showToast({
title: '未知错误',
icon: 'none',
duration: 500
});
}}
解析返回json文件
获取返回的json的参数pitch,yaw,为三维旋转角度,以此判断正脸
judge_prove_face_front:function(res){
let pitch = Math.abs(res.data.result.face_list[0].angle.pitch);
let yaw = Math.abs(res.data.result.face_list[0].angle.yaw);
if ((pitch < 5 ) && (yaw<5)) {
let font_face=this.data.tempImagePath
this.setData({
prove_face_front: true,
save_font_face:font_face
})
} else {
wx.showToast({
title: '未检测到正脸',
icon: 'none',
duration: 1000
});
}
}
播放语音,获取返回的json的参数yaw,用上一次获取的yaw减去这一次的yaw参数的绝对值判断是否摇头。
judge_left_change_right_head: function(res) {
if (this.data.music_count == 0) {
innerAudioConext.src = "/music/leftright.mp3"
innerAudioConext.play();
this.data.music_count++;
}
let yaw = parseInt(res.data.result.face_list[0].angle.yaw);
if ((yaw < 0 || yaw > 0) && (Math.abs(this.data.last_yaw - yaw)>40)) {
this.data.judge_left_change_right_head_count++;
this.setData({
last_yaw: yaw
})
console.log("摇头 " + this.data.judge_left_change_right_head_count)
} else {
wx.showToast({
title: '未检测到摇头',
icon: 'none',
duration: 1000
});
}
}
播放语音,获取返回的json的参数pitch,用上一次获取的pitch减去这一次的pitch参数的绝对值判断是否点头。
judge_down_to_up_head: function(res) {
if(this.data.music_count==1){
innerAudioConext.src = "/music/headupdown.mp3"
innerAudioConext.play();
this.data.music_count++;
}
let pitch = res.data.result.face_list[0].angle.pitch;
if ((pitch < 0 || pitch > 0) && (Math.abs(this.data.last_pitch - pitch) > 5.5)) {
this.data.judge_down_to_up_head_count++;
this.setData({
last_pitch: pitch
})
} else {
wx.showToast({
title: '未检测到点头',
icon: 'none',
duration: 1000
});
}
}
人脸对比
所有验证成功后,就可以直接上传人脸对比了,关于人脸对比同样使用静默人脸对比,由于源码解释众多,本篇文章就不再重复讲解了。
最后
只看开发者文档注定会踩许多坑,希望能将有坑的地方记录下来,让更多人注意。感谢其他文章论坛提问的帮助,链接如下:
微信小程序—setTimeOut定时器的坑
活体检测error_code":222200"