javaScript实现人脸探测与捕获

该博客介绍了一个使用 tracking.js 库实现实时人脸识别的登录系统。系统开启相机,通过检测人脸并标记其位置,当人脸稳定时拍摄并转换为Base64编码,然后发送给后端进行识别。成功识别后,展示用户信息;失败则引导用户选择密码登录。此外,还提供了注意事项和错误提示。
摘要由CSDN通过智能技术生成

javaScript实现人脸探测与捕获

思路
  • tracking.js库可以实现捕捉或实时跟踪相机返回的颜色或面部图像信息。基于对特定颜色的检测或人脸面部的出现和移动来触发相对应的检测事件,比如调用该库的人脸探测API可实现人脸探测。进入人脸登录页面系统自动打开相机,视频区域开始监听人脸。当人脸出现在相机前,触发人脸探测机制,通过 标签对人脸位置以方框的形式进行标记,当探测到的人脸趋于稳定状态,拍摄当前人脸,通过 绘制成图片。由于字节流更适用于互联网传输,将采集的人脸图片转为Base64码,以此通过接口传输给后端。
  • tracking.js下载
代码
<template>
  <div class="faceLogin">
    <el-row
      type="flex"
      justify="center"
      align="middle"
      style="width: 100%; height: 150px"
    >
      <el-col :span="24">
        <div class="titleRelation">
          <el-image :src="topImgage"></el-image>
          <div class="titlePosition">
            <h1>系统人脸登录</h1>
          </div>
          <div class="datePosition">
            <span>{{ nowTime }}</span>
          </div>
          <div class="btnPosition">
            <el-button type="primary" size="medium" @click="goCourse()"
              >密码登录</el-button
            >
          </div>
        </div>
      </el-col>
    </el-row>
    <el-row
      :gutter="50"
      type="flex"
      justify="center"
      align="middle"
      style="margin-top: 20px"
    >
      <el-col :span="7"
        ><div class="faceDiv">
          <div class="faceBorder">
            <div class="borderStyle">
              <div :class="loginStuats ? '' : 'line'"></div>
              <video
                id="video"
                width="300"
                height="230"
                preload
                autoplay
                loop
                muted
              ></video>
              <canvas
                :class="faceStuats ? '' : 'faceImg'"
                id="canvas"
                width="300"
                height="230"
              ></canvas>
              <div class="bottom"></div>
            </div>
            <canvas id="screenshotCanvas" width="300" height="230"></canvas>
          </div>
          <div class="faceStuatsText">
            <p>
              <i :class="faceStuatsTextIcon" style="margin-right: 10px"></i
              >{{ faceStuatsText }}
            </p>
          </div>
        </div></el-col
      >

      <el-col :span="13"
        ><div class="faceInfor">
          <div v-if="loginStuats == 1">
            <el-row
              type="flex"
              justify="center"
              align="middle"
              style="height: 80px"
            >
              <el-col :span="24" style="color: #55d9fb; letter-spacing: 5px"
                ><h1>欢迎使用</h1></el-col
              >
            </el-row>
            <el-row
              type="flex"
              justify="start"
              align="middle"
              style="height: 80px"
            >
              <el-col :span="5" :push="3" class="Infor1"><p>姓名:</p></el-col>
              <el-col :span="6" :push="2" class="Infor2"
                ><p>{{ userInfor.userName }}</p></el-col
              >
            </el-row>
            <el-row
              type="flex"
              justify="start"
              align="middle"
              style="height: 80px"
            >
              <el-col :span="5" :push="3" class="Infor1"><p>身份:</p></el-col>
              <el-col :span="6" :push="2" class="Infor2"
                ><p>{{ userInfor.identity }}</p></el-col
              >
            </el-row>
            <el-row
              type="flex"
              justify="start"
              align="middle"
              style="height: 80px"
            >
              <el-col :span="5" :push="3" class="Infor1"><p>班级:</p></el-col>
              <el-col :span="12" :push="2" class="Infor2"
                ><p>{{ userInfor.orgName }}</p></el-col
              >
            </el-row>
            <el-row
              type="flex"
              justify="start"
              align="middle"
              style="height: 80px"
            >
              <el-col :span="5" :push="3" class="Infor1"><p>时间:</p></el-col>
              <el-col :span="15" :push="2" class="Infor2"
                ><p>{{ userInfor.loginTime }}</p></el-col
              >
            </el-row>
          </div>
          <div v-else-if="loginStuats == 0">
            <el-row
              type="flex"
              justify="start"
              align="middle"
              style="height: 80px"
            >
              <el-col
                :span="23"
                :push="1"
                style="
                  font-size: 26px;
                  color: #55d9fb;
                  letter-spacing: 5px;
                  text-align: left;
                "
                ><p>人脸识别过程中请注意以下事项:</p></el-col
              >
            </el-row>
            <div v-for="(item, index) in faceAttent" :key="index">
              <el-row
                type="flex"
                justify="start"
                align="middle"
                style="height: 80px"
              >
                <el-col :span="23" :push="1" class="Infor2"
                  ><p>*{{ item }}</p></el-col
                >
              </el-row>
            </div>
          </div>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import "@/assets/tracking/build/tracking.js";
import "@/assets/tracking/build/data/face-min.js";
import "@/assets/tracking/build/data/mouth-min.js";
import { timeFormate } from "@/assets/js/nowtime.js";
export default {
  data() {
    return {
      topImgage:
        "http://gencun.ltd:8080/eduassistant/upload/login/titleTop1.jpg",
      nowTime: "",
      loginStuats: 0,
      userInfor: {
        userName: "",
        identity: "",
        orgName: "",
        loginTime: "",
      },
      faceAttent: [
        "请尽量使人脸显示在人脸识别区域的中心位置;",
        "请调整脸部姿态与位置使得绿色人脸标识框出现;",
        "当绿色人脸标识框出现时请保持脸部姿态与位置;",
        "若人脸识别失败,请选择密码登录。",
      ],
      faceStuats: 0,
      faceStuatsText: "未检测到人脸",
      faceStuatsTextIcon: "el-icon-warning",
      video: null,
      tracker: null,
      trackerTask: null,
      canvas: null,
      context: null,
      audio: null,
      screenshotCanvas: null,
      uploadLock: true, // 上传锁
    };
  },
  methods: {
    goCourse() {
      this.$router.push({
        path: "/password",
      });
    },
    initCamera() {
      this.video = document.getElementById("video");
      this.canvas = document.getElementById("canvas");
      this.screenshotCanvas = document.getElementById("screenshotCanvas");
      this.context = this.canvas.getContext("2d");
      this.audio = new SpeechSynthesisUtterance();
      // 固定写法
      this.tracker = new window.tracking.ObjectTracker("face");
      this.tracker.setInitialScale(4);
      this.tracker.setStepSize(2);
      this.tracker.setEdgesDensity(0.1);
      this.trackerTask = window.tracking.track("#video", this.tracker, {
        camera: true,
      });
      this.video.srcObject = window.stream;
      this.lookFace();
    },
    // 侦测人脸
    lookFace() {
      this.tracker.on("track", (event) => {
        // 清除人脸位置
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.context.strokeStyle = "#0764B7";
        if (!event.data.length == 1) {
          this.faceStuatsTextIcon = "el-icon-warning";
          this.faceStuatsText = "未检测到人脸";
        } else if (event.data.length == 1) {
          //绘画人脸位置
          this.context.strokeRect(
            event.data[0].x,
            event.data[0].y,
            event.data[0].width,
            event.data[0].height
          );
          this.faceStuats = 1;
          setTimeout(() => {
            this.audio.text = "已检测到人脸";
            speechSynthesis.speak(this.audio);
            this.faceStuatsTextIcon = "el-icon-s-custom";
            this.faceStuatsText = "已检测到人脸";
            this.audio.text = "正在识别中,请稍后";
            speechSynthesis.speak(this.audio);
            this.uploadLock && this.uploadFaceImg();
          }, 1000);
        } else if (event.data.length > 1) {
          this.faceStuatsTextIcon = "el-icon-warning";
          this.faceStuatsText = "请保持一张人脸";
        }
      });
    },
    // 上传图片
    uploadFaceImg() {
      this.uploadLock = false;
      let canvas = this.screenshotCanvas;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(this.video, 0, 0, canvas.width, canvas.height);
      let base64Img = canvas.toDataURL("image/jpeg");
      this.faceStuatsTextIcon = "el-icon-loading";
      this.faceStuatsText = "正在识别中,请稍后";
      this.$axios({
        method: "post",
        url: this.$api + "/faceLogin",
        data: base64Img,
      })
        .then((res) => {
          if (res.data.data.length) {
            this.userInfor.userName = res.data.data[0].userName;
            this.userInfor.identity = res.data.data[0].power;
            this.userInfor.orgName = res.data.data[0].depOrg;
            this.userInfor.loginTime = this.nowTime;
            this.$notify({
              title: "成功",
              message: "识别成功",
              type: "success",
            });
            this.audio.text = "人脸识别成功";
            speechSynthesis.speak(this.audio);
            this.loginStuats = 1;
            this.uploadLock = true;
            this.faceStuatsTextIcon = "el-icon-success";
            this.faceStuatsText = "人脸识别成功";
            this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
            setTimeout(() => {
              this.$router.push({
                path: "/courseList",
              });
            }, 2000);
          } else {
            this.$notify({
              title: "警告",
              message: "识别失败!",
              type: "warning",
            });
            this.audio.text = "人脸识别失败";
            speechSynthesis.speak(this.audio);
            this.loginStuats = 0;
            this.faceStuatsTextIcon = "el-icon-error";
            this.faceStuatsText = "人脸识别失败";
            this.$router.push({
              path: "/password",
            });
            this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.faceStuats = 0;
          }
        })
        .catch((err) => {
          console.log(err);
          this.$notify.error({
            title: "错误",
            message: "服务器错误,请联系管理员",
          });
        });
    },
    // 关闭摄像头(做笔记)
    stopMediaStreamTrack() {
      if (typeof window.stream === "object") {
        this.video.srcObject = null;
        window.stream.getTracks().forEach((track) => track.stop());
      }
    },
    // 处理实时显示时间
    nowTimes() {
      this.nowTime = timeFormate(new Date());
      setInterval(this.nowTimes, 1000);
      this.clear();
    },
    clear() {
      clearInterval(this.nowTimes);
      this.nowTimes = null;
    },
  },
  mounted() {
    this.nowTimes();
    this.initCamera();
  },
  destroyed() {
    this.trackerTask.stop();
    this.stopMediaStreamTrack();
    this.clear();
  },
  watch: {
    faceStuats: function () {
      if (this.faceStuats == 1) {
        this.trackerTask.stop();
      }
    },
  },
};
</script>
<style scoped>
.faceLogin {
  background-image: url("http://gencun.ltd:8080/eduassistant/upload/login/faceBackground.jpg");
  position: fixed;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-position: center center;
  width: 100%;
  height: 100%;
}
.titleRelation {
  position: relative;
  width: 100%;
}
.titlePosition {
  position: absolute;
  left: 0;
  right: 0;
  /* top: 0; */
  bottom: -4px;
  margin: auto;
  color: #fff;
  letter-spacing: 3px;
}
.datePosition {
  position: absolute;
  left: 5%;
  bottom: 60%;
  color: #fff;
  letter-spacing: 2px;
  font-size: 16px;
}
.btnPosition {
  position: absolute;
  right: 5%;
  bottom: 60%;
}
.faceDiv {
  width: 100%;
  height: 500px;
  border: 2px solid #0082da;
  border-radius: 10px;
}
.faceInfor {
  width: 100%;
  height: 440px;
  border: 2px solid #0082da;
  border-radius: 10px;
  background: linear-gradient(
    to bottom right,
    rgba(10, 80, 126, 0.8),
    rgba(12, 35, 69, 1)
  );
  margin: 0 auto;
  padding: 30px;
}
.faceBorder {
  position: relative;
  width: 100%;
  height: 74.5%;
}
.faceImg {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 230px;
  height: 200px;
  background: url("~@/assets/images/timg.png") no-repeat;
  background-size: 100% 100%;
  z-index: 8;
}
/* 人脸识别
*/
.borderStyle {
  width: 320px;
  height: 240px;
  border: 1px solid rgba(3, 169, 244, 0.2);
  position: absolute;
  overflow: hidden;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.borderStyle .line {
  height: calc(100% - 2px);
  width: 100%;
  background: linear-gradient(180deg, rgba(0, 255, 51, 0) 43%, #03a9f4 211%);
  border-bottom: 2px solid #03a9f4;
  transform: translateY(-100%);
  animation: radar-beam 2s infinite;
  animation-timing-function: cubic-bezier(0.3, 0, 0.43, 0.7);
  animation-delay: 1.4s;
}
@keyframes radar-beam {
  0% {
    transform: translateY(-100%);
  }

  100% {
    transform: translateY(0);
  }
}
.borderStyle:after,
.borderStyle:before,
.borderStyle .bottom:after,
.borderStyle .bottom:before {
  content: "";
  display: block;
  position: absolute;
  width: 25px;
  height: 35px;
  border: 3px solid transparent;
}
.borderStyle:after,
.borderStyle:before {
  top: 0;
  border-top-color: #03a9f4;
}
.borderStyle .bottom:after,
.borderStyle .bottom:before {
  bottom: 0;
  border-bottom-color: #03a9f4;
}
.borderStyle:before,
.borderStyle .bottom:before {
  left: 0;
  border-left-color: #03a9f4;
}
.borderStyle:after,
.borderStyle .bottom:after {
  right: 0;
  border-right-color: #03a9f4;
}
/* 人脸识别状态 */
.faceStuatsText {
  width: 100%;
  height: 25%;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #0b2345;
  opacity: 0.9;
  border-top: 1px solid #0082da;
  border-radius: 0 0 10px 10px;
  font-size: 22px;
  color: #fff;
  letter-spacing: 2px;
}
/* 右侧文字 */
.Infor1 {
  color: #55d9fb;
  font-size: 25px;
  letter-spacing: 3px;
}
.Infor2 {
  color: #fff;
  font-size: 25px;
  letter-spacing: 2px;
  text-align: left;
}
video,
canvas {
  width: 300px;
  height: 230px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: -1;
}
#screenshotCanvas {
  display: none;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值