<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>