<template>
<view>
<view v-show="showContainer" class="face-capture">
<text class="tip">请保持人像在取景框内</text>
<video id="faceVideo" :autoplay="true" object-fit="cover" :show-mute-btn="false" :show-play-btn="false"
:show-progress="false"></video>
<canvas id="refCanvas" :width="300" :height="300"></canvas>
<text class="contentp">{{ scanTip }}</text>
</view>
<view v-if="!showContainer" class="img-face">
<img class="imgurl" :src="imgUrl" />
</view>
<view v-if="!scanTip" style="padding:1rem 2rem ; border:1px solid #ccc;position:fixed" @click="tackPhoto()">
拍照
</view>
</view>
</template>
<script>
import "@/static/tracking/tracking-min.js";
import "@/static/tracking/face-min.js";
import "@/static/tracking/eye-min.js";
import "@/static/tracking/mouth-min.js";
export default {
data() {
return {
URL: null,
streamIns: null, // 视频流
showContainer: true, // 显示
tracker: null,
tipFlag: false, // 提示用户已经检测到
flag: false, // 判断是否已经拍照
context: null, // canvas上下文
removePhotoID: null, // 停止转换图片
scanTip: "人脸识别中...", // 提示文字
imgUrl: "",
canvas: null,
trackertask: null
};
},
mounted() {
this.initGetUserMedia();
},
methods: {
async initGetUserMedia() {
const constraints = {
video: {
width: 300,
height: 300
}
};
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
this.success(stream);
} catch (error) {
this.error(error);
}
},
success(stream) {
this.streamIns = stream;
const video = document.querySelector('#faceVideo video')
video.srcObject = stream;
// 苹果手机的系统弹框会阻止js的线程的继续执行 手动0.1秒之后自动执行代码
setTimeout(() => {
video.play();
this.initTracker(); // 人脸捕捉
}, 100);
},
error(err) {
this.scanTip = `访问用户媒体失败: ${err.name} - ${err.message}`;
if (err.name === 'NotAllowedError') {
this.scanTip = "用户拒绝了访问摄像头的请求。";
} else if (err.name === 'NotFoundError') {
this.scanTip = "没有找到任何可用的媒体设备。";
} else if (err.name === 'NotReadableError') {
this.scanTip = "媒体设备被占用或存在硬件问题。";
} else if (err.name === 'OverconstrainedError') {
this.scanTip = "指定的约束无法满足。";
} else if (err.name === 'SecurityError') {
this.scanTip = "安全错误,可能是由于不安全的上下文或其他限制。";
} else if (err.name === 'TypeError') {
this.scanTip = "设备请求类型错误,请检查约束对象。";
} else {
this.scanTip = `未知错误: ${err.message}`;
}
},
// 人脸捕捉 设置各种参数 实例化人脸捕捉实例对象,注意canvas上面的动画效果。
initTracker() {
this.context = document.querySelector('#refCanvas canvas').getContext("2d"); // 画布
this.canvas = document.querySelector('#refCanvas canvas');
this.tracker = new window.tracking.ObjectTracker("face"); // tracker实例
this.tracker.setInitialScale(4);
this.tracker.setStepSize(2); // 设置步长
this.tracker.setEdgesDensity(0.1);
try {
const video = document.querySelector('#faceVideo video')
this.trackertask = window.tracking.track(video, this.tracker); // 开始追踪
} catch (e) {
this.scanTip = "访问用户媒体失败,请重试";
}
//开始捕捉方法 一直不停的检测人脸直到检测到人脸
this.tracker.on("track", (e) => {
//画布描绘之前清空画布
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (e.data.length === 0) {
this.scanTip = "未检测到人脸";
} else {
e.data.forEach((rect) => {
//设置canvas 方框的颜色大小
this.context.strokeStyle = "#42e365";
this.context.lineWidth = 2;
this.context.strokeRect(rect.x, rect.y, rect.width, rect.height);
});
if (!this.tipFlag) {
this.scanTip = "检测成功,正在拍照,请保持不动2秒";
}
// 1.5秒后拍照,仅拍一次 给用户一个准备时间
// falg 限制一直捕捉人脸,只要拍照之后就停止检测
if (!this.flag) {
this.scanTip = "拍照中...";
this.flag = true;
this.removePhotoID = setTimeout(() => {
this.tackPhoto();
document.querySelector('#faceVideo video').pause();
this.tipFlag = true;
}, 1500);
}
}
});
},
// 拍照
tackPhoto() {
// 在画布上面绘制拍到的照片
this.context.drawImage(document.querySelector('#faceVideo video'), 0, 0, 300, 300);
// 保存为base64格式
this.imgUrl = this.saveAsPNG(document.querySelector('#refCanvas canvas'));
console.log('imgUrl', this.imgUrl)
/** 拿到base64格式图片之后就可以在this.compare方法中去调用后端接口比较了,也可以调用getBlobBydataURI方法转化成文件再去比较
* 我们项目里有一个设置个人头像的地方,先保存一下用户的图片,然后去拿这个图片的地址和当前拍照图片给后端接口去比较。
* */
// this.compare(imgUrl)
//判断图片大小
this.imgSize();
//this.faceToTengXun(); // 人脸比对
this.close();
},
imgSize() {
if (this.imgUrl) {
// 获取base64图片byte大小
const equalIndex = this.imgUrl.indexOf("="); // 获取=号下标
let size;
if (equalIndex > 0) {
const str = this.imgUrl.substring(0, equalIndex); // 去除=号
const strLength = str.length;
const fileLength = strLength - (strLength / 8) * 2; // 真实的图片byte大小
size = Math.floor(fileLength / 1024); // 向下取整
} else {
const strLength = this.imgUrl.length;
const fileLength = strLength - (strLength / 8) * 2;
size = Math.floor(fileLength / 1024); // 向下取整
}
if (size > 1024) {
// 图片超过1M 按比例压缩
this.imgUrl = document.querySelector('#refCanvas canvas').toDataURL("image/png", 1024 / size);
}
}
},
// 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,
});
},
// compare(url) {
// let blob = this.getBlobBydataURI(url, 'image/png')
// let formData = new FormData()
// formData.append("file", blob, "file_" + Date.parse(new Date()) + ".png")
// // TODO 得到文件后进行人脸识别
// },
// 保存为png,base64格式图片
saveAsPNG(c) {
return c.toDataURL("image/png", 0.4);
},
close() {
this.flag = false;
this.tipFlag = false;
this.showContainer = false;
this.context = null;
this.scanTip = "人脸识别中...";
clearTimeout(this.removePhotoID);
if (this.streamIns) {
this.streamIns.enabled = false;
this.streamIns.getTracks()[0].stop();
this.streamIns.getVideoTracks()[0].stop();
}
this.streamIns = null;
this.trackertask.stop();
this.tracker = null;
}
},
}
</script>
<style>
.face-capture {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.tip {
position: fixed;
top: 48px;
z-index: 5;
font-size: 18px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 25px;
}
video {
height: 300px;
width: 300px;
border-radius: 150px;
border: 1px solid #ccc;
}
.face-capture video,
.face-capture canvas {
position: fixed;
top: 120px;
height: 300px;
width: 300px;
object-fit: cover;
z-index: 2;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.face-capture .contentp {
position: fixed;
top: 500px;
font-size: 18px;
}
.face-capture .rect {
border: 2px solid #0aeb08;
position: fixed;
z-index: 4;
}
.img-face {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.img-face .imgurl {
position: fixed;
top: 120px;
width: 300px;
height: 300px;
border-radius: 150px;
}
/deep/ .uni-video-cover {
display: none !important;
}
</style>