FaceTracking.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <template>
  2. <div></div>
  3. </template>
  4. <script>
  5. import * as faceapi from "face-api.js";
  6. import { FACE_API_MODEL_PATH } from "@/constants/constants";
  7. import { mapGetters as globalMapGetters } from "vuex";
  8. window.faceapi = faceapi;
  9. const os = (function() {
  10. const ua = navigator.userAgent.toLowerCase();
  11. return {
  12. isWin2K: /windows nt 5.0/.test(ua),
  13. isXP: /windows nt 5.1/.test(ua),
  14. isVista: /windows nt 6.0/.test(ua),
  15. isWin7: /windows nt 6.1/.test(ua),
  16. isWin8: /windows nt 6.2/.test(ua),
  17. isWin81: /windows nt 6.3/.test(ua),
  18. isWin10: /windows nt 10.0/.test(ua),
  19. };
  20. })();
  21. let __cache4WebglAvailable = null;
  22. function webgl_available() {
  23. if (__cache4WebglAvailable) return __cache4WebglAvailable;
  24. var canvas = document.createElement("canvas");
  25. var gl = canvas.getContext("webgl");
  26. __cache4WebglAvailable = gl && gl instanceof WebGLRenderingContext;
  27. return __cache4WebglAvailable;
  28. }
  29. let __cache4TensorFlowWebPackStatus = null;
  30. function tensorFlowWebPackStatus() {
  31. if (__cache4TensorFlowWebPackStatus) return __cache4TensorFlowWebPackStatus;
  32. __cache4TensorFlowWebPackStatus = faceapi.tf.ENV.get("WEBGL_PACK");
  33. return __cache4TensorFlowWebPackStatus;
  34. }
  35. function getCPUModel() {
  36. if (typeof nodeRequire != "undefined") {
  37. var os = window.nodeRequire("os");
  38. const cpus = os.cpus();
  39. if (cpus.length > 0) {
  40. return cpus[0].model;
  41. }
  42. }
  43. return "null";
  44. }
  45. // if (os.isWin7) alert("是win7");
  46. // if (os.isWin10) alert("是win10");
  47. function getFaceDetectorOptions() {
  48. let inputSize = 320;
  49. if (os.isWin7) {
  50. inputSize = 256; // 在win7上无bug,速度快,效果较好
  51. } else if (os.isWin10) {
  52. inputSize = 320; // 在win10上,效果较好
  53. }
  54. window.____hideMe =
  55. window.____hideMe ||
  56. new faceapi.TinyFaceDetectorOptions({
  57. inputSize: inputSize,
  58. scoreThreshold: 0.5,
  59. });
  60. return window.____hideMe;
  61. // return new faceapi.SsdMobilenetv1Options({ minConfidence: 0.8 });
  62. // return new faceapi.MtcnnOptions({ minFaceSize: 200, scaleFactor: 0.8 });
  63. }
  64. const detectTimeArray = [];
  65. export default {
  66. name: "FaceTracking",
  67. computed: {
  68. ...globalMapGetters(["isEpcc"]),
  69. },
  70. async created() {
  71. await faceapi.nets.tinyFaceDetector.load(FACE_API_MODEL_PATH);
  72. // faceapi.nets.faceRecognitionNet.load(modelsPath);
  73. await faceapi.loadFaceLandmarkModel(FACE_API_MODEL_PATH);
  74. faceapi.tf.ENV.set("WEBGL_PACK", false);
  75. },
  76. async mounted() {
  77. let trackStarted = false;
  78. const that = this;
  79. async function trackHead() {
  80. const video = document.getElementById("video");
  81. if (
  82. video &&
  83. video.readyState === 4 &&
  84. faceapi.nets.tinyFaceDetector.params
  85. ) {
  86. trackStarted = true;
  87. } else {
  88. return;
  89. }
  90. console.log("start tracking ... ");
  91. await that.detectFaces();
  92. }
  93. if (!this.isEpcc) {
  94. // EPCC 关闭人脸实时检测
  95. this.trackHeadInterval = setInterval(() => {
  96. if (trackStarted) {
  97. clearInterval(this.trackHeadInterval);
  98. } else {
  99. trackHead();
  100. }
  101. }, 1000);
  102. }
  103. },
  104. beforeDestroy() {
  105. clearInterval(this.trackHeadInterval);
  106. clearTimeout(this.warningTimeout);
  107. clearTimeout(this.detectFacesTimeout);
  108. },
  109. methods: {
  110. async detectFaces() {
  111. this.singleTimeUsage = this.singleTimeUsage || 0;
  112. this.multipleTimeUsage = this.multipleTimeUsage || 0;
  113. if (
  114. this.singleTimeUsage > 10 * 1000 ||
  115. this.multipleTimeUsage > 0.5 * 1000
  116. ) {
  117. window._hmt.push([
  118. "_trackEvent",
  119. "正在考试页面",
  120. "关闭实时人脸检测,因为耗时过长",
  121. ]);
  122. return;
  123. }
  124. const detectStartTime = performance.now();
  125. const videoEl = document.getElementById("video");
  126. // this.___vWidth =
  127. // this.___vWidth ||
  128. // document.getElementById("video-container").clientWidth;
  129. const options = getFaceDetectorOptions();
  130. let result;
  131. try {
  132. result = await faceapi
  133. // .detectSingleFace(videoEl, options)
  134. .detectAllFaces(videoEl, options);
  135. } catch (e) {
  136. window._hmt.push(["_trackEvent", "正在考试页面", "实时人脸检测失败"]);
  137. throw e;
  138. }
  139. // console.log(result);
  140. const detectEndTime = performance.now();
  141. // console.log("WebGL: ", faceapi.tf.ENV.get("WEBGL_PACK"));
  142. console.log(
  143. "WebGL: ",
  144. webgl_available(),
  145. " WEBGL_PACK: ",
  146. tensorFlowWebPackStatus(),
  147. " single detect time: ",
  148. detectEndTime - detectStartTime,
  149. " result: ",
  150. result.length
  151. );
  152. this.singleTimeUsage = detectEndTime - detectStartTime;
  153. if (detectTimeArray.length < 6) {
  154. // 仅捕获一部分检测次数
  155. detectTimeArray.push(detectEndTime - detectStartTime);
  156. }
  157. if (detectTimeArray.length === 6) {
  158. detectTimeArray.shift();
  159. const avg =
  160. detectTimeArray.reduce((a, b) => a + b, 0) / detectTimeArray.length;
  161. const roundAvg = Math.round(avg / 100) * 100;
  162. window._hmt.push([
  163. "_trackEvent",
  164. "正在考试页面",
  165. "实时人脸检测平均时长",
  166. roundAvg + "ms",
  167. ]);
  168. console.log(detectTimeArray);
  169. detectTimeArray.push(0, 0); // 避免再次达到push条件和上传条件
  170. const roundAvg100 = Math.round(avg / 100) * 100;
  171. const osType = os.isWin7 ? "win7" : os.isWin10 ? "win10" : "other";
  172. const stats = `webgl: ${webgl_available()}; tf_backend: ${faceapi.tf.getBackend()}; os: ${osType}; cpu: ${getCPUModel()}`;
  173. window._hmt.push([
  174. "_trackEvent",
  175. "正在考试页面",
  176. "实时人脸检测统计" + roundAvg100 + "ms",
  177. stats,
  178. ]);
  179. this.multipleTimeUsage = roundAvg;
  180. }
  181. // init this.showWaringTime
  182. this.showWaringTime = this.showWaringTime || Date.now();
  183. if (result.length >= 2 && Date.now() - this.showWaringTime > 20 * 1000) {
  184. this.showWaringTime = Date.now();
  185. this.$Message.warning({
  186. content: "请独立完成考试",
  187. duration: 5,
  188. closable: true,
  189. });
  190. }
  191. if (result.length === 0 && Date.now() - this.showWaringTime > 20 * 1000) {
  192. this.showWaringTime = Date.now();
  193. this.$Message.warning({
  194. content: "请调整坐姿,诚信考试",
  195. duration: 5,
  196. closable: true,
  197. });
  198. }
  199. if (
  200. (!result || result.length !== 1) &&
  201. !videoEl.classList.contains("video-warning")
  202. ) {
  203. videoEl.classList.add("video-warning");
  204. this.warningTimeout = setTimeout(function() {
  205. videoEl.classList.remove("video-warning");
  206. }, 3000);
  207. }
  208. this.detectFacesTimeout = setTimeout(() => this.detectFaces(), 10 * 1000);
  209. },
  210. },
  211. };
  212. </script>
  213. <style>
  214. @keyframes warning-people {
  215. 0% {
  216. /* border: solid 5px white; */
  217. box-shadow: 0 0 20px white;
  218. }
  219. 100% {
  220. /* border: solid 5px red; */
  221. box-shadow: 0 0 20px gold;
  222. }
  223. }
  224. .video-warning {
  225. animation: warning-people 3s infinite;
  226. }
  227. </style>