前端捕获人脸识别实践

前言

公司有个项目涉及人脸识别,需要前端能力支持,需要用户进入摄像头后识别人脸进行截图,然后上传后端进行数据交换。不过目前主流摄像头支持调用,开源社区有个 trackingjs 类库问题解决计算机视觉解决方案。

实践

trackingjs 很多工作量帮你做好,只需要引入他的库就可以用了:

<div class="app-container">
      <video ref="videoRef" class="video-wrapper" preload autoplay loop muted />
      <canvas ref="captureFaceRef" width="600" height="560" class="capture-face-wrapper" />
    </div>
    <div class="result-wrapper">
      <h3>捕获到人脸数据:</h3>
      <canvas ref="shortCutRef" class="short-cut-wrapper" />
    </div>

想实现功能至少需要 videoRefshortCutRef 节点,videoRef 主要是调用摄像头获取流媒体进行播放,而 shortCutRef 节点就是人脸截图,你可以拿到里面的图片进行提交,至于图片转 base64 数据流根据需求定吧。上面多一个 captureFaceRef 主要是捕获人脸的状态圈,告知用户捕获状态,而这个状态 trackingjs 事件回调里包含其中。

本项目是 vue3 项目,所以先初始化相关的业务:

const videoRef = ref(null)
const captureFaceRef = ref(null)
const shortCutRef = ref(null)

// 捕捉人脸坐标对象
const saveArray = {}
let captureFaceCanvas = null
let captureFaceContext = null
let shortCutRefCanvas = null
let shortCutRefContext = null
// tracking 实例化对象
let tracker = null

然后在 mounted 生命周期拿到 dom 节点信息和初始化 trackingjs

onMounted(() => {
  captureFaceCanvas = captureFaceRef.value
  shortCutRefCanvas = shortCutRef.value
  captureFaceContext = captureFaceCanvas.getContext('2d')
  shortCutRefContext = shortCutRefCanvas.getContext('2d')

  initTracker()
  captureFacePhoto(shortCutRefCanvas)
 })

initTracker 方法要注意下声明 face 的模块,因为 trackingjs 机器学习模型很多,我这边只用到人脸,所以声明该模型:

const initTracker = () => {
  // 实例化 face 模块
  tracker = new window.tracking.ObjectTracker('face')
  // 初始化抓取人脸边框配置
  tracker.setInitialScale(4)
  tracker.setStepSize(2)
  tracker.setEdgesDensity(0.1)
	// 摄像头开启
  window.tracking.track(videoRef.value, tracker, {
      camera: true
  })
  // 追踪事件回调
  tracker.on('track', event => {
      captureFaceContext.clearRect(0, 0, captureFaceCanvas.width, captureFaceCanvas.height)
      event.data.forEach(function (rect) {
          captureFaceContext.strokeStyle = 'red'
          captureFaceContext.strokeRect(rect.x, rect.y, rect.width, rect.height)
          captureFaceContext.fillStyle = 'red'
          saveArray.x = rect.x
          saveArray.y = rect.y
          saveArray.width = rect.width
          saveArray.height = rect.height
      })
  })
}

上面就是根据配置进行个性化配置,具体可以查阅官方文档。

然后制作截图功能:

const captureFacePhoto = canvas => {
  // 将video对象内指定的区域捕捉绘制到画布上指定的区域,实现拍照
  shortCutRefContext.drawImage(videoRef.value, 140, 70, 340, 340, 0, 0, 140, 140)
  const getImage = canvas.toDataURL('image/png')
  console.log(getImage)
}

优化

至此为止基本功能已经实现了,不过在人脸识别过程中,却发现人脸识别经常失误,比如识别到侧边脸以及截图文件不完整。

const bestCaptureTimer = () => {
  if (saveArray.x > 130 &&
    saveArray.x + saveArray.width < 460 &&
    saveArray.y > 58 &&
    saveArray.y + saveArray.height < 388 &&
    saveArray.width < 330 &&
    saveArray.height < 330) {
    captureFacePhoto(shortCutRefCanvas)
    for (const key in saveArray) {
      delete saveArray[key]
    }
  }
}

这个定时器让用户在画布中央进行截图,提高人脸识别率。

对于需要检验摄像头设备完全可以用浏览器的 api 实现:

if (navigator.mediaDevices === undefined) {
  navigator.mediaDevices = {}
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
  navigator.mediaDevices.getUserMedia = constraints => {

    // 首先,如果有getUserMedia的话,就获得它
    const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia

    // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
    if (!getUserMedia) {
      return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
    }

    // 否则,为老的navigator.getUserMedia方法包裹一个Promise
    return new Promise((resolve, reject) => {
      getUserMedia.call(navigator, constraints, resolve, reject)
    })
  }
}

navigator.mediaDevices.getUserMedia({ video: true }).then(() => {
  // 人脸识别业务初始化
  const tracker = new window.tracking.ObjectTracker('face')
  // 一些业务...
}).catch(err => {
  console.warn(err.name + ": " + err.message)
  ElMessage.error({
    message: '没有检验到摄像头,请插入摄像头设备!',
    showClose: true,
    duration: 0
  })
})

demo 如下,记得授权摄像头访问