实现的逻辑是先检测是否有人脸,然后再检测是否张嘴了,最后生成照片传给后端比对人脸数据。本人是在工作中的项目需要考勤,前端需要活体检测。想到了这个简单的方法,在纯前端实现这些逻辑,当然精度不高,想要更好的实现,还得靠后端使用好的检测模型。
首先下载好相关的tracking.js,face.js,face-api.js(这个可以直接npm install face-api.js),以及一些静态的资源放public/models下
html部分(提示文字、video、canvas),css部分就不展示了,样式另行设计:
<div class="content_video">
<div class="face" :class="{ borderColor: borderColor }">
<p class="tip" style="font-size: 24px; color: blue; text-align: center;">{{ tip }}</p>
<div class="face-meiti" id="container">
<video ref="video" preload autoplay loop muted playsinline :webkit-playsinline="true"></video>
<canvas ref="canvas"> </canvas>
</div>
</div>
</div>
人脸识别部分:人脸识别使用 tracking.js和face.js,检测到人脸后即可进入面部动作识别区域
const borderColor=ref(false)
// import { useRouter } from "vue-router";
const show = ref(false);
const isIndex = ref(true);
const isPhoto = ref(false);
const videoEl = ref();
const localStream = ref();
const timer = ref();
const options = ref([]);
const showPicker = ref(false);
const form = reactive({
workerName: "",
programName: "",
programId: "",
});
const vedioOpen = ref(true)
const reJiance = ref(false)
const state = reactive({
options: '',
nose_x_anv: "",
nose_y_anv: "",
mouth_Y: '',
action: "张嘴",
isCheck: true,
})
//人脸检测
const tip = ref("请正对摄像头");
const mediaStreamTrack = ref();
const video = ref(); //播放器实例
const trackerTask = ref(); //tracking实例
const uploadLock = true; //上传锁
const faceflag = ref(false); //是否进行拍照
const src = ref();
const getUserMedia = ref("");
const canvas = ref();
const LorR = computed(() =>
store.state.UserInfo.workerName != undefined ? true : false
);
const selectPeople = computed(() => store.state.SalarySelectPeople);
const store = useStore();
//人脸检测区域
const init = async () => {
toast("正在申请摄像头权限", "info")
vedioOpen.value = false;
initTracker(canvas.value);
};
const initTracker = async () => {
const _this = this;
// 固定写法
let tracker = new window.tracking.ObjectTracker("face");
tracker.setInitialScale(4);
tracker.setStepSize(2);
tracker.setEdgesDensity(0.1);
//摄像头初始化
trackerTask.value = window.tracking.track(video.value, tracker, {
camera: true,
});
tracker.on("track", async (event) => {
if (event.data.length == 0) {
if (!faceflag.value) {
tip.value = "未检测到人脸";
}
} else if (event.data.length > 0) {
console.log(event.data.length);
event.data.forEach(async (rect, index) => {
if (index != 0) {
return;
}
// 防抖
if (!faceflag.value) {
faceflag.value = true;
tip.value = "请张张嘴";
//背景更改,提示作用
borderColor.value=true
//进入人脸动作检测方法
initHuoti(rect)
}
});
}
});
};
面部动作识别部分:
const jiance = ref();
const initHuoti = async (rect) => {
await faceApi.nets.tinyFaceDetector.loadFromUri('/models');
await faceApi.loadFaceLandmarkModel("/models");
state.options = new faceApi.TinyFaceDetectorOptions({
inputSize: 320,
scoreThreshold: 0.4
});
//设定定时器,重复获取当前摄像头的图片
jiance.value = setInterval(() => {
let context = canvas.value.getContext("2d", { willReadFrequently: true });
context.strokeStyle = "#a64ceb";
context.strokeRect(rect.x, rect.y, rect.width, rect.height);
canvas.value.width = video.value.videoWidth;
canvas.value.height = video.value.videoHeight;
context.drawImage(video.value, 0, 0, canvas.value.width, canvas.value.height);
let base64Img = canvas.value.toDataURL("image/jpeg");
face_test(base64Img)
}, 400)
}
//使用faceapi检测人脸图片的特征值
const face_test = async (base64Img) => {
const detections1 = await faceApi.detectSingleFace(canvas.value, state.options).withFaceLandmarks();
if (detections1) {
const landmarks = detections1.landmarks
const jawOutline = landmarks.getJawOutline()
const nose = landmarks.getNose()
const mouth = landmarks.getMouth()
const leftEye = landmarks.getLeftEye()
const rightEye = landmarks.getRightEye()
const leftEyeBbrow = landmarks.getLeftEyeBrow()
const rightEyeBrow = landmarks.getRightEyeBrow()
//将特征值给这个方法检测是否张嘴了
isOpenMouth(mouth, base64Img)
const resizedDetections = faceApi.resizeResults(detections1, { width: 280, height: 280 })
faceApi.draw.drawDetections(canvas.value, resizedDetections)
}
}
//是否张嘴,精度取决于其中的检测方法
const isOpenMouth = (mouth, base64Img) => {
const mouth_Y_list = mouth.map((item) => {
return item.y
})
const max = Math.max(...mouth_Y_list)
const min = Math.min(...mouth_Y_list)
const _y = max - min
if (state.mouth_Y === "") {
state.mouth_Y = _y
}
if (Math.abs(state.mouth_Y - _y) > 10) {
clearTimeout(jiance.value);
toast('检测成功,正在拍照', 'info', 1000);
tip.value = "正在拍照,请勿乱动";
borderColor.value=false
//上传照片
uploadimg(base64Img);
}
state.mouth_Y = _y
}
更好的获取摄像头方法,在tracking.js中做修改tracking.initUserMedia_方法,使用window.stream = MediaStream;存储相关流,方便后面页面关闭。
function getMediaDevices() {
try {
navigator.mediaDevices.enumerateDevices().then(function (devices) {
devices.forEach(function (device) {
switch (device?.kind) {
case 'audioinput':
console.log('音频输入设备(麦克风|话筒):', device);
break;
case 'audiooutput':
console.log('音频输出设备(扬声器|音响):', device);
break;
case 'videoinput':
console.log('视频输入设备(摄像头|相机):', device);
break;
default:
console.log('当前可用的媒体设备: ', device);
break;
}
});
}).catch(function (err) {
console.error(err);
});
} catch (err) {
console.error(err);
} finally {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
console.log("不支持mediaDevices.enumerateDevices(), 未识别到多媒体设备!");
}
}
};
tracking.initUserMedia_ = function(element, opt_options) {
getMediaDevices();
try {
var options = {
audio: true, // 注:这里 audio、video 默认都为false【一定要确保有麦克风或摄像头(有啥设备就开启啥设备)硬件设备的情况下才设为true 或 {...},否则会报DOMException: Requested device not found 等错!!】
video: true, // 获取视频 默认video: { facingMode: 'user' } }前置相机
}
if (navigator.mediaDevices.getUserMedia) {
// 访问用户媒体设备的 新标准API
navigator.mediaDevices.getUserMedia(options).then(function (MediaStream) {
element.srcObject=MediaStream
window.stream = MediaStream;
}).catch(function (err) {
console.error("访问用户媒体设备失败:权限被拒绝 或 未识别到多媒体设备!", err);
}).finally(() => {
console.log('navigator.mediaDevices.getUserMedia API')
});
}
else if (navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia) {
try {
//访问用户媒体设备的 旧标准API 兼容方法
navigator.getUserMedia(options, function (MediaStream) {
element.srcObject=MediaStream
window.stream = MediaStream;
}, function (err) {
console.error("访问用户媒体设备失败:权限被拒绝 或 未识别到多媒体设备!", err);
})
} catch (error) {
console.error(err);
}
finally {
console.log('navigator.getUserMedia API')
};
} else {
if (0 > location.origin.indexOf('https://')) {
console.error("提示:请尝试在本地localhost域名 或 https协议 的Web服务器环境中重新运行!");
}
}
} catch (error) {
console.error("访问用户媒体设备失败:", error);
}
};
//在使用页面关闭摄像头的方法
const stopMediaStreamTrack = function () {
if (typeof window.stream === "object") {
window.stream.getTracks().forEach(track => track.stop());
}
clearTimeout(jiance.value);
}