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>




结语

这样,一个简单又实用的人像识别就这样完成了。

作者:Vam的金豆之路

主要领域:前端开发

我的微信:maomin9761

微信公众号:前端历劫之路