小程序人脸动态识别摇头点头采坑建议

前言

  应需求,在小程序内部弄一个人脸动态活体扫描,检测一下摇摇头,点点头之类的。但是在网上的相关信息却没有,偶尔有几个思路,所以就在这里记录下自己的探究历程

说明

  首先说明的是这篇文章采用的是百度云的人脸识别,与腾讯的人脸识别不同的是,百度人脸识别的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请求参数中,特别重要的一点,如果你碰上这种错误返回:

接口返回错误222200

  那八成是由于请求参数外没有加 [ ],如上代码显示,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"

posted @ 2020-12-19 12:38  DAmarkday  阅读(1215)  评论(0编辑  收藏  举报