H5人脸识别功能

707 阅读1分钟
<template>
  <div class="faceAuth-wrapper">
      <NavigationBar title="签约"/>
    <div class="box flex-c">
      <div class="auth-area" :class="{'filter':step == 3}">
          <video ref="refVideo" id="video" autoplay></video>
          <canvas width="440" height="700" ref="refCanvas"></canvas>
          <div class="rect" v-for="(item,index) in profile" :style="{ width: item.width + 'px', height: item.height + 'px', left: item.left + 'px', top: item.top + 'px'}"></div>
      </div>

      <div class="auto-tip" v-if="step == 2 || step == 3">{{scanTip}}</div>

      <div class="tip" v-if="step == 1">
        <div class="tip-msg">验证时,请将面部放在圆圈内,按提示操作</div>
        <div class="tip-box flex">
          <div class="tip-item flex-c">
            <img class="img" src="@/assets/img/tip1.png">
            <div class="flex">
              <img class="icon" src="@/assets/img/tip-err.png">
              <span>斜着手机</span>
            </div>
          </div>
          <div class="tip-item flex-c">
            <img class="img" src="@/assets/img/tip2.png">
            <div class="flex">
              <img class="icon" src="@/assets/img/tip-err.png">
              <span>光线昏暗</span>
            </div>
          </div>
          <div class="tip-item flex-c">
            <img class="img" src="@/assets/img/tip3.png">
            <div class="flex">
              <img class="icon" src="@/assets/img/tip-err.png">
              <span>不能带帽子</span>
            </div>
          </div>
          <div class="tip-item flex-c">
            <img class="img" src="@/assets/img/tip4.png">
            <div class="flex">
              <img class="icon" src="@/assets/img/tip-err.png">
              <span>不能遮挡面部</span>
            </div>
          </div>
        </div>
      </div>
      <div v-if="step == 1" class="btn flex-c" @click="beginToAuth">{{errCount > 0?'重试':'确定'}}</div>
    </div>
  </div>
</template>

<script>
import Vconsole from 'vconsole';
import '@/assets/js/tracking-min.js'  // 前端人脸识别框架Tracking.js
import '@/assets/js/face-min.js' // 人脸识别的 JavaScript 接口
import { compare } from '@/api/faceAuth'  //api后端接口

 //export function compare(data) {
 // return request({
  //  url: '/hw/member-auth/hw-real-photo-contrast',
    //  method: 'post',
     // headers:{
       // 'Content-Type':'multipart/form-data'
     // },
     // data,
    //  noLoading:true
   // })
 // }
  
const errorMap = {
  'NotAllowedError': '摄像头已被禁用,请在当前浏览器设置中开启后重试',
  'AbortError': '硬件问题,导致无法访问摄像头',
  'NotFoundError': '未检测到可用摄像头',
  'NotReadableError': '操作系统上某个硬件、浏览器或者网页层面发生错误,导致无法访问摄像头',
  'OverConstrainedError': '未检测到可用摄像头',
  'SecurityError': '摄像头已被禁用,请在系统设置或者浏览器设置中开启后重试',
  'TypeError': '类型错误,未检测到可用摄像头'
};
export default {
    name:'faceAuth',
    data(){
      return{
        step:1, //1初始化 2开始识别 3识别成功
        URL: null,
        streamIns: null,        // 视频流
        tracker: null,
        tipFlag: false,         // 提示用户已经检测到
        flag: false,            // 判断是否已经拍照
        context: null,          // canvas上下文
        profile: [],            // 轮廓
        removePhotoID: null,    // 停止转换图片
        scanTip: '人脸识别中...',// 提示文字
        imgUrl: '',              // 实名图片
        errCount:0
      }
    },
    mounted(){
        new Vconsole();
        this.playVideo()
    },
    methods:{
        // 访问用户媒体设备
        getUserMedia(constrains, success, error) {
            if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
                //最新标准API
                navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error);
            } else if (navigator.webkitGetUserMedia) {
                //webkit内核浏览器
                navigator.webkitGetUserMedia(constrains).then(success).catch(error);
            } else if (navigator.mozGetUserMedia) {
                //Firefox浏览器
                navagator.mozGetUserMedia(constrains).then(success).catch(error);
            } else if (navigator.getUserMedia) {
                //旧版API
                navigator.getUserMedia(constrains).then(success).catch(error);
            } else {
                this.$toast('您的浏览器不支持访问用户媒体设备')
            }
        },
        
        //获取媒体流成功处理
        mediaSuccessCallback(stream) {
            this.streamIns = stream
            // webkit内核浏览器
            this.URL = window.URL || window.webkitURL
            if ("srcObject" in this.$refs.refVideo) {
                this.$refs.refVideo.srcObject = stream
            } else {
                this.$refs.refVideo.src = this.URL.createObjectURL(stream)
            }
            this.$refs.refVideo.onloadedmetadata = () => {
                this.$refs.refVideo.play()
            }
        },

        //获取媒体流错误处理
        mediaErrorCallback(error) {
            if (errorMap[error.name]) {
              this.$toast(errorMap[error.name]);
            }
        },

        //开始人脸识别
        beginToAuth(){
          if(this.$refs.refVideo.src || this.$refs.refVideo.srcObject){
            this.initTracker()
            this.step = 2
          }else{
            this.$toast('访问用户媒体失败,请重试')
          }
        },

        playVideo() {
            this.getUserMedia({ video: {
                    width: 1920, height: 1080, facingMode: "user" }     /* 前置优先 */
            }, this.mediaSuccessCallback, this.mediaErrorCallback)
        },

        // 人脸捕捉
        initTracker() {
            this.context = this.$refs.refCanvas.getContext("2d")    // 画布
            this.tracker = new tracking.ObjectTracker('face')     // tracker实例
            this.tracker.setInitialScale(4);
            this.tracker.setStepSize(2);
            this.tracker.setEdgesDensity(0.1);
            this.tracker.on('track', this.handleTracked)            // 绑定监听方法
            try {
                tracking.track('#video', this.tracker, { camera: true })      // 开始追踪
            } catch (e) {
                this.$toast('访问用户媒体失败,请重试');
                this.step = 1
            }
        },

        // 追踪事件
        handleTracked(e) {
            if (e.data.length === 0) {
                this.scanTip = '未检测到人脸'
                this.profile.shift();
            } else {
                if (!this.tipFlag) {
                    this.scanTip = '检测成功,正在拍照,请保持不动'
                }
                // 1秒后拍照,仅拍一次
                if (!this.flag) {
                    this.scanTip = '拍照中...'
                    this.flag = true
                    this.removePhotoID = setTimeout(() => {
                        this.tackPhoto()
                        this.tipFlag = true
                    }, 1000)
                }
                e.data.forEach(this.plot)
            }
        },

        // 绘制跟踪框
        plot({x, y, width: w, height: h}) {
            // 创建框对象
            this.profile.shift();
            this.profile.push({ width: w, height: h, left: x, top: y })
        },

        // 拍照
        tackPhoto() {
            this.context.drawImage(this.$refs.refVideo, 0, 0, 440, 700)
            // 保存为base64格式
            this.imgUrl = this.getBlobBydataURI(this.$refs.refCanvas.toDataURL())
            this.close();
            this.faceAuth()
        },

        faceAuth(){
          let formData = new FormData(); 
          formData.append('file', this.imgUrl);
          compare(formData).then(res=>{
            this.scanTip = '人脸识别成功'
            this.$toast('人脸识别成功')
            localStorage.setItem('userSignStep',2)
            if (this.streamIns) {
                this.streamIns.enabled = false
                this.streamIns.getTracks()[0].stop()
                this.streamIns.getVideoTracks()[0].stop()
            }
            this.streamIns = null
            this.$router.replace('/onlineSigning')
          }).catch(()=>{
            this.errCount ++;
            this.step = 1
            this.$refs.refVideo.play()
          })
        },

        // Base64转文件
        getBlobBydataURI(dataURI, type) {
            var binary = window.atob(dataURI.split(',')[1]);
            var array = [];
            for(var i = 0; i < binary.length; i++) {
                array.push(binary.charCodeAt(i));
            }
            return new Blob([new Uint8Array(array)], {
                type: type
            });
        },

        // 保存为png,base64格式图片
        saveAsPNG(c) {
            return c.toDataURL('image/png', 0.3)
        },

        // 关闭并清理资源
        close() {
            this.$refs.refVideo.pause()
            this.step = 3
            this.flag = false
            this.tipFlag = false
            this.tracker && this.tracker.removeListener('track', this.handleTracked)
            this.tracker = null
            this.context = null
            this.profile = []
            this.scanTip = '人脸识别中...'
            clearTimeout(this.removePhotoID)
        }
      }
}
</script>

<style lang="scss" scoped>
  .faceAuth-wrapper{
    height: 100vh;
    background-color: #fff;
  }

  .box{
    flex-direction: column;
  }

  .tip{
    margin-top: 76px;
    width: 100%;
  }

  .tip-msg{
    font-size: 28px;
    color: #999;
    text-align: center;
    margin-bottom: 74px;
    line-height: 1;
  }

  .tip-box{
    margin: 0 50px;
    justify-content: space-between;
  }

  .btn{
    width: 320px;
    margin-top: 125px;
  }

  .tip-item{
    color: #999;
    font-size: 20px;
    flex-direction: column;
    .img{
      width: 100px;
      height: 100px;
      margin-bottom: 16px;
    }
    .icon{
      width: 20px;
      height: 20px;
      margin-right: 4px;
    }
  }

  .auth-area{
    width: 440px;
    height: 440px;
    border-radius: 50%;
    // overflow: hidden;
    background-color: #505050;
    margin-top: 96px;
    position: relative;
    &.filter{
      filter: blur(10px);
    }
    video{
      width: 100%;
      height: 100%;
      object-fit: cover;
      border-radius: 50%;
      transform: rotateY(180deg);
    }
    canvas{
       width: 440px;
       height: 700px;
       position: absolute;
       top: 0;
       left: 0;
       z-index: 2;
       opacity: 0;
    }
    .rect {
        border: 2px solid #0aeb08;
        position: absolute;
        z-index: 3;
    }
  }

  .auto-tip{
    margin-top: 70px;
    color: #C9935C;
    font-size: 36px;
    text-align: center;
  }


</style>