12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196 |
- <template>
- <div class="page-container">
- <div id="video-container" style="position: relative" class="page-container">
- <div v-if="shouldShowSections" class="instruction-tips above-video">
- <div
- class="instruction-animation"
- :style="{
- width: '33.3%',
- 'font-size': 16,
- padding: '0 10px',
- 'margin-left': reverseSection * 33 + '%',
- }"
- data-intro="停留的时间:每一次停留时长可能不一样。"
- >
- 请将脸部移入此区域,停留<span style="color: blue">{{
- currentStep.stay
- }}</span
- >秒,并保持<span style="color: blue">{{
- shouldDetectExpression ? (currentStep.happy ? "笑容" : "严肃") : ""
- }}</span>
- </div>
- </div>
- <div class="instruction-tips above-video">
- <div
- class="instruction-animation"
- :style="{
- width: '100%',
- 'font-size': '18px',
- 'text-align': 'center',
- }"
- >
- <div v-if="currentStep.action === 'FACE_COMPARE'">
- 请调整脸部与摄像头的距离
- </div>
- <div v-else>
- 保持<span style="color: blue">{{
- shouldDetectExpression
- ? currentStep.happy
- ? "笑容"
- : "严肃"
- : ""
- }}</span>
- <Progress hide-info :percent="stepProgress" />
- </div>
- </div>
- </div>
- <div
- v-if="isDetecting"
- class="instruction-total above-video"
- style="z-index: 3"
- >
- <div class="total-text" data-intro="请在规定的时间内完成。">
- {{ instructions.total }}
- </div>
- </div>
- <div v-if="shouldShowSections" class="seperators above-video">
- <div class="line"></div>
- <div class="line"></div>
- </div>
- <!-- <div v-if="!behaving" class="blocks above-video">
- <div class="block-index">1</div>
- <div class="block-index">2</div>
- <div class="block-index">3</div>
- </div> -->
- <div v-if="shouldShowSections" class="blocks above-video">
- <div
- v-for="item in [3, 2, 1]"
- :key="item"
- :class="[
- 'block-index-size',
- currentStep.section !== item && 'block-index-blur',
- ]"
- ></div>
- </div>
- <div
- v-if="shouldShowSections"
- :class="[
- 'above-video',
- 'instruction-face',
- 'instruction-animation',
- behaving && 'instruction-face-animation-state',
- ]"
- :style="{
- width: '33.3%',
- 'margin-left': reverseSection * 33 + '%',
- }"
- data-intro="停留的位置:请将脸部停留在头像所处的列。检测成功,头像会停止抖动。"
- >
- <!-- <el-progress
- type="circle"
- :stroke-width="12"
- :show-text="false"
- :percentage="stepProgress"
- style="margin-top: -13px;"
- class="above-video"
- ></el-progress> -->
- <!-- <div
- style="margin: 0 auto; border-radius: 50%; margin-top: -50px; width: 200px; height: 200px; border: 10px solid black;"
- ></div> -->
- </div>
- <div
- v-if="behaving"
- :class="[
- 'above-video',
- 'instruction-face',
- 'instruction-animation',
- behaving && 'instruction-face-animation-state',
- ]"
- :style="{
- width: '33.3%',
- 'margin-left': 1 * 33 + '%',
- }"
- >
- <!-- <el-progress
- type="circle"
- :stroke-width="12"
- :show-text="false"
- :percentage="stepProgress"
- style="margin-top: -13px;"
- class="above-video"
- ></el-progress> -->
- </div>
- <video
- id="inputVideo"
- class="detect-video"
- style="transform: scaleX(-1);"
- autoplay
- muted
- @loadedmetadata="onPlay"
- ></video>
- <canvas id="overlay" class="above-video" />
- </div>
- <div style="position: absolute; top: 0; left: 0; display:none;">
- <img
- id="base-photo"
- src="/student_base_photo/0/6/1560392244118.jpg"
- style="width: 150px;"
- />
- </div>
- </div>
- </template>
- <script>
- import * as faceapi from "face-api.js";
- import MD5 from "js-md5";
- // import introJs from "intro.js";
- import throttle from "lodash-es/throttle";
- // models path
- const modelsPath = "/models/20190620/";
- window.faceapi = faceapi;
- // let withBoxes = true;
- const os = (function() {
- const ua = navigator.userAgent.toLowerCase();
- return {
- isWin2K: /windows nt 5.0/.test(ua),
- isXP: /windows nt 5.1/.test(ua),
- isVista: /windows nt 6.0/.test(ua),
- isWin7: /windows nt 6.1/.test(ua),
- isWin8: /windows nt 6.2/.test(ua),
- isWin81: /windows nt 6.3/.test(ua),
- isWin10: /windows nt 10.0/.test(ua),
- };
- })();
- // tiny_face_detector options
- function getFaceDetectorOptions() {
- let inputSize = 160;
- if (os.isWin7) {
- inputSize = 256; // 在win7上无bug,速度快,效果较好
- } else if (os.isWin10) {
- inputSize = 320; // 在win10上,效果较好
- }
- window.____hideMe =
- window.____hideMe ||
- new faceapi.TinyFaceDetectorOptions({
- inputSize, // 这行是解决Box.constructor - expected box to be IBoundingBox | IRect, instead have 问题的关键
- scoreThreshold: 0.5,
- });
- return window.____hideMe;
- // return new faceapi.SsdMobilenetv1Options({ minConfidence: 0.8 });
- // return new faceapi.MtcnnOptions({ minFaceSize: 200, scaleFactor: 0.8 });
- }
- export default {
- name: "FaceMotion",
- data() {
- return {
- isDetecting: false,
- shoudAdjustDistance: true,
- introStarted: false,
- asked: false,
- instructions: {
- total: 60,
- steps: [
- { section: 2, stay: 3, happy: true, finished: false },
- { section: 3, stay: 5, happy: true, finished: false },
- { section: 2, stay: 4, happy: true, finished: false },
- ],
- },
- behaving: false,
- behavingStartDate: null,
- behavingTimestampe: null,
- shouldDetectExpression: null,
- pauseDetecting: false,
- compareResult: null,
- finalResult: {
- examRecordDataId: 0,
- faceBiopsyItemId: 0,
- verifySteps: [
- {
- action: "FACE_COMPARE",
- errorMsg: "string",
- resourceType: "PIC",
- resourceUrl: "string",
- result: true,
- resultJson: "string",
- stay: 0,
- stepId: 0,
- },
- ],
- },
- };
- },
- computed: {
- shouldShowSections() {
- if (this.currentStep.section === 0) return false;
- if (this.introStarted) return true;
- if (this.isDetecting && this.shoudAdjustDistance) return false;
- return this.isDetecting;
- },
- currentStep() {
- return this.instructions.steps.find(v => !v.finished) || {};
- },
- corFinalResult() {
- const idx = this.finalResult.verifySteps.findIndex(
- v => v.stepId === this.currentStep.stepId
- );
- return this.finalResult.verifySteps[idx];
- },
- instructionsFinished() {
- return this.instructions.steps.every(v => v.finished);
- },
- reverseSection() {
- return [3, 2, 1][this.currentStep.section - 1] - 1;
- },
- stepProgress() {
- if (this.instructionsFinished) return 0;
- if (!this.behaving) return 100;
- let progress =
- 100 -
- (100 * (this.behavingTimestampe - this.behavingStartDate)) /
- (this.currentStep.stay * 1000);
- // console.log("progress: ", progress);
- if (progress > 100) {
- progress = 100;
- } else if (progress < 0) {
- progress = 0;
- }
- return progress;
- },
- },
- watch: {
- instructionsFinished(finished) {
- if (finished) {
- clearInterval(this.remainInteval);
- this.isDetecting = false;
- // this.$message({
- // message: "恭喜你,活体检测通过",
- // type: "success",
- // });
- this.$Message.success({
- content: "活体检测通过",
- duration: 5,
- });
- // this.resetTest();
- this.closeMe();
- }
- },
- "instructions.total"(total) {
- if (total <= 0) {
- console.log(this.corFinalResult);
- this.corFinalResult.timeout = true;
- this.corFinalResult.result = false;
- this.corFinalResult.errorMsg = "超时!活体检测失败!";
- this.failedTest("超时!活体检测失败!");
- }
- },
- },
- async created() {
- // console.log(faceapi);
- // console.log(faceapi.nets.tinyFaceDetector);
- this.$Spin.show({});
- await this.fetchData();
- this.resetTest();
- await faceapi.nets.tinyFaceDetector.load(modelsPath);
- await faceapi.loadFaceLandmarkModel(modelsPath);
- await faceapi.nets.faceExpressionNet.load(modelsPath);
- // await faceapi.nets.ssdMobilenetv1.load(modelsPath);
- // await faceapi.nets.faceRecognitionNet.load(modelsPath);
- faceapi.tf.ENV.set("WEBGL_PACK", false);
- // faceapi.nets.mtcnn.load(modelsPath);
- },
- mounted() {
- this.run();
- },
- methods: {
- async fetchData() {
- const examRecordDataId = this.$route.params.examRecordDataId;
- // FIXME: 失败了再取?
- const faceBiopsyInfoData = await this.$http.get(
- "/api/ecs_oe_student/faceBiopsy/getFaceBiopsyInfo?examRecordDataId=" +
- examRecordDataId
- );
- const faceBiopsyInfo = faceBiopsyInfoData.data;
- console.log(faceBiopsyInfo);
- this.faceBiopsyInfo = faceBiopsyInfo;
- },
- async closeMe() {
- const faceLiveResultData = await this.$http.post(
- "/api/ecs_oe_student/faceBiopsy/saveFaceBiopsyResult",
- this.finalResult
- );
- const faceLiveResult = faceLiveResultData.data;
- console.log(faceLiveResult);
- this.$emit("closeFaceMotion", faceLiveResult);
- },
- resetTest() {
- // this.isDetecting = true;
- // this.asked = false;
- this.shoudAdjustDistance = true;
- this.behavingStartDate = null;
- this.happyFailedTimes = 0;
- this.singleFaceFailedTimes = 0;
- const steps = this.faceBiopsyInfo.verifySteps
- // .filter(s => ["HAPPY", "SERIOUS"].includes(s.action))
- .map(s => {
- return {
- section: 0,
- stay: s.stay,
- happy: s.action === "HAPPY",
- finished: false,
- stepId: s.stepId,
- action: s.action,
- };
- });
- this.instructions = {
- total: 60,
- steps,
- };
- console.log(this.instructions);
- this.shouldDetectExpression = true;
- // this.shouldDoFaceRecognition = true;
- const examRecordDataId = this.$route.params.examRecordDataId;
- this.finalResult = {
- examRecordDataId,
- faceBiopsyItemId: this.faceBiopsyInfo.faceBiopsyItemId,
- verifySteps: this.faceBiopsyInfo.verifySteps,
- };
- },
- async run() {
- // load face detection and face landmark models
- // await changeFaceDetector(TINY_FACE_DETECTOR)
- // changeInputSize(224);
- // try to access users webcam and stream the images
- // to the video element
- const stream = await navigator.mediaDevices.getUserMedia({
- audio: false,
- // video: {},
- video: {
- // width: { min: "100vw" },
- // height: { min: "100vh" },
- width: 640,
- height: 480,
- frameRate: 15,
- // resizeMode: "crop-and-scale",
- },
- });
- // console.log(
- // "video stream settings",
- // stream.getVideoTracks()[0].getSettings()
- // );
- // console.log(
- // "video stream constraints",
- // stream.getVideoTracks()[0].getConstraints()
- // );
- // console.log(
- // "video stream capabilities",
- // stream.getVideoTracks()[0].getCapabilities()
- // );
- const videoEl = document.getElementById("inputVideo");
- videoEl.srcObject = stream;
- },
- async intro() {
- // this.$message({
- // type: "info",
- // message: "开始活体检测",
- // duration: 1500,
- // });
- this.$Message.info({ content: "开始活体检测", duration: 2 });
- // let loading;
- // loading = this.$Spin.show({});
- this.$Spin.show({});
- // this.isDetecting = true;
- this.behavingStartDate = null;
- // this.introStarted = true;
- // this.$nextTick(() => {
- // const io = introJs()
- // .setOptions({
- // nextLabel: "下一步",
- // prevLabel: "上一步",
- // skipLabel: "跳过",
- // doneLabel: "完成",
- // })
- // .start();
- // // loading.hide();
- // this.$Spin.hide();
- // const realStart = () => {
- // this.isDetecting = true;
- // clearInterval(this.remainInteval);
- // this.remainInteval = setInterval(() => {
- // this.instructions.total--;
- // if (this.instructions.total <= 0) {
- // clearInterval(this.remainInteval);
- // }
- // }, 1000);
- // this.onPlay();
- // };
- // io.onbeforeexit(() => {
- // // loading = this.$Spin.show({});
- // this.$Spin.show({});
- // });
- // io.onexit(() => {
- // setTimeout(() => {
- // realStart();
- // this.introStarted = false;
- // // console.log("exit intro");
- // // loading.hide();
- // this.$Spin.hide();
- // }, 300);
- // });
- // });
- const realStart = () => {
- this.isDetecting = true;
- clearInterval(this.remainInteval);
- this.remainInteval = setInterval(() => {
- this.instructions.total--;
- if (this.instructions.total <= 0) {
- clearInterval(this.remainInteval);
- }
- }, 1000);
- this.onPlay();
- };
- realStart();
- },
- async onPlay() {
- if (!this.doneCompare && this.pauseDetecting) {
- console.log({ pauseDetecting: this.pauseDetecting });
- return;
- }
- if (!this.asked) {
- this.asked = true;
- await this.increaseTestSpeed();
- // await new Promise(resolve => setTimeout(resolve, 3000));
- await this.intro();
- // this.$confirm("开始活体检测?", "确认开始")
- // .then(async () => {
- // })
- // .catch(() => {
- // this.$message({
- // type: "info",
- // message: "刷新可重新选择",
- // });
- // });
- }
- if (!this.isDetecting) return;
- const detectStartTime = performance.now();
- const videoEl = document.getElementById("inputVideo");
- this.___vWidth =
- this.___vWidth ||
- document.getElementById("video-container").clientWidth;
- const options = getFaceDetectorOptions();
- let result;
- /**
- * tiny
- * 无表情,无landmarks,60~70ms
- * 有表情,增加20~30ms
- * 有landmarks,增加20~30ms
- *
- * ssdMobilenetv1
- * 无表情,无landmarks,130ms
- * 有表情,增加30~40ms
- * 有landmarks,增加20~30ms
- *
- * mtcnn
- * 无表情,无landmarks,200ms 每次差异很大
- * 有表情,增加30~40ms
- * 有landmarks,增加20~30ms
- *
- */
- if (this.shouldDetectExpression) {
- // const canvas2 = faceapi.createCanvasFromMedia(videoEl);
- result = await faceapi
- // .detectSingleFace(videoEl, options)
- // .detectAllFaces(canvas2, options)
- .detectAllFaces(videoEl, options)
- .withFaceLandmarks()
- .withFaceExpressions();
- // if (result.length === 0) {
- // document.body.appendChild(canvas2);
- // }
- } else {
- result = await faceapi
- // .detectSingleFace(videoEl, options)
- .detectAllFaces(videoEl, options)
- .withFaceLandmarks();
- }
- // console.log(result);
- if (result && result.length >= 2) {
- this.corFinalResult.result = false;
- this.corFinalResult.errorMsg = "检测到多张人脸!活体检测失败!";
- this.failedTest("检测到多张人脸!活体检测失败!");
- }
- if (result && result.length === 0) {
- if (!this.shoudAdjustDistance) {
- // 只有不是调整人脸距离的时候增加失败次数
- this.singleFaceFailedTimes++;
- }
- if (this.singleFaceFailedTimes >= 5) {
- this.corFinalResult.result = false;
- this.corFinalResult.errorMsg =
- "活检过程中没有检测到人脸!活体检测失败!";
- this.failedTest("活检过程中没有检测到人脸!活体检测失败!");
- }
- }
- // 人脸比对 - 开始
- {
- if (
- this.shouldDoFaceRecognition &&
- faceapi.nets.ssdMobilenetv1.params &&
- faceapi.nets.faceRecognitionNet.params
- ) {
- const personFromVideo = await faceapi
- // .detectSingleFace(videoEl, options)
- .detectSingleFace(videoEl, options)
- .withFaceLandmarks()
- .withFaceDescriptor();
- const personFromBasePhoto = await faceapi
- .detectSingleFace(document.getElementById("base-photo"))
- .withFaceLandmarks()
- .withFaceDescriptor();
- if (personFromVideo && personFromBasePhoto) {
- // create FaceMatcher with automatically assigned labels
- // from the detection results for the reference image
- const faceMatcher = new faceapi.FaceMatcher(personFromBasePhoto);
- const bestMatch = faceMatcher.findBestMatch(
- personFromVideo.descriptor
- );
- if (bestMatch.distance > 0.8) {
- console.log("%c肯定不是王章军", "color: red");
- }
- if (bestMatch.distance >= 0.4 && bestMatch.distance <= 0.8) {
- console.log("有可能是王章军");
- }
- if (bestMatch.distance < 0.4) {
- console.log("%c肯定是王章军", "color: green");
- }
- console.log(bestMatch.toString());
- }
- }
- }
- // 人脸比对 - 结束
- if (result && result[0]) {
- result = result[0];
- // Object.entries(result.expressions).forEach(([key, value]) => {
- // if (value > 0.5) {
- // console.log(key, value);
- // }
- // });
- // console.log(Object.entries(result.expressions));
- // console.log(".......");
- // const canvasStartTime = performance.now();
- const canvas = document.getElementById("overlay");
- const dims = faceapi.matchDimensions(canvas, videoEl, true);
- const resizedResult = faceapi.resizeResults(result, dims);
- // const canvasEndTime = performance.now();
- // console.log(" canvas time: ", canvasEndTime - canvasStartTime);
- // console.log(resizedResult);
- // console.log(resizedResult.detection.box.top);
- // console.log(resizedResult.detection.box.left);
- let box;
- if (this.shouldDetectExpression || resizedResult.detection) {
- // 在检测表情时有detection属性,没有box属性。没有检测landmarks
- box = resizedResult.detection.box;
- } else {
- box = resizedResult.box;
- }
- // console.log(box.area);
- if (box.area > 60000 || box.area < 20000) {
- const message = box.area > 60000 ? "请远离摄像头" : "请靠近摄像头";
- this.tipHandler =
- this.tipHandler ||
- throttle(message => {
- // this.$message({
- // type: "warning",
- // message,
- // duration: 1000,
- // offset: 300,
- // });
- this.$Message.warning({ content: message, duration: 1 });
- }, 1500);
- this.tipHandler(message);
- if (this.shoudAdjustDistance) {
- setTimeout(() => this.onPlay(), 300);
- return;
- }
- } else {
- this.shoudAdjustDistance = false;
- if (this.currentStep.action === "FACE_COMPARE") {
- console.log("该做同步人脸比对了");
- // 同步人脸比对
- try {
- if (this.pauseDetecting || this.doneCompare) return;
- this.pauseDetecting = true;
- const cs = await this.snap();
- console.log(cs);
- if (!cs) return;
- this.compareResult = cs;
- // 后台的计算需要通过resultJson来判断: isPass isStranger existsSystemError
- this.finalResult.verifySteps[0].result = this.compareResult.isPass;
- this.finalResult.verifySteps[0].resourceType = "PIC";
- this.finalResult.verifySteps[0].resourceUrl = this.compareResult.fileUrl;
- this.finalResult.verifySteps[0].resultJson = this.compareResult.faceCompareResult;
- this.finalResult.verifySteps[0].errorMsg = this.compareResult.errorMsg;
- this.pauseDetecting = false;
- this.doneCompare = true;
- if (this.compareResult.isPass) {
- console.log("人脸同步比对成功");
- if (!this.instructionsFinished && this.currentStep)
- this.currentStep.finished = true;
- } else {
- this.failedTest("人脸同步比对失败");
- return;
- }
- } catch (error) {
- console.log(error);
- } finally {
- // this.pauseDetecting = false;
- this.videoStartPlay();
- }
- }
- }
- // 区域左边的一半
- const centerPoint = box.left + (box.right - box.left) / 2;
- if (
- (centerPoint >
- this.___vWidth * ((this.currentStep.section - 1) / 3) &&
- centerPoint < this.___vWidth * (this.currentStep.section / 3)) ||
- this.currentStep.section === 0
- ) {
- if (this.behavingStartDate === null) {
- // 到指定区块后才开始检测表情
- if (this.shouldDetectExpression) {
- if (
- (result.expressions.happy < 0.5 && this.currentStep.happy) ||
- (result.expressions.neutral < 0.5 && !this.currentStep.happy)
- ) {
- // this.$message({
- // type: "warning",
- // message: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
- // duration: 1000,
- // offset: 300,
- // });
- this.$Message.warning({
- content: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
- duration: 1,
- });
- setTimeout(() => this.onPlay(), 1000);
- return;
- }
- }
- this.behavingStartDate = new Date();
- }
- this.behaving = true;
- this.behavingTimestampe = Date.now();
- } else {
- this.behaving = false;
- this.behavingStartDate = null;
- if (!this.moveFaceMessage) {
- // this.moveFaceMessage = this.$message({
- // type: "info",
- // message: "请将您的脸部移向笑脸所在的区块",
- // duration: 3000,
- // offset: 300,
- // });
- this.moveFaceMessage = this.$Message.info({
- content: "请将您的脸部移向笑脸所在的区块",
- duration: 1,
- });
- setTimeout(() => {
- this.moveFaceMessage = null;
- }, 3000);
- }
- }
- const detectEndTime = performance.now();
- console.log("single detect time: ", detectEndTime - detectStartTime);
- if (this.shouldDetectExpression && this.behavingStartDate) {
- // 到指定区块后才开始检测表情
- if (result.expressions.happy < 0.5 && this.currentStep.happy) {
- this.happyFailedTimes++;
- if (this.happyFailedTimes % 2) {
- if (Date.now() - (this.showExpresionTipDate || 0) > 1500) {
- this.showExpresionTipDate = Date.now();
- // this.$message({
- // type: "warning",
- // message: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
- // duration: 1000,
- // offset: 300,
- // });
- this.$Message.warning({
- content: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
- duration: 1,
- });
- }
- }
- }
- // if(result.expressions.happy >= 0.5 && this.currentStep.happy) {
- // this.happyFailedTimes = 0; // 恢复的话,容易降低恶意用户攻击难度
- // }
- if (result.expressions.neutral < 0.5 && !this.currentStep.happy) {
- this.happyFailedTimes++;
- if (this.happyFailedTimes % 2) {
- if (Date.now() - (this.showExpresionTipDate || 0) > 1500) {
- this.showExpresionTipDate = Date.now();
- // this.$message({
- // type: "warning",
- // message: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
- // duration: 1000,
- // offset: 300,
- // });
- this.$Message.warning({
- content: this.currentStep.happy ? "请保持微笑" : "请保持严肃",
- duration: 1,
- });
- }
- }
- }
- if (this.happyFailedTimes >= 6) {
- this.corFinalResult.result = false;
- this.corFinalResult.errorMsg = "指定表情失败!活体检测失败!";
- this.failedTest("指定表情失败!活体检测失败!");
- }
- }
- const stayMoreForProgress = 500; // wait for progress reach 0/100
- if (
- this.behaving &&
- Date.now() - this.behavingStartDate - stayMoreForProgress >
- this.currentStep.stay * 1000
- ) {
- console.log("通过section" + this.currentStep.section);
- this.behaving = false;
- this.happyFailedTimes = 0;
- this.behavingStartDate = null;
- if (!this.instructionsFinished && this.currentStep) {
- this.corFinalResult.result = true;
- this.currentStep.finished = true;
- }
- }
- // console.log(resizedResult.alignedRect.relativeBox.y);
- // if (true) {
- // faceapi.draw.drawDetections(canvas, resizedResult);
- // }
- // faceapi.draw.drawFaceLandmarks(canvas, resizedResult);
- }
- setTimeout(() => this.onPlay(), 300);
- },
- failedTest(msg) {
- clearInterval(this.remainInteval);
- this.isDetecting = false;
- if (!this.instructionsFinished) {
- // this.$message({
- // message: msg || "活体检测失败",
- // type: "error",
- // });
- this.$Message.error({ content: msg || "活体检测失败", duration: 5 });
- }
- // this.resetTest();
- this.closeMe();
- },
- async increaseTestSpeed() {
- if (!this.__inThisMethodOnce) {
- this.__inThisMethodOnce = true;
- } else {
- return;
- }
- const videoEl = document.getElementById("inputVideo");
- const options = getFaceDetectorOptions();
- console.log("increaseTestSpeed ---");
- await new Promise(resolve => {
- const interval = setInterval(() => {
- if (
- videoEl.readyState === 4 &&
- faceapi.nets.tinyFaceDetector.params &&
- faceapi.nets.faceExpressionNet.params
- // !faceapi.nets.ssdMobilenetv1.params
- // !faceapi.nets.mtcnn.params
- ) {
- resolve();
- clearInterval(interval);
- }
- }, 300);
- });
- console.log(videoEl.readyState, faceapi.nets.tinyFaceDetector.params);
- console.log("increaseTestSpeed --- doing faceapi");
- const result = await faceapi
- .detectSingleFace(videoEl, options)
- .withFaceLandmarks()
- .withFaceExpressions();
- console.log("increaseTestSpeed --- result:", result);
- if (!result) {
- console.log("increaseTestSpeed --- end failed");
- } else {
- console.log("increaseTestSpeed --- end successfully");
- }
- this.$Spin.hide();
- },
- videoStartPlay() {
- const video = document.getElementById("inputVideo");
- video && video.play();
- },
- async snap() {
- if (this.disableSnap) {
- return;
- }
- try {
- this.disableSnap = true;
- const captureBlob = await this.getSnapShot();
- if (captureBlob.size < 10 * 1024) {
- this.$Message.error({
- content: "抓拍照片太小!",
- duration: 15,
- closable: true,
- });
- // 经查以前记录,不完整图片均为8192大小。此处设置小于10KB的图片为未抓拍成功
- window._hmt.push([
- "_trackEvent",
- "摄像头框",
- "抓拍照片较小",
- captureBlob.size,
- ]);
- throw "抓拍照片较小";
- }
- this.videoStartPlay();
- const [captureFilePath, signIdentifier] = await this.uploadToServer(
- captureBlob
- );
- return this.faceCompareSync(captureFilePath, signIdentifier);
- } catch (error) {
- console.log("同步照片比对流程失败");
- throw error;
- } finally {
- this.videoStartPlay();
- // this.disableSnap = false;
- }
- },
- async getSnapShot() {
- return new Promise((resolve, reject) => {
- const video = document.getElementById("inputVideo");
- if (video.readyState !== 4 || !video.srcObject.active) {
- this.$Message.error({
- content: "摄像头没有正常启用",
- duration: 5,
- closable: true,
- });
- window._hmt.push([
- "_trackEvent",
- "摄像头框",
- "摄像头状态",
- "摄像头没有正常启用-退出" +
- (this.lastSnapTime ? "(非初次抓拍)" : ""),
- ]);
- reject("摄像头没有正常启用");
- this.logout(
- "?LogoutReason=" +
- "摄像头没有正常启用-退出" +
- (this.lastSnapTime ? "(非初次抓拍)" : "")
- );
- return;
- }
- video.pause();
- var canvas = document.createElement("canvas");
- canvas.width = 220;
- canvas.height = 165;
- var context = canvas.getContext("2d");
- context.drawImage(video, 0, 0, 220, 165);
- canvas.toBlob(resolve, "image/png", 0.95);
- });
- },
- async uploadToServer(captureBlob) {
- async function blobToArray(blob) {
- return new Promise(resolve => {
- var reader = new FileReader();
- reader.addEventListener("loadend", function() {
- // reader.result contains the contents of blob as a typed array
- resolve(reader.result);
- });
- reader.readAsArrayBuffer(blob);
- });
- }
- //保存抓拍照片到服务器
- let resultUrl, signIdentifier;
- try {
- const buffer = await blobToArray(captureBlob);
- // console.log(buffer);
- // var view1 = new Uint8Array(buffer);
- // console.log(buffer[0], buffer[1], buffer[429721]);
- const fileMd5 = MD5(buffer);
- console.log(fileMd5);
- const params = new URLSearchParams();
- params.append("fileSuffix", "png");
- params.append("fileMd5", fileMd5);
- const res = await this.$http.get(
- "/api/ecs_oe_student/examControl/getCapturePhotoUpYunSign?" + params
- );
- // console.log(res);
- // let myHeaders = new Headers();
- // for (let [k, v] of Object.entries(res.data.headers)) {
- // // console.log(k, v);
- // if (k.includes("tion") || k.includes("Date") || k.includes("MD5")) {
- // if (k === "Date") k = "x-date";
- // myHeaders.append(k, v);
- // }
- let myFormData = new FormData();
- for (let [k, v] of Object.entries(res.data.formParams)) {
- myFormData.append(k, v);
- }
- myFormData.append("file", captureBlob);
- try {
- const res2 = await fetch(res.data.formUrl, {
- method: "POST",
- body: myFormData,
- });
- if (!res2.ok) {
- throw res2.status;
- }
- } catch (error) {
- window._hmt.push([
- "_trackEvent",
- "摄像头框",
- "抓拍照片保存失败--upyun",
- error,
- ]);
- throw error;
- }
- // console.log(response);
- resultUrl = res.data.accessUrl;
- signIdentifier = res.data.signIdentifier;
- // this.serverLog("debug/S-005001", "抓拍照片保存成功:");
- window._hmt.push(["_trackEvent", "摄像头框", "抓拍照片保存成功"]);
- } catch (e) {
- console.log(e);
- // this.serverLog("debug/S-006001", "抓拍照片保存失败");
- window._hmt.push([
- "_trackEvent",
- "摄像头框",
- "保存抓拍照片到服务器失败!",
- ]);
- this.$Message.error({
- content: "抓拍照片保存失败!",
- duration: 15,
- closable: true,
- });
- throw "抓拍照片保存失败!";
- }
- return [resultUrl, signIdentifier];
- },
- async faceCompareSync(captureFilePath, signIdentifier) {
- try {
- const res = await this.$http.post(
- "/api/ecs_oe_student_face/examCaptureQueue/compareFaceSync?signIdentifier=" +
- signIdentifier +
- "&fileUrl=" +
- encodeURIComponent(captureFilePath)
- );
- // // TODO: 识别成功、失败的通知或跳转
- // this.$emit("on-recognize-result", {
- // error: null,
- // pass: res.data.isPass,
- // stranger: res.data.isStranger,
- // });
- return res.data;
- } catch (e) {
- console.log(e);
- // this.$Message.error(e.message);
- throw "同步照片比较失败!";
- }
- },
- },
- };
- </script>
- <style scoped>
- .page-container {
- /* margin-left: 20px; */
- margin: 0 auto;
- width: 640px;
- height: 480px;
- overflow: hidden;
- }
- .above-video {
- z-index: 2;
- }
- .instruction-animation {
- transition: margin-left 2s ease-in-out 0.5s;
- }
- .instruction-tips {
- position: absolute;
- top: 0;
- left: 0;
- text-align: center;
- width: 100%;
- background-color: rgba(255, 255, 255, 0.6);
- padding: 20px 0;
- }
- .instruction-total {
- position: absolute;
- top: 50px;
- left: 0;
- text-align: center;
- width: 100%;
- background-color: rgba(255, 255, 255, 0);
- }
- .total-text {
- background-color: rgba(255, 255, 255, 0.6);
- width: 80px;
- height: 80px;
- line-height: 80px;
- font-size: 40px;
- border-radius: 50%;
- border: 3px solid gold;
- margin: 20px auto 0 auto;
- }
- .seperators {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(255, 255, 255, 0.1);
- display: flex;
- justify-content: space-evenly;
- }
- .seperators .line {
- width: 5px;
- height: 100%;
- background-color: red;
- }
- .blocks {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(255, 255, 255, 0.1);
- display: flex;
- justify-content: space-around;
- align-items: center;
- }
- .blocks .block-index {
- margin-top: 150px;
- width: 100px;
- height: 100px;
- border-radius: 50%;
- background-color: rgba(255, 0, 0, 0.7);
- color: yellow;
- font-size: 50px;
- line-height: 100px;
- text-align: center;
- }
- .blocks .block-index-size {
- margin-top: 175px;
- width: 100%;
- height: 100%;
- transition: all 1s ease-out;
- }
- .blocks .block-index-blur {
- background-color: rgba(100, 100, 100, 0.8);
- }
- .instruction-face {
- position: absolute;
- top: 30%;
- left: 0;
- width: 33%;
- height: 100px;
- background-size: contain;
- background-repeat: no-repeat;
- background-position-x: center;
- text-align: center;
- /* background-color: rgba(255, 255, 255, 0.6); */
- /* background-image: url(./smile-icon.png); */
- animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both infinite;
- transform: translate3d(0, 0, 0);
- backface-visibility: hidden;
- perspective: 1000px;
- }
- .instruction-face-animation-state {
- animation-iteration-count: 1;
- }
- #overlay,
- .overlay {
- position: absolute;
- top: 0;
- left: 0;
- }
- /* .detect-video {
- width: 100vw;
- height: 100vh;
- } */
- @keyframes shake {
- 10%,
- 90% {
- transform: translate3d(-1px, 0, 0);
- }
- 20%,
- 80% {
- transform: translate3d(2px, 0, 0);
- }
- 30%,
- 50%,
- 70% {
- transform: translate3d(-4px, 0, 0);
- }
- 40%,
- 60% {
- transform: translate3d(4px, 0, 0);
- }
- }
- </style>
- <style>
- .el-message__content {
- font-size: 24px !important;
- }
- </style>
|