Эх сурвалжийг харах

replace tracking.js to face-api.js

Michael Wang 6 жил өмнө
parent
commit
02b5dab297

+ 1 - 1
package.json

@@ -23,10 +23,10 @@
     "@chenfengyuan/vue-qrcode": "^1.0.0",
     "axios": "^0.18.0",
     "core-js": "^2.6.5",
+    "face-api.js": "^0.20.0",
     "iview": "^3.4.1",
     "js-md5": "^0.7.3",
     "moment": "^2.24.0",
-    "qm-tracking": "^1.0.0",
     "register-service-worker": "^1.6.2",
     "vue": "^2.6.10",
     "vue-router": "^3.0.6",

+ 110 - 74
src/features/OnlineExam/Examing/FaceTracking.vue

@@ -3,89 +3,50 @@
 </template>
 
 <script>
+import * as faceapi from "face-api.js";
+
+// models path
+const modelsPath = "/models/";
+
+function getFaceDetectorOptions() {
+  window.____hideMe =
+    window.____hideMe ||
+    new faceapi.TinyFaceDetectorOptions({
+      inputSize: 320,
+      scoreThreshold: 0.5,
+    });
+  return window.____hideMe;
+  // return new faceapi.SsdMobilenetv1Options({ minConfidence: 0.8 });
+  // return new faceapi.MtcnnOptions({ minFaceSize: 200, scaleFactor: 0.8 });
+}
+
+const detectTimeArray = [];
 export default {
   name: "FaceTracking",
+  async created() {
+    await faceapi.nets.tinyFaceDetector.load(modelsPath);
+    // faceapi.nets.faceRecognitionNet.load(modelsPath);
+    await faceapi.loadFaceLandmarkModel(modelsPath);
+    faceapi.tf.ENV.set("WEBGL_PACK", false);
+  },
   async mounted() {
-    const that = this;
-    var trackWarnings = 0;
-    var trackMultiplePerson = false;
-    // var trackerTask;
     let trackStarted = false;
+    const that = this;
 
-    function trackHead() {
-      var video = document.getElementById("video");
-      if (video && video.readyState === 4) {
+    async function trackHead() {
+      const video = document.getElementById("video");
+      if (
+        video &&
+        video.readyState === 4 &&
+        faceapi.nets.tinyFaceDetector.params
+      ) {
         trackStarted = true;
       } else {
         return;
       }
       console.log("start tracking ... ");
-      // var canvas = document.getElementById('canvas');
-      // var context = canvas.getContext('2d');
-
-      var tracker = new window.tracking.ObjectTracker("face");
-      tracker.setInitialScale(4);
-      tracker.setStepSize(2);
-      tracker.setEdgesDensity(0.1);
-
-      that.trackerTask = window.tracking.track("#video", tracker, {
-        camera: false,
-      });
-
-      tracker.on("track", function(event) {
-        // console.log("on track", event);
-        // console.log(trackWarnings, trackMultiplePerson);
-
-        if (event.data.length !== 1) {
-          trackWarnings++;
-          if (event.data.length > 1) {
-            trackMultiplePerson = true;
-          }
-
-          if (trackWarnings < 20) {
-            return;
-          }
-
-          if (!video.classList.contains("video-warning")) {
-            video.classList.add("video-warning");
-            setTimeout(function() {
-              video.classList.remove("video-warning");
-            }, 3000);
-          }
-
-          if (trackMultiplePerson) {
-            that.$Message.warning({
-              content: "请独立完成考试",
-              duration: 5,
-              closable: true,
-            });
-          } else {
-            that.$Message.warning({
-              content: "请调整坐姿,诚信考试",
-              duration: 5,
-              closable: true,
-            });
-          }
-          trackWarnings = 0;
-          trackMultiplePerson = false;
-        } else {
-          video.classList.remove("video-warning");
-          trackWarnings = 0;
-          trackMultiplePerson = false;
-        }
-        // context.clearRect(0, 0, canvas.width, canvas.height);
-
-        // event.data.forEach(function(rect) {
-        // 	context.strokeStyle = '#a64ceb';
-        // 	context.strokeRect(rect.x, rect.y, rect.width, rect.height);
-        // 	context.font = '11px Helvetica';
-        // 	context.fillStyle = "#fff";
-        // 	context.fillText('x: ' + rect.x + 'px', rect.x + rect.width + 5, rect.y + 11);
-        // 	context.fillText('y: ' + rect.y + 'px', rect.x + rect.width + 5, rect.y + 22);
-        // });
-      });
+      await that.detectFaces();
     }
-    // setTimeout(trackHead, 3000)
     this.trackHeadInterval = setInterval(() => {
       if (trackStarted) {
         clearInterval(this.trackHeadInterval);
@@ -93,11 +54,86 @@ export default {
         trackHead();
       }
     }, 1000);
-    // tracking.js
   },
   beforeDestroy() {
     clearInterval(this.trackHeadInterval);
-    this.trackerTask.stop();
+    clearTimeout(this.warningTimeout);
+    clearTimeout(this.detectFacesTimeout);
+  },
+  methods: {
+    async detectFaces() {
+      const detectStartTime = performance.now();
+      const videoEl = document.getElementById("video");
+      // this.___vWidth =
+      //   this.___vWidth ||
+      //   document.getElementById("video-container").clientWidth;
+
+      const options = getFaceDetectorOptions();
+      let result;
+
+      result = await faceapi
+        // .detectSingleFace(videoEl, options)
+        .detectAllFaces(videoEl, options);
+      // console.log(result);
+
+      const detectEndTime = performance.now();
+      // console.log("WebGL: ", faceapi.tf.ENV.get("WEBGL_PACK"));
+      console.log(
+        "WebGL: ",
+        faceapi.tf.ENV.get("WEBGL_PACK"),
+        " single detect time: ",
+        detectEndTime - detectStartTime
+      );
+
+      if (detectTimeArray.length < 31) {
+        // 仅捕获一部分检测次数
+        detectTimeArray.push(detectEndTime - detectStartTime);
+      }
+      if (detectTimeArray.length === 31) {
+        detectTimeArray.shift();
+        const avg =
+          detectTimeArray.reduce((a, b) => a + b, 0) / detectTimeArray.length;
+        const roundAvg = Math.round(avg / 10) * 10;
+        window._hmt.push([
+          "_trackEvent",
+          "正在考试页面",
+          "实时人脸检测平均时长",
+          roundAvg + "ms",
+        ]);
+        console.log(detectTimeArray);
+        detectTimeArray.push(0, 0); // 避免再次达到push条件和上传条件
+      }
+      if (result.length >= 2) {
+        this.$Message.warning({
+          content: "请独立完成考试",
+          duration: 5,
+          closable: true,
+        });
+      }
+
+      if (result.length === 0) {
+        this.findFirst = !this.findFirst; // 两次才告警
+        if (!this.findFirst) {
+          this.$Message.warning({
+            content: "请调整坐姿,诚信考试",
+            duration: 5,
+            closable: true,
+          });
+        }
+      }
+
+      if (
+        (!result || result.length !== 1) &&
+        !videoEl.classList.contains("video-warning")
+      ) {
+        videoEl.classList.add("video-warning");
+        this.warningTimeout = setTimeout(function() {
+          videoEl.classList.remove("video-warning");
+        }, 3000);
+      }
+
+      this.detectFacesTimeout = setTimeout(() => this.detectFaces(), 5000);
+    },
   },
 };
 </script>

+ 2 - 2
src/main.js

@@ -9,8 +9,8 @@ import "./styles/global.css";
 
 // import "./plugins/tracking.js";
 // import "./plugins/face.js";
-import "qm-tracking/tracking.js";
-import "qm-tracking/face.js";
+// import "qm-tracking/tracking.js";
+// import "qm-tracking/face.js";
 
 import axiosPlugin from "./utils/axios";
 import "./utils/monitors";

+ 52 - 5
yarn.lock

@@ -779,6 +779,16 @@
     error-stack-parser "^2.0.0"
     string-width "^2.0.0"
 
+"@tensorflow/tfjs-core@1.0.3":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@tensorflow/tfjs-core/-/tfjs-core-1.0.3.tgz#344f3fb1ed43c43b0e19e5e2764677770fd7a085"
+  integrity sha512-2UbjMQkmrykIIZuoRfmDPrtWm+6fdQRlYLCUJdiOIooeu/q4nye587HM1qKcdZosGPZTW6VvX+4VIVieYn5i0A==
+  dependencies:
+    "@types/seedrandom" "2.4.27"
+    "@types/webgl-ext" "0.0.30"
+    "@types/webgl2" "0.0.4"
+    seedrandom "2.4.3"
+
 "@types/babel__core@^7.1.0":
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.1.tgz#ce9a9e5d92b7031421e1d0d74ae59f572ba48be6"
@@ -866,6 +876,11 @@
   resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
   integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
 
+"@types/seedrandom@2.4.27":
+  version "2.4.27"
+  resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.27.tgz#9db563937dd86915f69092bc43259d2f48578e41"
+  integrity sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE=
+
 "@types/stack-utils@^1.0.1":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@@ -881,6 +896,16 @@
   resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
   integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
 
+"@types/webgl-ext@0.0.30":
+  version "0.0.30"
+  resolved "https://registry.yarnpkg.com/@types/webgl-ext/-/webgl-ext-0.0.30.tgz#0ce498c16a41a23d15289e0b844d945b25f0fb9d"
+  integrity sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg==
+
+"@types/webgl2@0.0.4":
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/@types/webgl2/-/webgl2-0.0.4.tgz#c3b0f9d6b465c66138e84e64cb3bdf8373c2c279"
+  integrity sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw==
+
 "@types/yargs@^12.0.9":
   version "12.0.12"
   resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916"
@@ -4209,6 +4234,15 @@ extsprintf@^1.2.0:
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
   integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
 
+face-api.js@^0.20.0:
+  version "0.20.0"
+  resolved "https://registry.yarnpkg.com/face-api.js/-/face-api.js-0.20.0.tgz#4956540c551080dab7de596857cefb3b34875305"
+  integrity sha512-9yDRChHdqT61AI+bGrNz5PJDHZl8+6CD4hoMChlteHAYInZIOEMhM6Wbn+2YcLVW+rUhufhanMQ9JjHV+x3ATg==
+  dependencies:
+    "@tensorflow/tfjs-core" "1.0.3"
+    tfjs-image-recognition-base "^0.6.0"
+    tslib "^1.9.3"
+
 fast-deep-equal@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
@@ -8437,11 +8471,6 @@ q@^1.1.2:
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
   integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
 
-qm-tracking@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/qm-tracking/-/qm-tracking-1.0.0.tgz#f4cd375f117a3aca54070d60400a4150d972838c"
-  integrity sha512-Gr7lztk8d36uphEkroi2eA7sHgFFf/UyBNDD6pOaWXBAt5P2QEhUZ5wn8jj0SnwXb/+cgdnhFzzUT3QUEm6PbA==
-
 qrcode@^1.3.0:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.3.3.tgz#5ef50c0c890cffa1897f452070f0f094936993de"
@@ -9060,6 +9089,11 @@ schema-utils@^1.0.0:
     ajv-errors "^1.0.0"
     ajv-keywords "^3.1.0"
 
+seedrandom@2.4.3:
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.3.tgz#2438504dad33917314bff18ac4d794f16d6aaecc"
+  integrity sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=
+
 select-hose@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -9836,6 +9870,14 @@ text-table@^0.2.0, text-table@~0.2.0:
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
   integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
 
+tfjs-image-recognition-base@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/tfjs-image-recognition-base/-/tfjs-image-recognition-base-0.6.0.tgz#3522dda57e18ff8988e1c277cc1240c00faca687"
+  integrity sha512-wFk3ivWjdQwsXgEfU1PdTf3smve2AbCjiwJKrq9lDGmKh75aL8UIy0bVNa15r+8sFaT4vJz/9AKOSI0w78wW0g==
+  dependencies:
+    "@tensorflow/tfjs-core" "1.0.3"
+    tslib "^1.9.3"
+
 thenify-all@^1.0.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
@@ -10028,6 +10070,11 @@ tslib@^1.9.0:
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
   integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
 
+tslib@^1.9.3:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
+  integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
+
 tty-browserify@0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"