Vue回爐重造之封裝一個實用的人臉識別組件


















前言

人臉識別技術(shù)現(xiàn)在越來越火,那么我們今天教大家實現(xiàn)一個人臉識別組件。
資源

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

源碼

由于我們的電腦有的有攝像頭,有的沒有攝像頭,所以我們需要根據(jù)不同的場景來封裝這個組件。先放個圖吧,大家可以看得更加直觀一些。

有攝像頭的話,我們就顯示(需要人像識別組件):






沒有攝像頭的話,我們就顯示(這個直接上傳人像即可):





判斷有無攝像頭,我們可以使用這個方法:



// 判斷有無攝像頭,推薦放在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; // 這是我自定義的一個狀態(tài),初始值為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: "不要重復(fù)上傳!",
        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、將人像放在框中自動截取,也可點擊手動截取。截取的圖片將會出現(xiàn)在下方未保存圖片欄;3、最后點擊保存,下方可預(yù)覽保存后的圖片。</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轉(zhuǎn)化為圖片
    convertCanvasToImage(canvas) {
      var image = new Image();
      image.src = canvas.toDataURL("image/png");
      return image;
    },
    //將base64轉(zhuǎn)換為文件,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);
      //獲取到轉(zhuǎn)化為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("關(guān)閉人臉識別窗口");
      this.imgView = false;
      this.clearCanvas();
      // 停止偵測
      this.trackerTask.stop();
      console.log(this.trackerTask);
      // 關(guān)閉攝像頭
      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>




結(jié)語

這樣,一個簡單又實用的人像識別就這樣完成了。

作者:Vam的金豆之路

主要領(lǐng)域:前端開發(fā)

我的微信:maomin9761

微信公眾號:前端歷劫之路