1. 程式人生 > 實用技巧 >小程式人臉動態識別搖頭點頭採坑建議

小程式人臉動態識別搖頭點頭採坑建議

前言

  應需求,在小程式內部弄一個人臉動態活體掃描,檢測一下搖搖頭,點點頭之類的。但是在網上的相關資訊卻沒有,偶爾有幾個思路,所以就在這裡記錄下自己的探究歷程

說明

  首先說明的是這篇文章採用的是百度雲的人臉識別,與騰訊的人臉識別不同的是,百度人臉識別的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"