浏览代码

优化实时人脸检测频率

Michael Wang 3 年之前
父节点
当前提交
f88fdf3432

+ 40 - 11
src/features/OnlineExam/Examing/FaceTracking.vue

@@ -6,6 +6,7 @@ import { onMounted, onUnmounted } from "vue";
 import { useTimers } from "@/setups/useTimers";
 import { store } from "@/store/store";
 import { throttle } from "lodash-es";
+import { MessageReactive } from "naive-ui";
 
 const { addTimeout, addInterval } = useTimers();
 // window.faceapi = faceapi;
@@ -141,9 +142,12 @@ async function detectTest() {
     return;
   }
 
+  let singleCost = 0;
   for (let idx = 0; idx < inputSizeList.length; idx++) {
     for (let n = 0; n < detectTimes; n++) {
-      await new Promise((resolve) => setTimeout(resolve, 3 * 1000));
+      await new Promise((resolve) =>
+        setTimeout(resolve, Math.min(singleCost * 2, 3 * 1000))
+      );
       if (store.exam.isDoingFaceLiveness) {
         logger({
           cnl: ["server", "console"],
@@ -168,16 +172,15 @@ async function detectTest() {
           new Promise((resolve) => setTimeout(resolve, 10 * 1000)),
         ]);
         const detectEndTime = performance.now();
-        if (detectStartTime - detectEndTime > 0.2 * 1000) {
+        singleCost = detectStartTime - detectEndTime;
+        if (singleCost > 0.2 * 1000) {
           disableFaceTracking = true;
           _hmt.push(["_trackEvent", "答题页面", "单次检测耗时过长:停止实时"]);
           logger({
             cnl: ["server"],
             key: "FaceTracking",
             act: "单次检测耗时过长:停止实时",
-            ext: {
-              cost: detectStartTime - detectEndTime,
-            },
+            ext: { cost: singleCost },
           });
           return;
         }
@@ -245,12 +248,17 @@ function getFaceDetectorOptions() {
   // return new faceapi.MtcnnOptions({ minFaceSize: 200, scaleFactor: 0.8 });
 }
 
+let displayedMsg: MessageReactive | null = null;
 const indepentExamingMsg = throttle(
-  () => $message.warning("请独立完成考试"),
+  () => (displayedMsg = $message.warning("请独立完成考试")),
   20 * 1000
 );
 const posureExamingMsg = throttle(
-  () => $message.warning("请调整坐姿,诚信考试"),
+  () => (displayedMsg = $message.warning("请调整坐姿,诚信考试")),
+  20 * 1000
+);
+const bgCheckExamingMsg = throttle(
+  () => $message.warning("请保持正确坐姿,确保脸部在摄像头内,背景无强光。"),
   20 * 1000
 );
 
@@ -296,7 +304,12 @@ async function detectFaces() {
   let result;
 
   try {
-    logger({ cnl: ["server"], key: "FaceTracking", act: "开始一次人脸检测" });
+    logger({
+      cnl: ["server"],
+      lvl: "debug",
+      key: "FaceTracking",
+      act: "开始一次人脸检测",
+    });
     result = await faceapi
       // .detectSingleFace(videoEl, options)
       .detectAllFaces(videoEl, options);
@@ -331,6 +344,7 @@ async function detectFaces() {
   const detectEndTime = performance.now();
   logger({
     cnl: ["server", "console"],
+    lvl: "debug",
     key: "FaceTracking",
     pgn: "实时人脸检测",
     act: "做完一次人脸检测,准备统计...",
@@ -387,6 +401,17 @@ async function detectFaces() {
     failTimes++;
   }
 
+  if (result.length === 1) {
+    try {
+      // 在失败一次后马上调整坐姿的话,会主动去除警告
+      displayedMsg?.destroy();
+    } catch (error) {
+      displayedMsg = null;
+      console.debug("ignore", error);
+    }
+    failTimes = 0;
+  }
+
   if (result.length !== 1 && !videoEl.classList.contains("video-warning")) {
     videoEl.classList.add("video-warning");
     addTimeout(() => videoEl.classList.remove("video-warning"), 3000);
@@ -399,14 +424,18 @@ async function detectFaces() {
     lvl: "debug",
     act: "准备下次人脸检测",
   });
+
+  const machineCap = 10 * multipleTimeUsage;
+  // 最小1秒,最大10秒
+  let desiredInterval = Math.min(10 * 1000, Math.max(1000, machineCap));
   detectFacesTimeout = addTimeout(async () => {
-    if (failTimes >= 5) {
-      $message.warning("请保持正确坐姿,确保脸部在摄像头内,背景无强光。");
+    if (failTimes >= 10) {
+      bgCheckExamingMsg();
       failTimes = 0;
       await detectTest();
     }
     await detectFaces();
-  }, 20 * 1000);
+  }, desiredInterval);
 }
 
 onUnmounted(() => {

+ 4 - 0
src/features/OnlineExam/Examing/setups/useFaceCompare.ts

@@ -5,6 +5,8 @@ import { showLogout } from "@/utils/utils";
 import axios, { Canceler } from "axios";
 import { onUnmounted, watch } from "vue";
 
+// 不管被请求多少次,interval只需要初始化一次
+let snapIntervalLock = false;
 /** 人脸后台Face++比对 */
 export function useFaceCompare() {
   const { addInterval, addTimeout } = useTimers();
@@ -19,6 +21,8 @@ export function useFaceCompare() {
     () => {
       if (!store.exam.faceCheckEnabled) return;
 
+      if (snapIntervalLock) return;
+      snapIntervalLock = true;
       initSnapInterval = addInterval(() => {
         logger({
           cnl: ["server"],

+ 4 - 0
src/features/OnlineExam/Examing/setups/useWXSocket.ts

@@ -65,6 +65,7 @@ function onAudioAnswer(event: MessageEvent<string>) {
   }
 }
 
+let wxSocketLock = false;
 export function useWXSocket() {
   const { startWS } = useWebSocket();
 
@@ -73,6 +74,9 @@ export function useWXSocket() {
     () => {
       if (!store.exam.WEIXIN_ANSWER_ENABLED) return;
 
+      if (wxSocketLock) return;
+      wxSocketLock = true;
+
       // init data
       store.exam.questionAnswerFileUrl = [];
       startWS(