Tracking.js 是一个独立的JavaScript库,用于跟踪从相机实时收到的数据。跟踪的数据既可以是颜色,也可以是人,也就是说我们可以通过检测到某特定颜色,或者检测一个人体/脸的出现与移动,来触发JavaScript 事件。它是非常易于使用的API,具有数个方法和事件(足够使用了)。

做项目要用到活体检测和拍照的

实现效果

image

活体检测组件

包需到下载 tracking-min.js 和 face-min.js 压缩文件,自行百度。

image

点击查看活体检测组件代码
<template>
  <el-dialog
    :visible="modalVisible"
    width="681px"
    custom-class="compaines-dialog"
    title="Complete Registration"
    class="face-dialog"
    v-dialogDrag
    :close-on-click-modal="false"
    @close="close"
    :append-to-body="true"
  >
    <div class="face" v-loading="faceloading">
      <p class="big-title">Liveness detection</p>
      <!-- <p v-if="steps === 'open' || steps === 'screen'">
        Please place your face into the outlined area,we will take the photo
        automatically!
      </p> -->
      <p v-if="steps === 'open' || steps === 'screen'">
        {{ faceTips[imgList.length] }}
      </p>
      <div
        v-if="steps === 'screen' && imgList.length > 0"
        style="text-align: center; font-size: 30px"
      >
        <svg-icon :iconClass="faceLocation[imgList.length]" />
      </div>
      <p v-if="steps === 'end'">
        <span v-if="faceOk" class="success">Successful!</span>
        <span v-else class="fail">Failed!</span>
      </p>
      <div class="video-container" v-show="steps !== 'end'">
        <video
          id="video"
          preload
          autoplay
          loop
          muted
          width="295"
          height="345"
          :style="reverse ? 'transform:rotateY(180deg);' : ''"
        ></video>
        <canvas id="canvas" width="295" height="345"></canvas>
        <canvas
          id="shortCut"
          width="295"
          height="345"
          style="opacity: 0"
        ></canvas>
        <canvas
          id="canvas1"
          width="295"
          height="345"
          style="opacity: 0"
        ></canvas>
        <i @click="reverseVideo" v-show="steps === 'screen'">
          <svg-icon iconClass="icon-camera" class="icon-camera" />
        </i>
      </div>
      <div v-show="steps === 'end'" style="text-align: center">
        <img v-if="faceOk" src="@/icons/img/face-success.png" alt="" />
        <img v-else src="@/icons/img/face-fail.png" alt="" />
      </div>
      <div class="btns">
        <div class="add-num w300" @click="start" v-if="steps === 'open'">
          TURN CAMERA ON
        </div>
        <div
          class="retake-btn w300"
          style="margin-right: 0"
          @click="close"
          v-if="steps === 'screen'"
        >
          TURN CAMERA OFF
        </div>
        <div
          class="retake-btn w300"
          @click="tryAgain"
          v-if="steps === 'end' && !faceOk"
        >
          TRY AGAIN
        </div>
        <div
          class="add-num w300"
          @click="finish"
          v-if="steps === 'end' && faceOk"
        >
          FINISH
        </div>
        <p class="tips" v-if="steps === 'screen'">
          (To complete the liveness detection your camera must remain on)
        </p>
      </div>
      <div class="imgs" v-show="false">
        <p>未保存图片</p>

        <p>已保存图片</p>
        <div id="img"></div>
      </div>
    </div>
  </el-dialog>
</template>
<script>
import './tracking-min.js'
import './face-min.js'
import { livenessCheck } from '@modules/kyc/api/system/system'
import debounce from 'lodash/debounce'

export default {
  name: 'testTracking',
  model: {
    prop: 'formData',
    event: 'input',
  },
  props: {
    modalVisible: {
      type: Boolean,
      default: false,
    },
    formData: {
      type: Object | String,
    },
    option: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      saveArray: {},
      imgView: false,
      timer: true,
      steps: 'open',
      imgList: [],
      faceTips: [
        'Please place your face into the outlined area,we will take the photo automatically!',
        'Look to the left',
        'Look to the right',
        'Tilt your head up',
        'Tilt your head down',
      ],
      faceLocation: [
        '',
        'arrow-left',
        'arrow-right',
        'arrow-up',
        'arrow-down',
        '',
      ],
      faceloading: false,
      faceOk: false,
      startPhoto: false,
      reverse: true,
    }
  },
  created() {
    this.start = debounce(this.start, 500)
  },
  methods: {
    // 打开摄像头
    start() {
      window.navigator.mediaDevices
        .getUserMedia({
          video: true,
        })
        .then((stream) => {
          this.openVideo()
        })
        .catch((err) => {
          this.$message.error('Cannot capture user camera')
        })
    },
    openVideo() {
      let that = this
      this.steps = 'screen'
      this.startPhoto = true
      let canvas = document.getElementById('canvas')
      let context = canvas.getContext('2d')
      let tracker = new tracking.ObjectTracker('face')
      tracker.setInitialScale(4)
      tracker.setStepSize(2)
      tracker.setEdgesDensity(0.1)
      this.trackerTask = tracking.track('#video', tracker, { camera: true })
      tracker.on('track', function (event) {
        context.clearRect(0, 0, canvas.width, canvas.height)
        event.data.forEach(function (rect) {
          if (that.reverse) {
            context.strokeRect(
              295 - rect.x - rect.width,
              rect.y,
              rect.width,
              rect.height
            )
          } else {
            context.strokeRect(rect.x, rect.y, rect.width, rect.height)
          }
          context.strokeStyle = '#fff'
          context.fillStyle = '#fff'
          that.saveArray.x = rect.x
          that.saveArray.y = rect.y
          that.saveArray.width = rect.width
          that.saveArray.height = rect.height
        })
      })
      this.timer = true
      this.setPhotoInterval()
    },
    setPhotoInterval() {
      const countFun = () => {
        setTimeout(() => {
          if (this.timer && this.startPhoto) {
            countFun()
            if (this.reverse) {
              if (
                this.saveArray.x < 150 &&
                this.saveArray.y < 150 &&
                this.saveArray.width > 150 &&
                this.saveArray.height > 150
              ) {
                this.getPhoto()
              }
            } else {
              if (
                295 - this.saveArray.x - this.saveArray.width < 150 &&
                this.saveArray.y < 150 &&
                this.saveArray.width > 150 &&
                this.saveArray.height > 150
              ) {
                this.getPhoto()
              }
            }
          }
        }, 1000)
      }
      countFun()
    },
    // 获取人像照片
    getPhoto() {
      try {
        let video = document.getElementById('video')
        let cut = document.getElementById('shortCut')
        let context2 = cut.getContext('2d')
        context2.drawImage(video, 0, 0, 295, 345)
        this.keepImg()
      } catch (error) {}
    },
    // 将canvas转化为图片
    convertCanvasToImage(canvas) {
      let image = new Image()
      image.src = canvas.toDataURL('image/png')
      return image
    },
    //将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
    dataURLtoFile(dataurl, filename) {
      let 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() {
      //先保存完整的截图
      let cut = document.getElementById('shortCut')
      let context = cut.getContext('2d')
      //从完整截图里面,截取左侧294x345大小的图片,添加到canvas1里面
      let imgData = context.getImageData(0, 0, 294, 345)
      let canvas1 = document.getElementById('canvas1')
      let context1 = canvas1.getContext('2d')
      context1.putImageData(imgData, 0, 0)
      let img = document.getElementById('img')
      //把canvas1里面的294x345大小的图片保存
      let photoImg = document.createElement('img')
      photoImg.src = this.convertCanvasToImage(canvas1).src
      img.appendChild(photoImg)
      this.imgList.push(
        this.dataURLtoFile(
          this.convertCanvasToImage(canvas1).src,
          `person${this.imgList.length}.jpg`
        )
      )
      this.timer = false
      //捕捉成功后停顿3秒,再捕捉下一张,捕捉5张后上传文件
      if (this.imgList.length === 5) {
        this.sendImages()
      } else {
        setTimeout(() => {
          this.timer = true
          this.setPhotoInterval()
        }, 3000)
      }
    },
    sendImages() {
      let formData = new FormData()
      this.imgList.forEach((item) => {
        formData.append('files', item)
      })
      this.faceloading = true
      formData.append('actionId', this.formData.id)
      livenessCheck(formData, { isUpload: true })
        .then((res) => {
          if (res.success) {
            this.faceOk = true
          } else {
            this.faceOk = false
          }
          this.faceloading = false
          this.steps = 'end'
        })
        .catch((err) => {
          this.openVideo()
          this.faceloading = false
          this.imgList = []
        })
    },
    tryAgain() {
      this.imgList = []
      this.openVideo()
    },
    finish() {
      this.closeFace()
      this.$emit('Liveness check success')
      this.$emit('close', 'success')
    },
    clearCanvas() {
      let c = document.getElementById('canvas')
      let c1 = document.getElementById('canvas1')
      let cxt = c.getContext('2d')
      let cxt1 = c1.getContext('2d')
      cxt.clearRect(0, 0, 581, 436)
      cxt1.clearRect(0, 0, 581, 436)
    },
    closeFace() {
      try {
        this.startPhoto = false
        this.timer = false
        this.imgList = []
        this.clearCanvas()
        // 关闭摄像头
        let video = document.getElementById('video')
        video.srcObject.getTracks()[0].stop()
        // 停止侦测
        this.trackerTask.stop()
      } catch (error) {}
    },
    close() {
      this.closeFace()
      if(this.faceOk){
        this.$emit('close', 'success')
      }else{
        this.$emit('close')
      }
    },
    reverseVideo() {
      this.reverse = !this.reverse
    },
  },
  watch: {},
}
</script>
<style lang="scss">
@import './face.scss';
</style>

拍照组件代码

点击查看拍照组件代码
<template>
  <el-dialog
    :visible="modalVisible"
    width="681px"
    custom-class="compaines-dialog"
    title="Complete Registration"
    class="face-dialog"
    @close="close"
  >
    <div class="face" v-loading="faceloading">
      <p class="big-title" v-if="steps !== 'save'">Take a profile picture</p>
      <p class="big-title success" v-if="steps === 'save'">Complete!</p>
      <p v-if="steps === 'open'">
        Please turn on your camera and center your face within the below guides.
      </p>
      <p v-if="steps === 'take'">Position your face within the outlined area,click the button and the image will be saved as your profile picture!</p>
      <p v-if="steps === 'save'">
        Happy with your picture? If not, you can take another one.
      </p>
      <div class="video-container">
        <video
          id="video"
          preload
          autoplay
          loop
          muted
          width="295"
          height="345"
          :style="reverse ? 'transform:rotateY(180deg);' : ''"
        ></video>
        <canvas id="canvas" width="295" height="345"></canvas>
        <canvas id="shortCut" v-show="false"></canvas>
        <img :src="imgSrc" alt="" v-show="steps === 'save'" />
        <i @click="reverseVideo" v-show="steps==='take'">
          <svg-icon iconClass="icon-camera" class="icon-camera" />
        </i>
      </div>
      <div class="btns">
        
        <div class="add-num w300" @click="start" v-if="steps === 'open'">
          TURN CAMERA ON
        </div>
        <div class="add-num w300" @click="getPhoto" v-if="steps === 'take'">
          SAVE THE IMAGE
        </div>

        <div class="retake-btn" @click="openVideo" v-if="steps === 'save'">
          RETAKE
        </div>
        <div class="add-num" @click="keepImg" v-if="steps === 'save'">
          USE PICTURE
        </div>
        <p class="tips" v-if="steps === 'take'">
          (To take a profile picture your camera must remain on)
        </p>
        <div class="imgs" v-show="false">
          <canvas
            id="canvas1"
            width="295"
            height="345"
            v-show="steps === 'save'"
          ></canvas>

          <p>save images</p>
          <div id="img"></div>
        </div>
      </div>
    </div>
  </el-dialog>
</template>
<script>
import './tracking-min.js'
import './face-min.js'
import { individualPhoto } from '@modules/kyc/api/system/system'
import debounce from 'lodash/debounce'
export default {
  name: 'testTracking',
  model: {
    prop: 'formData',
    event: 'input',
  },
  props: {
    modalVisible: {
      type: Boolean,
      default: false,
    },
    formData: {
      type: Object,
    },
    option: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      saveArray: {},
      imgView: false,
      timer: null,
      steps: 'open',
      imageFile: {},
      faceloading: false,
      btnLoading: false,
      imgSrc: '',
      reverse: true,
    }
  },
  created() {
    this.start = debounce(this.start, 500)
  },
  methods: {
    // 打开摄像头
    start() {
      window.navigator.mediaDevices
        .getUserMedia({
          video: true,
        })
        .then((stream) => {
          this.openVideo()
        })
        .catch((err) => {
          this.$message.error('Cannot capture user camera')
        })
    },
    openVideo() {
      let that = this
      this.steps = 'take'
      let saveArray = {}
      let canvas = document.getElementById('canvas')
      let context = canvas.getContext('2d')
      let tracker = new tracking.ObjectTracker('face')
      tracker.setInitialScale(4)
      tracker.setStepSize(1.5)
      tracker.setEdgesDensity(0.1)
      this.imgSrc = ''
      this.trackerTask = tracking.track('#video', tracker, { camera: true })
      tracker.on('track', function (event) {
        context.clearRect(0, 0, canvas.width, canvas.height)
        event.data.forEach(function (rect) {
          if (that.reverse) {
            context.strokeRect(
              295 - rect.x - rect.width,
              rect.y,
              rect.width,
              rect.height
            )
          } else {
            context.strokeRect(rect.x, rect.y, rect.width, rect.height)
          }
          context.strokeStyle = '#fff'
          context.fillStyle = '#fff'
          saveArray.x = rect.x
          saveArray.y = rect.y
          saveArray.width = rect.width
          saveArray.height = rect.height
        })
      })
    },
    // 获取人像照片
    getPhoto() {
      let video = document.getElementById('video')
      let can = document.getElementById('shortCut')
      can.width = video.videoWidth
      can.height = video.videoHeight
      let context2 = can.getContext('2d')
      if (this.reverse) {
        context2.scale(-1, 1)
        context2.translate(-video.videoWidth, 0)
      }
      context2.drawImage(video, 0, 0, video.videoWidth, video.videoHeight)
      this.imgSrc = this.convertCanvasToImage(can).src
      this.steps = 'save'
      this.clearCanvas()
      // 停止侦测
      this.trackerTask.stop()
      // 关闭摄像头
      video.srcObject.getTracks()[0].stop()
      // this.imgView = true
    },
    // 截屏
    screenshot() {
      this.getPhoto()
    },
    // 将canvas转化为图片
    convertCanvasToImage(canvas) {
      let image = new Image()
      image.src = canvas.toDataURL('image/png')
      return image
    },
    //将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
    dataURLtoFile(dataurl, filename) {
      let 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() {
      // let can = document.getElementById('shortCut')
      // let context = can.getContext('2d')
      // let imgData = context.getImageData(0, 0, 294, 345)
      // let canvas1 = document.getElementById('canvas1')
      // let context1 = canvas1.getContext('2d')
      // context1.putImageData(imgData, 0, 0)
      // let img = document.getElementById('img')
      // let photoImg = document.createElement('img')
      // photoImg.src = this.convertCanvasToImage(canvas1).src
      // img.appendChild(photoImg)

      // this.imageFile = this.dataURLtoFile(
      //   this.convertCanvasToImage(canvas1).src,
      //   'person.jpg'
      // )
      this.imageFile = this.dataURLtoFile(this.imgSrc, 'person.jpg')
      let formData = new FormData()
      formData.append('file', this.imageFile)
      this.faceloading = true
      individualPhoto(formData, { isUpload: true })
        .then((res) => {
          if (res.success) {
            this.faceloading = false
            this.closeFace()
            this.$emit('close', 'success')
            this.$store
              .dispatch('GetBusinessUser')
              .then((res) => {})
              .catch((err) => {})
          } else {
            this.openVideo()
            this.faceloading = false
            this.imgList = []
          }
        })
        .catch((err) => {
          this.openVideo()
          this.faceloading = false
          this.imgList = []
        })
    },
    clearCanvas() {
      let c = document.getElementById('canvas')
      let c1 = document.getElementById('canvas1')
      let cxt = c.getContext('2d')
      let cxt1 = c1.getContext('2d')
      cxt.clearRect(0, 0, 581, 436)
      cxt1.clearRect(0, 0, 581, 436)
    },
    closeFace() {
      try {
        this.steps = 'open'
        this.clearCanvas()
        // 关闭摄像头
        let video = document.getElementById('video')
        video.srcObject.getTracks()[0].stop()
        // 停止侦测
        this.trackerTask.stop()
      } catch (error) {}
    },
    close() {
      this.closeFace()
      this.$emit('close')
    },
    reverseVideo() {
      this.reverse = !this.reverse
    },
  },
  watch: {
    faceView(v) {
      if (v == false) {
        this.closeFace()
      }
    },
  },
  destroyed() {
    // clearInterval(this.timer)
  },
}
</script>
<style lang="scss">
@import './face.scss';
</style>

样式文件

点击查看样式代码
.face {
  p {
    text-align: center;
    font-size: 16px;
    color: #333333;
  }

  .big-title {
    margin-top: 36PX;
    font-size: 24PX;
    font-weight: 600;
    color: #030229;
    text-align: center;

  }

  .success {
    color: #47BFAF;
  }

  .fail {
    color: #C81223;
  }

  .video-container {
    background: url(~@/icons/img/face.png);
    background-size: cover;
    position: relative;
    width: 295PX;
    height: 345PX;
    border-radius: 4%;
    overflow: hidden;
    margin: 0 auto;
    margin-top: 34PX;

    img,
    video,
    #canvas,
    #shortCut,
    #canvas1 {
      position: absolute;
    }

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    video{
      object-fit: cover;
    }
    .icon-camera{
      position: absolute;
      font-size: 32px;
      bottom: 20px;
      right: 20px;
      cursor: pointer;
    }
  }

  .btns {
    padding: 10PX;
    text-align: center;
    margin-top: 6PX;

    .tips {
      font-size: 14PX;
      color: #666;
      margin-top: 16PX;

      line-height: 24PX;
    }
  }

  .imgs {
    padding: 10PX;

    p {
      font-size: 16PX;
    }
  }

  .add-num {
    display: inline-block;
    font-size: 16PX;
    padding: 12PX 14PX;
    margin-right: 4PX;
    border-radius: 4PX;
    color: #ffffff;
    background: #47BFAF;
    cursor: pointer;
  }

  .retake-btn {
    width: 145PX;
    display: inline-block;
    font-size: 16PX;
    padding: 12PX 14PX;
    margin-right: 24PX;
    border-radius: 4PX;
    color: #828282;
    background: #E7E7E7;
    cursor: pointer;
  }

  .w300 {
    width: 300PX;
  }
}

.face-dialog {
  .el-dialog__body {
    padding-top: 0;
    padding-bottom: 15px;
  }
}
Logo

前往低代码交流专区

更多推荐