JavaScript | 人脸识别并拍照

简介: 有一个实人认证的需求,需要做人脸识别并拍照提交给后端验证。用到了tracking.js来做人脸识别,人脸跟踪。调用摄像头需要使用https。

有一个实人认证的需求,需要做人脸识别并拍照提交给后端验证。用到了tracking.js来做人脸识别,人脸跟踪。调用摄像头需要使用https。

<!-- 人脸识别组件 -->
<template>
  <div id="seePerson">
    <div v-show="showContainer" class="face-capture" id="face-capture">
      <p class="tip">请保持人像在取景框内</p>
      <video
        id="video"
        :width="vwidth"
        :height="vheight"
        playsinline
        webkit-playsinline
      ></video>
      <canvas id="refCanvas" :width="cwidth" :height="cheight"></canvas>
      <!-- <img class="img-cover" :src="getImageUrl('yuan')" alt="" /> -->
      <div class="yuan"></div>
      <p class="contentp">{
  
  { scanTip }}</p>
    </div>
    <div v-if="!showContainer" class="img-face">
      <img class="imgurl" :src="imgUrl" />
    </div>
  </div>
</template>

<script lang="ts">

import { defineComponent, reactive, toRefs, ref, onMounted } from "vue";

import "../../../assets/js/tracking-min.js";
import "../../../assets/js/face-min.js";
import "../../../assets/js/eye-min.js";
import "../../../assets/js/mouth-min.js";

export default defineComponent({
  name: "seePerson",
  components: {},
  setup(props, ctx) {
    const data = reactive({
      screenSize: {
        width: window.screen.width,
        height: window.screen.height,
      },
      URL: null,
      streamIns: null, // 视频流
      showContainer: true, // 显示
      tracker: null,
      tipFlag: false, // 提示用户已经检测到
      flag: false, // 判断是否已经拍照
      context: null, // canvas上下文
      profile: [], // 轮廓
      removePhotoID: null, // 停止转换图片
      scanTip: "人脸识别中...", // 提示文字
      imgUrl: "",
      canvas: null,
      trackertask: null,
      vwidth: "266",
      vheight: "266",
      cwidth: "266",
      cheight: "266",
      userInfo: {},
      orderData: {},
    });

    //设置video canvas宽高
    const setVideoConfig = () => {
      const scale = data.screenSize.width / 375;
      data.vwidth = 266 * scale;
      data.vheight = 266 * scale;
      data.cwidth = 266 * scale;
      data.cheight = 266 * scale;
      playVideo();
    };
    // 访问用户媒体设备
    const getUserMedia = (constrains) => {
      if (navigator.mediaDevices.getUserMedia) {
        //最新标准API
        navigator.mediaDevices
          .getUserMedia(constrains)
          .then((res) => {
            handleSuccess(res);
          })
          .catch((err) => {
            handleError(err);
          });
      } else if (navigator.webkitGetUserMedia) {
        //webkit内核浏览器
        navigator
          .webkitGetUserMedia(constrains)
          .then((res) => {
            handleSuccess(res);
          })
          .catch((err) => {
            handleError(err);
          });
      } else if (navigator.mozGetUserMedia) {
        //Firefox浏览器
        navagator
          .mozGetUserMedia(constrains)
          .then((res) => {
            handleSuccess(res);
          })
          .catch((err) => {
            handleError(err);
          });
      } else if (navigator.getUserMedia) {
        //旧版API
        navigator
          .getUserMedia(constrains)
          .then((res) => {
            handleSuccess(res);
          })
          .catch((err) => {
            handleError(err);
          });
      } else {
        data.scanTip = "你的浏览器不支持访问用户媒体设备";
      }
    };
    const close = () => {
      data.flag = false;
      data.tipFlag = false;
      data.showContainer = false;
      data.context = null;
      data.scanTip = "人脸识别中...";
      clearTimeout(data.removePhotoID);
      if (data.streamIns) {
        data.streamIns.enabled = false;
        data.streamIns.getTracks()[0].stop();
        data.streamIns.getVideoTracks()[0].stop();
      }
      data.streamIns = null;
      data.trackertask.stop();
      data.tracker = null;
    };

    // 将base64转文件
    const dataURLtoFile = (dataurl: string, filename: string) => {
      // 获取到base64编码
      const arr = dataurl.split(",");
      // 将base64编码转为字符串
      const bstr = window.atob(arr[1]);
      let n = bstr.length;
      const u8arr = new Uint8Array(n); // 创建初始化为0的,包含length个元素的无符号整型数组
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, {
        type: "image/jpeg",
      });
    };

    // 保存为png,base64格式图片
    const saveAsPNG = (c) => {
      return c.toDataURL("image/png", 1);
    };

    // 拍照
    const tackPhoto = () => {
      // 在画布上面绘制拍到的照片
      data.context.drawImage(
        document.getElementById("video"),
        0,
        0,
        data.vwidth,
        data.vwidth
      );
      // 保存为base64格式
      data.imgUrl = saveAsPNG(document.getElementById("refCanvas"));
      console.log("data.imgUrl", data.imgUrl);

      //判断图片大小
      imgSize();
      // todo 这里可以调用后端接口将图片上传比对,然后调用close结束进程
      const file = dataURLtoFile(data.imgUrl, "人脸.jpg");

      console.log("file", file);
      ctx.emit("handleGetPhotoFace",file);

      close();
    };
    const imgSize = () => {
      if (data.imgUrl) {
        // 获取base64图片byte大小
        const equalIndex = data.imgUrl.indexOf("="); // 获取=号下标
        let size;
        if (equalIndex > 0) {
          const str = data.imgUrl.substring(0, equalIndex); // 去除=号
          const strLength = str.length;
          const fileLength = strLength - (strLength / 8) * 2; // 真实的图片byte大小
          size = Math.floor(fileLength / 1024); // 向下取整
          console.log("size", size + "KB");
        } else {
          const strLength = data.imgUrl.length;
          const fileLength = strLength - (strLength / 8) * 2;
          size = Math.floor(fileLength / 1024); // 向下取整
          console.log("size", size + "KB");
        }
        if (size > 1024) {
          // 图片超过1M 按比例压缩
          data.imgUrl = document
            .getElementById("refCanvas")
            .toDataURL("image/png", 1024 / size);
        }
      }
    };

    // 人脸捕捉 设置各种参数 实例化人脸捕捉实例对象,注意canvas上面的动画效果。
    const initTracker = () => {
      data.context = document.getElementById("refCanvas").getContext("2d"); // 画布
      data.canvas = document.getElementById("refCanvas");
      data.tracker = new window.tracking.ObjectTracker("face"); // tracker实例
      data.tracker.setInitialScale(4);
      data.tracker.setStepSize(2); // 设置步长
      data.tracker.setEdgesDensity(0.1);
      try {
        data.trackertask = window.tracking.track("#video", data.tracker); // 开始追踪
      } catch (e) {
        data.scanTip = "访问用户媒体失败,请重试";
      }
      //开始捕捉方法 一直不停的检测人脸直到检测到人脸
      data.tracker.on("track", (e) => {
        //画布描绘之前清空画布
        data.context.clearRect(0, 0, data.canvas.width, data.canvas.height);
        if (e.data.length === 0) {
          data.scanTip = "未检测到人脸";
        } else {
          e.data.forEach((rect) => {
            //设置canvas 方框的颜色大小
            data.context.strokeStyle = "#42e365";
            data.context.lineWidth = 2;
            data.context.strokeRect(rect.x, rect.y, rect.width, rect.height);
          });
          if (!data.tipFlag) {
            data.scanTip = "检测成功,正在拍照,请保持不动2秒";
          }
          // 1.5秒后拍照,仅拍一次 给用户一个准备时间
          // falg 限制一直捕捉人脸,只要拍照之后就停止检测
          if (!data.flag) {
            data.scanTip = "拍照中...";
            data.flag = true;
            data.removePhotoID = setTimeout(() => {
              tackPhoto();
              document.getElementById("video").pause();
              data.tipFlag = true;
            }, 2000);
          }
        }
      });
    };
    // 成功回调
    const handleSuccess = (stream) => {
      data.streamIns = stream;
      const video = document.getElementById("video");
      console.log("video", video);
      // webkit内核浏览器
      data.URL = window.URL || window.webkitURL;
      if ("srcObject" in video) {
        video.srcObject = stream;
      } else {
        video.src = data.URL.createObjectURL(stream);
      }

      // 苹果手机的系统弹框会阻止js的线程的继续执行 手动0.1秒之后自动执行代码
      setTimeout(() => {
        video.play();
        initTracker(); // 人脸捕捉
      }, 100);
    };
    // 失败回调
    const handleError = (err) => {
      data.scanTip = "访问用户媒体失败";
    };
    const playVideo = () => {
      getUserMedia({
        //摄像头拍摄的区域
        video: {
          width: 500,
          height: 500,
          facingMode: "user",
        } /* 前置优先 */,
      });
    };
    onMounted(() => {
      setVideoConfig();
    });

    return {
      ...toRefs(data),
    };
  },
});
</script>

<style lang="scss" scoped>
#seePerson {
  height: 100%;
  .face-capture {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    .img-cover {
      position: fixed;
      top: 63px;
      width: 375px;
      height: 375px;
      object-fit: cover;
      z-index: 3;
      background-repeat: no-repeat;
      background-size: 100% 100%;
    }
    .yuan {
      position: fixed;
      //   top: 63px;
      top: 100px;
      width: 275px;
      height: 275px;
      object-fit: cover;
      z-index: 3;
      //   background-repeat: no-repeat;
      //   background-size: 100% 100%;
      border: 1px solid #ccc;
      border-radius: 50%;
    }
    .contentp {
      position: fixed;
      top: 398px;
      font-size: 18px;
      font-weight: 500;
      color: #333333;
    }
    .rect {
      border: 2px solid #0aeb08;
      position: fixed;
      z-index: 4;
    }
    video,
    canvas {
      position: fixed;
      top: 101px;
      width: 275px;
      height: 275px;
      object-fit: cover;
      z-index: 1;
      background-repeat: no-repeat;
      background-size: 100% 100%;
      border-radius: 50%;
    }
  }

  .tip {
    position: fixed;
    top: 48px;
    z-index: 5;
    font-size: 18px;
    font-family: "PingFang SC Medium", PingFang SC;
    font-weight: 500;
    color: #333333;
    line-height: 25px;
  }

  .img-face {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    .imgurl {
      position: fixed;
      top: 117.5px;
      width: 266px;
      height: 266px;
      border-radius: 133px;
    }
  }
}
</style>
相关文章
|
JavaScript 前端开发 API
js调用网页摄像头进行直播/拍照
js调用网页摄像头进行直播/拍照
454 0
js调用网页摄像头进行直播/拍照
|
JavaScript 前端开发 对象存储
javascript 手机移动端 微信 拍照 录音 录视频并阿里oss上传
调用照相机,拍照  1 调用摄像机,视频  1 调用录音机,录音  1 第一种方法: var showimg = document.
2356 0
|
1月前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
26 1
JavaScript中的原型 保姆级文章一文搞懂
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
105 2
|
29天前
JS+CSS3文章内容背景黑白切换源码
JS+CSS3文章内容背景黑白切换源码是一款基于JS+CSS3制作的简单网页文章文字内容背景颜色黑白切换效果。
18 0
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
151 4
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
90 4
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
80 4
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
100 4
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
92 3