微信小程序- wx.faceDetect识别人脸并拍照(传经纬度)

1,136 阅读4分钟

步骤

  • 打开相机
  • 识别是否是人像
  • 是人像再拍照(将照片传给后端)

实现方式: wx.faceDetect

参考文件:

face.wxml

<view>
  <!-- 选择服务对象 -->
  <view class="bg-white pt-32 pr-32 pb-26 radius-25" wx:if="{{sourceType==1}}">
    <van-field required value="{{serviceObjectName }}" is-link readonly label="服务对象" placeholder="请选择服务对象" bind:tap="onServiceObjectClick" />
    <view bindtap="faceEnter" wx:if="{{serviceObjectName}}" class="blue-bg mg-20 pd-20 bg-blue radius-20 center">确认录入人脸
    </view>
  </view>
  <!-- 服务对象下拉 -->
  <van-popup show="{{ serviceObjectShow }}" bind:close="handleClose" round position="bottom">
    <van-search value="{{ nameKeyWord }}" placeholder="请输入名称" use-action-slot bind:change="onSearch" bind:search="getObjectList">
    </van-search>
    <van-cascader value="{{ serviceObjectId  }}" title="请选择对象" options="{{ serviceObjectList }}" bind:close="onServiceObjectClose" bind:change="onServiceObjectFinish" />
  </van-popup>

  <!-- 
    **注意点:camera或camera外层view需要加wx:if,否则会出现黑屏的情况,报错信息是'[Component] <camera>: 一个页面只能插入一个',其次注意写法,wx:if="{{(isShowCamera&&sourceType==1)||sourceType!=1}}"IOS手机会出现黑屏,需要改为wx:if="{{isShowCamera&&(sourceType==1||sourceType!=1)}}"**
   -->
  <view class="camera-div" wx:if="{{isShowCamera&&(sourceType==1||sourceType!=1)}}">
    <view class="flex_r white pd-32">
      <!-- 镜头翻转 -->
      <view bindtap="reverse">镜头翻转</view>
      <!-- <view bindtap="clickScanCode">扫二维码</view> -->
    </view>
    <!-- resolution:获取人脸图片后的清晰度 low:低  -->
    <camera device-position="{{devicePosition ?'front': 'back'}}" class="camera" flash="off" resolution='low' />
    <view class="title white" wx:if="{{tipsText}}">识别提示:{{ tipsText }}</view>
    临时文件:
    <image src="{{faceImage}}" mode="" />
    base64文件:
    <image src="data:image/png;base64,{{base64}}" style="width:{{screenWidth}}px;height:{{screenHeight}}px" />
    后端返回的图片:
    <image src="{{info.facePicture}}" />
  </view>
  <van-toast id="van-toast" />
</view>

face.js

import Toast from '@vant/weapp/toast/toast';
Page({
  /**
   * 页面的初始数据
   */
  data() {
    return {
      info: {},
      sourceType: '',//1添加人脸 2比对人脸 3人脸支付 4签退
      // 选择服务对象
      nameKeyWord: '',
      serviceObjectId: '',
      serviceObjectList: [],
      isShowCamera: false,
      // 经纬度
      latitude: '',
      longitude: '',

      isShow: false,
      tipsText: '', // 错误文案提示
      tempImg: '', // 本地图片路径
      cameraEngine: null, // 相机引擎
      devicePosition: false, // 摄像头朝向
      isAuthCamera: true, // 是否拥有相机权限
      isVerify: false,
      translateY: -24,
      timer: null,
      base64: "",
      personId: "",
      isFlag2: true,
      screenWidth: 375,
      screenHeight: 640,
      faceImage: '', //人脸图片

      // 订单ID
      orderId: '',
      pictureUrl: '',
    }
  },
  onLoad(options) {
    this.clearTimer();
    this.setData({
      cameraEngine: wx.createCameraContext(),
      isShow: true,
      sourceType: options.sourceType,
      pictureURL: pictureURL,
    })
    if (options.sourceType == 3) {
      this.setData({
        orderId: options.orderId,
        serviceObjectId: options.serviceObjectId,
        pictureUrl: options.pictureUrl
      })
    }
    this.getBaiduLatLon().then(res => {
      this.setData({
        latitude: res.lat,
        longitude: res.lon,
      })
    }).catch(err => {
      console.error(err);
    });
    if (options.sourceType != 1) {
      this.setData({ isShowCamera: true }, () => {
        this.initData();
        this.lineAnimation();
      });
    }
  },
  onServiceObjectClick() {
    this.setData({
      nameKeyWord: '',
      isVerify: false
    })
    this.getObjectList()
  },
  // 选择服务对象
  onSearch() {
    this.setData({
      nameKeyWord: e.detail
    })
    this.getObjectList()
  },
  handleClose() { },
  getObject() {
    var that = this
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let newArr = []
        wx.http('/', { serviceObjectName: this.data.nameKeyWord }).then(data => {
          newArr = data.rows.map((item) => {
            return {
              text: `${item.serviceObjectName}(${item.phone})`,
              value: String(item.serviceObjectId),
              serviceObjectType: item.serviceObjectType
            }
          })
          that.setData({
            serviceObjectList: newArr,
          })
        })
        resolve({ code: 200, data: newArr });
      }, 1000);
    });
  },
  // 获取服务对象列表
  async getObjectList() {
    let info = await this.getObject()
    if (info.code == 200) {
      this.setData({
        serviceObjectList: info.data,
        serviceObjectShow: true
      });
    }
  },
  onServiceObjectClose() {
    this.setData({
      serviceObjectShow: false,
    });
  },
  onServiceObjectFinish(e) {
    const {
      selectedOptions,
      value
    } = e.detail;
    const text = selectedOptions[0].text
    this.setData({
      serviceObjectName: text,
      serviceObjectId: value,
      serviceObjectShow: false
    })
  },
  //获取经纬度(百度)
  getBaiduLatLon() {
    this.setData({
      latitude: '',
      longitude: '',
    })
    var that = this
    return new Promise((resolve, reject) => {
      var util = require('/utils/WSCoordinate.js')
      let params = {}
      wx.getLocation({
        type: 'gcj02', //gcj02   wgs84
        success(res) {
          params.lon = util.transformFromGCJToBaidu(res.latitude, res.longitude).longitude
          params.lat = util.transformFromGCJToBaidu(res.latitude, res.longitude).latitude
          resolve({
            code: 200,
            lat: params.lat,
            lon: params.lon
          }) //成功回调
          that.setData({
            latitude: params.lat,
            longitude: params.lon,
          })
        },
        fail(err) {
          if (err.errCode == 2) {
            Toast.fail('请打开手机定位!');
          } else if (!err.errCode) {
            Toast.fail('获取定位失败!');
          }
        }
      })
    })

  },
  onUnload() {
    this.clearTimer();
  },
  onHide() {
    this.clearTimer();
  },
  clearTimer() {
    if (this.data.timer) {
      clearInterval(this.data.timer);
      this.setData({
        timer: null,
      })
    }
    this.setData({
      isFlag2: false,
    })
  },
  faceEnter() {
    this.setData({ isShowCamera: true }, () => {
      this.initData();
      this.lineAnimation();
    });
  },
  // 初始化相机引擎
  initData() {
    // 1、初始化人脸识别
    wx.initFaceDetect();
    // 2、创建 camera 上下文 CameraContext 对象
    // 在faceEnter或onLoad中
    // 3、获取 Camera 实时帧数据
    const listener = this.data.cameraEngine.onCameraFrame((frame) => {
      console.log(888888888, frame.data, frame.width, frame.height)
      if (this.data.isVerify) return

      //动态设置canvas的宽高,不设置会导致部分机型人脸不完整导致不能识别-----很重要!很重要!很重要!
      if (this.data.isFlag2) {
        this.setData({
          isFlag2: false,
          screenWidth: frame.width,
          screenHeight: frame.height,
        })
      }
      // 4、人脸识别,使用前需要通过 wx.initFaceDetect 进行一次初始化,推荐使用相机接口返回的帧数据
      wx.faceDetect({
        frameBuffer: frame.data,
        width: frame.width,
        height: frame.height,
        enablePoint: true,
        enableConf: true,
        enableAngle: true,
        enableMultiFace: true,
        success: async (faceData) => {
          console.log(1111, faceData)
          let face = faceData.faceInfo[0]
          if (face.x == -1 || face.y == -1) {
            this.setData({
              tipsText: '检测不到人'
            })
          }
          if (faceData.faceInfo.length > 1) {
            this.setData({
              tipsText: '请保证只有一个人'
            })
          } else {
            const {
              pitch,
              roll,
              yaw
            } = face.angleArray;
            const standard = 0.5
            if (Math.abs(pitch) >= standard || Math.abs(roll) >= standard ||
              Math.abs(yaw) >= standard) {
              this.setData({
                tipsText: '请平视摄像头'
              })
            } else if (face.confArray.global <= 0.8 || face.confArray.leftEye <=
              0.8 || face.confArray.mouth <= 0.8 || face.confArray.nose <= 0.8 ||
              face.confArray.rightEye <= 0.8) {
              this.setData({
                tipsText: '请勿遮挡五官'
              })
            } else {
              if (this.data.isVerify) return
              //人脸位置校验
              var centerWidth = 250;
              var centerHeight = 250;
              if (face.x > (frame.width - centerWidth) / 2 && face.x < (frame
                .width - centerWidth) / 2 + centerWidth && face.y > (frame
                  .height - centerHeight) / 2 && face.y < (frame.height -
                    centerHeight) / 2 + centerHeight) {
                this.setData({
                  tipsText: '校验中...',
                  isVerify: true
                })
                // 太快获取的人脸可能比较抽象,给用户一个准备时间
                setTimeout(async () => {
                  let img = await this.takePhoto();
                  // let img = await this.takePhoto(frame);
                  this.setData({
                    base64: img
                  })
                  this.searchUserFace();
                }, 500)
              } else {
                this.setData({
                  tipsText: '请将人脸对准中心位置'
                })
              }
            }
          }
        },
        fail: (err) => {
          console.log(2222, err)
          if (this.data.isVerify) return
          if (err.x == -1 || err.y == -1) {
            this.setData({
              tipsText: '检测不到人'
            })
          } else {
            this.setData({
              tipsText: err.errMsg || '网络错误,请退出页面重试'
            })
          }
        },
      })
    })
    // 5、开始监听帧数据
    listener.start()
    this.setData({
      listener: listener
    })
  },
  reverse() {
    let a = this.data.devicePosition
    this.setData({
      devicePosition: !a,
      isVerify: false
    })
  },
  clickScanCode() {
    // 只允许通过相机扫码
    // #ifdef MP-WEIXIN
    wx.scanCode({
      onlyFromCamera: true,
      success: (res) => {
        var data = JSON.parse(res.result.replace(/\ufeff/g, ""));
      }
    });
    // #endif
  },
  // 获取图片
  async takePhoto() {
    return new Promise((resolve, reject) => {
      // const ctx = wx.createCameraContext();
      const ctx = this.data.cameraEngine
      ctx.takePhoto({
        quality: 'high',
        success: (res) => {
          console.log(res, 'takePhoto')
          let url = res.tempImagePath
          this.setData({
            faceImage: url
          })
          // resolve(url);
          // 调用函数将图片转换为Base64
          const fs = wx.getFileSystemManager();
          fs.readFile({
            filePath: url,
            encoding: 'base64', // 指定编码格式为base64
            success: function (res) {
              console.log('图片的Base64编码是: ', res.data);
              resolve(res.data);
            },
            fail: function (err) {
              console.error('转换Base64失败', err);
            }
          });
        },
        fail: (err) => {
          resolve({ code: 500 });

        }
      })
    })

  },
  searchUserFace() {
    // 1.人脸识别错误 把isVerify设置为false继续识别
    // 2.人脸识别成功,做对应的逻辑
    let { sourceType, orderId, base64, serviceObjectId, pictureUrl, latitude, longitude } = this.data
    let params = {
      faceBase: base64,
      serviceObjectId: serviceObjectId,
      latitude: latitude,
      longitude: longitude
    }
    let url = sourceType == 1 ? '/add' : '/check'
    wx.http(url, params, "POST").then(res => {
      
        wx.stopFaceDetect(); //关闭人脸识别
        this.data.listener.stop(); //关闭camera摄像头监听
        this.setData({
          tipsText: '校验成功'
        })
    }).catch(err => {
      this.setData({
        tipsText: err.msg
      })
      setTimeout(() => {
        this.setData({
          isVerify: false
        })
      }, 500)
    })
  },
  clickPushDetail() {
    // 跳转到其他页面
  },
  lineAnimation() {
    if (this.data.timer) return
    this.setData({
      timer: setInterval(() => {
        this.setData({
          translateY: this.data.translateY + 8
        })
        if (this.data.translateY >= 460) {
          this.setData({
            translateY: 10
          })
        }
      }, 40)
    })

  },


  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {

  }
})