Vue回炉重造之封装一个实用的人脸识别组件

简介: 人脸识别技术现在越来越火,那么我们今天教大家实现一个人脸识别组件。

前言


人脸识别技术现在越来越火,那么我们今天教大家实现一个人脸识别组件。


资源


  • element UI
  • Vue.js
  • tracking-min.js
  • face-min.js

源码

由于我们的电脑有的有摄像头,有的没有摄像头,所以我们需要根据不同的场景来封装这个组件。先放个图吧,大家可以看得更加直观一些。

有摄像头的话,我们就显示(需要人像识别组件):没有摄像头的话,我们就显示(这个直接上传人像即可):判断有无摄像头,我们可以使用这个方法:

// 判断有无摄像头,推荐放在created里
    var deviceList = [];
    navigator.mediaDevices
      .enumerateDevices()
      .then(devices => {
        devices.forEach(device => {
          deviceList.push(device.kind);
        });
        if (deviceList.indexOf("videoinput") == "-1") {
          console.info("没有摄像头");
          return false;
        } else {
          console.info("有摄像头");
          this.videoinput = true; // 这是我自定义的一个状态,初始值为false
        }
      })
      .catch(function(err) {
        alert(err.name + ": " + err.message);
      });
复制代码

完整代码:

index.vue

<template>
<!-- 人脸识别 -->
    <el-dialog
      :visible.sync="openFaceView"
      width="581px"
      :show-close="false"
      v-loading="faceloading"
      element-loading-text="人脸识别中"
    >
      <div class="ovf" style="padding:20px;">
        <el-upload
          v-if="!videoinput"
          class="upload-demo"
          action
          multiple
          :limit="1"
          :file-list="fileList"
          :on-change="handleChange"
          :on-exceed="handleExceed"
          :before-remove="beforeRemove"
          :auto-upload="false"
        >
          <el-button size="small" type="primary">点击上传人像图片</el-button>
        </el-upload>
        <div v-if="videoinput">
          <el-button size="small" type="primary" @click="checkFace">点击进行人脸识别</el-button>
          <div slot="tip" class="el-upload__tip">此功能需到非IE浏览器进行</div>
        </div>
        <div class="dialog-footer">
          <el-button @click="openFaceView = false">取 消</el-button>
          <el-button type="primary" @click="postFace()">确 定</el-button>
        </div>
      </div>
    </el-dialog>
    <el-dialog :visible.sync="checkFaceView" width="581px" :show-close="false">
      <Face :faceView="checkFaceView" @canvasToImage="getImgFile"></Face>
    </el-dialog>
</template>
<script>
import { verifyFace } from "../../request/api"; //引入人脸识别接口
import Face from "./Face"; // 引入人脸识别组件
export default {
  name: "MyClassRoom",
  data() {
    return {
      openFaceView:true,
      faceloading: false,
      videoinput: false,
      fileList: [],
      face: "",
  }
  },
  components: {
    Face
  },
  methods: {
   // 弹出人脸识别框
    checkFace() {
      this.checkFaceView = true;
    },
    // 限制上传照片
    handleExceed() {
      this.$message.warning({
        message: "不要重复上传!",
        offset: 380,
        duration: 1000
      });
    },
    // 移除人像图片
    beforeRemove(file) {
      return this.$confirm(`确定移除 ${file.name}?`);
    },
    // 上传的文件
    handleChange(file) {
      this.face = file.raw;
    },
    // 获取截取图片
    getImgFile(d) {
      this.face = d;
      this.checkFaceView = false;
    },
    // 人脸识别完毕
    postFace() {
      this.faceloading = true;
      this.checkFaceView=false;
      let formData = new FormData();
      formData.append("face", this.face);
      /*人脸识别接口,把获取到的照片传到后台,我这里使用了封装axios。需要注意使用   config.headers = {'Content-Type':'multipart/form-data'} 传照片
      */
      verifyFace(formData, { isUpload: true }) 
        .then(res => {
          console.log(res);
          if (res.code == 0) {
            this.faceloading = false;
            this.$message.success({
              message: "人脸识别成功!",
              offset: 380,
              duration: 1000
            });
          } else {
            this.$message.error({
              message: "人脸识别失败!",
              offset: 380,
              duration: 1000
            });
            this.faceloading = false;
          }
        })
        .catch(err => {
          console.log(err);
        });
    }
  },
  created() {
    // 判断有无摄像头
    var deviceList = [];
    navigator.mediaDevices
      .enumerateDevices()
      .then(devices => {
        devices.forEach(device => {
          deviceList.push(device.kind);
        });
        if (deviceList.indexOf("videoinput") == "-1") {
          console.info("没有摄像头");
          return false;
        } else {
          console.info("有摄像头");
          this.videoinput = true;
        }
      })
      .catch(function(err) {
        alert(err.name + ": " + err.message);
      });
  },
}
</script>
复制代码

Face.vue

<!-- 人脸识别 -->
<template>
  <div class="face">
    <div class="container">
      <video id="video" preload autoplay loop muted></video>
      <canvas id="canvas" width="581" height="436"></canvas>
      <canvas id="canvas1" width="581" height="436"></canvas>
    </div>
    <div class="btns">
      <el-button type="primary" @click="start">打开摄像头</el-button>
      <el-button type="primary" @click="screenshot">手动截图</el-button>
      <el-button type="primary" @click="keepImg">保存图片</el-button>
      <p class="tips">1、首先打开摄像头;2、将人像放在框中自动截取,也可点击手动截取。截取的图片将会出现在下方未保存图片栏;3、最后点击保存,下方可预览保存后的图片。</p>
    </div>
    <div class="imgs" v-show="imgView">
      <p>未保存图片</p>
      <canvas id="shortCut" width="140" height="140"></canvas>
      <p>已保存图片</p>
      <div id="img"></div>
    </div>
  </div>
</template>
<script>
import "../../assets/js/tracking-min.js"; // 需要引入(下载链接在文末)
import "../../assets/js/face-min.js"; // // 需要引入(下载链接在文末)
export default {
  name: "testTracking",
  props: ["faceView"],
  data() {
    return {
      saveArray: {},
      imgView: false
    };
  },
  methods: {
    // 打开摄像头
    start() {
      var saveArray = {};
      var canvas = document.getElementById("canvas");
      var context = canvas.getContext("2d");
      // eslint-disable-next-line no-undef
      var tracker = new window.tracking.ObjectTracker("face");
      tracker.setInitialScale(4);
      tracker.setStepSize(2);
      tracker.setEdgesDensity(0.1);
      // eslint-disable-next-line no-undef
      this.trackerTask = window.tracking.track("#video", tracker, {
        camera: true
      });
      tracker.on("track", function(event) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        event.data.forEach(function(rect) {
          context.strokeStyle = "#fff";
          context.strokeRect(rect.x, rect.y, rect.width, rect.height);
          context.fillStyle = "#fff";
          saveArray.x = rect.x;
          saveArray.y = rect.y;
          saveArray.width = rect.width;
          saveArray.height = rect.height;
        });
      });
      var canvas1 = document.getElementById("canvas1");
      var context1 = canvas1.getContext("2d");
      context1.strokeStyle = "#69fff1";
      context1.moveTo(190, 118);
      context1.lineTo(390, 118);
      context1.lineTo(390, 318);
      context1.lineTo(190, 318);
      context1.lineTo(190, 118);
      context1.stroke();
      setInterval(() => {
        if (
          saveArray.x > 200 &&
          saveArray.x + saveArray.width < 400 &&
          saveArray.y > 120 &&
          saveArray.y + saveArray.height < 320 &&
          saveArray.width < 180 &&
          saveArray.height < 180
        ) {
          console.log(saveArray);
          this.getPhoto();
          for (var key in saveArray) {
            delete saveArray[key];
          }
        }
      }, 2000);
    },
    // 获取人像照片
    getPhoto() {
      var video = document.getElementById("video");
      var can = document.getElementById("shortCut");
      var context2 = can.getContext("2d");
      context2.drawImage(video, 210, 130, 210, 210, 0, 0, 140, 140);
      this.imgView = true;
    },
    // 截屏
    screenshot() {
      this.getPhoto();
    },
    // 将canvas转化为图片
    convertCanvasToImage(canvas) {
      var image = new Image();
      image.src = canvas.toDataURL("image/png");
      return image;
    },
    //将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
    dataURLtoFile(dataurl, filename) {
      var arr = dataurl.split(","),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, { type: mime });
    },
    // 保存图片
    keepImg() {
      var can = document.getElementById("shortCut");
      var img = document.getElementById("img");
      var photoImg = document.createElement("img");
      photoImg.src = this.convertCanvasToImage(can).src;
      img.appendChild(photoImg);
      //获取到转化为base64的图片地址
        this.$emit(
          "canvasToImage",
          this.dataURLtoFile(this.convertCanvasToImage(can).src, "person.jpg")
        );
      console.log(
        this.dataURLtoFile(this.convertCanvasToImage(can).src, "person.jpg")
      );
    },
    clearCanvas() {
      var c = document.getElementById("canvas");
      var c1 = document.getElementById("canvas1");
      var cxt = c.getContext("2d");
      var cxt1 = c1.getContext("2d");
      cxt.clearRect(0, 0, 581, 436);
      cxt1.clearRect(0, 0, 581, 436);
    },
    closeFace() {
      console.log("关闭人脸识别窗口");
      this.imgView = false;
      this.clearCanvas();
      // 停止侦测
      this.trackerTask.stop();
      console.log(this.trackerTask);
      // 关闭摄像头
      var video = document.getElementById("video");
      video.srcObject.getTracks()[0].stop();
    }
  },
  watch: {
    faceView(v) {
      if (v == false) {
        this.closeFace();
      }
    },
    imgView(v) {
      if (v == true) {
        this.$message.success({
          message: "截取成功!点击保存图片",
          offset: 380,
          duration: 1000
        });
      }
    }
  },
  destroyed() {}
};
</script>
<style scoped lang="scss">
.face {
  .container {
    background: #000;
    position: relative;
    width: 581px;
    height: 436px;
    #canvas1 {
      position: absolute;
    }
    video,
    #canvas,
    #canvas1 {
      position: absolute;
      width: 581px;
      height: 436px;
    }
  }
  .btns {
    padding: 10px;
    .tips {
      font-size: 14px;
      color: #666;
      margin: 10px 0;
      line-height: 24px;
    }
  }
  .imgs {
    padding: 10px;
    p {
      font-size: 16px;
    }
  }
}
</style>



相关文章
|
5月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
507 2
|
8月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
978 0
|
8月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
8月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
654 8
|
8月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
444 1
|
10月前
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
875 6
|
8月前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
414 0
|
4月前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
428 137
|
10月前
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
1216 4
|
9月前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
1253 78

热门文章

最新文章