javaScript实现人脸探测与捕获
思路
- tracking.js库可以实现捕捉或实时跟踪相机返回的颜色或面部图像信息。基于对特定颜色的检测或人脸面部的出现和移动来触发相对应的检测事件,比如调用该库的人脸探测API可实现人脸探测。进入人脸登录页面系统自动打开相机,视频区域开始监听人脸。当人脸出现在相机前,触发人脸探测机制,通过 标签对人脸位置以方框的形式进行标记,当探测到的人脸趋于稳定状态,拍摄当前人脸,通过 绘制成图片。由于字节流更适用于互联网传输,将采集的人脸图片转为Base64码,以此通过接口传输给后端。
-
代码
<template>
<div class="faceLogin">
<el-row
type="flex"
justify="center"
align="middle"
style="width: 100%; height: 150px"
>
<el-col :span="24">
<div class="titleRelation">
<el-image :src="topImgage"></el-image>
<div class="titlePosition">
<h1>系统人脸登录</h1>
</div>
<div class="datePosition">
<span>{{ nowTime }}</span>
</div>
<div class="btnPosition">
<el-button type="primary" size="medium" @click="goCourse()"
>密码登录</el-button
>
</div>
</div>
</el-col>
</el-row>
<el-row
:gutter="50"
type="flex"
justify="center"
align="middle"
style="margin-top: 20px"
>
<el-col :span="7"
><div class="faceDiv">
<div class="faceBorder">
<div class="borderStyle">
<div :class="loginStuats ? '' : 'line'"></div>
<video
id="video"
width="300"
height="230"
preload
autoplay
loop
muted
></video>
<canvas
:class="faceStuats ? '' : 'faceImg'"
id="canvas"
width="300"
height="230"
></canvas>
<div class="bottom"></div>
</div>
<canvas id="screenshotCanvas" width="300" height="230"></canvas>
</div>
<div class="faceStuatsText">
<p>
<i :class="faceStuatsTextIcon" style="margin-right: 10px"></i
>{{ faceStuatsText }}
</p>
</div>
</div></el-col
>
<el-col :span="13"
><div class="faceInfor">
<div v-if="loginStuats == 1">
<el-row
type="flex"
justify="center"
align="middle"
style="height: 80px"
>
<el-col :span="24" style="color: #55d9fb; letter-spacing: 5px"
><h1>欢迎使用</h1></el-col
>
</el-row>
<el-row
type="flex"
justify="start"
align="middle"
style="height: 80px"
>
<el-col :span="5" :push="3" class="Infor1"><p>姓名:</p></el-col>
<el-col :span="6" :push="2" class="Infor2"
><p>{{ userInfor.userName }}</p></el-col
>
</el-row>
<el-row
type="flex"
justify="start"
align="middle"
style="height: 80px"
>
<el-col :span="5" :push="3" class="Infor1"><p>身份:</p></el-col>
<el-col :span="6" :push="2" class="Infor2"
><p>{{ userInfor.identity }}</p></el-col
>
</el-row>
<el-row
type="flex"
justify="start"
align="middle"
style="height: 80px"
>
<el-col :span="5" :push="3" class="Infor1"><p>班级:</p></el-col>
<el-col :span="12" :push="2" class="Infor2"
><p>{{ userInfor.orgName }}</p></el-col
>
</el-row>
<el-row
type="flex"
justify="start"
align="middle"
style="height: 80px"
>
<el-col :span="5" :push="3" class="Infor1"><p>时间:</p></el-col>
<el-col :span="15" :push="2" class="Infor2"
><p>{{ userInfor.loginTime }}</p></el-col
>
</el-row>
</div>
<div v-else-if="loginStuats == 0">
<el-row
type="flex"
justify="start"
align="middle"
style="height: 80px"
>
<el-col
:span="23"
:push="1"
style="
font-size: 26px;
color: #55d9fb;
letter-spacing: 5px;
text-align: left;
"
><p>人脸识别过程中请注意以下事项:</p></el-col
>
</el-row>
<div v-for="(item, index) in faceAttent" :key="index">
<el-row
type="flex"
justify="start"
align="middle"
style="height: 80px"
>
<el-col :span="23" :push="1" class="Infor2"
><p>*{{ item }}</p></el-col
>
</el-row>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import "@/assets/tracking/build/tracking.js";
import "@/assets/tracking/build/data/face-min.js";
import "@/assets/tracking/build/data/mouth-min.js";
import { timeFormate } from "@/assets/js/nowtime.js";
export default {
data() {
return {
topImgage:
"http://gencun.ltd:8080/eduassistant/upload/login/titleTop1.jpg",
nowTime: "",
loginStuats: 0,
userInfor: {
userName: "",
identity: "",
orgName: "",
loginTime: "",
},
faceAttent: [
"请尽量使人脸显示在人脸识别区域的中心位置;",
"请调整脸部姿态与位置使得绿色人脸标识框出现;",
"当绿色人脸标识框出现时请保持脸部姿态与位置;",
"若人脸识别失败,请选择密码登录。",
],
faceStuats: 0,
faceStuatsText: "未检测到人脸",
faceStuatsTextIcon: "el-icon-warning",
video: null,
tracker: null,
trackerTask: null,
canvas: null,
context: null,
audio: null,
screenshotCanvas: null,
uploadLock: true,
};
},
methods: {
goCourse() {
this.$router.push({
path: "/password",
});
},
initCamera() {
this.video = document.getElementById("video");
this.canvas = document.getElementById("canvas");
this.screenshotCanvas = document.getElementById("screenshotCanvas");
this.context = this.canvas.getContext("2d");
this.audio = new SpeechSynthesisUtterance();
this.tracker = new window.tracking.ObjectTracker("face");
this.tracker.setInitialScale(4);
this.tracker.setStepSize(2);
this.tracker.setEdgesDensity(0.1);
this.trackerTask = window.tracking.track("#video", this.tracker, {
camera: true,
});
this.video.srcObject = window.stream;
this.lookFace();
},
lookFace() {
this.tracker.on("track", (event) => {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.context.strokeStyle = "#0764B7";
if (!event.data.length == 1) {
this.faceStuatsTextIcon = "el-icon-warning";
this.faceStuatsText = "未检测到人脸";
} else if (event.data.length == 1) {
this.context.strokeRect(
event.data[0].x,
event.data[0].y,
event.data[0].width,
event.data[0].height
);
this.faceStuats = 1;
setTimeout(() => {
this.audio.text = "已检测到人脸";
speechSynthesis.speak(this.audio);
this.faceStuatsTextIcon = "el-icon-s-custom";
this.faceStuatsText = "已检测到人脸";
this.audio.text = "正在识别中,请稍后";
speechSynthesis.speak(this.audio);
this.uploadLock && this.uploadFaceImg();
}, 1000);
} else if (event.data.length > 1) {
this.faceStuatsTextIcon = "el-icon-warning";
this.faceStuatsText = "请保持一张人脸";
}
});
},
uploadFaceImg() {
this.uploadLock = false;
let canvas = this.screenshotCanvas;
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(this.video, 0, 0, canvas.width, canvas.height);
let base64Img = canvas.toDataURL("image/jpeg");
this.faceStuatsTextIcon = "el-icon-loading";
this.faceStuatsText = "正在识别中,请稍后";
this.$axios({
method: "post",
url: this.$api + "/faceLogin",
data: base64Img,
})
.then((res) => {
if (res.data.data.length) {
this.userInfor.userName = res.data.data[0].userName;
this.userInfor.identity = res.data.data[0].power;
this.userInfor.orgName = res.data.data[0].depOrg;
this.userInfor.loginTime = this.nowTime;
this.$notify({
title: "成功",
message: "识别成功",
type: "success",
});
this.audio.text = "人脸识别成功";
speechSynthesis.speak(this.audio);
this.loginStuats = 1;
this.uploadLock = true;
this.faceStuatsTextIcon = "el-icon-success";
this.faceStuatsText = "人脸识别成功";
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
setTimeout(() => {
this.$router.push({
path: "/courseList",
});
}, 2000);
} else {
this.$notify({
title: "警告",
message: "识别失败!",
type: "warning",
});
this.audio.text = "人脸识别失败";
speechSynthesis.speak(this.audio);
this.loginStuats = 0;
this.faceStuatsTextIcon = "el-icon-error";
this.faceStuatsText = "人脸识别失败";
this.$router.push({
path: "/password",
});
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.faceStuats = 0;
}
})
.catch((err) => {
console.log(err);
this.$notify.error({
title: "错误",
message: "服务器错误,请联系管理员",
});
});
},
stopMediaStreamTrack() {
if (typeof window.stream === "object") {
this.video.srcObject = null;
window.stream.getTracks().forEach((track) => track.stop());
}
},
nowTimes() {
this.nowTime = timeFormate(new Date());
setInterval(this.nowTimes, 1000);
this.clear();
},
clear() {
clearInterval(this.nowTimes);
this.nowTimes = null;
},
},
mounted() {
this.nowTimes();
this.initCamera();
},
destroyed() {
this.trackerTask.stop();
this.stopMediaStreamTrack();
this.clear();
},
watch: {
faceStuats: function () {
if (this.faceStuats == 1) {
this.trackerTask.stop();
}
},
},
};
</script>
<style scoped>
.faceLogin {
background-image: url("http://gencun.ltd:8080/eduassistant/upload/login/faceBackground.jpg");
position: fixed;
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: center center;
width: 100%;
height: 100%;
}
.titleRelation {
position: relative;
width: 100%;
}
.titlePosition {
position: absolute;
left: 0;
right: 0;
bottom: -4px;
margin: auto;
color: #fff;
letter-spacing: 3px;
}
.datePosition {
position: absolute;
left: 5%;
bottom: 60%;
color: #fff;
letter-spacing: 2px;
font-size: 16px;
}
.btnPosition {
position: absolute;
right: 5%;
bottom: 60%;
}
.faceDiv {
width: 100%;
height: 500px;
border: 2px solid #0082da;
border-radius: 10px;
}
.faceInfor {
width: 100%;
height: 440px;
border: 2px solid #0082da;
border-radius: 10px;
background: linear-gradient(
to bottom right,
rgba(10, 80, 126, 0.8),
rgba(12, 35, 69, 1)
);
margin: 0 auto;
padding: 30px;
}
.faceBorder {
position: relative;
width: 100%;
height: 74.5%;
}
.faceImg {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 230px;
height: 200px;
background: url("~@/assets/images/timg.png") no-repeat;
background-size: 100% 100%;
z-index: 8;
}
.borderStyle {
width: 320px;
height: 240px;
border: 1px solid rgba(3, 169, 244, 0.2);
position: absolute;
overflow: hidden;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.borderStyle .line {
height: calc(100% - 2px);
width: 100%;
background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #03a9f4 211%);
border-bottom: 2px solid #03a9f4;
transform: translateY(-100%);
animation: radar-beam 2s infinite;
animation-timing-function: cubic-bezier(0.3, 0, 0.43, 0.7);
animation-delay: 1.4s;
}
@keyframes radar-beam {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
.borderStyle:after,
.borderStyle:before,
.borderStyle .bottom:after,
.borderStyle .bottom:before {
content: "";
display: block;
position: absolute;
width: 25px;
height: 35px;
border: 3px solid transparent;
}
.borderStyle:after,
.borderStyle:before {
top: 0;
border-top-color: #03a9f4;
}
.borderStyle .bottom:after,
.borderStyle .bottom:before {
bottom: 0;
border-bottom-color: #03a9f4;
}
.borderStyle:before,
.borderStyle .bottom:before {
left: 0;
border-left-color: #03a9f4;
}
.borderStyle:after,
.borderStyle .bottom:after {
right: 0;
border-right-color: #03a9f4;
}
.faceStuatsText {
width: 100%;
height: 25%;
display: flex;
justify-content: center;
align-items: center;
background-color: #0b2345;
opacity: 0.9;
border-top: 1px solid #0082da;
border-radius: 0 0 10px 10px;
font-size: 22px;
color: #fff;
letter-spacing: 2px;
}
.Infor1 {
color: #55d9fb;
font-size: 25px;
letter-spacing: 3px;
}
.Infor2 {
color: #fff;
font-size: 25px;
letter-spacing: 2px;
text-align: left;
}
video,
canvas {
width: 300px;
height: 230px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: -1;
}
#screenshotCanvas {
display: none;
}
</style>